Skip to content
Snippets Groups Projects
Select Git revision
  • f127f0fb49c0f687f80588e0e1008e95be37d6ce
  • main default protected
  • studip-rector
  • ci-opt
  • course-members-export-as-word
  • data-vue-app
  • pipeline-improvements
  • webpack-optimizations
  • rector
  • icon-renewal
  • http-client-and-factories
  • jsonapi-atomic-operations
  • vueify-messages
  • tic-2341
  • 135-translatable-study-areas
  • extensible-sorm-action-parameters
  • sorm-configuration-trait
  • jsonapi-mvv-routes
  • docblocks-for-magic-methods
19 results

container.js

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.
    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>