diff --git a/lib/classes/JsonApi/Routes/Courseware/StructuralElementsImageUpload.php b/lib/classes/JsonApi/Routes/Courseware/StructuralElementsImageUpload.php
index 3f85d9bb3cf0a60ed90fbb8562190182e1af7c28..aaca497e79764394a6c1140264addd015df56fc0 100644
--- a/lib/classes/JsonApi/Routes/Courseware/StructuralElementsImageUpload.php
+++ b/lib/classes/JsonApi/Routes/Courseware/StructuralElementsImageUpload.php
@@ -34,12 +34,13 @@ class StructuralElementsImageUpload extends NonJsonApiController
         $fileRef = $this->handleUpload($request, $publicFolder, $structuralElement);
 
         // remove existing image
-        if ($structuralElement->image) {
+        if (is_a($structuralElement->image, \FileRef::class)) {
             $structuralElement->image->getFileType()->delete();
         }
 
         // refer to newly uploaded image
         $structuralElement->image_id = $fileRef->id;
+        $structuralElement->image_type = \FileRef::class;
         $structuralElement->store();
 
         return $response->withStatus(201);
diff --git a/resources/vue/components/SearchWithFilter.vue b/resources/vue/components/SearchWithFilter.vue
index 983012ea4aded3c617c58493eb15971ad7aaafd2..a488203b82b8a2c816ee8098c64128b8845617b3 100644
--- a/resources/vue/components/SearchWithFilter.vue
+++ b/resources/vue/components/SearchWithFilter.vue
@@ -1,43 +1,43 @@
 <template>
-    <form @submit.prevent="onSearch">
-
-        <slot name="filters"></slot>
-
-        <input
-            class="search-bar-input"
-            type="text"
-            v-model="searchTerm"
-            @focus="onInputFocus"
-            :aria-label="$gettext('Geben Sie einen Suchbegriff mit mindestens 3 Zeichen ein.')"
-        />
-
-        <button
-            v-if="showSearchResults"
-            class="search-bar-erase"
-            type="button"
-            :title="$gettext('Suchformular zurücksetzen')"
-            @click="onReset"
-        >
-            <StudipIcon shape="decline" :size="20" />
-        </button>
-
-        <button
-            type="button"
-            :title="$gettext('Suchfilter einstellen')"
-            class="search-bar-filter"
-            :class="{ active: showFilterPanel }"
-        >
-            <StudipIcon shape="filter" :role="showFilterPanel ? 'info_alt' : 'clickable'" :size="20" />
-        </button>
-
-        <button type="submit" :value="$gettext('Suchen')" aria-controls="search" class="submit-search">
-            <StudipIcon shape="search" :size="20" />
-        </button>
-
-        <div class="filterpanel" ref="filterPanel" v-if="'TODO' || showFilterPanel">
+    <div>
+        <form @submit.prevent="onSearch">
+            <slot name="filters"></slot>
+
+            <input
+                class="search-bar-input"
+                type="text"
+                v-model="searchTerm"
+                :aria-label="$gettext('Geben Sie einen Suchbegriff mit mindestens 3 Zeichen ein.')"
+            />
+
+            <button
+                v-if="showSearchResults"
+                class="search-bar-erase"
+                type="button"
+                :title="$gettext('Suchformular zurücksetzen')"
+                @click="onReset"
+            >
+                <StudipIcon shape="decline" :size="20" />
+            </button>
+
+            <button
+                type="button"
+                :title="$gettext('Suchfilter einstellen')"
+                class="search-bar-filter"
+                :class="{ active: showFilterPanel }"
+                @click="onToggleFilterPanel"
+            >
+                <StudipIcon shape="filter" :role="showFilterPanel ? 'info_alt' : 'clickable'" :size="20" />
+            </button>
+
+            <button type="submit" :value="$gettext('Suchen')" aria-controls="search" class="submit-search">
+                <StudipIcon shape="search" :size="20" />
+            </button>
+        </form>
+        <div class="filterpanel" ref="filterPanel" v-if="showFilterPanel">
             <slot></slot>
         </div>
-    </form>
+    </div>
 </template>
 
 <script>
@@ -70,21 +70,9 @@ export default {
         onSearch() {
             this.$emit('search', this.searchTerm);
         },
-        onInputFocus() {
-            this.showFilterPanel = true;
-            const check = ({ target }) => {
-                const filterPanel = this.$refs.filterPanel?.$el;
-                if (
-                    filterPanel === target ||
-                    filterPanel?.contains(target) ||
-                    target.classList.contains('search-bar-input')
-                ) {
-                    return;
-                }
-                this.showFilterPanel = false;
-                window.removeEventListener('click', check);
-            };
-            window.addEventListener('click', check.bind(this));
+        onToggleFilterPanel() {
+            console.debug('toggle filter panel', !this.showFilterPanel);
+            this.showFilterPanel = !this.showFilterPanel;
         },
     },
     mounted() {
@@ -114,6 +102,11 @@ input {
     width: 100%;
 }
 
+input.search-bar-input {
+    line-height: 1.5;
+    padding-block: 0.25em;
+}
+
 button {
     align-items: center;
     background-color: var(--content-color-20);
@@ -141,14 +134,8 @@ button.search-bar-erase {
     background-color: var(--white);
     border: thin solid var(--content-color-40);
     box-sizing: border-box;
-    height: 12em;
-    margin: 0px;
-    margin-block-start: 45px;
-    max-width: calc(100% - 30px);
+    margin-block-start: 1rem;
     padding: 10px;
-    position: absolute;
-    width: 50em;
-    z-index: 1;
 }
 
 .filterpanel::before,
diff --git a/resources/vue/components/courseware/CoursewareShelfDialogAdd.vue b/resources/vue/components/courseware/CoursewareShelfDialogAdd.vue
index 32b261402091b762fd4246b20c8d9f7caf214d73..bff4d4da9f21e3007336d632757365e21c47b671 100644
--- a/resources/vue/components/courseware/CoursewareShelfDialogAdd.vue
+++ b/resources/vue/components/courseware/CoursewareShelfDialogAdd.vue
@@ -27,19 +27,21 @@
                         {{ $gettext('Bild hochladen') }}
                         <br>
                         <input class="cw-file-input" ref="upload_image" type="file" accept="image/*" @change="checkUploadFile"/>
-                        <courseware-companion-box
+                        <CoursewareCompanionBox
                             v-if="uploadFileError"
                             :msgCompanion="uploadFileError"
                             mood="sad"
                             class="cw-companion-box-in-form"
                         />
                 </label>
-                <label v-if="selectedStockImage">
+                <template v-if="selectedStockImage">
                     <StockImageSelectableImageCard :stock-image="selectedStockImage" />
-                    <button class="button" type="button" @click="selectedStockImage = null">
-                        {{ $gettext('Entfernen') }}
-                    </button>
-                </label>
+                    <label>
+                        <button class="button" type="button" @click="selectedStockImage = null">
+                            {{ $gettext('Bild entfernen') }}
+                        </button>
+                    </label>
+                </template>
                 <label v-else>
                         {{ $gettext('oder') }}
                         <br>
@@ -130,6 +132,7 @@
 </template>
 
 <script>
+import CoursewareCompanionBox from './CoursewareCompanionBox.vue';
 import StockImageSelectableImageCard from '../stock-images/SelectableImageCard.vue';
 import StockImageChooser from '../stock-images/ChooserDialog.vue';
 import StudipSelect from './../StudipSelect.vue';
@@ -141,6 +144,7 @@ export default {
     name: 'courseware-shelf-dialog-add',
     mixins: [colorMixin],
     components: {
+        CoursewareCompanionBox,
         StockImageSelectableImageCard,
         StockImageChooser,
         StudipWizardDialog,
@@ -221,6 +225,7 @@ export default {
                 this.uploadFileError = this.$gettext('Diese Datei ist kein Bild. Bitte wählen Sie ein Bild aus.');
             } else {
                 this.uploadFileError = '';
+                this.selectedStockImage = null;
             }
         },
         async createUnit() {
@@ -281,7 +286,9 @@ export default {
             }
         },
         onSelectStockImage(stockImage) {
-            // TODO: remove file selected for upload
+            if (this.$refs?.upload_image) {
+                this.$refs.upload_image.value = null;
+            }
             this.selectedStockImage = stockImage;
             this.showStockImageChooser = false;
         },
diff --git a/resources/vue/components/courseware/CoursewareStructuralElement.vue b/resources/vue/components/courseware/CoursewareStructuralElement.vue
index 222691b2e6b25fc28c184b5b0a2fff75f2d44199..4f8bb95efa076ca9f1ba6d8e989c541358806284 100644
--- a/resources/vue/components/courseware/CoursewareStructuralElement.vue
+++ b/resources/vue/components/courseware/CoursewareStructuralElement.vue
@@ -237,7 +237,7 @@
                                         >
                                             <template #open-indicator="selectAttributes">
                                                 <span v-bind="selectAttributes"
-                                                    ><studip-icon shape="arr_1down" size="10"
+                                                    ><studip-icon shape="arr_1down" :size="10"
                                                 /></span>
                                             </template>
                                             <template #no-options>
@@ -303,22 +303,33 @@
                             </courseware-tab>
                             <courseware-tab :name="textEdit.image" :index="2">
                                 <form class="default" @submit.prevent="">
-                                    <img
-                                        v-if="showPreviewImage"
-                                        :src="image"
-                                        class="cw-structural-element-image-preview"
-                                        :alt="$gettext('Vorschaubild')"
-                                    />
-                                    <label v-if="showPreviewImage">
-                                        <button class="button" @click="deleteImage" v-translate>Bild löschen</button>
-                                    </label>
+                                    <template v-if="hasImage">
+                                        <img
+                                            :src="image"
+                                            class="cw-structural-element-image-preview"
+                                            :alt="$gettext('Vorschaubild')"
+                                            />
+                                        <label>
+                                            <button class="button" @click="deleteImage" v-translate>Bild löschen</button>
+                                        </label>
+                                    </template>
+
                                     <div v-if="uploadFileError" class="messagebox messagebox_error">
                                         {{ uploadFileError }}
                                     </div>
-                                    <label v-if="!showPreviewImage">
-                                        <translate>Bild hochladen</translate>
-                                        <input ref="upload_image" type="file" accept="image/*" @change="checkUploadFile" />
-                                    </label>
+
+                                    <template v-if="!hasImage">
+                                        <label>
+                                            {{ $gettext('Bild hochladen') }}
+                                            <input ref="upload_image" type="file" accept="image/*" @change="checkUploadFile" />
+                                        </label>
+                                        {{ $gettext('oder') }}
+                                        <br>
+                                        <button class="button" type="button" @click="showStockImageChooser = true">
+                                            {{ $gettext('Aus dem Bilderpool auswählen') }}
+                                        </button>
+                                        <StockImageChooser v-if="showStockImageChooser" @close="showStockImageChooser = false" @select="onSelectStockImage" />
+                                    </template>
                                 </form>
                             </courseware-tab>
                             <courseware-tab v-if="(inCourse && !isTask) || inContent" :name="textEdit.approval" :index="3">
@@ -686,6 +697,7 @@ import colorMixin from '@/vue/mixins/courseware/colors.js';
 import CoursewareDateInput from './CoursewareDateInput.vue';
 import { FocusTrap } from 'focus-trap-vue';
 import IsoDate from './IsoDate.vue';
+import StockImageChooser from '../stock-images/ChooserDialog.vue';
 import StudipDialog from '../StudipDialog.vue';
 import draggable from 'vuedraggable';
 import { mapActions, mapGetters } from 'vuex';
@@ -711,6 +723,7 @@ export default {
         CoursewareDateInput,
         FocusTrap,
         IsoDate,
+        StockImageChooser,
         StudipDialog,
         draggable,
     },
@@ -783,7 +796,9 @@ export default {
             deletingPreviewImage: false,
             processing: false,
             keyboardSelected: null,
-            assistiveLive: ''
+            assistiveLive: '',
+            showStockImageChooser: false,
+            selectedStockImage: null,
         };
     },
 
@@ -910,11 +925,18 @@ export default {
         },
 
         image() {
+            if (this.selectedStockImage) {
+                return this.selectedStockImage.attributes["download-urls"].small
+            }
             return this.structuralElement.relationships?.image?.meta?.['download-url'] ?? null;
         },
 
-        showPreviewImage() {
-            return this.image !== null && this.deletingPreviewImage === false;
+        imageType() {
+            return this.structuralElement.relationships?.image?.data?.type ?? null;
+        },
+
+        hasImage() {
+            return (this.image || this.selectedStockImage ) && this.deletingPreviewImage === false;
         },
 
         structuralElementLoaded() {
@@ -1234,6 +1256,7 @@ export default {
             uploadImageForStructuralElement: 'uploadImageForStructuralElement',
             deleteImageForStructuralElement: 'deleteImageForStructuralElement',
             companionSuccess: 'companionSuccess',
+            setStockImageForStructuralElement: 'setStockImageForStructuralElement',
             showElementEditDialog: 'showElementEditDialog',
             showElementAddDialog: 'showElementAddDialog',
             showElementExportDialog: 'showElementExportDialog',
@@ -1369,24 +1392,30 @@ export default {
             if (!this.blocked) {
                 await this.lockObject({ id: this.currentId, type: 'courseware-structural-elements' });
             }
+
             const file = this.$refs?.upload_image?.files[0];
-            if (file) {
-                if (file.size > 2097152) {
-                    return false;
+            try {
+                this.uploadFileError = '';
+                if (file) {
+                    await this.uploadImageForStructuralElement({
+                        structuralElement: this.currentElement,
+                        file,
+                    });
+                } else if (this.selectedStockImage) {
+                    await this.setStockImageForStructuralElement({
+                        structuralElement: this.currentElement,
+                        stockImage: this.selectedStockImage,
+                    })
+                } else if (this.deletingPreviewImage) {
+                    await this.deleteImageForStructuralElement(this.currentElement);
                 }
 
-                this.uploadFileError = '';
-                this.uploadImageForStructuralElement({
-                    structuralElement: this.currentElement,
-                    file,
-                }).catch((error) => {
-                    console.error(error);
-                    this.uploadFileError = this.$gettext('Fehler beim Hochladen der Datei.');
-                });
-                await this.loadStructuralElement(this.currentElement.id);
-            } else if (this.deletingPreviewImage) {
-                await this.deleteImageForStructuralElement(this.currentElement);
+                this.loadStructuralElement(this.currentElement.id);
+            } catch(error) {
+                console.error(error);
+                this.uploadFileError = this.$gettext('Das Bild für das neue Lernmaterial konnte nicht gespeichert werden.');
             }
+
             this.showElementEditDialog(false);
 
             if (this.currentElement.attributes['release-date'] !== '') {
@@ -1399,10 +1428,13 @@ export default {
                     new Date(this.currentElement.attributes['withdraw-date']).getTime() / 1000;
             }
 
-            await this.updateStructuralElement({
-                element: this.currentElement,
-                id: this.currentId,
-            });
+            const element = {
+                id: this.currentElement.id,
+                type: this.currentElement.type,
+                attributes: this.currentElement.attributes,
+            };
+
+            await this.updateStructuralElement({ element, id: this.currentId});
             await this.unlockObject({ id: this.currentId, type: 'courseware-structural-elements' });
             this.$emit('select', this.currentId);
             this.initCurrent();
@@ -1715,7 +1747,15 @@ export default {
                     , {containerTitle: container.attributes.title, pos: currentIndex + 1, listLength: this.containerList.length}
                 );
             this.storeSort();
-        }
+        },
+        onSelectStockImage(stockImage) {
+            if (this.$refs?.upload_image) {
+                this.$refs.upload_image.value = null;
+            }
+            this.selectedStockImage = stockImage;
+            this.showStockImageChooser = false;
+            this.deletingPreviewImage = false;
+        },
     },
     created() {
         this.pluginManager.registerComponentsLocally(this);
diff --git a/resources/vue/components/stock-images/AttributesFieldset.vue b/resources/vue/components/stock-images/AttributesFieldset.vue
index ca161caf0b6306ff07f34f72219cb776afe9b955..ad3f50d9d5ccc1c2f0f1b31b72ad368e7df98fd5 100644
--- a/resources/vue/components/stock-images/AttributesFieldset.vue
+++ b/resources/vue/components/stock-images/AttributesFieldset.vue
@@ -1,27 +1,26 @@
 <template>
-    <fieldset>
-        <legend>{{ $gettext('Attribute') }}</legend>
-        <label>
-            {{ $gettext('Titel') }}<span aria-hidden="true">*</span>
+    <div>
+        <label class="studiprequired">
+            {{ $gettext('Titel') }}<span title="Dies ist ein Pflichtfeld" aria-hidden="true" class="asterisk">*</span>
             <input type="text" required v-model="metadata.title" />
         </label>
-        <label>
-            {{ $gettext('Beschreibung') }}<span aria-hidden="true">*</span>
+        <label class="studiprequired">
+            {{ $gettext('Beschreibung') }}<span title="Dies ist ein Pflichtfeld" aria-hidden="true" class="asterisk">*</span>
             <textarea required v-model="metadata.description" />
         </label>
-        <label>
-            {{ $gettext('Erstellt durch') }}<span aria-hidden="true">*</span>
+        <label class="studiprequired">
+            {{ $gettext('Erstellt durch') }}<span title="Dies ist ein Pflichtfeld" aria-hidden="true" class="asterisk">*</span>
             <input type="text" required v-model="metadata.author" />
         </label>
-        <label>
-            {{ $gettext('Lizenz') }}<span aria-hidden="true">*</span>
+        <label class="studiprequired">
+            {{ $gettext('Lizenz') }}<span title="Dies ist ein Pflichtfeld" aria-hidden="true" class="asterisk">*</span>
             <textarea required v-model="metadata.license" />
         </label>
         <label>
             {{ $gettext('Tags') }}
             <TagsInput v-model="tags" :suggestions="suggestedTags" />
         </label>
-    </fieldset>
+    </div>
 </template>
 <script>
 import TagsInput from './TagsInput.vue';
diff --git a/resources/vue/components/stock-images/Chooser.vue b/resources/vue/components/stock-images/Chooser.vue
index 84b60cc86cdb185cc90503a4eb88bd3c50715dff..6e6ebc09c27bdfa5489059b157632d837fe18700 100644
--- a/resources/vue/components/stock-images/Chooser.vue
+++ b/resources/vue/components/stock-images/Chooser.vue
@@ -51,7 +51,7 @@ export default {
     data: () => ({
         activeFilters: {
             colors: [],
-            orientation: 'any',
+            orientation: 'landscape',
         },
         query: '',
     }),
@@ -83,8 +83,6 @@ ul {
     justify-content: flex-start;
     align-items: center;
     list-style: none;
-    padding: 1rem;
-
-    margin-block-start: 12em;
+    padding: 1rem 0;
 }
 </style>
diff --git a/resources/vue/components/stock-images/ChooserDialog.vue b/resources/vue/components/stock-images/ChooserDialog.vue
index 0b94f753688bb97f9269a382c7925dc3f4f33dd7..bd677571db2b4348ec1b75959be2e6bac7f84d6e 100644
--- a/resources/vue/components/stock-images/ChooserDialog.vue
+++ b/resources/vue/components/stock-images/ChooserDialog.vue
@@ -1,5 +1,11 @@
 <template>
-    <studip-dialog :title="$gettext('Bild auswählen')" :closeText="$gettext('Schließen')" height="420" @close="onClose">
+    <studip-dialog
+        width="880"
+        :title="$gettext('Bild auswählen')"
+        :closeText="$gettext('Schließen')"
+        height="420"
+        @close="onClose"
+    >
         <template v-slot:dialogContent>
             <Chooser :stock-images="stockImages" @select="onSelectImage" />
         </template>
@@ -12,10 +18,6 @@ import Chooser from './Chooser.vue';
 
 export default {
     data: () => ({
-        filters: {
-            orientation: 'any',
-            colors: [],
-        },
         query: '',
         selectedImage: null,
     }),
@@ -52,10 +54,10 @@ export default {
             });
         },
         onClose() {
-            this.$emit("close");
+            this.$emit('close');
         },
         onSelectImage(stockImage) {
-            this.$emit("select", stockImage);
+            this.$emit('select', stockImage);
         },
     },
     mounted() {
diff --git a/resources/vue/components/stock-images/ChooserSearch.vue b/resources/vue/components/stock-images/ChooserSearch.vue
index 82a3e47c1927863167b5f0fbacf68caed9a9d55d..05202d907ef6507c842d7ce6b4fb4b431a586cc5 100644
--- a/resources/vue/components/stock-images/ChooserSearch.vue
+++ b/resources/vue/components/stock-images/ChooserSearch.vue
@@ -137,6 +137,7 @@ export default {
 <style scoped>
 .stock-images-filters-colors {
     display: flex;
+    flex-wrap: wrap;
     gap: 0.25em;
 }
 .stock-images-filters-colors input[type='checkbox'] {
diff --git a/resources/vue/components/stock-images/ColorFilterWidget.vue b/resources/vue/components/stock-images/ColorFilterWidget.vue
new file mode 100644
index 0000000000000000000000000000000000000000..14e87e76fc8fda4c88dc14158b7a611958814b14
--- /dev/null
+++ b/resources/vue/components/stock-images/ColorFilterWidget.vue
@@ -0,0 +1,74 @@
+<template>
+    <SidebarWidget :title="$gettext('Farbe')">
+        <template #content>
+            <VueSelect multiple v-model="selectedColors" :options="mixinColors" @input="onVueSelectInput" label="name">
+                <template #option="{ name, hex }">
+                    <b class="stock-images-filters-color-swatch" :style="`background-color: ${hex}`"></b>
+                    {{ name }}
+                </template>
+
+                <template #selected-option="{ name, hex }">
+                    <b class="stock-images-filters-color-swatch" :style="`background-color: ${hex}`"></b>
+                </template>
+
+                <template #no-options>{{ $gettext('Keine Auswahlmöglichkeiten') }}</template>
+            </VueSelect>
+        </template>
+    </SidebarWidget>
+</template>
+<script>
+import colorMixin from '@/vue/mixins/courseware/colors.js';
+import SidebarWidget from '../SidebarWidget.vue';
+import { orientations } from './filters.js';
+import VueSelect from 'vue-select';
+import 'vue-select/dist/vue-select.css';
+
+export default {
+    model: {
+        prop: 'filters',
+        event: 'change',
+    },
+    props: {
+        filters: {
+            type: Object,
+            required: true,
+        },
+    },
+    mixins: [colorMixin],
+    components: {
+        SidebarWidget,
+        VueSelect,
+    },
+    data: () => ({
+        selectedColors: [],
+    }),
+    methods: {
+        onVueSelectInput(selectedColors) {
+            const colors = selectedColors.map(({ hex }) => hex);
+            this.$emit('change', { ...this.filters, colors });
+        },
+    },
+    mounted() {
+        this.selectedColors = this.mixinColors.filter(({ hex }) => this.filters.colors.includes(hex));
+    },
+    watch: {
+        filters: {
+            handler(newValue) {
+                this.selectedColors = this.mixinColors.filter(({ hex }) => this.filters.colors.includes(hex));
+            },
+            deep: true,
+        },
+    },
+};
+</script>
+
+<style scoped>
+.stock-images-filters-color-swatch {
+    box-shadow: 0 0 0 1px var(--base-color-20);
+    box-sizing: border-box;
+    display: inline-block;
+    width: 20px;
+    height: 20px;
+    transition: all 0.1s;
+}
+</style>
diff --git a/resources/vue/components/stock-images/ImagesList.vue b/resources/vue/components/stock-images/ImagesList.vue
index d141f7137b1442840abfe2fe948374bc78c95d7b..597b9ef8d6e80c3405f6b1b1c8f1bc451853676f 100644
--- a/resources/vue/components/stock-images/ImagesList.vue
+++ b/resources/vue/components/stock-images/ImagesList.vue
@@ -147,4 +147,8 @@ table.default {
 thead th input {
     margin-inline: 1em;
 }
+
+thead th:first-child {
+    width: 3em;
+}
 </style>
diff --git a/resources/vue/components/stock-images/ImagesListItem.vue b/resources/vue/components/stock-images/ImagesListItem.vue
index 46feccf37234fbbb4a8c332c5720a5c5e12cb4d9..8ffd9ef813388df5a9843468ac7c943fd00971b2 100644
--- a/resources/vue/components/stock-images/ImagesListItem.vue
+++ b/resources/vue/components/stock-images/ImagesListItem.vue
@@ -128,10 +128,6 @@ tr > td:nth-child(3) img {
     vertical-align: middle;
 }
 
-tr > td:nth-child(1n + 3) {
-    font-size: smaller;
-}
-
 .stock-image-author,
 .stock-image-tags {
     font-size: 0.8em;
diff --git a/resources/vue/components/stock-images/NavigationWidget.vue b/resources/vue/components/stock-images/NavigationWidget.vue
new file mode 100644
index 0000000000000000000000000000000000000000..177eefe07a08bc772c9042f96e26d75226bdd382
--- /dev/null
+++ b/resources/vue/components/stock-images/NavigationWidget.vue
@@ -0,0 +1,31 @@
+<template>
+    <SidebarWidget>
+        <template #content>
+            <ul
+                class="widget-list widget-links sidebar-navigation navigation-level-3"
+                :aria-label="$gettext('Dritte Navigationsebene')"
+            >
+                <li class="active">
+                    <a aria-current="page" id="nav_overview_index" class="active" :href="overviewUrl">
+                        {{ $gettext('Übersicht') }}
+                    </a>
+                </li>
+            </ul>
+        </template>
+    </SidebarWidget>
+</template>
+
+<script>
+import SidebarWidget from '../SidebarWidget.vue';
+
+export default {
+    components: {
+        SidebarWidget,
+    },
+    computed: {
+        overviewUrl() {
+            return `${window.STUDIP.URLHelper.base_url}/dispatch.php/contents/overview`;
+        },
+    },
+};
+</script>
diff --git a/resources/vue/components/stock-images/OrientationFilterWidget.vue b/resources/vue/components/stock-images/OrientationFilterWidget.vue
new file mode 100644
index 0000000000000000000000000000000000000000..29ee57813c85b416116b6595e13dd52bcb8081ef
--- /dev/null
+++ b/resources/vue/components/stock-images/OrientationFilterWidget.vue
@@ -0,0 +1,36 @@
+<template>
+    <SidebarWidget :title="$gettext('Seitenausrichtung')">
+        <template #content>
+                <select v-model="filters.orientation" class="sidebar-selectlist">
+                    <option v-for="[value, orientation] in Object.entries(orientations)" :value="value">
+                        {{ orientation.text }}
+                    </option>
+                </select>
+        </template>
+    </SidebarWidget>
+</template>
+<script>
+import SidebarWidget from '../SidebarWidget.vue';
+import { orientations } from './filters.js';
+
+export default {
+    model: {
+        prop: 'filters',
+        event: 'change',
+    },
+    props: {
+        filters: {
+            type: Object,
+            required: true,
+        },
+    },
+    components: {
+        SidebarWidget,
+    },
+    computed: {
+        orientations() {
+            return orientations;
+        },
+    },
+};
+</script>
diff --git a/resources/vue/components/stock-images/Page.vue b/resources/vue/components/stock-images/Page.vue
index 983392234c5547c05985bd08ba0f63f330a922f9..bd7da4c4c2c02f631449e1603937288983622460 100644
--- a/resources/vue/components/stock-images/Page.vue
+++ b/resources/vue/components/stock-images/Page.vue
@@ -1,6 +1,5 @@
 <template>
     <div>
-        <ImagesFilters v-model="filters" />
         <ImagesPagination :per-page="perPage" :stock-images="filteredStockImages" v-model="page">
             <ImagesList
                 :checked-images="checkedImages"
@@ -14,7 +13,10 @@
             />
         </ImagesPagination>
         <MountingPortal mountTo="#stock-images-widget" name="sidebar-stock-images">
+            <NavigationWidget />
             <SearchWidget :query="query" @search="onSearch" />
+            <OrientationFilterWidget v-model="filters" />
+            <ColorFilterWidget v-model="filters" />
             <ActionsWidget @initiateUpload="onUploadDialogShow" />
         </MountingPortal>
         <EditDialog
@@ -35,10 +37,12 @@
 <script>
 import { mapActions, mapGetters } from 'vuex';
 import ActionsWidget from './ActionsWidget.vue';
+import ColorFilterWidget from './ColorFilterWidget.vue';
 import EditDialog from './EditDialog.vue';
-import ImagesFilters from './ImagesFilters.vue';
 import ImagesList from './ImagesList.vue';
 import ImagesPagination from './ImagesPagination.vue';
+import NavigationWidget from './NavigationWidget.vue';
+import OrientationFilterWidget from './OrientationFilterWidget.vue';
 import SearchWidget from './SearchWidget.vue';
 import UploadDialog from './UploadDialog.vue';
 import { orientations, similarColors } from './filters.js';
@@ -64,7 +68,17 @@ const search = (stockImages, query) => {
 const sort = (stockImages) => _.sortBy([...stockImages], 'attributes.title');
 
 export default {
-    components: { ActionsWidget, EditDialog, ImagesFilters, ImagesList, ImagesPagination, SearchWidget, UploadDialog },
+    components: {
+        ActionsWidget,
+        ColorFilterWidget,
+        EditDialog,
+        ImagesList,
+        ImagesPagination,
+        NavigationWidget,
+        OrientationFilterWidget,
+        SearchWidget,
+        UploadDialog,
+    },
     data: () => ({
         checkedImages: [],
         filters: {
diff --git a/resources/vue/components/stock-images/SelectableImageCard.vue b/resources/vue/components/stock-images/SelectableImageCard.vue
index 5ba4e9be3139bc6fa2fd5e165f51deec8a529dcd..ac157bc965335b300ea5b14e7fc447a672b26a5b 100644
--- a/resources/vue/components/stock-images/SelectableImageCard.vue
+++ b/resources/vue/components/stock-images/SelectableImageCard.vue
@@ -1,6 +1,7 @@
 <template>
-    <div>
-        <Thumbnail :url="thumbnailUrl" contain class="stock-images-image-card__thumbnail" />
+    <div class="stock-images-selectable-image">
+        <Thumbnail :url="thumbnailUrl" contain class="stock-images-image-card__thumbnail" width="8rem" />
+        <div>{{ stockImage.attributes?.title ?? '' }}</div>
     </div>
 </template>
 
@@ -27,6 +28,19 @@ export default {
 </script>
 
 <style scoped>
+.stock-images-selectable-image {
+    overflow: hidden;
+    position: relative;
+}
+.stock-images-selectable-image > :last-child {
+    position: absolute;
+    background: #ffffffa0;
+    bottom: 0;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    width: 100%;
+}
 .stock-images-image-card__thumbnail {
     background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAAAAACo4kLRAAAAIElEQVQY02N8xgADInAWEwMWMFQEGX/BmW8GiZMoFAQAPcMDB0TNGWIAAAAASUVORK5CYII=);
 }
diff --git a/resources/vue/components/stock-images/ThumbnailCard.vue b/resources/vue/components/stock-images/ThumbnailCard.vue
index b5b211692cfc114f635d1337644efc0f8d932268..0f71322a017c91161d4851b32fdcf45e458c906b 100644
--- a/resources/vue/components/stock-images/ThumbnailCard.vue
+++ b/resources/vue/components/stock-images/ThumbnailCard.vue
@@ -77,9 +77,3 @@ export default {
     },
 };
 </script>
-
-<style>
-.stock-images-thumbnail-card {
-    font-size: smaller;
-}
-</style>
diff --git a/resources/vue/components/stock-images/UploadBox.vue b/resources/vue/components/stock-images/UploadBox.vue
index 2ed4d19d58d5540de7e5762b1b9040331734b31c..cba1f1a45d777b95470a71a8f118f5c4be814c1c 100644
--- a/resources/vue/components/stock-images/UploadBox.vue
+++ b/resources/vue/components/stock-images/UploadBox.vue
@@ -88,7 +88,6 @@ span.or {
 }
 
 .upload-button-holder button {
-    font-size: 20px;
     margin: 0;
     padding: 1em;
 }
diff --git a/resources/vue/components/stock-images/components.js b/resources/vue/components/stock-images/components.js
index e09063cbe5d3001cb0121654f7f3c4ea8055cda7..2cd8d2fc2d21827d06a16c437c7da8314012bcc1 100644
--- a/resources/vue/components/stock-images/components.js
+++ b/resources/vue/components/stock-images/components.js
@@ -3,6 +3,7 @@ export { default as StockImagesAttributesFieldset } from './AttributesFieldset.v
 export { default as StockImagesChooser } from './Chooser.vue';
 export { default as StockImagesChooserDialog } from './ChooserDialog.vue';
 export { default as StockImagesChooserSearch } from './ChooserSearch.vue';
+export { default as StockImagesColorFilterWidget } from './ColorFilterWidget.vue';
 export { default as StockImagesEditDialog } from './EditDialog.vue';
 export { default as StockImagesSelectableImageCard } from './SelectableImageCard.vue';
 export { default as StockImagesImagesFilters } from './ImagesFilters.vue';
@@ -10,6 +11,8 @@ export { default as StockImagesImagesList } from './ImagesList.vue';
 export { default as StockImagesImagesListItem } from './ImagesListItem.vue';
 export { default as StockImagesImagesPagination } from './ImagesPagination.vue';
 export { default as StockImagesMetadataBox } from './MetadataBox.vue';
+export { default as StockImagesNavigationWidget } from './NavigationWidget.vue';
+export { default as StockImagesOrientationFilterWidget } from './OrientationFilterWidget.vue';
 export { default as StockImagesPage } from './Page.vue';
 export { default as StockImagesSearchWidget } from './SearchWidget.vue';
 export { default as StockImagesTagsInput } from './TagsInput.vue';
diff --git a/resources/vue/courseware-index-app.js b/resources/vue/courseware-index-app.js
index 337b56f83b11e8738e67a3f610affdb288e7c799..19f170735b0086c3b4d68f7357edbd13dfede438 100644
--- a/resources/vue/courseware-index-app.js
+++ b/resources/vue/courseware-index-app.js
@@ -8,6 +8,7 @@ import VueRouter from 'vue-router';
 import Vuex from 'vuex';
 import axios from 'axios';
 import { mapResourceModules } from '@elan-ev/reststate-vuex';
+import { StockImagesPlugin } from './plugins/stock-images.js';
 
 const mountApp = async (STUDIP, createApp, element) => {
     const getHttpClient = () =>
@@ -154,6 +155,8 @@ const mountApp = async (STUDIP, createApp, element) => {
         store,
     });
 
+    Vue.use(StockImagesPlugin, { store });
+
     app.$mount(element);
 
     return app;
diff --git a/resources/vue/store/courseware/courseware.module.js b/resources/vue/store/courseware/courseware.module.js
index 5eb40a00855bb9764194d44e443ecb1fd04b3954..a8e6b71db96ba0da551736cd70ba367eba8710a8 100644
--- a/resources/vue/store/courseware/courseware.module.js
+++ b/resources/vue/store/courseware/courseware.module.js
@@ -996,6 +996,15 @@ export const actions = {
         });
     },
 
+    setStockImageForStructuralElement({ dispatch, state }, { structuralElement, stockImage }) {
+        const { id, type } = structuralElement;
+        structuralElement.relationships.image = { data: { type: 'stock-images', id: stockImage.id } };
+
+        return dispatch('lockObject', { id, type })
+            .then(() => dispatch('updateStructuralElement', { element: structuralElement, id }))
+            .then(() => dispatch('lockObject', { id, type }));
+    },
+
     async deleteImageForStructuralElement({ dispatch, state }, structuralElement) {
         const url = `courseware-structural-elements/${structuralElement.id}/image`;
         await state.httpClient.delete(url);