diff --git a/resources/assets/stylesheets/scss/courseware.scss b/resources/assets/stylesheets/scss/courseware.scss index a4508514d0c9bb9e6a113a0e5d9e6ce25330a5cd..7a41ab33030780d084dcede52e86fcbe3429c3f3 100644 --- a/resources/assets/stylesheets/scss/courseware.scss +++ b/resources/assets/stylesheets/scss/courseware.scss @@ -5665,6 +5665,36 @@ w i z a r d e l e m e n t s } } } +form.default .courseware-structural-element-selector { + list-style: none; + padding-left: 0; + .courseware-structural-element-selector-item { + .radiobutton { + background: none; + border: none; + padding: 0; + } + a label { + cursor: pointer; + } + label { + display: inline-block; + margin-bottom: 0; + text-indent: 0; + vertical-align: middle; + } + img { + vertical-align: middle; + &.inactive { + opacity: 0.5; + } + } + ul { + list-style: none; + padding-left: 18px; + } + } +} /* * * * * * * * * * * * * * * * * * w i z a r d e l e m e n t s e n d * * * * * * * * * * * * * * * * * */ diff --git a/resources/vue/components/StudipWizardDialog.vue b/resources/vue/components/StudipWizardDialog.vue index 7f73c1e160b935d6b0989b66d2253fa3c5846322..5f0339bd7ce4cd3723b2413ef6cacd2805bc43b3 100644 --- a/resources/vue/components/StudipWizardDialog.vue +++ b/resources/vue/components/StudipWizardDialog.vue @@ -129,7 +129,7 @@ export default { }, width: { type: String, - default: '880' + default: '900' }, slots: { type: Array, diff --git a/resources/vue/components/courseware/CoursewareStructuralElementDialogCopy.vue b/resources/vue/components/courseware/CoursewareStructuralElementDialogCopy.vue index 8eda4d32583650550a0f7d5906ff33569b5e2aac..556d0085f50cf90fae6146464c1cf3e6568191a2 100644 --- a/resources/vue/components/courseware/CoursewareStructuralElementDialogCopy.vue +++ b/resources/vue/components/courseware/CoursewareStructuralElementDialogCopy.vue @@ -21,10 +21,10 @@ :aria-description="text.sourceSelf" /> <label v-if="inCourseContext" @click="source = 'self'" for="cw-element-copy-source-self"> - <div class="icon"><studip-icon shape="seminar" size="32"/></div> + <div class="icon"><studip-icon shape="seminar" :size="32"/></div> <div class="text">{{ text.sourceSelf }}</div> - <studip-icon shape="radiobutton-unchecked" size="24" class="unchecked" /> - <studip-icon shape="check-circle" size="24" class="check" /> + <studip-icon shape="radiobutton-unchecked" :size="24" class="unchecked" /> + <studip-icon shape="check-circle" :size="24" class="check" /> </label> <input id="cw-element-copy-source-courses" @@ -34,10 +34,10 @@ :aria-description="text.sourceCourses" /> <label @click="source = 'courses'" for="cw-element-copy-source-courses"> - <div class="icon"><studip-icon shape="seminar" size="32"/></div> + <div class="icon"><studip-icon shape="seminar" :size="32"/></div> <div class="text">{{ text.sourceCourses }}</div> - <studip-icon shape="radiobutton-unchecked" size="24" class="unchecked" /> - <studip-icon shape="check-circle" size="24" class="check" /> + <studip-icon shape="radiobutton-unchecked" :size="24" class="unchecked" /> + <studip-icon shape="check-circle" :size="24" class="check" /> </label> <input id="cw-element-copy-source-users" @@ -47,10 +47,10 @@ :aria-description="text.sourceUsers" /> <label @click="source = 'users'" for="cw-element-copy-source-users"> - <div class="icon"><studip-icon shape="content" size="32"/></div> + <div class="icon"><studip-icon shape="content" :size="32"/></div> <div class="text">{{ text.sourceUsers }}</div> - <studip-icon shape="radiobutton-unchecked" size="24" class="unchecked" /> - <studip-icon shape="check-circle" size="24" class="check" /> + <studip-icon shape="radiobutton-unchecked" :size="24" class="unchecked" /> + <studip-icon shape="check-circle" :size="24" class="check" /> </label> </fieldset> <template v-if="source === 'courses'"> @@ -75,7 +75,7 @@ > <template #open-indicator="selectAttributes"> <span v-bind="selectAttributes" - ><studip-icon shape="arr_1down" size="10" + ><studip-icon shape="arr_1down" :size="10" /></span> </template> <template #no-options="{}"> @@ -113,10 +113,10 @@ :aria-description="unit.element.attributes.title" /> <label :key="'label-' + unit.id" :for="'cw-element-copy-unit-' + unit.id"> - <div class="icon"><studip-icon shape="courseware" size="32"/></div> + <div class="icon"><studip-icon shape="courseware" :size="32"/></div> <div class="text">{{ unit.element.attributes.title }}</div> - <studip-icon shape="radiobutton-unchecked" size="24" class="unchecked" /> - <studip-icon shape="check-circle" size="24" class="check" /> + <studip-icon shape="radiobutton-unchecked" :size="24" class="unchecked" /> + <studip-icon shape="check-circle" :size="24" class="check" /> </label> </template> </fieldset> @@ -129,48 +129,17 @@ </template> <template v-slot:element> <form v-if="selectedUnit" class="default" @submit.prevent=""> - <fieldset class="radiobutton-set"> - <input id="cw-element-copy-element" type="radio" checked :aria-description="selectedElementTitle" /> - <label for="cw-element-copy-element" @click="e => e.preventDefault()"> - <div class="icon"><studip-icon shape="content2" size="32"/></div> - <div class="text">{{ selectedElementTitle }}</div> - <studip-icon shape="check-circle" size="24" class="check" /> - </label> - </fieldset> - <button - v-if="selectedElementParent" - class="button" - @click="selectElement(selectedElementParent.id)" - > - {{ $gettextInterpolate( - $gettext('zurück zu %{ parentTitle }'), - { parentTitle: selectedElementParentTitle } - ) }} - </button> - <fieldset> - <legend>{{ $gettext('Unterseiten') }}</legend> - <ul class="cw-element-selector-list"> - <li - v-for="child in children" - :key="child.id" - > - <button - class="cw-element-selector-item" - @click="selectElement(child.id)" - > - {{ child.attributes.title }} - </button> - </li> - <li v-if="children.length === 0"> - {{ $gettext('Es wurden keine Unterseiten gefunden') }} - </li> - </ul> - </fieldset> + <courseware-structural-element-selector + v-model="selectedElement" + :rootId="selectedUnitRootId" + :validateAncestors="true" + :targetId="currentElement" + /> </form> <courseware-companion-box - v-else - mood="pointing" - :msgCompanion="$gettext('Bitte wählen Sie ein Lernmaterial aus.')" + v-else + mood="pointing" + :msgCompanion="$gettext('Bitte wählen Sie ein Lernmaterial aus.')" /> </template> <template v-slot:edit> @@ -190,7 +159,7 @@ > <template #open-indicator="selectAttributes"> <span v-bind="selectAttributes" - ><studip-icon shape="arr_1down" size="10" + ><studip-icon shape="arr_1down" :size="10" /></span> </template> <template #no-options> @@ -222,6 +191,7 @@ <script> import CoursewareCompanionBox from './CoursewareCompanionBox.vue'; +import CoursewareStructuralElementSelector from './CoursewareStructuralElementSelector.vue'; import colorMixin from '@/vue/mixins/courseware/colors.js'; import StudipSelect from './../StudipSelect.vue'; import StudipWizardDialog from './../StudipWizardDialog.vue'; @@ -233,6 +203,7 @@ export default { mixins: [colorMixin], components: { CoursewareCompanionBox, + CoursewareStructuralElementSelector, StudipWizardDialog, StudipSelect, }, @@ -244,7 +215,7 @@ export default { { id: 2, valid: false, name: 'unit', title: this.$gettext('Lernmaterial'), icon: 'courseware', description: this.$gettext('Wählen Sie das Lernmaterial aus, in dem sich der zu kopierende Lerninhalt befindet.') }, { id: 3, valid: false, name: 'element', title: this.$gettext('Seite'), icon: 'content2', - description: this.$gettext('Wählen Sie die zu kopierende Seite aus. Vorausgewählt ist die oberste Seite des ausgewählten Lernmaterials. Unterseiten erreichen Sie über die Schaltflächen im Bereich "Unterseiten". Sie können über die "zurück zu" Schaltfläche das übergeordnete Element anwählen. Die ausgewählte Seite ist mit einem Kontrollhaken markiert.') }, + description: this.$gettext('Wählen Sie die zu kopierende Seite aus. Um Unterseiten anzuzeigen, klicken Sie auf den Seitennamen. Mit einem weiteren Klick werden die Unterseiten wieder zugeklappt.') }, { id: 4, valid: true, name: 'edit', title: this.$gettext('Anpassen'), icon: 'edit', description: this.$gettext('Sie können hier die Daten der zu kopierenden Seite anpassen. Eine Anpassung ist optional, Sie können die Seite auch unverändert kopieren.') }, ], @@ -472,7 +443,7 @@ export default { if (newUnit !== null) { this.wizardSlots[1].valid = true; await this.loadStructuralElement({id: this.selectedUnitRootId, options: {include: 'children'}}); - this.selectedElement = this.structuralElementById({id: this.selectedUnitRootId}); + this.selectedElement = null; } else { this.wizardSlots[1].valid = false; } diff --git a/resources/vue/components/courseware/CoursewareStructuralElementDialogLink.vue b/resources/vue/components/courseware/CoursewareStructuralElementDialogLink.vue index 38d3953527bfdb98a525ded1b41bedcbf2f4fcb4..78c6725255fa4573827ee20827fe371bac33e3cd 100644 --- a/resources/vue/components/courseware/CoursewareStructuralElementDialogLink.vue +++ b/resources/vue/components/courseware/CoursewareStructuralElementDialogLink.vue @@ -41,59 +41,24 @@ /> </template> <template v-slot:element> - <form class="default" @submit.prevent=""> - <template v-if="selectedUnit"> - <fieldset class="radiobutton-set"> - <input id="cw-element-link-element" type="radio" checked :aria-description="selectedElementTitle"/> - <label for="cw-element-link-element" @click="e => e.preventDefault()"> - <div class="icon"><studip-icon shape="content2" size="32"/></div> - <div class="text">{{ selectedElementTitle }}</div> - <studip-icon shape="check-circle" size="24" class="check" /> - </label> - </fieldset> - <button - v-if="selectedElementParent" - class="button" - @click="selectElement(selectedElementParent.id)" - > - {{ $gettextInterpolate( - $gettext('zurück zu %{ parentTitle }'), - { parentTitle: selectedElementParentTitle } - ) }} - </button> - <fieldset> - <legend>{{ $gettext('Unterseiten') }}</legend> - <ul class="cw-element-selector-list"> - <li - v-for="child in children" - :key="child.id" - > - <button - class="cw-element-selector-item" - @click="selectElement(child.id)" - > - {{ child.attributes.title }} - </button> - - </li> - <li v-if="children.length === 0"> - {{ $gettext('Es wurden keine Unterseiten gefunden.') }} - </li> - </ul> - </fieldset> - </template> - <courseware-companion-box - v-if="!selectedUnit" - mood="pointing" - :msgCompanion="$gettext('Bitte wählen Sie zuerst das Lernmaterial aus.')" + <form v-if="selectedUnit" class="default" @submit.prevent=""> + <courseware-structural-element-selector + v-model="selectedElement" + :rootId="selectedUnitRootId" /> </form> + <courseware-companion-box + v-else + mood="pointing" + :msgCompanion="$gettext('Bitte wählen Sie zuerst das Lernmaterial aus.')" + /> </template> </studip-wizard-dialog> </template> <script> import CoursewareCompanionBox from './CoursewareCompanionBox.vue'; +import CoursewareStructuralElementSelector from './CoursewareStructuralElementSelector.vue'; import StudipWizardDialog from './../StudipWizardDialog.vue'; import StudipProgressIndicator from '../StudipProgressIndicator.vue'; @@ -103,6 +68,7 @@ export default { name: 'courseware-structural-element-dialog-link', components: { CoursewareCompanionBox, + CoursewareStructuralElementSelector, StudipWizardDialog, StudipProgressIndicator }, @@ -112,7 +78,7 @@ export default { {id: 1, valid: false, name: 'unit', title: this.$gettext('Lernmaterial'), icon: 'courseware', description: this.$gettext('Wählen Sie das Lernmaterial aus, in dem sich der zu verknüpfende Lerninhalt befindet. Die Lerninhalte, die verknüpft werden können, müssen unter Arbeitsplatz/Courseware vorher erstellt werden.')}, {id: 2, valid: false, name: 'element', title: this.$gettext('Seite'), icon: 'content2', - description: this.$gettext('Wählen Sie die zu verknüpfende Seite aus. Vorausgewählt ist die oberste Seite des ausgewählten Lernmaterials. Unterseiten erreichen Sie über die Schaltflächen im Bereich "Unterseiten". Sie können über die "zurück zu" Schaltfläche das übergeordnete Element anwählen. Die ausgewählte Seite ist mit einem Kontrollhaken markiert.')}, ], + description: this.$gettext('Wählen Sie die zu verknüpfende Seite aus. Um Unterseiten anzuzeigen, klicken Sie auf den Seitennamen. Mit einem weiteren Klick werden die Unterseiten wieder zugeklappt.')}, ], loadingUnits: false, selectedUnit: null, selectedElement: null, @@ -237,6 +203,9 @@ export default { if (this.selectedUnit === null) { this.requirements.push({slot: this.wizardSlots[0], text: this.$gettext('Lernmaterial') }); } + if (this.selectedElement === null) { + this.requirements.push({slot: this.wizardSlots[1], text: this.$gettext('Seite') }); + } } }, watch: { @@ -253,7 +222,7 @@ export default { if (newUnit !== null) { this.wizardSlots[0].valid = true; await this.loadStructuralElement({id: this.selectedUnitRootId, options: {include: 'children'}}); - this.selectedElement = this.structuralElementById({id: this.selectedUnitRootId}); + this.selectedElement = null; } else { this.wizardSlots[0].valid = false; } diff --git a/resources/vue/components/courseware/CoursewareStructuralElementSelector.vue b/resources/vue/components/courseware/CoursewareStructuralElementSelector.vue new file mode 100644 index 0000000000000000000000000000000000000000..0e8acda3bbcecc526859cc5a9638a977c4cd5e06 --- /dev/null +++ b/resources/vue/components/courseware/CoursewareStructuralElementSelector.vue @@ -0,0 +1,141 @@ +<template> + <ul class="courseware-structural-element-selector" role="radiogroup"> + <courseware-structural-element-selector-item + v-if="rootElement !== null" + :element="rootElement" + :siblings="[]" + :selectedId="selectedId" + :focusedElementId="focusedElementId" + :rootId="rootId" + :validateAncestors="validateAncestors" + :targetId="targetId" + :targetAncestors="targetAncestors" + :selectablePurposes="selectablePurposes" + @input="handleInput" + @focus="handleFocus" + /> + </ul> +</template> + +<script> +import CoursewareStructuralElementSelectorItem from './CoursewareStructuralElementSelectorItem.vue'; +import { mapActions, mapGetters } from 'vuex' + +export default { + name: 'courseware-structural-element-selector', + components: { + CoursewareStructuralElementSelectorItem + }, + model: { + prop: 'element' + }, + props: { + element: { + type: Object + }, + rootId: { + type: String, + required: true + }, + validateAncestors: { + type: Boolean, + default: false + }, + targetId: { + type: String, + default: null + }, + selectablePurposes: { + type: Array, + default: () => [] + } + }, + data() { + return { + rootElement: null, + focusedElementId: '' + }; + }, + computed: { + ...mapGetters({ + userId: 'userId', + coursewareUnits: 'courseware-units/all', + structuralElementById: 'courseware-structural-elements/byId', + context: 'context', + childrenById: 'courseware-structure/children', + currentElement: 'currentElement' + }), + children() { + if (!this.rootElement) { + return []; + } + + return this.childrenById(this.rootElement.id) + .map((id) => this.structuralElementById({ id })) + .filter(Boolean); + }, + selectedId() { + return this.element?.id ?? ''; + }, + targetElement() { + return this.structuralElementById({ id: this.targetId }); + }, + targetAncestors() { + if (!this.targetElement || !this.validateAncestors) { + 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(`CoursewareStructuralElement#ancestors: Could not find parent by ID: "${parentId}".`); + } + + return element; + }; + + const visitAncestors = function* (node) { + const parent = finder(node); + if (parent) { + yield parent; + yield* visitAncestors(parent); + } + }; + + return [...visitAncestors(this.targetElement)].reverse(); + }, + }, + methods: { + ...mapActions({ + loadStructuralElement: 'courseware-structural-elements/loadById', + companionError: 'companionError', + companionSuccess: 'companionSuccess', + }), + handleInput(id) { + this.$emit('input', this.structuralElementById({ id })); + this.focusedElementId = id; + }, + handleFocus(id) { + this.focusedElementId = id; + }, + async loadRootElement(rootId) { + this.rootElement = null; + await this.loadStructuralElement({id: rootId, options: {include: 'children'}}); + this.rootElement = this.structuralElementById({ id: rootId}); + } + }, + async mounted() { + await this.loadRootElement(this.rootId); + }, + watch: { + async rootId(newRootId) { + await this.loadRootElement(newRootId); + this.focusedElementId = ''; + } + } +}; +</script> diff --git a/resources/vue/components/courseware/CoursewareStructuralElementSelectorItem.vue b/resources/vue/components/courseware/CoursewareStructuralElementSelectorItem.vue new file mode 100644 index 0000000000000000000000000000000000000000..f6b3cf6a66ee2c3b1b79bfd37ec783b9f06bddaa --- /dev/null +++ b/resources/vue/components/courseware/CoursewareStructuralElementSelectorItem.vue @@ -0,0 +1,272 @@ +<template> + <li class="courseware-structural-element-selector-item"> + <span + class="radiobutton" + :tabindex="tabindex" + :ref="'radiobutton-' + element.id" + role="radio" + :aria-checked="selected ? 'true' : 'false'" + :aria-labelledby="labelId" + @click="handleClickInput(element.id)" + @keydown="handleKeyInput($event, element.id)" + > + <template v-if="selectable"> + <studip-icon v-if="selected" shape="radiobutton-checked" /> + <studip-icon v-else shape="radiobutton-unchecked" /> + </template> + <studip-icon v-else shape="decline" role="inactive" /> + </span> + <template v-if="hasChildren"> + <a href="#" :aria-expanded="isOpen ? 'true' : 'false'" @click.prevent="toggleChildrenVisibility"> + <studip-icon v-if="!isOpen" shape="arr_1right" /> + <studip-icon v-if="isOpen" shape="arr_1down" /> + <label :id="labelId"> + {{ element.attributes.title }} + <span v-if="!selectable" class="sr-only">{{ $gettext('nicht wählbar') }}</span> + </label> + </a> + <ul v-if="isOpen"> + <courseware-structural-element-selector-item + v-for="child in children" + :key="child.id" + :element="child" + :siblings="children" + :selectedId="selectedId" + :focusedElementId="focusedElementId" + :rootId="rootId" + :validateAncestors="validateAncestors" + :targetId="targetId" + :targetAncestors="targetAncestors" + :selectablePurposes="selectablePurposes" + @input="handleInput" + @focus="handleFocus" + @selectable="updateSelectable" + /> + </ul> + </template> + <template v-else> + <studip-icon shape="arr_1right" role="inactive" class="inactive"/> + <label :id="labelId"> + {{ element.attributes.title }} + <span v-if="!selectable" class="sr-only">{{ $gettext('nicht wählbar') }}</span> + </label> + </template> + </li> +</template> +<script> +import { mapActions, mapGetters } from 'vuex' + +export default { + name: 'courseware-structural-element-selector-item', + props: { + element: { + type: Object + }, + siblings: { + type: Array + }, + selectedId: { + type: String, + required: true + }, + focusedElementId: { + type: String + }, + rootId: { + type: String, + required: true + }, + validateAncestors: { + type: Boolean, + default: false + }, + targetId: { + type: String, + default: null + }, + targetAncestors: { + type: Array + }, + selectablePurposes: { + type: Array + } + }, + data() { + return { + isOpen: false, + selectable: true, + } + }, + computed: { + ...mapGetters({ + userId: 'userId', + coursewareUnits: 'courseware-units/all', + structuralElementById: 'courseware-structural-elements/byId', + context: 'context', + currentElement: 'currentElement' + }), + children() { + const children = this.element?.relationships?.children?.data?.map(child => child.id); + if (!children) { + return []; + } + + return children.map((id) => this.structuralElementById({ id })).filter(Boolean); + }, + hasChildren() { + return this.children.length > 0; + }, + selected() { + return this.selectedId === this.element?.id; + }, + focused() { + return this.focusedElementId === this.element?.id; + }, + labelId() { + return this.element.id + '_checkbox-label'; + }, + isRoot() { + return this.rootId === this.element.id; + }, + tabindex() { + if (this.focusedElementId !== '') { + return this.focused ? 0 : -1; + } + return this.isRoot ? 0 : -1; + }, + nextElementId() { + if (this.hasChildren && this.isOpen) { + return this.children[0].id; + } + + return this.nextSiblingId; + }, + nextSiblingId() { + if (this.isRoot) { + return null; + } + const index = this.siblings.findIndex(element => element.id === this.element.id) + 1; + if (this.siblings.length > index) { + return this.siblings[index].id; + } else { + return this.$parent.nextSiblingId; + } + }, + previousElementId() { + if (this.isRoot) { + return null; + } + const index = this.siblings.findIndex(element => element.id === this.element.id) - 1; + if (index > -1) { + const childrenCount = this.siblings[index].relationships.children.data.length; + const previousElement = this.$parent.$children.find(child => child.element?.id === this.siblings[index].id); + if (childrenCount > 0 && previousElement.isOpen) { + const element = this.structuralElementById({ id: this.siblings[index].relationships.children.data[childrenCount - 1].id }); + if ( + element.relationships.children.data.length > 0 + && previousElement.$children.find(child => child.element?.id === element.id).isOpen + ) { + return element.relationships.children.data[element.relationships.children.data.length -1].id; + } + return element.id; + } else { + return this.siblings[index].id; + } + } else { + return this.$parent.element.id; + } + }, + }, + methods: { + ...mapActions({ + loadStructuralElement: 'courseware-structural-elements/loadById', + companionError: 'companionError', + companionSuccess: 'companionSuccess', + }), + loadChildren() { + const children = this.element?.relationships?.children?.data?.map(child => child.id) ?? []; + children.forEach((id) => this.loadStructuralElement({id: id, options: {include: 'children'}})); + }, + toggleChildrenVisibility() { + if (!this.isOpen) { + this.loadChildren(); + } + this.isOpen = !this.isOpen; + }, + handleInput(id) { + this.$emit('input', id); + }, + handleFocus(id) { + this.$emit('focus', id); + }, + validate() { + if ( + this.element.id === this.targetId + || this.targetAncestors.find(ancestor => ancestor.id === this.element.id) + ) { + this.selectable = false; + this.$emit('selectable', false); + } + }, + updateSelectable() { + this.selectable = false; + this.$emit('selectable', false); + }, + filterSelectablePurposes() { + if (this.selectablePurposes.length === 0) { + return; + } + this.selectable = this.selectablePurposes.includes(this.element.attributes.purpose); + }, + handleClickInput(id) { + if (this.selectable) { + this.handleInput(id); + } + }, + handleKeyInput(event, id) { + switch(event.keyCode) { + case 37: // arrow left + case 38: // arrow up + event.preventDefault(); + if (this.previousElementId !== null) { + this.$emit('focus', this.previousElementId); + } + break; + case 39: // arrow right + case 40: // arrow down + event.preventDefault(); + if (this.nextElementId !== null) { + this.$emit('focus', this.nextElementId); + } + break; + } + }, + selectRoot() { + if (this.focusedElementId === '' && this.isRoot && this.selectable) { + this.handleInput(this.element.id); + } + } + }, + mounted() { + this.loadChildren(); + if (this.validateAncestors) { + this.validate(); + } + this.filterSelectablePurposes(); + this.selectRoot(); + }, + watch: { + focusedElementId(newId) { + if (this.focused) { + this.$refs['radiobutton-'+ this.element.id].focus(); + if (this.selectable) { + this.handleInput(newId); + } else { + this.handleInput(''); + } + } + this.selectRoot(); + } + } +} +</script> diff --git a/resources/vue/components/courseware/CoursewareTasksDialogDistribute.vue b/resources/vue/components/courseware/CoursewareTasksDialogDistribute.vue index 4a8f64b94b916f5ec100778ad99b2624ae34b1bd..78d6d46d6846eed2d6a838cc832157634cb85b55 100644 --- a/resources/vue/components/courseware/CoursewareTasksDialogDistribute.vue +++ b/resources/vue/components/courseware/CoursewareTasksDialogDistribute.vue @@ -43,36 +43,11 @@ </template> <template v-slot:task> <form v-if="selectedSourceUnit" class="default" @submit.prevent=""> - <fieldset class="radiobutton-set"> - <input - id="cw-task-dist-task" - type="radio" - :checked="selectedTaskIsTask" - :aria-description="selectedTaskTitle" - /> - <label for="cw-task-dist-task" @click="(e) => e.preventDefault()"> - <div class="icon"><studip-icon shape="content2" size="32" /></div> - <div class="text">{{ selectedTaskTitle }}</div> - <studip-icon v-if="selectedTaskIsTask" shape="check-circle" size="24" class="check" /> - <studip-icon v-else shape="decline-circle" size="24" class="unchecked" /> - </label> - </fieldset> - <button v-if="selectedTaskParent" class="button" @click="selectTask(selectedTaskParent.id)"> - {{ $gettext('zurück zur übergeordneten Seite') }} - </button> - <fieldset> - <legend>{{ $gettext('Unterseiten') }}</legend> - <ul class="cw-element-selector-list"> - <li v-for="child in taskChildren" :key="child.id"> - <button class="cw-element-selector-item" @click="selectTask(child.id)"> - {{ child.attributes.title }} - </button> - </li> - <li v-if="taskChildren.length === 0"> - {{ $gettext('Es wurden keine Unterseiten gefunden.') }} - </li> - </ul> - </fieldset> + <courseware-structural-element-selector + v-model="selectedTask" + :rootId="selectedSourceUnitRootId" + :selectablePurposes="['template']" + /> </form> <courseware-companion-box v-else @@ -145,39 +120,10 @@ </template> <template v-slot:targetelement> <form v-if="selectedTargetUnit && selectedTaskIsTask" class="default" @submit.prevent=""> - <fieldset class="radiobutton-set"> - <input - id="cw-task-dist-target-element" - type="radio" - checked - :aria-description="selectedTargetElementTitle" - /> - <label for="cw-task-dist-target-element" @click="(e) => e.preventDefault()"> - <div class="icon"><studip-icon shape="content2" size="32" /></div> - <div class="text">{{ selectedTargetElementTitle }}</div> - <studip-icon shape="check-circle" size="24" class="check" /> - </label> - </fieldset> - <button - v-if="selectedTargetElementParent" - class="button" - @click="selectTargetElement(selectedTargetElementParent.id)" - > - {{ $gettext('zurück zur übergeordneten Seite') }} - </button> - <fieldset> - <legend>{{ $gettext('Unterseiten') }}</legend> - <ul class="cw-element-selector-list"> - <li v-for="child in targetChildren" :key="child.id"> - <button class="cw-element-selector-item" @click="selectTargetElement(child.id)"> - {{ child.attributes.title }} - </button> - </li> - <li v-if="targetChildren.length === 0"> - {{ $gettext('Es wurden keine Unterseiten gefunden.') }} - </li> - </ul> - </fieldset> + <courseware-structural-element-selector + v-model="selectedTargetElement" + :rootId="selectedTargetUnitRootId" + /> </form> <courseware-companion-box v-if="!selectedTaskIsTask" @@ -292,6 +238,7 @@ <script> import CoursewareCompanionBox from './CoursewareCompanionBox.vue'; +import CoursewareStructuralElementSelector from './CoursewareStructuralElementSelector.vue'; import StudipWizardDialog from './../StudipWizardDialog.vue'; import { mapActions, mapGetters } from 'vuex'; @@ -300,6 +247,7 @@ export default { name: 'courseware-tasks-dialog-distribute', components: { CoursewareCompanionBox, + CoursewareStructuralElementSelector, StudipWizardDialog, }, data() { @@ -322,7 +270,7 @@ export default { title: this.$gettext('Aufgabenvorlage'), icon: 'category-task', description: this.$gettext( - 'Wählen Sie die zu verteilende Aufgabenvorlage aus. Vorausgewählt ist die oberste Seite des ausgewählten Lernmaterials. Unterseiten erreichen Sie über die Schaltflächen im Bereich "Unterseiten". Sie können über die "zurück zu" Schaltfläche das übergeordnete Element anwählen. Die ausgewählte Aufgabenvorlage ist mit einem Kontrollhaken markiert. Nur Seiten der Kategorie "Aufgabenvorlage" können verteilt werden.' + 'Wählen Sie die zu verteilende Aufgabenvorlage aus. Vorausgewählt ist die oberste Seite des ausgewählten Lernmaterials. Um Unterseiten anzuzeigen, klicken Sie auf den Seitennamen. Mit einem weiteren Klick werden die Unterseiten wieder zugeklappt. Nur Seiten der Kategorie "Aufgabenvorlage" können verteilt werden.' ), }, { @@ -352,7 +300,7 @@ export default { title: this.$gettext('Zielseite'), icon: 'content2', description: this.$gettext( - 'Wählen Sie hier die Seite aus unterhalb der die Aufgabe verteilt werden soll. Zum bearbeiten der Aufgabe müssen Lernende Zugriff auf die Seite haben. Prüfen Sie ggf. die Leserechte und die Sichtbarkeit.' + 'Wählen Sie hier die Seite aus unterhalb der die Aufgabe verteilt werden soll. Um Unterseiten anzuzeigen, klicken Sie auf den Seitennamen. Mit einem weiteren Klick werden die Unterseiten wieder zugeklappt. Zum Bearbeiten der Aufgabe müssen Lernende Zugriff auf die Seite haben. Prüfen Sie ggf. die Leserechte und die Sichtbarkeit.' ), }, { @@ -679,7 +627,7 @@ export default { id: this.selectedTargetUnitRootId, options: { include: 'children' }, }); - this.selectedTargetElement = this.structuralElementById({ id: this.selectedTargetUnitRootId }); + this.selectedTargetElement = null; } else { this.wizardSlots[3].valid = false; }