aboutsummaryrefslogtreecommitdiff
path: root/resources
diff options
context:
space:
mode:
authorRon Lucke <lucke@elan-ev.de>2023-06-15 08:03:09 +0000
committerDavid Siegfried <david.siegfried@uni-vechta.de>2023-06-15 08:03:09 +0000
commit650de7b907980cda5e0fbf7c9c425e4c485f4a46 (patch)
tree1231d843de0c0dfb5d36e1d742d7294d9cd1d42d /resources
parentc7cd732edfb20f073b1eee64ff9d87cef979b475 (diff)
Neuanordnung der Block-Hinzufügen-Liste mit Suchfunktion
Closes #2489 Merge request studip/studip!1796
Diffstat (limited to 'resources')
-rw-r--r--resources/assets/stylesheets/scss/courseware.scss61
-rw-r--r--resources/vue/components/courseware/CoursewareRibbonToolbar.vue1
-rw-r--r--resources/vue/components/courseware/CoursewareToolsBlockadder.vue252
3 files changed, 219 insertions, 95 deletions
diff --git a/resources/assets/stylesheets/scss/courseware.scss b/resources/assets/stylesheets/scss/courseware.scss
index a2c1b35..827c308 100644
--- a/resources/assets/stylesheets/scss/courseware.scss
+++ b/resources/assets/stylesheets/scss/courseware.scss
@@ -1791,6 +1791,67 @@ b l o c k a d d e r
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: $base-color #f5f5f5;
+
+ .input-group.files-search {
+ &.search {
+ border: thin solid var(--dark-gray-color-30);
+ margin-bottom: 0px;
+ input {
+ border: none;
+ }
+ }
+
+ .input-group-append {
+ .button {
+ border: none;
+ border-left: thin solid var(--dark-gray-color-30);
+ &.active {
+ background-color: var(--base-color);
+ }
+ }
+ .reset-search {
+ border: none;
+ background-color: var(--white);
+ }
+ }
+
+ .active-filter {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ border: solid thin var(--black);
+ background-color: var(--content-color-10);
+ margin: 3px;
+ padding: 2px 3px;
+
+ .removefilter {
+ border: none;
+ background-color: transparent;
+ }
+ }
+ }
+
+ .cw-block-search {
+ width: inherit;
+ }
+
+ .filterpanel {
+ margin-bottom: 5px;
+ padding: 2px;
+ border: thin solid var(--dark-gray-color-30);
+ border-top: none;
+ background-color: #fff;
+
+ .button {
+ min-width: inherit;
+ margin: 4px 2px;
+
+ &.button-active {
+ background-color: var(--base-color);
+ color: var(--white);
+ }
+ }
+ }
}
.cw-collapsible {
diff --git a/resources/vue/components/courseware/CoursewareRibbonToolbar.vue b/resources/vue/components/courseware/CoursewareRibbonToolbar.vue
index edf356e..08da42a 100644
--- a/resources/vue/components/courseware/CoursewareRibbonToolbar.vue
+++ b/resources/vue/components/courseware/CoursewareRibbonToolbar.vue
@@ -31,6 +31,7 @@
:index="1"
>
<courseware-tools-blockadder
+ v-if="showBlockAdder"
id="cw-ribbon-tool-blockadder"
:stickyRibbon="stickyRibbon"
@blockAdded="$emit('blockAdded')"
diff --git a/resources/vue/components/courseware/CoursewareToolsBlockadder.vue b/resources/vue/components/courseware/CoursewareToolsBlockadder.vue
index 97e5c47..5f6ffd4 100644
--- a/resources/vue/components/courseware/CoursewareToolsBlockadder.vue
+++ b/resources/vue/components/courseware/CoursewareToolsBlockadder.vue
@@ -2,83 +2,68 @@
<div class="cw-tools-element-adder">
<courseware-tabs class="cw-tools-element-adder-tabs">
<courseware-tab :name="$gettext('Blöcke')" :selected="showBlockadder" :index="0" :style="{ maxHeight: maxHeight + 'px' }">
- <courseware-collapsible-box :title="textBlockHelper">
- <courseware-block-helper :blockTypes="blockTypes" />
- </courseware-collapsible-box>
- <courseware-collapsible-box :title="textAdderFavs" :open="favoriteBlockTypes.length > 0">
- <div class="cw-element-adder-wrapper" v-if="!showEditFavs">
- <courseware-companion-box
- v-if="favoriteBlockTypes.length === 0"
- mood="sad"
- :msgCompanion="textFavsEmpty"
- />
- <courseware-blockadder-item
- v-for="(block, index) in favoriteBlockTypes"
- :key="index"
- :title="block.title"
- :icon="block.icon"
- :type="block.type"
- :description="block.description"
- @blockAdded="$emit('blockAdded')"
+ <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>
- <div class="cw-element-adder-favs-wrapper" v-if="showEditFavs">
- <div class="cw-element-adder-all-blocks" :class="{ 'fav-edit-active': showEditFavs }">
- <courseware-blockadder-item
- v-for="(block, index) in blockTypes"
- :key="index"
- :title="block.title"
- :type="block.type"
- :description="block.description"
- @blockAdded="$emit('blockAdded')"
- />
- </div>
- <div class="cw-element-adder-favs">
- <div
- v-for="(block, index) in blockTypes"
- :key="'fav-item-' + index"
- class="cw-block-fav-item"
- :class="[isBlockFav(block) ? 'cw-block-fav-item-active' : '']"
- @click="toggleFavItem(block)"
- ></div>
- </div>
- </div>
- <button v-show="!showEditFavs" class="button" @click="showEditFavs = true">
- <translate>Favoriten bearbeiten</translate>
- </button>
- <button v-show="showEditFavs" class="button" @click="endEditFavs">
- <translate>Favoriten bearbeiten schließen</translate>
+ </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>
- </courseware-collapsible-box>
- <courseware-collapsible-box :title="textAdderAll">
- <div class="cw-element-adder-all-blocks" :class="{ 'fav-edit-active': showEditFavs }">
- <courseware-blockadder-item
- v-for="(block, index) in blockTypes"
- :key="index"
- :title="block.title"
- :type="block.type"
- :description="block.description"
- @blockAdded="$emit('blockAdded')"
- />
- </div>
- </courseware-collapsible-box>
- <courseware-collapsible-box
- v-for="(category, index) in blockCategories"
- :key="index"
- :title="category.title"
- :open="category.type === 'basis' && favoriteBlockTypes.length === 0"
- >
- <div v-for="(block, index) in blockTypes" :key="index">
- <courseware-blockadder-item
- v-if="block.categories.includes(category.type)"
- :title="block.title"
- :icon="block.icon"
- :type="block.type"
- :description="block.description"
- @blockAdded="$emit('blockAdded')"
- />
- </div>
- </courseware-collapsible-box>
+ </div>
+
+ <div v-if="filteredBlockTypes.length > 0">
+ <courseware-blockadder-item
+ v-for="(block, index) in filteredBlockTypes"
+ :key="index"
+ :title="block.title"
+ :type="block.type"
+ :description="block.description"
+ @blockAdded="$emit('blockAdded')"
+ />
+ </div>
+ <div v-else>
+ <courseware-companion-box
+ :msgCompanion="$gettext('Es wurden keine passenden Blöcke gefunden.')"
+ mood="pointing"
+ />
+ </div>
</courseware-tab>
<courseware-tab :name="$gettext('Abschnitte')" :selected="showContaineradder" :index="1" :style="{ maxHeight: maxHeight + 'px' }">
<courseware-collapsible-box
@@ -109,7 +94,6 @@ import CoursewareTab from './CoursewareTab.vue';
import CoursewareCollapsibleBox from './CoursewareCollapsibleBox.vue';
import CoursewareBlockadderItem from './CoursewareBlockadderItem.vue';
import CoursewareContainerAdderItem from './CoursewareContainerAdderItem.vue';
-import CoursewareBlockHelper from './CoursewareBlockHelper.vue';
import CoursewareCompanionBox from './CoursewareCompanionBox.vue';
import { mapActions, mapGetters } from 'vuex';
@@ -121,7 +105,6 @@ export default {
CoursewareCollapsibleBox,
CoursewareBlockadderItem,
CoursewareContainerAdderItem,
- CoursewareBlockHelper,
CoursewareCompanionBox,
},
props: {
@@ -134,11 +117,10 @@ export default {
return {
showBlockadder: true,
showContaineradder: false,
- showEditFavs: false,
- textAdderFavs: this.$gettext('Favoriten'),
- textAdderAll: this.$gettext('Alle Blöcke'),
- textBlockHelper: this.$gettext('Blockassistent'),
- textFavsEmpty: this.$gettext('Sie haben noch keine Lieblingsblöcke ausgewählt.'),
+ searchInput: '',
+ currentFilterCategory: '',
+ filteredBlockTypes: [],
+ categorizedBlocks: []
};
},
computed: {
@@ -166,12 +148,11 @@ export default {
},
blockCategories() {
return [
- { title: this.$gettext('Standard'), type: 'basis' },
+ { title: this.$gettext('Favoriten'), type: 'favorite' },
{ title: this.$gettext('Texte'), type: 'text' },
{ title: this.$gettext('Multimedia'), type: 'multimedia' },
- { title: this.$gettext('Aufgaben & Interaktion'), type: 'interaction' },
+ { title: this.$gettext('Interaktion'), type: 'interaction' },
{ title: this.$gettext('Gestaltung'), type: 'layout' },
- { title: this.$gettext('Dateien'), type: 'files' },
{ title: this.$gettext('Externe Inhalte'), type: 'external' },
{ title: this.$gettext('Biografie'), type: 'biography' },
];
@@ -188,7 +169,8 @@ export default {
...mapActions({
removeFavoriteBlockType: 'removeFavoriteBlockType',
addFavoriteBlockType: 'addFavoriteBlockType',
- coursewareContainerAdder: 'coursewareContainerAdder'
+ coursewareContainerAdder: 'coursewareContainerAdder',
+ companionWarning: 'companionWarning'
}),
displayContainerAdder() {
this.showContaineradder = true;
@@ -199,13 +181,6 @@ export default {
this.showBlockadder = true;
this.disableContainerAdder();
},
- toggleFavItem(block) {
- if (this.isBlockFav(block)) {
- this.removeFavoriteBlockType(block.type);
- } else {
- this.addFavoriteBlockType(block.type);
- }
- },
isBlockFav(block) {
let isFav = false;
this.favoriteBlockTypes.forEach((type) => {
@@ -219,15 +194,85 @@ export default {
disableContainerAdder() {
this.coursewareContainerAdder(false);
},
- endEditFavs() {
- this.showEditFavs = false;
- this.$emit('scrollTop');
+ 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 = '';
+ }
},
mounted() {
if (this.containerAdder === true) {
this.displayContainerAdder();
}
+ this.filteredBlockTypes = this.blockTypes;
+ setTimeout(() => this.$refs.searchBox.focus(), 800);
},
watch: {
adderStorage(newValue) {
@@ -244,6 +289,23 @@ export default {
if (oldValue === true && newValue === false) {
this.disableContainerAdder();
}
+ },
+ 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();
+ }
}
}
};