Skip to content
Snippets Groups Projects
Select Git revision
  • f004dc9a79f67f901b7b89f66f2965bccf7a10fb
  • main default protected
  • step-3263
  • feature/plugins-cli
  • feature/vite
  • step-2484-peerreview
  • biest/issue-5051
  • tests/simplify-jsonapi-tests
  • fix/typo-in-1a70031
  • feature/broadcasting
  • database-seeders-and-factories
  • feature/peer-review-2
  • feature-feedback-jsonapi
  • feature/peerreview
  • feature/balloon-plus
  • feature/stock-images-unsplash
  • tic-2588
  • 5.0
  • 5.2
  • biest/unlock-blocks
  • biest-1514
21 results

vue-gettext.d.ts

Blame
  • Forked from Stud.IP / Stud.IP
    Source project has a limited visibility.
    Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    CoursewareTreeItem.vue 17.62 KiB
    <template>
        <li v-if="showItem" :draggable="editMode ? true : null" :aria-selected="editMode ? keyboardSelected : null">
            <div class="cw-tree-item-wrapper">
                <span
                    v-if="editMode && depth > 0 && canEdit"
                    class="cw-sortable-handle"
                    :tabindex="0"
                    aria-describedby="operation"
                    ref="sortableHandle"
                    role="button"
                    @keydown="handleKeyEvent"
                >
                </span>
                <courseware-tree-item-updater
                    v-if="editMode && editingItem"
                    :structuralElement="element"
                    @close="editingItem = false"
                    @childrenUpdated="$emit('childrenUpdated')"
                />
                <router-link
                    v-else
                    :to="'/structural_element/' + element.id"
                    class="cw-tree-item-link"
                    :class="{
                        'cw-tree-item-link-current': isCurrent,
                        'cw-tree-item-link-edit': editMode,
                        'cw-tree-item-link-selected': keyboardSelected,
                    }"
                >
                    {{ element.attributes?.title || '' }}
                    <button v-if="editMode && canEdit" class="cw-tree-item-edit-button" @click.prevent="editingItem = true">
                        <studip-icon shape="edit" />
                    </button>
    
                    <span v-if="task">| {{ solverName }}</span>
                    <span
                        v-if="hasReleaseOrWithdrawDate"
                        class="cw-tree-item-flag-date"
                        :title="$gettext('Diese Seite hat eine zeitlich beschränkte Sichtbarkeit')"
                    ></span>
                    <span
                        v-if="hasWriteApproval"
                        class="cw-tree-item-flag-write"
                        :title="$gettext('Diese Seite kann von Teilnehmenden bearbeitet werden')"
                    ></span>
                    <span
                        v-if="hasNoReadApproval"
                        class="cw-tree-item-flag-cant-read"
                        :title="$gettext('Diese Seite kann von Teilnehmenden nicht gesehen werden')"
                    ></span>
                    <template v-if="!userIsTeacher && inCourse">
                        <span
                            v-if="complete"
                            class="cw-tree-item-sequential cw-tree-item-sequential-complete"
                            :title="$gettext('Diese Seite wurde von Ihnen vollständig bearbeitet')"
                        >
                        </span>
                        <span
                            v-else
                            class="cw-tree-item-sequential cw-tree-item-sequential-percentage"
                            :title="$gettextInterpolate($gettext('Fortschritt: %{progress}%'), { progress: itemProgress })"
                        >
                            {{ itemProgress }} %
                        </span>
                    </template>
                </router-link>
            </div>
            <ol
                v-if="hasChildren && !editMode"
                :class="{
                    'cw-tree-chapter-list': isRoot,
                    'cw-tree-subchapter-list': isFirstLevel,
                }"
            >
                <courseware-tree-item
                    v-for="child in children"
                    :key="child.id"
                    :element="child"
                    :currentElement="currentElement"
                    :depth="depth + 1"
                    class="cw-tree-item"
                />
            </ol>
            <draggable
                v-if="editMode"
                :class="{ 'cw-tree-chapter-list-empty': nestedChildren.length === 0 }"
                tag="ol"
                :component-data="draggableData"
                class="cw-tree-draggable-list"
                handle=".cw-sortable-handle"
                v-bind="dragOptions"
                :elementId="element.id"
                :list="nestedChildren"
                :group="{ name: 'g1' }"
                @end="endDrag"
            >
                <courseware-tree-item
                    v-for="el in nestedChildren"
                    :key="el.id"
                    :element="el"
                    :currentElement="currentElement"
                    :depth="depth + 1"
                    :newPos="el.newPos"
                    :newParentId="el.newParentId"
                    :siblingCount="nestedChildren.length"
                    class="cw-tree-item"
                    :elementid="el.id"
                    @sort="sort"
                    @moveItemUp="moveItemUp"
                    @moveItemDown="moveItemDown"
                    @moveItemPrevLevel="moveItemPrevLevel"
                    @moveItemNextLevel="moveItemNextLevel"
                    @childrenUpdated="$emit('childrenUpdated')"
                />
            </draggable>
            <ol
                v-if="editMode && canEdit && isFirstLevel"
                class="cw-tree-adder-list"
            >
                <courseware-tree-item-adder :parentId="element.id" />
            </ol>
        </li>
    </template>
    
    <script>
    import CoursewareTreeItemAdder from './CoursewareTreeItemAdder.vue';
    import CoursewareTreeItemUpdater from './CoursewareTreeItemUpdater.vue';
    import draggable from 'vuedraggable';
    
    import { mapGetters, mapActions } from 'vuex';
    
    export default {
        name: 'courseware-tree-item',
        components: {
            CoursewareTreeItemAdder,
            CoursewareTreeItemUpdater,
            draggable,
        },
        props: {
            element: {
                type: Object,
                required: true,
            },
            currentElement: {
                type: Object,
            },
            depth: {
                type: Number,
                default: 0,
            },
            keyboardSelectedProp: {
                type: Boolean,
                default: false,
            },
            newPos: {
                type: Number,
            },
            newParentId: {
                type: Number,
            },
            siblingCount: {
                type: Number,
            },
        },
        data() {
            return {
                keyboardSelected: false,
                editingItem: false,
            };
        },
        computed: {
            ...mapGetters({
                childrenById: 'courseware-structure/children',
                structuralElementById: 'courseware-structural-elements/byId',
                context: 'context',
                taskById: 'courseware-tasks/byId',
                userById: 'users/byId',
                groupById: 'status-groups/byId',
                viewMode: 'viewMode',
                courseware: 'courseware',
                progressData: 'progresses',
                userIsTeacher: 'userIsTeacher',
            }),
            draggableData() {
                return {
                    attrs: {
                        role: 'listbox',
                        ['aria-label']: this.$gettextInterpolate(this.$gettext('Unterseiten von %{elementName}'), {
                            elementName: this.element.attributes?.title,
                        }),
                    },
                };
            },
            children() {
                if (!this.element) {
                    return [];
                }
    
                return this.childrenById(this.element.id)
                    .map((id) => this.structuralElementById({ id }))
                    .filter(Boolean)
                    .sort((a, b) => a.attributes.position - b.attributes.position);
            },
            nestedChildren() {
                return this.element.nestedChildren ?? [];
            },
            hasChildren() {
                return this.childrenById(this.element.id).length;
            },
            isRoot() {
                return this.depth === 0;
            },
            isFirstLevel() {
                return this.depth === 1;
            },
            isCurrent() {
                return this.element.id === this.currentElement?.id;
            },
            hasReleaseOrWithdrawDate() {
                return (
                    this.element.attributes?.['release-date'] !== null ||
                    this.element.attributes?.['withdraw-date'] !== null
                );
            },
            hasWriteApproval() {
                const writeApproval = this.element.attributes?.['write-approval'];
    
                if (!writeApproval || Object.keys(writeApproval).length === 0) {
                    return false;
                }
                return (
                    (writeApproval.all || writeApproval.groups.length > 0 || writeApproval.users.length > 0) &&
                    this.element.attributes?.['can-edit']
                );
            },
            hasNoReadApproval() {
                if (this.context.type === 'users') {
                    return false;
                }
                const readApproval = this.element.attributes?.['read-approval'];
    
                if (!readApproval || Object.keys(readApproval).length === 0 || this.hasWriteApproval) {
                    return false;
                }
                return !readApproval.all && readApproval.groups.length === 0 && readApproval.users.length === 0;
            },
            hasPurposeClass() {
                return this.purposeClass !== '';
            },
            purposeClass() {
                if (
                    (this.isFirstLevel && this.context.type === 'users') ||
                    (this.context.type === 'courses' && this.element.attributes?.purpose === 'task')
                ) {
                    return this.element.attributes?.purpose;
                }
                return '';
            },
            task() {
                if (this.element.relationships?.task?.data) {
                    return this.taskById({
                        id: this.element.relationships?.task?.data?.id,
                    });
                }
    
                return null;
            },
            taskProgress() {
                return this.task ? this.task.attributes.progress + '%' : '';
            },
            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 '';
            },
            isTask() {
                return this.element.attributes?.purpose === 'task';
            },
            showItem() {
                if (this.isTask) {
                    return this.task !== undefined;
                }
    
                return true;
            },
            editMode() {
                return this.viewMode === 'edit';
            },
            dragOptions() {
                return {
                    animation: 0,
                    disabled: false,
                    ghostClass: 'cw-tree-item-ghost',
                };
            },
            canEdit() {
                return this.element.attributes?.['can-edit'] ?? false;
            },
            inCourse() {
                return this.context.type === 'courses';
            },
            progress() {
                return this.progressData?.[this.element.id];
            },
            itemProgress() {
                return this.progress?.progress?.self ?? 0;
            },
            complete() {
                return this.itemProgress === 100;
            },
        },
        methods: {
            ...mapActions({
                loadTask: 'loadTask',
                setAssistiveLiveContents: 'setAssistiveLiveContents',
            }),
            endDrag(e) {
                let sortArray = [];
                for (let child of e.to.childNodes) {
                    sortArray.push({ id: child.attributes.elementid.nodeValue, type: 'courseware-structural-elements' });
                }
    
                let data = {
                    id: e.item._underlying_vm_.id,
                    newPos: e.newIndex,
                    oldPos: e.oldIndex,
                    oldParent: e.item._underlying_vm_.relationships.parent.data.id,
                    newParent: e.to.__vue__.$attrs.elementId,
                    sortArray: sortArray,
                };
    
                if (data.oldParent === data.newParent && data.oldPos === data.newPos) {
                    return;
                }
                if (data.oldParent !== data.newParent) {
                    sortArray.splice(data.newPos, 0, { id: data.id, type: 'courseware-structural-elements' });
                }
    
                data.sortArray = sortArray;
                this.$emit('sort', data);
            },
            sort(data) {
                this.$emit('sort', data);
            },
            handleKeyEvent(e) {
                switch (e.keyCode) {
                    case 13: // enter
                        e.preventDefault();
                        if (this.keyboardSelected) {
                            this.storeKeyboardSorting();
                        } else {
                            this.keyboardSelected = true;
                            const assistiveLive = this.$gettextInterpolate(
                                this.$gettext(
                                    '%{elementTitle} ausgewählt. Aktuelle Position in der Liste: %{pos} von %{listLength}. Drücken Sie die Aufwärts- und Abwärtspfeiltasten, um die Position zu ändern, die Leertaste zum Ablegen, die Escape-Taste zum Abbrechen. Mit Pfeiltasten links und rechts kann die Position in der Hierarchie verändert werden.'
                                ),
                                {
                                    elementTitle: this.element.attributes.title,
                                    pos: this.element.attributes.position + 1,
                                    listLength: this.siblingCount,
                                }
                            );
    
                            this.setAssistiveLiveContents(assistiveLive);
                        }
                        break;
                }
                if (this.keyboardSelected) {
                    const data = {
                        element: this.element,
                        parents: [],
                    };
                    switch (e.keyCode) {
                        case 27: // esc
                        case 9: //tab
                            this.abortKeyboardSorting();
                            break;
                        case 37: // left
                            e.preventDefault();
                            this.$emit('moveItemPrevLevel', data);
                            break;
                        case 38: // up
                            e.preventDefault();
                            this.$emit('moveItemUp', data);
                            break;
                        case 39: // right
                            e.preventDefault();
                            this.$emit('moveItemNextLevel', data);
                            break;
                        case 40: // down
                            e.preventDefault();
                            this.$emit('moveItemDown', data);
                            break;
                    }
                }
            },
            moveItemPrevLevel(data) {
                data.parents.push(this.element.id);
                this.$emit('moveItemPrevLevel', data);
            },
            moveItemUp(data) {
                data.parents.push(this.element.id);
                this.$emit('moveItemUp', data);
            },
            moveItemNextLevel(data) {
                data.parents.push(this.element.id);
                this.$emit('moveItemNextLevel', data);
            },
            moveItemDown(data) {
                data.parents.push(this.element.id);
                this.$emit('moveItemDown', data);
            },
            abortKeyboardSorting() {
                this.$emit('childrenUpdated');
                const assistiveLive = this.$gettextInterpolate(this.$gettext('%{elementTitle}. Neuordnung abgebrochen.'), {
                    elementTitle: this.element.attributes.title,
                });
                this.setAssistiveLiveContents(assistiveLive);
                this.$nextTick(() => {
                    this.keyboardSelected = false;
                });
            },
            storeKeyboardSorting() {
                const data = {
                    id: this.element.id,
                    newPos: this.element.newPos,
                    oldPos: this.element.attributes.position,
                    oldParent: this.element.relationships.parent.data.id,
                    newParent: this.element.newParentId,
                    sortArray: this.element.sortArray,
                };
                this.keyboardSelected = false;
    
                if (data.newParent === undefined || data.newPos === undefined) {
                    const assistiveLive = this.$gettextInterpolate(
                        this.$gettext('%{elementTitle}. Neuordnung nicht möglich.'),
                        { elementTitle: this.element.attributes.title }
                    );
                    this.setAssistiveLiveContents(assistiveLive);
                    return;
                }
    
                if (data.oldParent === data.newParent && data.oldPos === data.newPos) {
                    const assistiveLive = this.$gettextInterpolate(
                        this.$gettext('%{elementTitle}. Neuordnung abgebrochen.'),
                        { elementTitle: this.element.attributes.title }
                    );
                    this.setAssistiveLiveContents(assistiveLive);
                    return;
                }
                this.$emit('sort', data);
                const assistiveLive = this.$gettextInterpolate(
                    this.$gettext('%{elementTitle}, abgelegt. Entgültige Position in der Liste: %{pos} von %{listLength}.'),
                    { elementTitle: this.element.attributes.title, pos: data.newPos + 1, listLength: this.siblingCount }
                );
                this.setAssistiveLiveContents(assistiveLive);
            },
        },
        mounted() {
            if (this.element.relationships?.task?.data) {
                this.loadTask({
                    taskId: this.element.relationships.task.data.id,
                });
            }
            if (this.newPos || this.newParentId) {
                this.keyboardSelected = true;
                this.$refs.sortableHandle.focus();
            }
        },
        watch: {
            newPos() {
                this.keyboardSelected = true;
                this.$refs.sortableHandle.focus();
            },
            newParentId() {
                this.keyboardSelected = true;
                this.$refs.sortableHandle.focus();
            },
        },
    };
    </script>