diff --git a/resources/vue/components/courseware/CoursewareStructuralElement.vue b/resources/vue/components/courseware/CoursewareStructuralElement.vue index 78af7a4992d9197eebf10f059eb69eaa63803767..493c312e44d55da0a2dc5f355240e40b5606fadd 100755 --- a/resources/vue/components/courseware/CoursewareStructuralElement.vue +++ b/resources/vue/components/courseware/CoursewareStructuralElement.vue @@ -396,7 +396,7 @@ <script> import ContainerComponents from './container-components.js'; -import CoursewarePluginComponents from './plugin-components.js' +import CoursewarePluginComponents from './plugin-components.js'; import CoursewareStructuralElementPermissions from './CoursewareStructuralElementPermissions.vue'; import CoursewareAccordionContainer from './CoursewareAccordionContainer.vue'; import CoursewareCompanionBox from './CoursewareCompanionBox.vue'; @@ -430,7 +430,7 @@ export default { IsoDate, StudipDialog, }, - props: {}, + props: ['orderedStructuralElements'], mixins: [CoursewareExport], @@ -481,6 +481,9 @@ export default { courseware: 'courseware', consumeMode: 'consumeMode', containerById: 'courseware-containers/byId', + relatedContainers: 'courseware-containers/related', + relatedStructuralElements: 'courseware-structural-elements/related', + relatedUsers: 'users/related', structuralElementById: 'courseware-structural-elements/byId', userIsTeacher: 'userIsTeacher', pluginManager: 'pluginManager', @@ -488,13 +491,13 @@ export default { showAddDialog: 'showStructuralElementAddDialog', showExportDialog: 'showStructuralElementExportDialog', showInfoDialog: 'showStructuralElementInfoDialog', - showDeleteDialog : 'showStructuralElementDeleteDialog', - showOerDialog : 'showStructuralElementOerDialog', + showDeleteDialog: 'showStructuralElementDeleteDialog', + showOerDialog: 'showStructuralElementOerDialog', oerEnabled: 'oerEnabled', oerTitle: 'oerTitle', licenses: 'licenses', exportState: 'exportState', - exportProgress: 'exportProgress' + exportProgress: 'exportProgress', }), textOer() { @@ -502,7 +505,7 @@ export default { title: this.$gettext('Seite auf') + ' ' + this.oerTitle + ' ' + this.$gettext('veröffentlichen'), confirm: this.$gettext('Veröffentlichen'), close: this.$gettext('Schließen'), - } + }; }, inCourse() { @@ -555,87 +558,38 @@ export default { }, ancestors() { - if (!this.currentElement) { - return []; - } - if (this.currentElement.relationships.ancestors.data) { - return this.currentElement.relationships.ancestors.data.map(({ id }) => - this.structuralElementById({ id }) - ); - } - return []; - }, - parent() { if (!this.structuralElement) { return []; } - if (this.structuralElement.relationships.parent.data) { - let id = this.structuralElement.relationships.parent.data.id; - return this.structuralElementById({ id }); - } - return []; - }, - hasSiblings() { - if (this.parent.length !== 0) { - return this.parent.relationships.children.data.length > 1; - } else { - return false; - } + + return this.relatedStructuralElements({ parent: this.structuralElement, relationship: 'ancestors' }); }, prevElement() { - if (this.hasSiblings) { - let view = this; - let siblings = this.parent.relationships.children.data; - let id = ''; - siblings.forEach((el, index) => { - if (el.id === view.currentId && index !== 0) { - id = siblings[index - 1].id; - } - }); - if (id === '') { - return this.parent; - } else { - return this.structuralElementById({ id }); - } - } else if (this.parent.length !== 0) { - return this.parent; - } else { + const currentIndex = this.orderedStructuralElements.indexOf(this.structuralElement.id); + if (currentIndex <= 0) { return null; } + const previousId = this.orderedStructuralElements[currentIndex - 1]; + const previous = this.structuralElementById({ id: previousId }); + + return previous; }, nextElement() { - let view = this; - if (this.structuralElement.relationships.children.data.length > 0) { - let id = this.structuralElement.relationships.children.data[0].id; - return this.structuralElementById({ id }); - } else if (this.hasSiblings) { - let siblings = this.parent.relationships.children.data; - let id = ''; - siblings.forEach((el, index) => { - if (el.id === view.currentId && siblings.length > index + 1) { - id = siblings[index + 1].id; - } - }); - if (id === '') { - return this.getNextParentSibling(this.currentId); - } else { - return this.structuralElementById({ id }); - } - } else { - return this.getNextParentSibling(this.currentId); + const currentIndex = this.orderedStructuralElements.indexOf(this.structuralElement.id); + const lastIndex = this.orderedStructuralElements.length - 1; + if (currentIndex === -1 || currentIndex === lastIndex) { + return null; } + const nextId = this.orderedStructuralElements[currentIndex + 1]; + const next = this.structuralElementById({ id: nextId }); + + return next; }, empty() { if (this.containers === null) { return true; } else { - let noBlockFound = true; - this.containers.forEach((container) => { - if (container.relationships.blocks.data.length > 0) { - noBlockFound = false; - } - }); - return noBlockFound; + return !this.containers.some((container) => container.relationships.blocks.data.length > 0); } }, containers() { @@ -643,12 +597,12 @@ export default { return []; } - const containers = this.$store.getters['courseware-containers/related']({ - parent: this.structuralElement, - relationship: 'containers', - }); - - return containers; + return ( + this.relatedContainers({ + parent: this.structuralElement, + relationship: 'containers', + }) ?? [] + ); }, noContainers() { if (this.containers === null) { @@ -679,7 +633,7 @@ export default { }, owner() { - const owner = this.$store.getters['users/related']({ + const owner = this.relatedUsers({ parent: this.structuralElement, relationship: 'owner', }); @@ -688,7 +642,7 @@ export default { }, editor() { - const editor = this.$store.getters['users/related']({ + const editor = this.relatedUsers({ parent: this.structuralElement, relationship: 'editor', }); @@ -764,7 +718,7 @@ export default { }, }, - async mounted() { + mounted() { if (!this.currentId) { this.setCurrentId(this.$route.params.id); } @@ -797,7 +751,7 @@ export default { this.initCurrent(); }, initCurrent() { - this.currentElement = JSON.parse(JSON.stringify(this.structuralElement)); + this.currentElement = _.cloneDeep(this.structuralElement); this.uploadFileError = ''; }, async menuAction(action) { @@ -941,29 +895,6 @@ export default { containerComponent(container) { return 'courseware-' + container.attributes['container-type'] + '-container'; }, - getNextParentSibling(element_id) { - let current = this.structuralElementById({ id: element_id }); - if (current.relationships.parent.data === null) { - return null; - } - let parent = this.structuralElementById({ id: current.relationships.parent.data.id }); - if (parent.relationships.parent.data === null) { - return null; - } - let grandParent = this.structuralElementById({ id: parent.relationships.parent.data.id }); - let parentSiblings = grandParent.relationships.children.data; - let id = ''; - parentSiblings.forEach((el, index) => { - if (parseInt(el.id, 10) === parseInt(parent.id, 10) && parentSiblings.length > index + 1) { - id = parentSiblings[index + 1].id; - } - }); - if (id === '') { - this.getNextParentSibling(parent.id); - } else { - return this.structuralElementById({ id }); - } - }, setBookmark() { this.addBookmark(this.structuralElement); this.companionInfo({ info: this.$gettext('Das Lesezeichen wurde gesetzt') }); diff --git a/resources/vue/components/courseware/IndexApp.vue b/resources/vue/components/courseware/IndexApp.vue index 2dfe493b8bb6b1c7f2d5638e5eb4c36665019722..5a657e2df149265cfd614746b132ec78b51e1004 100755 --- a/resources/vue/components/courseware/IndexApp.vue +++ b/resources/vue/components/courseware/IndexApp.vue @@ -1,6 +1,8 @@ <template> <div v-if="courseware"> - <courseware-structural-element></courseware-structural-element> + <courseware-structural-element + :ordered-structural-elements="orderedStructuralElements" + ></courseware-structural-element> <MountingPortal mountTo="#courseware-action-widget" name="sidebar-actions"> <courseware-action-widget></courseware-action-widget> </MountingPortal> @@ -25,8 +27,14 @@ export default { CoursewareViewWidget, CoursewareActionWidget, }, + data: () => ({ orderedStructuralElements: [] }), computed: { - ...mapGetters(['courseware', 'userId', 'blockAdder']), + ...mapGetters({ + courseware: 'courseware', + relatedStructuralElement: 'courseware-structural-elements/related', + structuralElements: 'courseware-structural-elements/all', + userId: 'userId', + }), }, methods: { ...mapActions(['loadCoursewareStructure', 'loadTeacherStatus', 'coursewareBlockAdder']), @@ -34,12 +42,54 @@ export default { async mounted() { await this.loadCoursewareStructure(); await this.loadTeacherStatus(this.userId); - // console.debug('IndexApp mounted for courseware:', this.courseware, this.$store); }, watch: { $route() { this.coursewareBlockAdder({}); //reset block adder on navigate + }, + structuralElements(newElements, oldElements) { + const nodes = buildNodes(this.structuralElements, this.relatedStructuralElement.bind(this)); + this.orderedStructuralElements = [...visitTree(nodes, findRoot(nodes))]; + }, + }, +}; + +function buildNodes(structuralElements, relatedStructuralElement) { + return structuralElements.reduce((memo, element) => { + memo.push({ + id: element.id, + parent: + relatedStructuralElement({ + parent: element, + relationship: 'parent', + })?.id ?? null, + + children: + relatedStructuralElement({ + parent: element, + relationship: 'children', + })?.map((child) => child.id) ?? [], + }); + + return memo; + }, []); +} + +function findRoot(nodes) { + return nodes.find((node) => node.parent === null); +} + +function findNode(nodes, id) { + return nodes.find((node) => node.id === id); +} + +function* visitTree(nodes, current) { + if (current) { + yield current.id; + + for (let index = 0; index < current.children.length; index++) { + yield* visitTree(nodes, findNode(nodes, current.children[index])); } } -}; +} </script>