diff --git a/kolibri/core/assets/src/views/sortable/DragContainer.vue b/kolibri/core/assets/src/views/sortable/DragContainer.vue index 10698966044..aba161fbd2d 100644 --- a/kolibri/core/assets/src/views/sortable/DragContainer.vue +++ b/kolibri/core/assets/src/views/sortable/DragContainer.vue @@ -52,6 +52,8 @@ // hook up event listeners this.sortable.on('sortable:start', this.handleStart); this.sortable.on('sortable:stop', this.handleStop); + this.sortable.on('sortable:moveDown', this.moveDownOne); + this.sortable.on('sortable:moveUp', this.moveUpOne); }, handleStart() { // handle cancelation of drags diff --git a/kolibri/plugins/coach/assets/src/views/plan/CreateExamPage/CreateQuizSection.vue b/kolibri/plugins/coach/assets/src/views/plan/CreateExamPage/CreateQuizSection.vue index c8416a71652..52a6503a3bb 100644 --- a/kolibri/plugins/coach/assets/src/views/plan/CreateExamPage/CreateQuizSection.vue +++ b/kolibri/plugins/coach/assets/src/views/plan/CreateExamPage/CreateQuizSection.vue @@ -243,8 +243,8 @@ :noDrag="true" :isFirst="index === 0" :isLast="index === activeQuestions.length - 1" - @moveUp="shiftOne(index, -1)" - @moveDown="shiftOne(index, +1)" + @moveUp="() => handleKeyboardDragUp(index, activeQuestions)" + @moveDown="() => handleKeyboardDragDown(index, activeQuestions)" /> @@ -344,6 +344,7 @@ import { injectQuizCreation } from '../../../composables/useQuizCreation'; import commonCoach from '../../common'; import { PageNames } from '../../../constants'; + import useDrag from './useDrag.js'; import TabsWithOverflow from './TabsWithOverflow'; const logger = logging.getLogger(__filename); @@ -417,6 +418,7 @@ canExpandAll, } = useAccordion(activeQuestions); + const { moveUpOne, moveDownOne } = useDrag(); const dragActive = ref(false); return { @@ -462,6 +464,9 @@ displaySectionTitle, displayQuestionTitle, + moveDownOne, + moveUpOne, + // Computed allSections, activeSectionIndex, @@ -665,6 +670,14 @@ // Used to mitigate the issue of text being selected while dragging this.dragActive = true; }, + handleKeyboardDragDown(oldIndex, array) { + const newArray = this.moveDownOne(oldIndex, array); + this.handleQuestionOrderChange({ newArray }); + }, + handleKeyboardDragUp(oldIndex, array) { + const newArray = this.moveUpOne(oldIndex, array); + this.handleQuestionOrderChange({ newArray }); + }, openSelectResources() { this.$router.push({ name: PageNames.QUIZ_SELECT_RESOURCES, diff --git a/kolibri/plugins/coach/assets/src/views/plan/CreateExamPage/SectionEditor.vue b/kolibri/plugins/coach/assets/src/views/plan/CreateExamPage/SectionEditor.vue index a8ff7e816c8..d80f69ba501 100644 --- a/kolibri/plugins/coach/assets/src/views/plan/CreateExamPage/SectionEditor.vue +++ b/kolibri/plugins/coach/assets/src/views/plan/CreateExamPage/SectionEditor.vue @@ -105,45 +105,19 @@ " class="section-order-list" > - - - - - - - - {{ displaySectionTitle(section, index).toUpperCase() }} - - - - - - - {{ currentSection$() }} - - - + handleKeyboardDragUp(index, sectionOrderList)" + @moveDown="() => handleKeyboardDragDown(index, sectionOrderList)" + /> + + {{ sectionOrderingTitle(section) }} + @@ -219,8 +193,10 @@ import Draggable from 'kolibri.coreVue.components.Draggable'; import DragContainer from 'kolibri.coreVue.components.DragContainer'; import DragHandle from 'kolibri.coreVue.components.DragHandle'; + import DragSortWidget from 'kolibri.coreVue.components.DragSortWidget'; import { PageNames } from '../../../constants/index'; import { injectQuizCreation } from '../../../composables/useQuizCreation'; + import useDrag from './useDrag.js'; export default { name: 'SectionEditor', @@ -228,6 +204,7 @@ Draggable, DragContainer, DragHandle, + DragSortWidget, }, mixins: [commonCoreStrings], setup(_, context) { @@ -268,6 +245,8 @@ removeSection, } = injectQuizCreation(); + const { moveDownOne, moveUpOne } = useDrag(); + const showCloseConfirmation = ref(false); function handleCancelClose() { @@ -308,6 +287,9 @@ const description = ref(activeSection?.value?.description || ''); const section_title = ref(activeSection?.value?.section_title?.trim() || ''); + // This is used to track the section that was moved + const reorderedSectionIndex = ref(null); + const sectionTitleInvalidText = computed(() => { if (section_title.value.trim() === '') { // Always allow empty section titles @@ -360,6 +342,7 @@ }); return { + reorderedSectionIndex, sectionTitleInvalidText, sectionTitleInvalid: computed(() => Boolean(sectionTitleInvalidText.value)), formDataHasChanged, @@ -380,6 +363,9 @@ updateSection, updateQuiz, handleDeleteSection, + // dragging a11y + moveDownOne, + moveUpOne, // Form models learners_see_fixed_order, description, @@ -453,6 +439,10 @@ methods: { handleSectionSort(e) { this.sectionOrderList = e.newArray; + const reorderedId = this.allSections[this.activeSectionIndex].section_id; + this.reorderedSectionIndex = this.sectionOrderList.findIndex( + section => section.section_id === reorderedId, + ); }, applySettings() { if (this.sectionTitleInvalid) { @@ -476,7 +466,36 @@ question_sources, }); } - this.$emit('closePanel'); + + if ( + this.reorderedSectionIndex !== null && + this.reorderedSectionIndex !== this.activeSectionIndex + ) { + this.$router.replace({ + name: PageNames.EXAM_CREATION_ROOT, + params: { + classId: this.$route.params.classId, + quizId: this.$route.params.quizId, + sectionIndex: this.reorderedSectionIndex, + }, + }); + } else { + this.$emit('closePanel'); + } + }, + handleKeyboardDragDown(oldIndex, array) { + const newArray = this.moveDownOne(oldIndex, array); + this.sectionOrderList = newArray; + }, + handleKeyboardDragUp(oldIndex, array) { + const newArray = this.moveUpOne(oldIndex, array); + this.sectionOrderList = newArray; + }, + sectionOrderingTitle(section) { + const sectionIndexOrder = this.allSections.findIndex( + s => s.section_id === section.section_id, + ); + return displaySectionTitle(section, sectionIndexOrder).toUpperCase(); }, }, }; @@ -506,6 +525,9 @@ } .section-order-list { + display: flex; + flex-wrap: nowrap; + align-items: center; height: 2.5em; margin-top: 0.5em; border: 1px solid; @@ -525,6 +547,11 @@ font-size: 1em; } + .drag-title { + display: inline-block; + padding: 8px; + } + .bottom-buttons-style { position: absolute; right: 0; diff --git a/kolibri/plugins/coach/assets/src/views/plan/CreateExamPage/useDrag.js b/kolibri/plugins/coach/assets/src/views/plan/CreateExamPage/useDrag.js new file mode 100644 index 00000000000..69e03d19340 --- /dev/null +++ b/kolibri/plugins/coach/assets/src/views/plan/CreateExamPage/useDrag.js @@ -0,0 +1,22 @@ +export default function useDrag() { + function _shiftOne(oldIndex, newIndex, array) { + const items = [...array]; + const oldItem = items[newIndex]; + items[newIndex] = items[oldIndex]; + items[oldIndex] = oldItem; + return items; + } + + function moveUpOne(oldIndex, array) { + return _shiftOne(oldIndex, oldIndex - 1, array); + } + + function moveDownOne(oldIndex, array) { + return _shiftOne(oldIndex, oldIndex + 1, array); + } + + return { + moveUpOne, + moveDownOne, + }; +}
- {{ displaySectionTitle(section, index).toUpperCase() }} -
- {{ currentSection$() }} -