diff --git a/app/views/contents/courseware/courseware_manager.php b/app/views/contents/courseware/courseware_manager.php deleted file mode 100644 index 7d06bb78204934d77c4cecf654e797b8bed282a3..0000000000000000000000000000000000000000 --- a/app/views/contents/courseware/courseware_manager.php +++ /dev/null @@ -1 +0,0 @@ -<div id="courseware-manager-app" entry-type="users" entry-id="<?= $user_id ?>"></div> diff --git a/app/views/course/courseware/manager.php b/app/views/course/courseware/manager.php deleted file mode 100644 index 3107cf5a44f5cbc3ca307858a3f086da93b2d1aa..0000000000000000000000000000000000000000 --- a/app/views/course/courseware/manager.php +++ /dev/null @@ -1 +0,0 @@ -<div id="courseware-manager-app" entry-type="courses" entry-id="<?= Context::getId() ?>"></div> diff --git a/resources/assets/javascripts/bootstrap/courseware.js b/resources/assets/javascripts/bootstrap/courseware.js index 503ae89f873a53ad0a236a4e03f5aaf44fa221e7..ccb789217d8beb47e2ee983a5c77077a2b0c3bd5 100644 --- a/resources/assets/javascripts/bootstrap/courseware.js +++ b/resources/assets/javascripts/bootstrap/courseware.js @@ -43,17 +43,6 @@ STUDIP.domReady(() => { }); } - if (document.getElementById('courseware-manager-app')) { - STUDIP.Vue.load().then(({ createApp }) => { - import( - /* webpackChunkName: "courseware-manager-app" */ - '@/vue/courseware-manager-app.js' - ).then(({ default: mountApp }) => { - return mountApp(STUDIP, createApp, '#courseware-manager-app'); - }); - }); - } - if (document.getElementById('courseware-content-bookmark-app')) { STUDIP.Vue.load().then(({ createApp }) => { import( diff --git a/resources/assets/stylesheets/scss/courseware.scss b/resources/assets/stylesheets/scss/courseware.scss index c04365d283a4b50cb83b9586804d79752349cadb..7de515e01409606892a98697e45d383f6daa40a1 100644 --- a/resources/assets/stylesheets/scss/courseware.scss +++ b/resources/assets/stylesheets/scss/courseware.scss @@ -2433,81 +2433,6 @@ d a s h b o a r d e n d p r o g r e s s e n d * * * * * * * * * * * */ -/* * * * * * -o b l o n g -* * * * * */ - -.cw-oblong-large { - border: solid thin $content-color-40; - width: 520px; - - .cw-oblong-value, - .cw-oblong-description { - display: inline-block; - height: 90px; - vertical-align: top; - line-height: 90px; - text-align: center; - } - - .cw-oblong-value { - width: 89px; - color: $black; - background-color: $content-color-20; - border-right: solid thin $content-color-40; - font-size: xx-large; - } - .cw-oblong-description { - width: 426px; - color: $black; - font-size: large; - img { - vertical-align: middle; - margin-right: 10px; - } - - } -} - -.cw-oblong-small { - border: solid thin $content-color-40; - width: 340px; - margin-right: 1em; - margin-bottom: 5px; - - .cw-oblong-value, - .cw-oblong-description { - display: inline-block; - height: 60px; - vertical-align: top; - line-height: 60px; - text-align: center; - } - - .cw-oblong-value { - width: 59px; - background-color: $content-color-20; - border-right: solid thin $content-color-40; - color: $black; - font-size: x-large; - } - .cw-oblong-description { - width: calc(100% - 64px); - background-color: $white; - color: $black; - overflow: hidden; - img { - vertical-align: middle; - margin-right: 8px; - } - - } -} - -/* * * * * * * * * * -o b l o n g e n d -* * * * * * * * * */ - /* * * * * * * * * * * * * * * p r o g r e s s c i r c l e * * * * * * * * * * * * * * */ @@ -2595,249 +2520,7 @@ p r o g r e s s c i r c l e p r o g r e s s c i r c l e e n d * * * * * * * * * * * * * * * * * */ -/* * * * * * * -m a n a g e r -* * * * * * */ - -.cw-course-manager { - display: flex; - flex-wrap: wrap; - max-width: 1600px; - - .cw-course-manager-tabs { - width: calc(50% - 10px); - margin-right: 20px; - - &:last-child{ - margin: 0; - } - - .cw-tabs-content { - min-height: 400px; - padding: 10px; - resize: vertical; - overflow-y: auto; - scrollbar-width: thin; - scrollbar-color: $base-color $white; - } - } -} - -.cw-manager-element { - - .cw-sort-ease-move { - transition: all 0.4s ease-in-out; - } - - .cw-manager-element-title { - - img { - cursor: pointer; - vertical-align: text-bottom; - } - - .cw-manager-element-breadcrumb { - display: inline; - .cw-manager-element-breadcrumb-home, - .cw-manager-element-breadcrumb-item { - cursor: pointer; - color: $base-color; - - &::after { - content: ' / '; - } - &:hover { - color: $active-color; - } - } - } - .cw-manager-element-actions { - position: relative; - display: inline; - float: right; - cursor: pointer; - z-index: 32; - } - header { - padding: 0.25em 0 0.5em 0; - font-size: 1.6em; - font-weight: 700; - - button.cw-insert-element { - background: transparent; - border: none; - cursor: pointer; - padding: 0; - } - } - } - .cw-manager-element-containers { - margin-bottom: 8px; - } - .cw-manager-container { - margin-bottom: 10px; - border: solid thin $content-color-40; - - &:last-child { - margin-bottom: 0; - } - - .cw-manager-container-title { - font-weight: 700; - display: flex; - gap: 5px; - justify-content: space-between; - padding: 4px 4px 4px 8px; - color: $base-color; - background-color: $content-color-20; - - &.cw-manager-container-clickable-title { - cursor: pointer; - justify-content: start; - } - - img { - vertical-align: middle; - } - } - .cw-manager-container-blocks { - margin: 4px; - } - .cw-manager-block { - border: solid thin $content-color-40; - display: flex; - gap: 5px; - justify-content: space-between; - padding: 1em; - margin-bottom: 4px; - background-color: $white; - - &.cw-manager-block-clickable { - cursor: pointer; - justify-content: start; - - &:hover { - background-color: $base-color; - color: $white; - } - } - - img { - vertical-align: middle; - } - - &:last-child { - margin-bottom: 0; - } - } - - } - - .cw-manager-element-item-wrapper { - margin-bottom: 4px; - - &:last-child { - margin-bottom: 0; - } - } - - .cw-manager-element-item { - display: flex; - border: solid thin $content-color-40; - padding: 1em; - justify-content: space-between; - vertical-align: middle; - background-color: $white; - color: $base-color; - - img { - vertical-align: middle; - } - .cw-manager-element-item-solver-name { - flex: fit-content; - padding-left: 0.25em; - } - - &:hover { - color: $white; - background-color: $base-color; - } - - &.cw-manager-element-item-sorting:hover{ - background-color: $white; - color: $base-color; - } - - &.cw-manager-element-item-inserter { - padding-left: 2em; - justify-content: start; - @include background-icon(arr_2left, clickable, 16); - background-position: 6px center; - background-repeat: no-repeat; - &:hover { - @include background-icon(arr_2left, info-alt, 16); - } - - img { - margin-right: 1em; - } - } - } - - .cw-manager-filing { - border: solid thin $content-color-40; - margin-top: 4px; - @include background-icon(arr_eol-down, clickable, 24); - background-color: $white; - background-position: calc(50% - 10em) calc(50% - 1px); - background-repeat: no-repeat; - padding: 1em 0; - color: $base-color; - text-align: center; - width: 100%; - font-weight: 600; - cursor: pointer; - - &:hover { - border-color: $base-color; - } - - &.cw-manager-filing-active { - @include background-icon(arr_eol-down, info-alt, 24); - background-color: $base-color; - border: solid thin $base-color; - color: $white; - } - &.cw-manager-filing-disabled { - @include background-icon(arr_eol-down, inactive, 24); - background-color: $white; - color: $dark-gray-color-80; - } - } - - .cw-manager-block-buttons, - .cw-manager-container-buttons, - .cw-manager-element-item-buttons { - button { - background: transparent; - border: none; - cursor: pointer; - padding: 0; - - &[disabled] { - visibility: hidden; - } - } - } - - .cw-collapsible-content { - display: none; - &.cw-collapsible-content-open { - display: block; - } - } -} .cw-import-zip { margin-bottom: 1em; @@ -2862,10 +2545,6 @@ m a n a g e r } } -/* * * * * * * * * * * -m a n a g e r e n d -* * * * * * * * * * */ - /* * * * * * b l o c k s * * * * * */ @@ -5188,31 +4867,6 @@ cw tiles cw tiles end */ -/* cw manager copy */ - -.cw-manager-copy-selector { - ul { - padding: 0; - margin: 0; - list-style: none; - } - .cw-manager-copy-selector-course { - display: block; - color: $base-color; - line-height: 2em; - - &:hover { - color: $active-color; - } - - img { - vertical-align: text-bottom; - } - } -} - -/* cw manager copy end*/ - /* courseware template preview */ .cw-template-preview { display: flex; diff --git a/resources/vue/components/courseware/CoursewareCourseManager.vue b/resources/vue/components/courseware/CoursewareCourseManager.vue deleted file mode 100644 index ad9ae98119c37fe473a2428bdb396f485e50558e..0000000000000000000000000000000000000000 --- a/resources/vue/components/courseware/CoursewareCourseManager.vue +++ /dev/null @@ -1,236 +0,0 @@ -<template> - <div class="cw-course-manager-wrapper"> - <div class="cw-course-manager"> - <courseware-tabs class="cw-course-manager-tabs"> - <courseware-tab :name="$gettext('Diese Courseware')" :selected="true" :index="0"> - <courseware-manager-element - type="current" - :currentElement="currentElement" - @selectElement="setCurrentId" - @reloadElement="reloadElements" - /> - </courseware-tab> - <courseware-tab :name="$gettext('Export')" :index="1"> - <button - class="button" - @click.prevent="doExportCourseware" - :class="{ - disabled: exportRunning, - }" - > - <translate>Alles exportieren</translate> - </button> - <courseware-companion-box v-show="exportRunning" :msgCompanion="$gettext('Export läuft, bitte haben sie einen Moment Geduld...')" mood="pointing"/> - <div v-if="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> - </courseware-tab> - </courseware-tabs> - - <courseware-tabs class="cw-course-manager-tabs"> - <courseware-tab :name="$gettext('FAQ')" :index="0"> - <courseware-collapsible-box :open="true" :title="$gettext('Wie finde ich die gewünschte Stelle?')"> - <p><translate> - Wählen Sie auf der linken Seite "Diese Courseware" aus. - Beim Laden der Seite ist dies immer gewählt. Die Überschrift - gibt an, welche Seite Sie gerade ausgewählt haben. Darunter befinden - sich die Abschnitte der Seite und innerhalb dieser deren Blöcke. - Möchten Sie eine Seite, die unterhalb der gewählten liegt, bearbeiten, - können Sie diese über die Schaltflächen im Bereich "Unterseiten" wählen. - Über der Überschrift wird eine Navigation eingeblendet, mit der Sie beliebig - weit hoch in der Hierarchie springen können. - </translate></p> - </courseware-collapsible-box> - <courseware-collapsible-box :title="$gettext('Wie sortiere ich Objekte?')"> - <p><translate> - Seiten, Abschnitte und Blöcke lassen sich in ihrer Reihenfolge sortieren. - Hierzu wählen Sie auf der linken Seite unter "Diese Courseware" die Schaltfläche "Unterseiten sortieren", - "Abschnitte sortieren" oder "Blöcke sortieren". - An den Objekten werden Pfeile angezeigt, mit diesen können die Objekte an die gewünschte - Position gebracht werden. Um die neue Sortierung zu speichern, wählen Sie "Sortieren beenden". - Sie können die Änderungen auch rückgängig machen, indem Sie "Sortieren abbrechen" wählen. - </translate></p> - </courseware-collapsible-box> - <courseware-collapsible-box :title="$gettext('Wie verschiebe ich Objekte?')"> - <p><translate> - Seiten, Abschnitte und Blöcke lassen sich verschieben. - Hierzu wählen Sie auf der linken Seite unter "Diese Courseware" die Schaltfläche - "Seite an dieser Stelle einfügen", "Abschnitt an dieser Stelle einfügen" oder - "Block an dieser Stelle einfügen". Wählen Sie dann auf der rechten Seite unter - "Verschieben" das Objekt aus, das Sie verschieben möchten. Verschiebbare Objekte - erkennen Sie an den zwei nach links zeigenden gelben Pfeilen. - </translate></p> - </courseware-collapsible-box> - <courseware-collapsible-box :title="$gettext('Wie kopiere ich Objekte?')"> - <p><translate> - Seiten, Abschnitte und Blöcke lassen sich aus einer anderen Veranstaltung und Ihren - eigenen Inhalten kopieren. - Hierzu wählen Sie auf der linken Seite unter "Diese Courseware" die Schaltfläche - "Seite an dieser Stelle einfügen", "Abschnitt an dieser Stelle einfügen" oder - "Block an dieser Stelle einfügen". Wählen Sie dann auf der rechten Seite unter - "Kopieren" erst die Veranstaltung aus der Sie kopieren möchten oder Ihre eigenen - Inhalte. Wählen sie dann das Objekt aus, das Sie kopieren möchten. Kopierbare Objekte - erkennen Sie an den zwei nach links zeigenden gelben Pfeilen. - </translate></p> - </courseware-collapsible-box> - </courseware-tab> - <courseware-tab :name="$gettext('Verschieben')" :selected="true" :index="1"> - <courseware-manager-element - type="self" - :currentElement="selfElement" - :moveSelfPossible="moveSelfPossible" - :moveSelfChildPossible="moveSelfChildPossible" - @selectElement="setSelfId" - @reloadElement="reloadElements" - /> - </courseware-tab> - - <courseware-tab :name="$gettext('Kopieren')" :index="2"> - <courseware-manager-copy-selector @loadSelf="reloadElements" @reloadElement="reloadElements" /> - </courseware-tab> - - <courseware-tab :name="$gettext('Verknüpfen')" :index="3"> - <courseware-manager-link-selector @loadSelf="reloadElements" @reloadElement="reloadElements" /> - </courseware-tab> - - <courseware-tab :name="$gettext('Importieren')" :index="4"> - <courseware-manager-import /> - </courseware-tab> - <courseware-tab v-if="context.type === 'courses'" :name="$gettext('Aufgabe verteilen')" :index="4"> - <courseware-manager-task-distributor /> - </courseware-tab> - </courseware-tabs> - </div> - <courseware-companion-overlay /> - </div> -</template> -<script> -import CoursewareTabs from './CoursewareTabs.vue'; -import CoursewareTab from './CoursewareTab.vue'; -import CoursewareCollapsibleBox from './CoursewareCollapsibleBox.vue'; -import CoursewareManagerElement from './CoursewareManagerElement.vue'; -import CoursewareManagerCopySelector from './CoursewareManagerCopySelector.vue'; -import CoursewareManagerLinkSelector from './CoursewareManagerLinkSelector.vue'; -import CoursewareManagerTaskDistributor from './CoursewareManagerTaskDistributor.vue'; -import CoursewareCompanionOverlay from './CoursewareCompanionOverlay.vue'; -import CoursewareCompanionBox from './CoursewareCompanionBox.vue'; -import CoursewareManagerImport from './CoursewareManagerImport.vue'; -import CoursewareExport from '@/vue/mixins/courseware/export.js'; -import { mapActions, mapGetters } from 'vuex'; - -export default { - name: 'courseware-course-manager', - components: { - CoursewareTabs, - CoursewareTab, - CoursewareCollapsibleBox, - CoursewareManagerElement, - CoursewareManagerCopySelector, - CoursewareManagerLinkSelector, - CoursewareCompanionOverlay, - CoursewareCompanionBox, - CoursewareManagerTaskDistributor, - CoursewareManagerImport - }, - - mixins: [CoursewareExport], - - data() { - return { - exportRunning: false, - currentElement: {}, - currentId: null, - selfElement: {}, - selfId: null, - }; - }, - - computed: { - ...mapGetters({ - courseware: 'courseware', - context: 'context', - structuralElementById: 'courseware-structural-elements/byId', - exportState: 'exportState', - exportProgress: 'exportProgress' - }), - moveSelfPossible() { - if (this.selfElement.relationships === undefined) { - return false - } else if (this.selfElement.relationships.parent.data === null) { - return false; - } else if (this.currentElement.id === this.selfElement.relationships.parent.data.id) { - return false; - } else if (this.currentId === this.selfId) { - return false; - } else { - return true; - } - }, - moveSelfChildPossible() { - return this.currentId !== this.selfId; - }, - }, - - methods: { - ...mapActions({ - loadCoursewareStructure: 'courseware-structure/load', - createStructuralElement: 'createStructuralElement', - updateStructuralElement: 'updateStructuralElement', - deleteStructuralElement: 'deleteStructuralElement', - loadStructuralElement: 'loadStructuralElement', - lockObject: 'lockObject', - unlockObject: 'unlockObject', - addBookmark: 'addBookmark', - companionInfo: 'companionInfo', - }), - async reloadElements() { - await this.setCurrentId(this.currentId); - await this.setSelfId(this.selfId); - this.$emit("reload"); - }, - async setCurrentId(target) { - this.currentId = target; - await this.loadStructuralElement(this.currentId); - this.initCurrent(); - }, - initCurrent() { - this.currentElement = _.cloneDeep(this.structuralElementById({ id: this.currentId })); - }, - async setSelfId(target) { - this.selfId = target; - await this.loadStructuralElement(this.selfId); - this.initSelf(); - }, - initSelf() { - this.selfElement = _.cloneDeep(this.structuralElementById({ id: this.selfId })); - }, - - async doExportCourseware() { - if (this.exportRunning) { - return false; - } - - this.exportRunning = true; - - await this.loadCoursewareStructure(); - await this.sendExportZip( - this.courseware.relationships.root.data.id, - {withChildren: true} - ); - - this.exportRunning = false; - }, - }, - watch: { - courseware(newValue, oldValue) { - let currentId = newValue.relationships.root.data.id; - this.setCurrentId(currentId); - this.setSelfId(currentId); - }, - }, - -}; -</script> diff --git a/resources/vue/components/courseware/CoursewareManagerBlock.vue b/resources/vue/components/courseware/CoursewareManagerBlock.vue deleted file mode 100644 index b28c085e8dafeb4eaae7cf2f374076ecabd8bbb7..0000000000000000000000000000000000000000 --- a/resources/vue/components/courseware/CoursewareManagerBlock.vue +++ /dev/null @@ -1,48 +0,0 @@ -<template> - <div :class="{ 'cw-manager-block-clickable': inserter }" class="cw-manager-block" @click="clickItem"> - <span v-if="inserter"> - <studip-icon shape="arr_2left" role="sort" /> - </span> - {{ block.attributes.title }} - <div v-if="sortBlocks" class="cw-manager-block-buttons"> - <button :disabled="!canMoveUp" @click="moveUp" :title="$gettext('Element nach oben verschieben')"> - <studip-icon shape="arr_2up" role="sort" /> - </button> - <button :disabled="!canMoveDown" @click="moveDown" :title="$gettext('Element nach unten verschieben')"> - <studip-icon shape="arr_2down" role="sort" /> - </button> - </div> - </div> -</template> - -<script> -export default { - name: 'courseware-manager-block', - props: { - block: Object, - inserter: Boolean, - sortBlocks: Boolean, - elementType: String, - canMoveUp: Boolean, - canMoveDown: Boolean, - sectionId: Number - }, - methods: { - clickItem() { - if (this.inserter) { - this.$emit('insertBlock', {block: this.block, source: this.elementType}); - } - }, - moveUp() { - if (this.sortBlocks) { - this.$emit('moveUp', this.block.id, this.sectionId); - } - }, - moveDown() { - if (this.sortBlocks) { - this.$emit('moveDown', this.block.id, this.sectionId); - } - }, - }, -}; -</script> diff --git a/resources/vue/components/courseware/CoursewareManagerContainer.vue b/resources/vue/components/courseware/CoursewareManagerContainer.vue deleted file mode 100644 index 9849c17ad0e894bee1029988d47ea600611d45f2..0000000000000000000000000000000000000000 --- a/resources/vue/components/courseware/CoursewareManagerContainer.vue +++ /dev/null @@ -1,282 +0,0 @@ -<template> - <div class="cw-manager-container"> - <div - :class="{ 'cw-manager-container-clickable-title': inserter }" - class="cw-manager-container-title" - @click="clickItem" - > - <span v-if="inserter"> - <studip-icon shape="arr_2left" role="sort" /> - </span> - {{ container.attributes.title }} ({{container.attributes.width}}) - <div v-if="sortContainers" class="cw-manager-container-buttons"> - <button :disabled="!canMoveUp" @click="moveUp" :title="$gettext('Element nach oben verschieben')"> - <studip-icon shape="arr_2up" role="sort" /> - </button> - <button :disabled="!canMoveDown" @click="moveDown" :title="$gettext('Element nach unten verschieben')"> - <studip-icon shape="arr_2down" role="sort" /> - </button> - </div> - </div> - <courseware-collapsible-box :open="false" :title="$gettext('Blöcke')" class="cw-manager-container-blocks"> - <div v-if="canSortChildren"> - <button v-show="!sortBlocksActive && isCurrent" class="button sort" @click="sortBlocks"> - <translate>Blöcke sortieren</translate> - </button> - <button v-show="sortBlocksActive && isCurrent" class="button accept" @click="storeBlocksSort"> - <translate>Sortieren beenden</translate> - </button> - <button v-show="sortBlocksActive && isCurrent" class="button cancel" @click="resetBlocksSort"> - <translate>Sortieren abbrechen</translate> - </button> - </div> - <p v-if="!hasChildren"> - <translate>Dieser Abschnitt enthält keine Blöcke.</translate> - </p> - <div v-else-if="sectionsWithBlocksCurrentState.length === 1"> - <transition-group name="cw-sort-ease" tag="div"> - <courseware-manager-block - v-for="(block, blockIndex) in sectionsWithBlocksCurrentState[0].blocks" - :key="block.id" - :block="block" - :inserter="blockInserter" - :sortBlocks="sortBlocksActive" - :canMoveUp="blockIndex !== 0" - :canMoveDown="blockIndex + 1 !== sectionsWithBlocksCurrentState[0].blocks.length" - :elementType="elementType" - :sectionId="0" - @insertBlock="insertBlock" - @moveUp="moveBlockUp" - @moveDown="moveBlockDown" - /> - </transition-group> - </div> - <div v-else> - <courseware-collapsible-box - v-for="(section, index) in sectionsWithBlocksCurrentState" - :key="section.id" - :open="true" - :title="section.name" - class="cw-manager-container-blocks" - > - <transition-group name="cw-sort-ease" tag="div"> - <courseware-manager-block - v-for="(block, blockIndex) in sectionsWithBlocksCurrentState[index].blocks" - :key="block.id" - :block="block" - :inserter="blockInserter" - :sortBlocks="sortBlocksActive" - :canMoveUp="blockIndex !== 0 || index !== 0" - :canMoveDown="index + 1 !== sectionsWithBlocksCurrentState.length || blockIndex + 1 !== sectionsWithBlocksCurrentState[index].blocks.length" - :elementType="elementType" - :sectionId="index" - @insertBlock="insertBlock" - @moveUp="moveBlockUp" - @moveDown="moveBlockDown" - /> - </transition-group> - </courseware-collapsible-box> - </div> - <courseware-manager-filing - v-if="isCurrent && !sortContainers && !sortBlocksActive" - :parentId="container.id" - :parentItem="container" - itemType="block" - @deactivated="reloadContainer" - /> - </courseware-collapsible-box> - </div> -</template> - -<script> -import CoursewareCollapsibleBox from './CoursewareCollapsibleBox.vue'; -import CoursewareManagerBlock from './CoursewareManagerBlock.vue'; -import CoursewareManagerFiling from './CoursewareManagerFiling.vue'; -import { mapGetters, mapActions } from 'vuex'; - -export default { - name: 'courseware-manager-container', - components: { - CoursewareCollapsibleBox, - CoursewareManagerBlock, - CoursewareManagerFiling, - }, - props: { - container: Object, - isCurrent: Boolean, - inserter: Boolean, - blockInserter: Boolean, - sortContainers: Boolean, - elementType: String, - canMoveUp: Boolean, - canMoveDown: Boolean - }, - data() { - return { - sortBlocksActive: false, - sectionsWithBlocksCurrentState: [], - }; - }, - computed: { - ...mapGetters({ - blockById: 'courseware-blocks/byId', - }), - hasChildren() { - return this.getBlocksCount >= 1; - }, - canSortChildren() { - return this.getBlocksCount > 1; - }, - containerType() { - return this.container.attributes['container-type']; - }, - hasSections() { - return this.containerType === 'tabs' || this.containerType === 'accordion'; - }, - getBlocksCount() { - if (this.sectionsWithBlocksCurrentState === null) { - return 0; - } else { - let blocks = 0; - - this.sectionsWithBlocksCurrentState.forEach((section) => { - if (section.blocks !== undefined) { - blocks += section.blocks.length; - } - }); - - // If there are more that one section and only one block, - // we make sotring of that block possible, by just assuming that there are 2 blocks. - // By doing this, we only provide the sorting feature when there are more than one section (section). - if (this.sectionsWithBlocksCurrentState.length > 1 && blocks == 1) { - blocks++; - } - - return blocks; - } - } - }, - mounted() { - this.initSections(); - }, - methods: { - ...mapActions({ - sortBlocksInContainer: 'sortBlocksInContainer', - updateContainer: 'updateContainer', - loadContainer: 'loadContainer', - lockObject: 'lockObject', - unlockObject: 'unlockObject' - }), - reloadContainer() { - this.loadContainer(this.container.id); - }, - clickItem() { - if (this.inserter) { - this.$emit('insertContainer', {container: this.container, source: this.elementType}); - } - }, - getSectionsWithBlocks() { - if (!this.container) { - return []; - } - if (!this.container.attributes.payload.sections) { - return []; - } - - const blockSections = _.cloneDeep(this.container.attributes.payload.sections); - - blockSections.forEach((section) => { - if(section.blocks !== undefined) { - section.blocks = section.blocks.flatMap( - (id) => { - return this.blockById({ id }) ?? [] //remove blocks which could not be loaded - } - ); - } - }); - - return blockSections; - }, - initSections() { - this.sectionsWithBlocksCurrentState = this.getSectionsWithBlocks(); - }, - insertBlock(data) { - this.$emit('insertBlock', data); - this.initSections(); - }, - sortBlocks() { - this.sortBlocksActive = true; - }, - async storeBlocksSort() { - const container = JSON.parse(JSON.stringify(this.container)); - - this.sectionsWithBlocksCurrentState.forEach((section, index)=> { - if (section.blocks !== undefined) { - container.attributes.payload.sections[index].blocks = section.blocks.map(({ id }) => ( id )); - } - }); - await this.lockObject({id: container.id, type: 'courseware-containers'}); - await this.updateContainer({ container: container, structuralElementId: this.container.relationships['structural-element'].data.id }); - await this.unlockObject({id: container.id, type: 'courseware-containers'}); - - this.sortBlocksActive = false; - }, - resetBlocksSort() { - this.sectionsWithBlocksCurrentState = this.getSectionsWithBlocks(); - this.sortBlocksActive = false; - }, - moveUp() { - if (this.sortContainers) { - this.$emit('moveUp', this.container.id); - } - }, - moveDown() { - if (this.sortContainers) { - this.$emit('moveDown', this.container.id); - } - }, - moveBlockUp(blockId, sectionId) { - let view = this; - this.sectionsWithBlocksCurrentState[sectionId].blocks.every((block, index) => { - if (block.id === blockId) { - if (index === 0) { - if (sectionId !== 0) { - view.sectionsWithBlocksCurrentState[sectionId-1].blocks.push(view.sectionsWithBlocksCurrentState[sectionId].blocks.splice(index, 1)[0]); - } - return false; - } - view.sectionsWithBlocksCurrentState[sectionId].blocks.splice(index - 1, 0, view.sectionsWithBlocksCurrentState[sectionId].blocks.splice(index, 1)[0]); - return false; - } else { - return true; - } - }); - }, - moveBlockDown(blockId, sectionId) { - let view = this; - this.sectionsWithBlocksCurrentState[sectionId].blocks.every((block, index) => { - if (block.id === blockId) { - if (index === view.sectionsWithBlocksCurrentState[sectionId].blocks.length - 1) { - if (sectionId !== view.sectionsWithBlocksCurrentState.length - 1) { - view.sectionsWithBlocksCurrentState[sectionId + 1].blocks.unshift(view.sectionsWithBlocksCurrentState[sectionId].blocks.splice(index, 1)[0]); - } - return false; - } - view.sectionsWithBlocksCurrentState[sectionId].blocks.splice(index + 1, 0, view.sectionsWithBlocksCurrentState[sectionId].blocks.splice(index, 1)[0]); - return false; - } else { - return true; - } - }); - }, - }, - watch: { - container: { - handler() { - this.initSections(); - }, - deep: true - } - }, -}; -</script> diff --git a/resources/vue/components/courseware/CoursewareManagerCopySelector.vue b/resources/vue/components/courseware/CoursewareManagerCopySelector.vue deleted file mode 100644 index 415e79a81bc916a7357c17c8d82dd5c5e4b4441f..0000000000000000000000000000000000000000 --- a/resources/vue/components/courseware/CoursewareManagerCopySelector.vue +++ /dev/null @@ -1,238 +0,0 @@ -<template> - <div class="cw-manager-copy-selector"> - <div v-if="sourceEmpty" class="cw-manager-copy-selector-source"> - <button class="button" @click="selectSource('own'); loadOwnCourseware()"><translate>Aus persönlichen Lernmaterialien kopieren</translate></button> - <button class="button" @click="selectSource('remote')"><translate>Aus Veranstaltung kopieren</translate></button> - </div> - <div v-else> - <courseware-companion-box v-if="copyAllInProgress" :msgCompanion="copyAllInProgressText" mood="pointing" /> - <button class="button" @click="reset"><translate>Quelle auswählen</translate></button> - <button v-show="!sourceOwn && hasRemoteCid" class="button" @click="selectNewCourse"><translate>Veranstaltung auswählen</translate></button> - <button v-show="!sourceOwn && hasRemoteCid" class="button" @click="mergeContent"><translate>Alle Inhalte kopieren</translate></button> - <div v-if="sourceRemote"> - <h2 v-if="!hasRemoteCid"><translate>Veranstaltungen</translate></h2> - <ul v-if="!hasRemoteCid && semesterMap.length > 0"> - <li v-for="semester in semesterMap" :key="semester.id"> - <h3>{{semester.attributes.title}}</h3> - <ul> - <li v-for="course in coursesBySemester(semester)" :key="course.id"> - <a - href="#" - class="cw-manager-copy-selector-course" - @click="loadRemoteCourseware(course.id)" - > - <studip-icon :shape="getCourseIcon(course)" /> - {{course.attributes.title}} - </a> - </li> - </ul> - </li> - </ul> - <courseware-companion-box - v-if="!hasRemoteCid && semesterMap.length === 0" - :msgCompanion="$gettext('Es wurden keine Veranstaltung mit Courseware-Inhalten gefunden.')" - mood="sad" - /> - <courseware-manager-element - v-if="remoteId !== '' && hasRemoteCid" - type="remote" - :currentElement="remoteElement" - @selectElement="setRemoteId" - @loadSelf="loadSelf" - @reloadElement="reloadElement" - /> - <courseware-companion-box - v-if="remoteId === '' && hasRemoteCid" - :msgCompanion="$gettext('In dieser Veranstaltung wurden noch keine Inhalte angelegt.')" - mood="sad" - /> - </div> - <div v-if="sourceOwn"> - <courseware-manager-element - v-if="ownId !== ''" - type="own" - :currentElement="ownElement" - @selectElement="setOwnId" - @loadSelf="loadSelf" - /> - <courseware-companion-box - v-else - :msgCompanion="$gettext('Sie haben noch keine eigenen Inhalte angelegt.')" - mood="sad" - /> - </div> - </div> - </div> -</template> - -<script> -import CoursewareManagerElement from './CoursewareManagerElement.vue'; -import { mapActions, mapGetters } from 'vuex'; -import CoursewareCompanionBox from './CoursewareCompanionBox.vue'; - -export default { - name: 'courseware-manager-copy-selector', - components:{ - CoursewareManagerElement, - CoursewareCompanionBox, - }, - props: {}, - data() { - return { - source: '', - courses: [], - remoteCid: '', - remoteCoursewareInstance: {}, - remoteId: '', - remoteElement: {}, - ownCoursewareInstance: {}, - ownId: '', - ownElement: {}, - semesterMap: [], - copyAllInProgress: false, - copyAllInProgressText: '' - } - }, - computed: { - ...mapGetters({ - courseware: 'courseware', - semesterById: 'semesters/byId', - structuralElementById: 'courseware-structural-elements/byId', - userId: 'userId', - }), - sourceEmpty() { - return this.source === ''; - }, - sourceOwn() { - return this.source === 'own'; - }, - sourceRemote() { - return this.source === 'remote'; - }, - hasRemoteCid() { - return this.remoteCid !== ''; - }, - loadedCourses() { - return [...this.courses].sort((a, b) => a.attributes.title > b.attributes.title); - } - }, - methods: { - ...mapActions({ - loadAnotherCourseware: 'courseware-structure/loadAnotherCourseware', - loadUsersCourses: 'loadUsersCourses', - loadStructuralElement: 'loadStructuralElement', - loadSemester: 'semesters/loadById', - copyStructuralElement: 'copyStructuralElement', - loadElement: 'courseware-structural-elements/loadById', - - }), - selectSource(source) { - this.source = source; - this.copyAllInProgress = false; - }, - async loadRemoteCourseware(cid) { - this.remoteCid = cid; - this.remoteCoursewareInstance = await this.loadAnotherCourseware({ id: this.remoteCid, type: 'courses'}); - if (this.remoteCoursewareInstance !== null) { - this.setRemoteId(this.remoteCoursewareInstance.relationships.root.data.id); - } else { - this.remoteId = ''; - } - }, - async loadOwnCourseware() { - this.ownCoursewareInstance = await this.loadAnotherCourseware({ id: this.userId, type: 'users' }); - if (this.ownCoursewareInstance !== null) { - this.setOwnId(this.ownCoursewareInstance.relationships.root.data.id); - } else { - this.ownId = ''; - } - }, - reset() { - this.selectSource(''); - this.remoteCid = ''; - this.copyAllInProgress = false; - }, - selectNewCourse() { - this.remoteCid = ''; - this.remoteId = ''; - this.copyAllInProgress = false; - }, - async setRemoteId(target) { - this.remoteId = target; - await this.loadStructuralElement(this.remoteId); - this.initRemote(); - }, - initRemote() { - this.remoteElement = this.structuralElementById({ id: this.remoteId }); - }, - async setOwnId(target) { - this.ownId = target; - await this.loadStructuralElement(this.ownId); - this.initOwn(); - }, - initOwn() { - this.ownElement = this.structuralElementById({ id: this.ownId }); - }, - loadSelf(data) { - this.$emit('loadSelf', data); - }, - loadSemesterMap() { - let view = this; - let semesters = []; - this.courses.every(course => { - let semId = course.relationships['start-semester'].data.id; - if(!semesters.includes(semId)) { - semesters.push(semId); - } - return true; - }); - semesters.every(semester => { - view.loadSemester({id: semester}).then( () => { - view.semesterMap.push(view.semesterById({id: semester})); - view.semesterMap.sort((a, b) => a.attributes.start < b.attributes.start); - }); - return true; - }); - }, - coursesBySemester(semester) { - return this.loadedCourses.filter(course => { - return course.relationships['start-semester'].data.id === semester.id} - ); - }, - getCourseIcon(course) { - if (course.attributes['course-type'] === 99) { - return 'studygroup'; - } - - return 'seminar'; - }, - reloadElement() { - this.$emit("reloadElement"); - }, - async mergeContent() { - this.copyAllInProgressText = this.$gettext('Inhalte werden kopiert…'); - this.copyAllInProgress = true; - let parentId = this.courseware.relationships.root.data.id; //current root - let elementId = this.remoteCoursewareInstance.relationships.root.data.id; // remote root - try { - await this.copyStructuralElement({ - parentId: parentId, - elementId: elementId, - migrate: true - }); - } catch(error) { - console.debug(error); - this.copyAllInProgressText = this.$gettext('Beim Kopiervorgang sind Fehler aufgetreten.'); - } - await this.loadElement({id: parentId, options: {include: 'children'}}) - this.copyAllInProgressText = this.$gettext('Kopiervorgang abgeschlossen.'); - - } - }, - async mounted() { - this.courses = await this.loadUsersCourses({ userId: this.userId, withCourseware: true }); - this.loadSemesterMap(); - } - -} -</script> diff --git a/resources/vue/components/courseware/CoursewareManagerElement.vue b/resources/vue/components/courseware/CoursewareManagerElement.vue deleted file mode 100644 index 232d21c053e52b341d07e6a89498acf433e1d73c..0000000000000000000000000000000000000000 --- a/resources/vue/components/courseware/CoursewareManagerElement.vue +++ /dev/null @@ -1,654 +0,0 @@ -<template> - <div class="cw-manager-element"> - <div v-if="currentElement"> - <courseware-companion-box v-if="insertingInProgress" :msgCompanion="text.inProgress" mood="pointing" /> - <courseware-companion-box v-if="copyingFailed && !insertingInProgress" :msgCompanion="copyProcessFailedMessage" mood="sad" /> - <div class="cw-manager-element-title"> - <nav aria-label="Breadcrumb" class="cw-manager-element-breadcrumb"> - <a - v-for="element in breadcrumb" - :key="element.id" - :title="element.attributes.title" - href="#" - class="cw-manager-element-breadcrumb-item" - @click="selectChapter(element.id)" - > - {{ element.attributes.title }} - </a> - </nav> - <header> - <button class="cw-insert-element" - v-if="elementInserterActive && moveSelfPossible && canEdit" - :title="elementTitle" - @click="insertElement({element: currentElement, source: type})" - > - <studip-icon shape="arr_2left" size="24" role="clickable" /> - </button> - {{ elementName }} - </header> - </div> - <courseware-collapsible-box - v-if="!elementsOnly" - :open="true" - :title="$gettext('Abschnitt')" - class="cw-manager-element-containers" - > - <div v-if="canSortContainers"> - <button v-show="!sortContainersActive && isCurrent" class="button sort" @click="sortContainers"> - <translate>Abschnitte sortieren</translate> - </button> - <button v-show="sortContainersActive && isCurrent" class="button accept" @click="storeContainersSort"> - <translate>Sortieren beenden</translate> - </button> - <button v-show="sortContainersActive && isCurrent" class="button cancel" @click="resetContainersSort"> - <translate>Sortieren abbrechen</translate> - </button> - </div> - <p v-if="!hasContainers"> - <translate>Dieses Element enthält keine Abschnitte.</translate> - </p> - <transition-group name="cw-sort-ease" tag="div"> - <courseware-manager-container - v-for="(container, index) in sortArrayContainers" - :key="container.id" - :container="container" - :isCurrent="isCurrent" - :sortContainers="sortContainersActive" - :inserter="containerInserterActive && moveSelfChildPossible" - :elementType="type" - :blockInserter="blockInserterActive" - :canMoveUp="index !== 0" - :canMoveDown="index+1 !== sortArrayContainers.length" - @insertContainer="insertContainer" - @insertBlock="insertBlock" - @moveUp="moveUpContainer" - @moveDown="moveDownContainer" - /> - </transition-group> - <courseware-manager-filing - v-if="isCurrent && !sortContainersActive && canEdit" - :parentId="currentElement.id" - :parentItem="currentElement" - itemType="container" - /> - </courseware-collapsible-box> - <courseware-collapsible-box :open="true" :title="$gettext('Unterseiten')" class="cw-manager-element-subchapters"> - <div v-if="canSortChildren"> - <button v-show="!sortChildrenActive && isCurrent" class="button sort" @click="sortChildren"> - <translate>Unterseiten sortieren</translate> - </button> - <button v-show="sortChildrenActive && isCurrent" class="button accept" @click="storeChildrenSort"> - <translate>Sortieren beenden</translate> - </button> - <button v-show="sortChildrenActive && isCurrent" class="button cancel" @click="resetChildrenSort"> - <translate>Sortieren abbrechen</translate> - </button> - </div> - <p v-if="!hasChildren"> - <translate>Dieses Element enthält keine Unterseiten.</translate> - </p> - <transition-group name="cw-sort-ease" tag="div"> - <courseware-manager-element-item - v-for="(child, index) in sortArrayChildren" - :key="child.id" - :element="child" - :sortChapters="sortChildrenActive" - :inserter="elementInserterActive && moveSelfChildPossible && filingData.parentItem.id !== child.id" - :type="type" - :canMoveUp="index !== 0" - :canMoveDown="index+1 !== sortArrayChildren.length" - @selectChapter="selectChapter" - @insertElement="insertElement" - @moveUp="moveUpChild" - @moveDown="moveDownChild" - /> - </transition-group> - <courseware-manager-filing - v-if="isCurrent && !sortChildrenActive && canEdit" - :parentId="currentElement.id" - :parentItem="currentElement" - itemType="element" - /> - </courseware-collapsible-box> - </div> - </div> -</template> - -<script> -import StudipIcon from '../StudipIcon.vue'; -import CoursewareCollapsibleBox from './CoursewareCollapsibleBox.vue'; -import CoursewareManagerContainer from './CoursewareManagerContainer.vue'; -import CoursewareManagerElementItem from './CoursewareManagerElementItem.vue'; -import CoursewareManagerFiling from './CoursewareManagerFiling.vue'; -import CoursewareCompanionBox from './CoursewareCompanionBox.vue'; -import { mapActions, mapGetters } from 'vuex'; -import { forEach } from 'jszip'; - -export default { - name: 'courseware-manager-element', - components: { - CoursewareCollapsibleBox, - CoursewareManagerContainer, - CoursewareManagerElementItem, - CoursewareManagerFiling, - CoursewareCompanionBox, - StudipIcon, - }, - props: { - type: { - validator(value) { - return ['current', 'self', 'remote', 'own', 'import', 'link'].includes(value); - }, - }, - remoteCoursewareRangeId: String, - currentElement: Object, - moveSelfPossible: { - default: true - }, - moveSelfChildPossible: { - default: true - }, - elementsOnly: { - default: false - } - }, - data() { - return { - elementInserterActive: false, - containerInserterActive: false, - blockInserterActive: false, - sortChildrenActive: false, - sortContainersActive: false, - sortArrayChildren: [], - discardStateArrayChildren: [], - sortArrayContainers: [], - discardStateArrayContainers: [], - insertingInProgress: false, - copyingFailed: false, - linkingFailed: false, - text: { - inProgress: this.$gettext('Vorgang läuft. Bitte warten Sie einen Moment.'), - copyProcessFailed: [], - linkProcessFailed: [], - }, - }; - }, - computed: { - ...mapGetters({ - childrenById: 'courseware-structure/children', - containerById: 'courseware-containers/byId', - filingData: 'filingData', - structuralElementById: 'courseware-structural-elements/byId', - }), - isCurrent() { - return this.type === 'current'; - }, - isSelf() { - return this.type === 'self'; - }, - isRemote() { - return this.type === 'remote'; - }, - isImport() { - return this.type === 'import'; - }, - isOwn() { - return this.type === 'own'; - }, - isSorting() { - return this.sortChildrenActive || this.sortContainersActive || this.sortBlocksActive; - }, - canEdit() { - if (this.currentElement.attributes) { - return this.currentElement.attributes['can-edit']; - } else { - return false; - } - }, - breadcrumb() { - if (!this.currentElement) { - return []; - } - - const finder = (parent) => { - const parentId = parent.relationships?.parent?.data?.id; - if (!parentId) { - return null; - } - const element = this.structuralElementById({id: parentId}); - if (!element) { - console.error("CoursewareManagerElement#breadcrumb: Could not find parent by ID."); - } - - return element; - }; - - const visitAncestors = function* (node) { - const parent = finder(node); - if (parent) { - yield parent; - yield *visitAncestors(parent); - } - }; - - return [...visitAncestors(this.currentElement)].reverse() - }, - elementName() { - if (this.currentElement.attributes) { - return this.currentElement.attributes.title - } - - return ''; - }, - elementTitle() { - let title = this.elementName; - if (this.elementInserterActive && this.moveSelfPossible && this.canEdit) { - if (this.isRemote || this.isOwn) { - title = this.$gettextInterpolate( - this.$gettext('%{ elementTitle } kopieren'), - {elementTitle: this.elementName} - ); - } else { - title = this.$gettextInterpolate( - this.$gettext('%{ elementTitle } verschieben'), - {elementTitle: this.elementName} - ); - } - } - - return title; - }, - hasChildren() { - if (this.children === null) { - return false; - } else { - return this.children.length >= 1; - } - }, - canSortChildren() { - if (this.children === null) { - return false; - } else { - return this.children.length > 1 && this.canEdit; - } - }, - hasContainers() { - if (this.containers === null) { - return false; - } else { - return this.containers.length >= 1; - } - }, - canSortContainers() { - if (this.containers === null) { - return false; - } else { - return this.containers.length > 1 && this.canEdit; - } - }, - emptyContainers() { - if (this.containers === null) { - return true; - } else { - return this.containers.length === 0; - } - }, - containers() { - if (!this.currentElement || !this.currentElement.relationships) { - return []; - } - - return this.currentElement.relationships.containers.data.map(({id}) => this.containerById({ id })); - }, - children() { - if (!this.currentElement) { - return []; - } - - return this.childrenById(this.currentElement.id) - .map((id) => this.structuralElementById({ id })) - .filter(Boolean); - }, - copyProcessFailedMessage() { - let message = this.$gettext('Der Kopiervorgang ist fehlgeschlagen.'); - if (this.text.copyProcessFailed.length) { - message = this.text.copyProcessFailed.join('<br>'); - } - return message; - } - }, - methods: { - ...mapActions({ - createStructuralElement: 'createStructuralElement', - updateStructuralElement: 'updateStructuralElement', - deleteStructuralElement: 'deleteStructuralElement', - copyStructuralElement: 'copyStructuralElement', - linkStructuralElement: 'linkStructuralElement', - loadStructuralElement: 'loadStructuralElement', - loadContainer: 'loadContainer', - updateContainer: 'updateContainer', - deleteContainer: 'deleteContainer', - copyContainer: 'copyContainer', - updateBlock: 'updateBlock', - deleteBlock: 'deleteBlock', - copyBlock: 'copyBlock', - lockObject: 'lockObject', - unlockObject: 'unlockObject', - sortContainersInStructualElements: 'sortContainersInStructualElements', - sortChildrenInStructualElements: 'sortChildrenInStructualElements', - setFilingData: 'cwManagerFilingData', - }), - - selectChapter(target) { - this.resetFilingData(); - this.$emit('selectElement', target); - }, - - validateSource(source) { - return ['self', 'remote', 'own', 'link'].includes(source); - }, - - afterInsertCompletion() { - this.$nextTick(() => { - // will run after $emit is done - this.resetFilingData(); - setTimeout(() => { - this.insertingInProgress = false; - }, 250); - }); - }, - - resetFilingData() { - this.setFilingData({}); - }, - - showFailedCopyProcessCompanion() { - this.copyingFailed = true; - this.insertingInProgress = false; - }, - - showFailedLinkProcessCompanion() { - this.linkingFailed = true; - this.insertingInProgress = false; - }, - - async insertElement(data) { - let source = data.source; - let element = data.element; - - if (!this.validateSource(source)) { - console.log('unreliable source:'); - console.log(source); - console.log(element); - return; - } - if(!this.insertingInProgress) { - this.insertingInProgress = true; - if (source === 'self') { - element.relationships.parent.data.id = this.filingData.parentItem.id; - element.attributes.position = this.childrenById(this.filingData.parentItem.id).length + 1; - await this.lockObject({ id: element.id, type: 'courseware-structural-elements' }); - await this.updateStructuralElement({ - element: element, - id: element.id, - }); - await this.unlockObject({ id: element.id, type: 'courseware-structural-elements' }); - this.loadStructuralElement(this.currentElement.id); - this.$emit('reloadElement'); - } - if (source === 'remote' || source === 'own') { - //create Element - let parentId = this.filingData.parentItem.id; - await this.copyStructuralElement({ - parentId: parentId, - elementId: element.id, - migrate: false - }).catch((error) => { - let message = this.$gettextInterpolate( - this.$gettext('%{ pageTitle } konnte nicht kopiert werden.'), - {pageTitle: element.attributes.title} - ); - this.text.copyProcessFailed.push(message); - this.showFailedCopyProcessCompanion(); - }); - this.$emit('loadSelf', parentId); - } - if (source === 'link') { - let parentId = this.filingData.parentItem.id; - await this.linkStructuralElement({ - parentId: parentId, - elementId: element.id, - }).catch((error) => { - let message = this.$gettextInterpolate( - this.$gettext('%{ pageTitle } konnte nicht verknüpft werden.'), - {pageTitle: element.attributes.title} - ); - this.text.linkProcessFailed.push(message); - this.showFailedLinkProcessCompanion(); - }); - this.$emit('loadSelf', parentId); - } - this.afterInsertCompletion(); - } - }, - async insertContainer(data) { - let source = data.source; - let container = data.container; - - if (!this.validateSource(source)) { - console.log('unreliable source:'); - console.log(source); - console.log(container); - return; - } - if(!this.insertingInProgress) { - this.insertingInProgress = true; - if (source === 'self') { - container.relationships['structural-element'].data.id = this.filingData.parentItem.id; - container.attributes.position = this.filingData.parentItem.relationships.containers.data.length + 1; - await this.lockObject({id: container.id, type: 'courseware-containers'}); - await this.updateContainer({ - container: container, - structuralElementId: this.currentElement.id - }); - await this.unlockObject({id: container.id, type: 'courseware-containers'}); - this.$emit('reloadElement'); - } else if (source === 'remote' || source === 'own') { - let parentId = this.filingData.parentItem.id; - await this.copyContainer({ - parentId: parentId, - container: container, - }).catch((error) => { - let message = this.$gettextInterpolate( - this.$gettext('Abschnitt "%{ containerTitle }" konnte nicht kopiert werden'), - {containerTitle: container.attributes.title} - ); - this.text.copyProcessFailed.push(message); - this.showFailedCopyProcessCompanion(); - }); - this.$emit('loadSelf', parentId); - } - this.afterInsertCompletion(); - } - - }, - async insertBlock(data) { - let source = data.source; - let block = data.block; - - if (!this.validateSource(source)) { - console.debug('unreliable source:', source, block); - return; - } - - if(!this.insertingInProgress) { - this.insertingInProgress = true; - if (source === 'self') { - block.relationships.container.data.id = this.filingData.parentItem.id; - block.attributes.position = this.filingData.parentItem.relationships.blocks.data.length + 1; - - let sourceContainer = await this.containerById({id: block.relationships.container.data.id}); - sourceContainer.attributes.payload.sections.forEach(section => { - let index = section.blocks.indexOf(block.id); - if(index !== -1) { - section.blocks.splice(index, 1); - } - }); - await this.lockObject({id: sourceContainer.id, type: 'courseware-containers'}); - await this.updateContainer({ - container: sourceContainer, - structuralElementId: sourceContainer.relationships['structural-element'].data.id - }); - await this.unlockObject({id: sourceContainer.id, type: 'courseware-containers'}); - - let destinationContainer = await this.containerById({id: this.filingData.parentItem.id}); - destinationContainer.attributes.payload.sections[destinationContainer.attributes.payload.sections.length-1].blocks.push(block.id); - await this.lockObject({id: destinationContainer.id, type: 'courseware-containers'}); - await this.updateContainer({ - container: destinationContainer, - structuralElementId: destinationContainer.relationships['structural-element'].data.id - }); - await this.unlockObject({id: destinationContainer.id, type: 'courseware-containers'}); - - await this.lockObject({id: block.id, type: 'courseware-blocks'}); - await this.updateBlock({ - block: block, - containerId: this.filingData.parentItem.id - }); - await this.unlockObject({id: block.id, type: 'courseware-blocks'}); - await this.loadContainer(sourceContainer.id); - await this.loadContainer(destinationContainer.id); - this.$emit('reloadElement'); - } else if (source === 'remote' || source === 'own') { - let parentId = this.filingData.parentItem.id; - await this.copyBlock({ - parentId: parentId, - block: block, - }).catch((error) => { - let message = this.$gettextInterpolate( - this.$gettext('Block "%{ blockTitle }" konnte nicht kopiert werden'), - {blockTitle: block.attributes.title} - ); - this.text.copyProcessFailed.push(message); - this.showFailedCopyProcessCompanion(); - }); - await this.loadContainer(parentId); - this.$emit('loadSelf',this.filingData.parentItem.relationships['structural-element'].data.id); - } - this.afterInsertCompletion(); - } - }, - - sortChildren() { - this.discardStateArrayChildren = [...this.children]; //copy array because of watcher? - this.sortChildrenActive = true; - }, - sortContainers() { - this.discardStateArrayContainers = [...this.containers]; - this.sortContainersActive = true; - }, - - storeChildrenSort() { - this.sortChildrenInStructualElements({parent: this.currentElement, children: this.sortArrayChildren}); - - this.discardStateArrayChildren = []; - this.sortChildrenActive = false; - }, - resetChildrenSort() { - this.sortArrayChildren = this.discardStateArrayChildren; - this.sortChildrenActive = false; - }, - - storeContainersSort() { - this.sortContainersInStructualElements({structuralElement: this.currentElement, containers: this.sortArrayContainers}); - - this.discardStateArrayContainers = []; - this.sortContainersActive = false; - }, - resetContainersSort() { - this.sortArrayContainers = this.discardStateArrayContainers; - this.sortContainersActive = false; - }, - - moveUpChild(childId) { - this.moveUp(childId, this.sortArrayChildren); - }, - moveDownChild(childId) { - this.moveDown(childId, this.sortArrayChildren); - }, - moveUpContainer(containerId) { - this.moveUp(containerId, this.sortArrayContainers); - }, - moveDownContainer(containerId) { - this.moveDown(containerId, this.sortArrayContainers); - }, - - moveUp(itemId, sortArray) { - sortArray.every((item, index) => { - if (item.id === itemId) { - if (index === 0) { - return false; - } - sortArray.splice(index - 1, 0, sortArray.splice(index, 1)[0]); - return false; - } else { - return true; - } - }); - }, - moveDown(itemId, sortArray) { - sortArray.every((item, index) => { - if (item.id === itemId) { - if (index === sortArray.length - 1) { - return false; - } - sortArray.splice(index + 1, 0, sortArray.splice(index, 1)[0]); - return false; - } else { - return true; - } - }); - }, - updateFilingData(data) { - if (Object.keys(data).length !== 0) { - switch (data.itemType) { - case 'element': - this.elementInserterActive = true; - break; - case 'container': - this.containerInserterActive = true; - break; - case 'block': - this.blockInserterActive = true; - break; - } - this.copyingFailed = false; - this.text.copyProcessFailed = []; - this.linkingFailed = false; - this.text.linkProcessFailed = []; - } else { - this.elementInserterActive = false; - this.containerInserterActive = false; - this.blockInserterActive = false; - } - } - }, - mounted() { - this.updateFilingData(this.filingData); - }, - watch: { - filingData(newValue) { - if (!['self', 'remote', 'own', 'import', 'link'].includes(this.type)) { - return false; - } - this.updateFilingData(newValue); - }, - containers(newContainers) { - this.sortArrayContainers = newContainers; - }, - children(newChildren) { - this.sortArrayChildren = newChildren; - } - }, -}; -</script> diff --git a/resources/vue/components/courseware/CoursewareManagerElementItem.vue b/resources/vue/components/courseware/CoursewareManagerElementItem.vue deleted file mode 100644 index 7b827d175cc90dda360c405a4dc23f1df29f1675..0000000000000000000000000000000000000000 --- a/resources/vue/components/courseware/CoursewareManagerElementItem.vue +++ /dev/null @@ -1,141 +0,0 @@ -<template> - <div class="cw-manager-element-item-wrapper"> - <a - v-if="!sortChapters" - href="#" - class="cw-manager-element-item" - :class="[inserter ? 'cw-manager-element-item-inserter' : '']" - :title="elementTitle" - @click="clickItem"> - {{ element.attributes.title }} - <span v-if="task" class="cw-manager-element-item-solver-name">| {{ solverName }}</span> - </a> - <div - v-else - class="cw-manager-element-item cw-manager-element-item-sorting" - > - {{ element.attributes.title }} - <div v-if="sortChapters" class="cw-manager-element-item-buttons"> - <button :disabled="!canMoveUp" @click="moveUp" :title="$gettext('Element nach oben verschieben')"> - <studip-icon shape="arr_2up" role="sort" /> - </button> - <button :disabled="!canMoveDown" @click="moveDown" :title="$gettext('Element nach unten verschieben')"> - <studip-icon shape="arr_2down" role="sort" /> - </button> - </div> - </div> - </div> -</template> - -<script> -import { mapGetters, mapActions } from 'vuex'; - -export default { - name: 'courseware-manager-element-item', - props: { - element: Object, - inserter: Boolean, - sortChapters: Boolean, - type: String, - canMoveUp: Boolean, - canMoveDown: Boolean - }, - computed: { - ...mapGetters({ - taskById: 'courseware-tasks/byId', - userById: 'users/byId', - groupById: 'status-groups/byId', - }), - isTask() { - return this.element.attributes.purpose === 'task'; - }, - task() { - if (this.element.relationships.task.data) { - return this.taskById({ - id: this.element.relationships.task.data.id, - }); - } - - return null; - }, - solver() { - if (this.task) { - const solver = this.task.relationships.solver.data; - if (solver.type === 'users') { - return this.userById({ id: solver.id }); - } - if (solver.type === 'status-groups') { - return this.groupById({ id: solver.id }); - } - } - - return null; - }, - solverName() { - if (this.solver) { - if (this.solver.type === 'users') { - return this.solver.attributes['formatted-name']; - } - if (this.solver.type === 'status-groups') { - return this.solver.attributes.name; - } - } - - return ''; - }, - elementTitle() { - let title = this.element.attributes.title; - if (this.inserter) { - if (this.type === 'remote' || this.type === 'own') { - title = this.$gettextInterpolate( - this.$gettext('%{ elementTitle } kopieren'), - {elementTitle: this.element.attributes.title} - ); - } else { - title = this.$gettextInterpolate( - this.$gettext('%{ elementTitle } verschieben'), - {elementTitle: this.element.attributes.title} - ); - } - } - - return title; - } - }, - methods: { - ...mapActions({ - loadTask: 'loadTask', - }), - clickItem() { - if (this.sortChapters) { - return false; - } - if (this.inserter) { - this.$emit('insertElement', {element: this.element, source: this.type}); - } else { - this.$emit('selectChapter', this.element.id); - } - }, - moveUp() { - if (this.sortChapters) { - this.$emit('moveUp', this.element.id); - } - }, - moveDown() { - if (this.sortChapters) { - this.$emit('moveDown', this.element.id); - } - }, - loadElementTask() { - if (this.element.relationships.task.data && this.task === undefined) { - this.loadTask({ - taskId: this.element.relationships.task.data.id, - }); - } - } - }, - mounted() { - this.loadElementTask(); - }, -}; -</script> diff --git a/resources/vue/components/courseware/CoursewareManagerFiling.vue b/resources/vue/components/courseware/CoursewareManagerFiling.vue deleted file mode 100644 index 71aeb612e1cfccf4f49c30b4ba54b18f9db919c6..0000000000000000000000000000000000000000 --- a/resources/vue/components/courseware/CoursewareManagerFiling.vue +++ /dev/null @@ -1,71 +0,0 @@ -<template> - <button - class="cw-manager-filing" - :class="{ 'cw-manager-filing-active': active, 'cw-manager-filing-disabled': disabled }" - :aria-pressed="active" - @click="toggleFiling" - > - <span v-if="itemType === 'element'"><translate>Seite an dieser Stelle einfügen</translate> </span> - <span v-if="itemType === 'container'"><translate>Abschnitt an dieser Stelle einfügen</translate> </span> - <span v-if="itemType === 'block'"><translate>Block an dieser Stelle einfügen</translate> </span> - </button> -</template> - -<script> -import { mapActions, mapGetters } from 'vuex'; - -export default { - name: 'courseware-manager-filing', - props: { - parentId: String, - parentItem: Object, - itemType: String, // element || container || block - }, - data() { - return { - active: false, - disabled: false, - data: {}, - }; - }, - computed: { - ...mapGetters({ - filingData: 'filingData', - }), - }, - methods: { - ...mapActions({ - cwManagerFilingData: 'cwManagerFilingData' - }), - toggleFiling() { - if (this.disabled) { - return false; - } - if (this.active) { - this.cwManagerFilingData({}); - } else { - this.cwManagerFilingData({ parentId: this.parentId, itemType: this.itemType, parentItem: this.parentItem }); - } - }, - }, - watch: { - filingData(newValue, oldValue) { - if (Object.keys(newValue).length !== 0) { - if (newValue.parentId === this.parentId && newValue.itemType === this.itemType) { - this.active = true; - } else { - this.disabled = true; - } - } else { - this.active = false; - this.disabled = false; - if (Object.keys(oldValue).length !== 0) { - if (oldValue.parentId === this.parentId && oldValue.itemType === this.itemType) { - this.$emit('deactivated'); - } - } - } - }, - }, -}; -</script> diff --git a/resources/vue/components/courseware/CoursewareManagerImport.vue b/resources/vue/components/courseware/CoursewareManagerImport.vue deleted file mode 100644 index 6fd3b476be23930c9f6431642e56b90d58d90e24..0000000000000000000000000000000000000000 --- a/resources/vue/components/courseware/CoursewareManagerImport.vue +++ /dev/null @@ -1,194 +0,0 @@ -<template> - <div> - <courseware-companion-box v-show="!importRunning && importDone && importErrors.length === 0" :msgCompanion="$gettext('Import erfolgreich!')" mood="special"/> - <courseware-companion-box v-show="!importRunning && importDone && importErrors.length > 0" :msgCompanion="$gettext('Import abgeschlossen. Es sind Fehler aufgetreten!')" mood="unsure"/> - <courseware-companion-box v-show="!importRunning && !importDone && importErrors.length > 0" :msgCompanion="$gettext('Import fehlgeschlagen. Es sind Fehler aufgetreten!')" mood="sad"/> - <courseware-companion-box v-show="importRunning" :msgCompanion="$gettext('Import läuft. Bitte verlassen Sie die Seite nicht bis der Import abgeschlossen wurde.')" mood="pointing"/> - <form class="default" @submit.prevent=""> - - <fieldset v-show="importRunning"> - <legend><translate>Import läuft...</translate></legend> - <div v-if="importRunning" class="cw-import-zip"> - <header><translate>Importiere Dateien</translate>:</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 && importRunning" class="cw-import-zip"> - <header><translate>Importiere Elemente</translate>:</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><translate>Fehlermeldungen</translate></legend> - <ul> - <li v-for="(error, index) in importErrors" :key="index"> {{error}} </li> - </ul> - </fieldset> - <fieldset v-show="!importRunning"> - <legend><translate>Import</translate></legend> - <label> - <translate>Importdatei</translate> - <input class="cw-file-input" ref="importFile" type="file" accept=".zip" @change="setImport" /> - </label> - <label> - <translate>Importverhalten</translate> - <select v-model="importBehavior"> - <option value="default"><translate>Inhalte anhängen</translate></option> - <option value="migrate"><translate>Inhalte zusammenführen</translate></option> - </select> - </label> - </fieldset> - <footer v-show="!importRunning"> - <button - class="button" - @click.prevent="doImportCourseware" - :disabled="!importZip" - > - <translate>Importieren</translate> - </button> - </footer> - </form> - </div> -</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-manager-import', - components: { - CoursewareCompanionBox, - }, - mixins: [CoursewareImport], - data() { - return { - importBehavior: 'default', - importRunning: false, - importZip: null, - zip: null - } - }, - computed: { - ...mapGetters({ - courseware: 'courseware', - 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({ - loadCoursewareStructure: 'courseware-structure/load', - setImportFilesProgress: 'setImportFilesProgress', - setImportStructuresProgress: 'setImportStructuresProgress', - setImportErrors: 'setImportErrors', - }), - - setImport(event) { - this.importZip = event.target.files[0]; - this.setImportFilesProgress(0); - this.setImportStructuresProgress(0); - this.setImportErrors([]); - }, - - async doImportCourseware() { - if (this.importZip === null) { - return false; - } - - this.importRunning = true; - - let view = this; - - view.zip = new JSZip(); - - await view.zip.loadAsync(this.importZip).then(async function () { - let errors = []; - let missingFiles = false; - if (view.zip.file('courseware.json') === null) { - errors.push(view.$gettext('Das Archiv enthält keine courseware.json Datei.')); - missingFiles = true; - } - if (view.zip.file('files.json') === null) { - errors.push(view.$gettext('Das Archiv enthält keine files.json Datei.')); - missingFiles = true; - } - if (view.zip.file('data.xml') !== null) { - errors.push(view.$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) { - view.setImportErrors(errors); - return; - } - - let data = await view.zip.file('courseware.json').async('string'); - let courseware = null; - let data_files = await view.zip.file('files.json').async('string'); - let files = null; - let jsonErrors = false; - try { - courseware = JSON.parse(data); - } catch (error) { - jsonErrors = true; - errors.push(view.$gettext('Die Beschreibung der Courseware-Inhalte ist nicht valide.')); - errors.push(error); - } - try { - files = JSON.parse(data_files); - } catch (error) { - jsonErrors = true; - errors.push(view.$gettext('Die Beschreibung der Dateien ist nicht valide.')); - errors.push(error); - } - if (jsonErrors) { - view.setImportErrors(errors); - return; - } - - await view.loadCoursewareStructure(); - const rootId = view.courseware.relationships.root.data.id; - - await view.importCourseware(courseware, rootId, files, view.importBehavior); - }); - - this.importZip = null; - this.importRunning = false; - this.$refs.importFile.value = ''; - }, - - getFileSizeText(size) { - if (size / 1024 < 1000) { - return (size / 1024).toFixed(2) + ' kB'; - } else { - return (size / 1048576).toFixed(2) + ' MB'; - } - }, - }, - mounted() { - let view = this; - - window.onbeforeunload = function() { - return view.importRunning ? true : null - } - } -} -</script> diff --git a/resources/vue/components/courseware/CoursewareManagerLinkSelector.vue b/resources/vue/components/courseware/CoursewareManagerLinkSelector.vue deleted file mode 100644 index a735024a2adeeec32b4db1bebc1249580cef7585..0000000000000000000000000000000000000000 --- a/resources/vue/components/courseware/CoursewareManagerLinkSelector.vue +++ /dev/null @@ -1,74 +0,0 @@ -<template> - <div class="cw-manager-link-selector"> - <courseware-manager-element - v-if="ownId !== null" - type="link" - :currentElement="ownElement" - :elementsOnly="true" - @selectElement="setOwnId" - @loadSelf="loadSelf" - /> - </div> -</template> - -<script> -import CoursewareManagerElement from './CoursewareManagerElement.vue'; -import { mapActions, mapGetters } from 'vuex'; - -export default { - name: 'courseware-manager-link-selector', - components: { CoursewareManagerElement }, - - data() { - return { - ownCoursewareInstance: {}, - ownId: null, - ownElement: {}, - } - }, - - computed: { - ...mapGetters({ - courseware: 'courseware', - structuralElementById: 'courseware-structural-elements/byId', - userId: 'userId', - }), - }, - - methods: { - ...mapActions({ - loadAnotherCourseware: 'courseware-structure/loadAnotherCourseware', - loadStructuralElementById: 'courseware-structural-elements/loadById', - }), - async loadOwnCourseware() { - this.ownCoursewareInstance = await this.loadAnotherCourseware({ id: this.userId, type: 'users' }); - if (this.ownCoursewareInstance !== null) { - await this.setOwnId(this.ownCoursewareInstance.relationships.root.data.id); - } else { - this.ownId = ''; - } - }, - async setOwnId(target) { - this.ownId = target; - const options = { - include: 'children' - }; - await this.loadStructuralElementById({ id: this.ownId, options }); - this.initOwn(); - }, - initOwn() { - this.ownElement = this.structuralElementById({ id: this.ownId }); - }, - reloadElement() { - this.$emit('reloadElement'); - }, - loadSelf(data) { - this.$emit('loadSelf', data); - }, - }, - - async mounted() { - await this.loadOwnCourseware(); - }, -} -</script> diff --git a/resources/vue/components/courseware/CoursewareManagerTaskDistributor.vue b/resources/vue/components/courseware/CoursewareManagerTaskDistributor.vue deleted file mode 100644 index 7189fa3c797d94677752c2aaf78656ba05ccef9c..0000000000000000000000000000000000000000 --- a/resources/vue/components/courseware/CoursewareManagerTaskDistributor.vue +++ /dev/null @@ -1,355 +0,0 @@ -<template> - <div class="cw-manager-task-distributor"> - <form class="default" @submit.prevent=""> - <fieldset> - <legend><translate>Aufgabe</translate></legend> - <label> - <translate>Aufgabentitel</translate> - <input type="text" v-model="taskTitle" /> - </label> - <label> - <translate>Aufgabenvorlage</translate> - <select v-model="selectedElementId"> - <option value="" disabled> - <translate>wählen Sie eine Vorlage aus</translate> - </option> - <option v-for="template in taskTemplates" :key="template.id" :value="template.id"> - {{ template.attributes.title }} - </option> - </select> - </label> - <label> - <translate>Abgabefrist</translate> - <input type="date" v-model="submissionDate" /> - </label> - <label> - <translate>Inhalte ergänzen</translate> - <select class="size-s" v-model="solverMayAddBlocks"> - <option value="true"><translate>ja</translate></option> - <option value="false"><translate>nein</translate></option> - </select> - </label> - <label> - <translate>Typ</translate> - <select v-model="taskSolverType"> - <option value="autor"><translate>für Studierende</translate></option> - <option value="group"><translate>für Gruppen</translate></option> - </select> - </label> - </fieldset> - <fieldset v-show="taskSolverType === 'autor'" class="cw-manager-task-distributor-task-solvers"> - <legend><translate>Aufgabe Studierenden zuweisen</translate></legend> - <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><translate>Name</translate></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> - </fieldset> - <fieldset v-show="taskSolverType === 'group'" class="cw-manager-task-distributor-task-solvers"> - <legend><translate>Aufgabe Gruppen zuweisen</translate></legend> - <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><translate>Gruppenname</translate></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> - </fieldset> - <footer> - <button class="button" name="create_task" :disabled="!targetSelected" @click="createTask"> - <translate>Aufgabe verteilen</translate> - </button> - <span - v-if="!targetSelected" - class="tooltip tooltip-icon " - :data-tooltip="$gettext('Bitte wählen aus, an welcher Stelle die Aufgabe eingefügt werden soll.')" - tabindex="0" - title="" - > - </span> - </footer> - </form> - </div> -</template> - -<script> -import { mapActions, mapGetters } from 'vuex'; -import CoursewareCompanionBox from './CoursewareCompanionBox.vue'; - -export default { - name: 'courseware-manager-task-distributor', - components: { - CoursewareCompanionBox, - }, - data() { - return { - ownCoursewareInstance: null, - ownCoursewareElements: [], - taskSolverType: 'autor', - selectedElementId: '', - selectedAutors: [], - bulkSelectAutors: false, - selectedGroups: [], - bulkSelectGroups: false, - taskTitle: '', - submissionDate: '', - solverMayAddBlocks: true, - }; - }, - computed: { - ...mapGetters({ - context: 'context', - userId: 'userId', - structuralElementById: 'courseware-structural-elements/byId', - relatedCourseMemberships: 'course-memberships/related', - relatedCourseStatusGroups: 'status-groups/related', - relatedUser: 'users/related', - filingData: 'filingData', - }), - 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; - }, - ownCoursewareRootId() { - if (this.ownCoursewareInstance !== null) { - return this.ownCoursewareInstance.relationships.root.data.id; - } else { - return ''; - } - }, - ownCoursewareRoot() { - if (this.ownCoursewareRootId !== '') { - return this.structuralElementById({ id: this.ownCoursewareRootId }); - } else { - return null; - } - }, - taskTemplates() { - let templates = this.ownCoursewareElements.filter((elem) => { - return elem.attributes.purpose === 'template'; - }); - - return templates; - }, - targetSelected() { - return this.filingData.itemType === 'element'; - }, - }, - methods: { - ...mapActions({ - loadCourseMemberships: 'course-memberships/loadRelated', - loadCourseStatusGroups: 'status-groups/loadRelated', - loadRemoteCoursewareStructure: 'loadRemoteCoursewareStructure', - loadStructuralElementById: 'courseware-structural-elements/loadById', - loadStructuralElement: 'loadStructuralElement', - createTaskGroup: 'createTaskGroup', - companionWarning: 'companionWarning', - companionSuccess: 'companionSuccess', - }), - async loadOwnCourseware() { - this.ownCoursewareInstance = await this.loadRemoteCoursewareStructure({ - rangeId: this.userId, - rangeType: 'users', - }); - await this.loadStructuralElementById({ id: this.ownCoursewareRootId, options: { include: 'children' } }); - let children = this.ownCoursewareRoot.relationships.children.data; - for (let i = 0; i < children.length; i++) { - this.ownCoursewareElements.push(this.structuralElementById({ id: children[i].id })); - } - }, - async createTask() { - if (!this.targetSelected) { - return; - } - - if (this.taskTitle === '') { - this.companionWarning({ - info: this.$gettext('Bitte wählen Sie einen Aufgabentitel aus.'), - }); - - return false; - } - if (this.selectedElementId.trim() === '') { - this.companionWarning({ - info: this.$gettext('Bitte wählen Sie eine Aufgabenvorlage aus.'), - }); - - return false; - } - if (this.submissionDate === '') { - this.companionWarning({ - info: this.$gettext('Bitte wählen Sie eine Abgabefrist aus.'), - }); - - return false; - } - if (!['autor', 'group'].includes(this.taskSolverType)) { - this.companionWarning({ - info: this.$gettext('Bitte wählen Sie aus, an wen die Aufgabe verteilt werden sollen.'), - }); - - return false; - } - if (this.taskSolverType === 'autor') { - if (this.selectedAutors.length === 0) { - this.companionWarning({ - info: this.$gettext('Bitte wählen Sie mindestens einen Studierenden aus.'), - }); - return false; - } - } - if (this.taskSolverType === 'group') { - if (this.selectedGroups.length === 0) { - this.companionWarning({ - info: this.$gettext('Bitte wählen Sie mindestens eine Gruppe aus.'), - }); - return 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.filingData.parentItem.id, - type: 'courseware-structural-elements', - }, - }, - 'task-template': { - data: { - id: this.selectedElementId, - 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.resetTask(); - - this.companionSuccess({ - info: this.$gettext('Aufgabe wurde verteilt.'), - }); - }, - resetTask() { - this.taskTitle = ''; - this.taskSolverType = 'autor'; - this.selectedElementId = ''; - this.submissionDate = ''; - this.solverMayAddBlocks = true; - this.bulkSelectAutors = false; - this.selectedAutors = []; - this.bulkSelectGroups = false; - this.selectedGroups = []; - } - }, - mounted() { - 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' }); - this.loadOwnCourseware(); - }, - watch: { - 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/CoursewareOblong.vue b/resources/vue/components/courseware/CoursewareOblong.vue deleted file mode 100644 index b1e601dea8aa99fca5cc19259051bc0b772d4f7f..0000000000000000000000000000000000000000 --- a/resources/vue/components/courseware/CoursewareOblong.vue +++ /dev/null @@ -1,44 +0,0 @@ -<template> - <div class="cw-oblong" :class="['cw-oblong-' + oblongSize]"> - <div class="cw-oblong-value"> - <slot name="oblongValue"></slot> - </div> - <div class="cw-oblong-description"> - <studip-icon v-if="icon" :shape="icon" role="info" :size="iconSize"></studip-icon>{{ name }} - </div> - </div> -</template> - -<script> -export default { - name: 'courseware-oblong', - props: { - icon: String, - name: String, - color: String, - size: String, - }, - computed: { - oblongSize() { - switch (this.size) { - case 'large': - return 'large'; - case 'small': - return 'small'; - default: - return 'small'; - } - }, - iconSize() { - switch (this.size) { - case 'large': - return 48; - case 'small': - return 24; - default: - return 24; - } - }, - }, -}; -</script> diff --git a/resources/vue/components/courseware/ManagerApp.vue b/resources/vue/components/courseware/ManagerApp.vue deleted file mode 100644 index 777c5af052f0e5a5bc78ca747e11f889c653ea9c..0000000000000000000000000000000000000000 --- a/resources/vue/components/courseware/ManagerApp.vue +++ /dev/null @@ -1,40 +0,0 @@ -<template> - <courseware-course-manager @reload="rebuildStructure"></courseware-course-manager> -</template> - -<script> -import CoursewareCourseManager from './CoursewareCourseManager.vue'; -import { mapActions, mapGetters } from 'vuex'; - -export default { - components: { CoursewareCourseManager }, - computed: { - ...mapGetters({ - courseware: 'courseware', - structuralElements: 'courseware-structural-elements/all', - }), - }, - methods: { - ...mapActions({ - buildStructure: 'courseware-structure/build', - invalidateStructureCache: 'courseware-structure/invalidateCache', - loadCoursewareStructure: 'courseware-structure/load', - }), - async rebuildStructure() { - // compute order of structural elements once more - await this.buildStructure(); - - // throw away stale cache - this.invalidateStructureCache(); - }, - }, - async mounted() { - await this.loadCoursewareStructure(); - }, - watch: { - async structuralElements(newElements, oldElements) { - this.rebuildStructure(); - }, - }, -}; -</script> diff --git a/resources/vue/courseware-manager-app.js b/resources/vue/courseware-manager-app.js deleted file mode 100644 index 8d6454a3a379aa3d5ab887cc2611cc8e5fea2727..0000000000000000000000000000000000000000 --- a/resources/vue/courseware-manager-app.js +++ /dev/null @@ -1,90 +0,0 @@ -import CoursewareModule from './store/courseware/courseware.module'; -import CoursewareStructureModule from './store/courseware/structure.module'; -import ManagerApp from './components/courseware/ManagerApp.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', - }, - }); - - const httpClient = getHttpClient(); - - const store = new Vuex.Store({ - modules: { - courseware: CoursewareModule, - 'courseware-structure': CoursewareStructureModule, - ...mapResourceModules({ - names: [ - 'courses', - 'course-memberships', - 'courseware-blocks', - 'courseware-block-comments', - 'courseware-block-feedback', - 'courseware-containers', - 'courseware-instances', - 'courseware-structural-elements', - 'courseware-task-groups', - 'courseware-tasks', - 'courseware-user-data-fields', - 'courseware-user-progresses', - 'files', - 'file-refs', - 'folders', - 'status-groups', - 'users', - 'institutes', - 'institute-memberships', - 'semesters', - 'sem-classes', - 'sem-types', - ], - httpClient: httpClient, - }), - }, - }); - - // get id of parent structural element - let entry_id = null; - let entry_type = null; - let elem; - - 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; - } - } - } - - store.dispatch('setUrlHelper', STUDIP.URLHelper); - store.dispatch('setUserId', 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, - }); - - const app = createApp({ - render: (h) => h(ManagerApp), - store, - }); - - app.$mount(element); - - return app; -}; - -export default mountApp; diff --git a/resources/vue/store/courseware/courseware.module.js b/resources/vue/store/courseware/courseware.module.js index 7c1f2fec6dd6d240fc688e94a43f4b76ce63f45a..5eb40a00855bb9764194d44e443ecb1fd04b3954 100644 --- a/resources/vue/store/courseware/courseware.module.js +++ b/resources/vue/store/courseware/courseware.module.js @@ -13,8 +13,7 @@ const getDefaultState = () => { httpClient: null, lastElement: null, msg: 'Dehydrated', - msgCompanionOverlay: - 'Hallo! Ich bin Ihr persönlicher Companion. Wussten Sie schon, dass Courseware jetzt noch einfacher zu bedienen ist?', + msgCompanionOverlay: '', styleCompanionOverlay: 'default', pluginManager: null, showCompanionOverlay: false, @@ -24,7 +23,6 @@ const getDefaultState = () => { userId: null, viewMode: 'read', dashboardViewMode: 'default', - filingData: {}, userIsTeacher: false, teacherStatusLoaded: false, @@ -170,9 +168,6 @@ const getters = { pluginManager(state) { return state.pluginManager; }, - filingData(state) { - return state.filingData; - }, showStructuralElementEditDialog(state) { return state.showStructuralElementEditDialog; }, @@ -1008,10 +1003,6 @@ export const actions = { return dispatch('loadStructuralElement', structuralElement.id); }, - cwManagerFilingData(context, msg) { - context.commit('cwManagerFilingDataSet', msg); - }, - async loadRelatedPaginated({ dispatch, rootGetters }, { type, parent, relationship, options }) { const limit = 100; let offset = 0; @@ -1431,10 +1422,6 @@ export const mutations = { state.pluginManager = pluginManager; }, - cwManagerFilingDataSet(state, data) { - state.filingData = data; - }, - setShowStructuralElementEditDialog(state, showEdit) { state.showStructuralElementEditDialog = showEdit; }, diff --git a/resources/vue/store/courseware/structure.module.js b/resources/vue/store/courseware/structure.module.js index 0eba83936a10253ead770e1208a372cc6f144739..230ea17350e7e9469d885251b2c619a0500aebcc 100644 --- a/resources/vue/store/courseware/structure.module.js +++ b/resources/vue/store/courseware/structure.module.js @@ -107,23 +107,6 @@ const actions = { return instance; }, - // load the structure of a specified courseware - async loadAnotherCourseware({ commit, dispatch, rootGetters }, context) { - const instance = await dispatch('loadInstance', context); - - const root = rootGetters['courseware-structural-elements/related']({ - parent: { id: instance.id, type: instance.type }, - relationship: 'root', - }); - if (!root) { - throw new Error(`Could not find root of courseware { id: ${instance.id}, type: ${instance.type}`); - } - - await dispatch('loadDescendants', { root }); - - return instance; - }, - loadInstance({ commit, dispatch, rootGetters }, context) { let parent = context; parent = {