Skip to content
Snippets Groups Projects
Forked from Stud.IP / Stud.IP
1616 commits behind the upstream repository.
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
CoursewareToolbarBlocks.vue 10.72 KiB
<template>
    <div class="cw-toolbar-blocks">
        <form @submit.prevent="loadSearch">
            <div class="input-group files-search search cw-block-search">
                <input
                    ref="searchBox"
                    type="text"
                    v-model="searchInput"
                    @click.stop
                    :label="$gettext('Geben Sie einen Suchbegriff mit mindestens 3 Zeichen ein.')"
                />
                <span class="input-group-append" @click.stop>
                    <button
                        v-if="searchInput"
                        type="button"
                        class="button reset-search"
                        id="reset-search"
                        :title="$gettext('Suche zurücksetzen')"
                        @click="resetSearch"
                    >
                        <studip-icon shape="decline" :size="20"></studip-icon>
                    </button>
                    <button
                        type="submit"
                        class="button"
                        id="search-btn"
                        :title="$gettext('Suche starten')"
                        @click="loadSearch"
                    >
                        <studip-icon shape="search" :size="20"></studip-icon>
                    </button>
                </span>
            </div>
        </form>

        <div class="filterpanel">
            <span class="sr-only">{{ $gettext('Kategorien Filter') }}</span>
            <button
                v-for="category in blockCategories"
                :key="category.type"
                class="button"
                :class="{ 'button-active': category.type === currentFilterCategory }"
                :aria-pressed="category.type === currentFilterCategory ? 'true' : 'false'"
                @click="selectCategory(category.type)"
            >
                {{ category.title }}
            </button>
        </div>

        <div v-if="filteredBlockTypes.length > 0" class="cw-blockadder-item-list">
            <draggable
                v-if="filteredBlockTypes.length > 0"
                class="cw-blockadder-item-list"
                tag="div"
                role="listbox"
                v-model="filteredBlockTypes"
                handle=".cw-sortable-handle-blockadder"
                :group="{ name: 'blocks', pull: 'clone', put: 'false' }"
                :clone="cloneBlock"
                :sort="false"
                :emptyInsertThreshold="20"
                @start="dragBlockStart($event)"
                @end="dropNewBlock($event)"
                ref="sortables"
                sectionId="0"
            >
                <courseware-blockadder-item
                    v-for="(block, index) in filteredBlockTypes"
                    :key="index"
                    :title="block.title"
                    :type="block.type"
                    :data-blocktype="block.type"
                    :description="block.description"
                    @blockAdded="$emit('blockAdded')"
                />
            </draggable>
        </div>
        <courseware-companion-box
            v-else
            :msgCompanion="$gettext('Es wurden keine passenden Blöcke gefunden.')"
            mood="pointing"
        />
    </div>
</template>

<script>
import CoursewareBlockadderItem from './CoursewareBlockadderItem.vue';
import CoursewareCompanionBox from '../layouts/CoursewareCompanionBox.vue';
import containerMixin from '@/vue/mixins/courseware/container.js';
import draggable from 'vuedraggable';

import { mapActions, mapGetters } from 'vuex';

export default {
    name: 'courseware-toolbar-blocks',
    mixins: [containerMixin],
    components: {
        CoursewareBlockadderItem,
        CoursewareCompanionBox,
        draggable,
    },
    data() {
        return {
            searchInput: '',
            currentFilterCategory: '',
            filteredBlockTypes: [],
            categorizedBlocks: [],

            isDragging: false,
        };
    },
    computed: {
        ...mapGetters({
            unorderedBlockTypes: 'blockTypes',
            favoriteBlockTypes: 'favoriteBlockTypes',
        }),
        blockTypes() {
            let blockTypes = JSON.parse(JSON.stringify(this.unorderedBlockTypes));
            blockTypes.sort((a, b) => {
                return a.title > b.title ? 1 : b.title > a.title ? -1 : 0;
            });
            return blockTypes;
        },
        blockCategories() {
            return [
                { title: this.$gettext('Favoriten'), type: 'favorite' },
                { title: this.$gettext('Texte'), type: 'text' },
                { title: this.$gettext('Multimedia'), type: 'multimedia' },
                { title: this.$gettext('Interaktion'), type: 'interaction' },
                { title: this.$gettext('Gestaltung'), type: 'layout' },
                { title: this.$gettext('Externe Inhalte'), type: 'external' },
                { title: this.$gettext('Biografie'), type: 'biography' },
            ];
        },
    },
    methods: {
        ...mapActions({
            companionWarning: 'companionWarning',
            createBlock: 'createBlockInContainer',
            setAdderStorage: 'coursewareBlockAdder',
        }),
        loadSearch() {
            let searchTerms = this.searchInput.trim();
            if (searchTerms.length < 3 && !this.currentFilterCategory) {
                this.companionWarning({
                    info: this.$gettext(
                        'Leider ist Ihr Suchbegriff zu kurz. Der Suchbegriff muss mindestens 3 Zeichen lang sein.'
                    ),
                });
                return;
            }
            this.filteredBlockTypes = this.blockTypes;

            // filter results by given filter first so only these results are searched if an additional search term is given
            if (this.currentFilterCategory) {
                this.filterBlockTypesByCategory();
                this.categorizedBlocks = this.filteredBlockTypes;
            } else {
                this.categorizedBlocks = this.blockTypes;
            }

            searchTerms = searchTerms.toLowerCase().split(' ');

            // sort out block types that don't contain all search words
            searchTerms.forEach((term) => {
                this.filteredBlockTypes = this.filteredBlockTypes.filter(
                    (block) =>
                        block.title.toLowerCase().includes(term) || block.description.toLowerCase().includes(term)
                );
            });

            // add block types to the search if a search term matches a tag even if they aren't in the given category
            if (this.searchInput.trim().length > 0) {
                this.filteredBlockTypes.push(...this.getBlockTypesByTags(searchTerms));
                // remove possible duplicates
                this.filteredBlockTypes = [
                    ...new Map(this.filteredBlockTypes.map((item) => [item['title'], item])).values(),
                ];
            }
        },
        filterBlockTypesByCategory() {
            if (this.currentFilterCategory !== 'favorite') {
                this.filteredBlockTypes = this.filteredBlockTypes.filter((block) =>
                    block.categories.includes(this.currentFilterCategory)
                );
            } else {
                this.filteredBlockTypes = this.favoriteBlockTypes;
            }
        },
        getBlockTypesByTags(searchTags) {
            return this.categorizedBlocks.filter((block) => {
                const lowercaseTags = block.tags.map((blockTag) => blockTag.toLowerCase());
                for (const tag of searchTags) {
                    if (lowercaseTags.filter((blockTag) => blockTag.includes(tag.toLowerCase())).length > 0) {
                        return true;
                    }
                }
                return false;
            });
        },
        selectCategory(type) {
            if (this.currentFilterCategory !== type) {
                this.currentFilterCategory = type;
            } else {
                this.resetCategory();
            }
        },
        resetCategory() {
            this.currentFilterCategory = '';
            if (!this.searchInput) {
                this.filteredBlockTypes = this.blockTypes;
            } else {
                this.loadSearch();
            }
        },
        resetSearch() {
            this.filteredBlockTypes = this.blockTypes;
            this.searchInput = '';
            this.currentFilterCategory = '';
        },
        cloneBlock(original) {
            original.attributes = {
                'block-type': original.type,
                payload: {
                    file_id: '',
                    folder_id: '',
                    background_image_id: '',
                    files: [],
                    url: 'studip.de',
                    sort: 'none',
                    tool_id: '',
                    cards: [],
                    text: ' ',
                    shapes: {},
                    type: 'Persönliches Ziel',
                    content: [{ color: 'blue', label: '', value: '0' }],
                },
            };
            original.relationships = {
                'user-data-field': {
                    data: { id: null },
                },
            };
            return original;
        },
        dragBlockStart(e) {
            this.isDragging = true;
        },
        async dropNewBlock(e) {
            const target = e.to.__vue__.$attrs;
            const blockType = e.item.__vue__.$attrs['data-blocktype'];

            // only execute if dropped in destined list
            if (!target.containerId) {
                return;
            }
            // set chosen container and section and pass block data
            this.setAdderStorage({
                container: this.containerById({ id: target.containerId }),
                section: target.sectionId,
                type: blockType,
                position: e.newIndex,
            });

            await this.addNewBlock();
            this.resetAdderStorage();
            this.isDragging = false;
        },
    },
    mounted() {
        this.filteredBlockTypes = this.blockTypes;
        setTimeout(() => this.$refs.searchBox.focus(), 800);
    },
    watch: {
        searchInput(newValue, oldValue) {
            if (newValue.length >= 3 && newValue !== oldValue) {
                this.loadSearch();
            }
            if (newValue.length < oldValue.length && newValue.length < 3) {
                if (!this.currentFilterCategory) {
                    this.filteredBlockTypes = this.blockTypes;
                } else {
                    this.loadSearch();
                }
            }
        },
        currentFilterCategory(newValue) {
            if (newValue) {
                this.loadSearch();
            }
        },
    },
};
</script>