Newer
Older
<div id="cw-toolbar-blocks-header" class="cw-toolbar-tool-header">
<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 id="filterpanel" 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>
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
<div class="cw-toolbar-tool-content" :style="toolContentStyle">
<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>
</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',
props: {
toolbarContentHeight: {
type: Number,
required: true,
},
},
data() {
return {
searchInput: '',
currentFilterCategory: '',
filteredBlockTypes: [],
};
},
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' },
const filterPanelHeight = document.getElementById("filterpanel")?.offsetHeight ?? 75;
const height = this.toolbarContentHeight - filterPanelHeight - 40;
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());
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 = '';
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
},
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();
}