aboutsummaryrefslogtreecommitdiff
path: root/resources/vue/components/courseware
diff options
context:
space:
mode:
Diffstat (limited to 'resources/vue/components/courseware')
-rw-r--r--resources/vue/components/courseware/CoursewareContentPermissions.vue6
-rw-r--r--resources/vue/components/courseware/blocks/CoursewareBiographyCareerBlock.vue4
-rw-r--r--resources/vue/components/courseware/blocks/CoursewareBlockActions.vue1
-rw-r--r--resources/vue/components/courseware/blocks/CoursewareBlockEdit.vue12
-rw-r--r--resources/vue/components/courseware/blocks/CoursewareCanvasBlock.vue15
-rw-r--r--resources/vue/components/courseware/blocks/CoursewareDocumentBlock.vue40
-rw-r--r--resources/vue/components/courseware/blocks/CoursewareIframeBlock.vue19
-rw-r--r--resources/vue/components/courseware/blocks/CoursewareImageMapBlock.vue4
-rw-r--r--resources/vue/components/courseware/blocks/CoursewareKeyPointBlock.vue4
-rw-r--r--resources/vue/components/courseware/containers/CoursewareContainerActions.vue1
-rw-r--r--resources/vue/components/courseware/layouts/CoursewareCompanionBox.vue49
-rw-r--r--resources/vue/components/courseware/layouts/CoursewareCompanionOverlay.vue58
-rw-r--r--resources/vue/components/courseware/structural-element/CoursewareRootContent.vue171
-rw-r--r--resources/vue/components/courseware/structural-element/CoursewareStructuralElementDialogImport.vue4
-rw-r--r--resources/vue/components/courseware/tasks/CoursewareTasksDialogDistribute.vue4
-rw-r--r--resources/vue/components/courseware/tasks/TaskGroupsAddSolversDialog.vue4
-rw-r--r--resources/vue/components/courseware/toolbar/CoursewareToolbar.vue48
-rw-r--r--resources/vue/components/courseware/toolbar/CoursewareToolbarBlocks.vue170
-rw-r--r--resources/vue/components/courseware/toolbar/CoursewareToolbarClipboard.vue14
-rw-r--r--resources/vue/components/courseware/unit/CoursewareUnitItem.vue2
-rw-r--r--resources/vue/components/courseware/unit/CoursewareUnitItemDialogLayout.vue119
-rw-r--r--resources/vue/components/courseware/unit/CoursewareUnitProgress.vue2
22 files changed, 465 insertions, 286 deletions
diff --git a/resources/vue/components/courseware/CoursewareContentPermissions.vue b/resources/vue/components/courseware/CoursewareContentPermissions.vue
index d2412ef..60f0964 100644
--- a/resources/vue/components/courseware/CoursewareContentPermissions.vue
+++ b/resources/vue/components/courseware/CoursewareContentPermissions.vue
@@ -42,7 +42,7 @@
<td class="perm">
<input
class="right"
- :title="$gettextInterpolate($gettext('Leserechte für %{ userName }'), { userName: user_perm.username })"
+ :title="$gettextInterpolate($gettext('Leserechte für %{ userName }'), { userName: user_perm.username }, true)"
type="radio"
:name="`${user_perm.id}_right`"
value="read"
@@ -53,7 +53,7 @@
<td class="perm">
<input
class="right"
- :title="$gettextInterpolate($gettext('Lese- und Schreibrechte für %{ userName }'), { userName: user_perm.username })"
+ :title="$gettextInterpolate($gettext('Lese- und Schreibrechte für %{ userName }'), { userName: user_perm.username }, true)"
type="radio"
:name="`${user_perm.id}_right`"
value="write"
@@ -75,7 +75,7 @@
<td class="actions">
<button
class="cw-permission-delete"
- :title="$gettextInterpolate($gettext('Entfernen der Rechte von %{ userName }'), { userName: user_perm.username })"
+ :title="$gettextInterpolate($gettext('Entfernen der Rechte von %{ userName }'), { userName: user_perm.username }, true)"
@click.prevent="confirmDeleteUserPerm(index)"
>
</button>
diff --git a/resources/vue/components/courseware/blocks/CoursewareBiographyCareerBlock.vue b/resources/vue/components/courseware/blocks/CoursewareBiographyCareerBlock.vue
index c366618..98fab71 100644
--- a/resources/vue/components/courseware/blocks/CoursewareBiographyCareerBlock.vue
+++ b/resources/vue/components/courseware/blocks/CoursewareBiographyCareerBlock.vue
@@ -17,8 +17,8 @@
class="cw-timeline-item"
>
<div class="cw-timeline-item-icon cw-timeline-item-icon-color-studip-blue">
- <studip-icon v-if="item.type === 'school'" shape="doctoral-cap" role="clickable" size="32"/>
- <studip-icon v-if="item.type === 'experience'" shape="tools" role="clickable" size="32"/>
+ <studip-icon v-if="item.type === 'school'" shape="doctoral-cap" role="clickable" :size="32"/>
+ <studip-icon v-if="item.type === 'experience'" shape="tools" role="clickable" :size="32"/>
</div>
<div
class="cw-timeline-item-content cw-timeline-item-content-color-studip-blue"
diff --git a/resources/vue/components/courseware/blocks/CoursewareBlockActions.vue b/resources/vue/components/courseware/blocks/CoursewareBlockActions.vue
index 89beb0a..eea76fe 100644
--- a/resources/vue/components/courseware/blocks/CoursewareBlockActions.vue
+++ b/resources/vue/components/courseware/blocks/CoursewareBlockActions.vue
@@ -3,6 +3,7 @@
<studip-action-menu
:items="menuItems"
:context="block.attributes.title"
+ :collapseAt="1"
@editBlock="editBlock"
@setVisibility="setVisibility"
@showInfo="showInfo"
diff --git a/resources/vue/components/courseware/blocks/CoursewareBlockEdit.vue b/resources/vue/components/courseware/blocks/CoursewareBlockEdit.vue
index d1ed09f..82d1abc 100644
--- a/resources/vue/components/courseware/blocks/CoursewareBlockEdit.vue
+++ b/resources/vue/components/courseware/blocks/CoursewareBlockEdit.vue
@@ -2,7 +2,7 @@
<section class="cw-block-edit">
<header v-if="preview">{{ $gettext('Bearbeiten') }}</header>
<div class="cw-block-features-content">
- <div @click="deactivateToolbar(); exitHandler = true;">
+ <div @click="exitHandler = true;">
<slot name="edit" />
</div>
<div class="cw-button-box">
@@ -31,16 +31,6 @@ export default {
beforeMount() {
this.originalBlock = this.block;
},
- methods: {
- ...mapActions({
- coursewareBlockAdder: 'coursewareBlockAdder',
- coursewareShowToolbar: 'coursewareShowToolbar'
- }),
- deactivateToolbar() {
- this.coursewareBlockAdder({});
- this.coursewareShowToolbar(false);
- },
- },
beforeDestroy() {
if (this.exitHandler) {
this.$emit('store');
diff --git a/resources/vue/components/courseware/blocks/CoursewareCanvasBlock.vue b/resources/vue/components/courseware/blocks/CoursewareCanvasBlock.vue
index 669a5b8..24a59f5 100644
--- a/resources/vue/components/courseware/blocks/CoursewareCanvasBlock.vue
+++ b/resources/vue/components/courseware/blocks/CoursewareCanvasBlock.vue
@@ -127,7 +127,7 @@
<studip-file-chooser
v-model="currentFileId"
selectable="file"
- :courseId="studipContext.id"
+ :courseId="context.id"
:userId="userId"
:isImage="true"
:excludedCourseFolderTypes="excludedCourseFolderTypes"
@@ -187,7 +187,7 @@ export default {
currentUserView: 'own',
currentFile: {},
- context: {},
+ canvasContext: {},
paint: false,
write: false,
clickX: [],
@@ -221,7 +221,6 @@ export default {
},
computed: {
...mapGetters({
- studipContext: 'context',
fileRefById: 'file-refs/byId',
getUserDataById: 'courseware-user-data-fields/byId',
relatedUserData: 'user-data-field/related',
@@ -301,9 +300,7 @@ export default {
return this.currentUploadFolderId !== "";
},
canSwitchView() {
- // this feature is not something to offer in the Arbeitsplatz!
- let context = this.$store.getters.context;
- if (context.type !== 'courses') {
+ if (this.context.type !== 'courses') {
return false;
}
if (this.currentShowUserData === 'off') {
@@ -419,7 +416,7 @@ export default {
} else {
canvas.height = 500;
}
- this.context = canvas.getContext('2d');
+ this.canvasContext = canvas.getContext('2d');
this.setColor('blue');
this.currentSize = this.sizes['normal'];
this.currentTool = this.tools['pen'];
@@ -427,7 +424,7 @@ export default {
},
redraw() {
let view = this;
- let context = view.context;
+ let context = view.canvasContext;
context.clearRect(0, 0, context.canvas.width, context.canvas.height); // Clears the canvas
context.fillStyle = '#ffffff';
context.fillRect(0, 0, context.canvas.width, context.canvas.height); // set background
@@ -658,7 +655,7 @@ export default {
},
async store() {
let user = this.usersById({id: this.userId});
- let imageBase64 = this.context.canvas.toDataURL("image/jpeg", 1.0);
+ let imageBase64 = this.canvasContext.canvas.toDataURL("image/jpeg", 1.0);
let image = await fetch(imageBase64);
let imageBlob = await image.blob();
let file = {};
diff --git a/resources/vue/components/courseware/blocks/CoursewareDocumentBlock.vue b/resources/vue/components/courseware/blocks/CoursewareDocumentBlock.vue
index 72cfeb3..55e7b01 100644
--- a/resources/vue/components/courseware/blocks/CoursewareDocumentBlock.vue
+++ b/resources/vue/components/courseware/blocks/CoursewareDocumentBlock.vue
@@ -131,8 +131,8 @@
</button>
<select v-model="currentScale" :aria-label="$gettext('Zoom')" @change="updateZoom">
<option v-show="false" :value="currentScale">{{ formattedZoom }}%</option>
- <option v-for="(value, index) in scaleValues" :key="index" :value="value">
- {{ value * 100 }}%
+ <option v-for="(value, index) in scaleValues" :key="index" :value="value.scale">
+ {{ value.name }}
</option>
</select>
</div>
@@ -305,7 +305,7 @@ export default {
pdfAnnotationLayer: null,
pdfAnnotation: false,
pdfRotate: 0,
- PdfViewer: null,
+ pdfViewer: null,
pdfEventBus: null,
pdfLinkService: null,
pdfFindController: null,
@@ -322,8 +322,8 @@ export default {
pageNum: 1,
pageCount: 0,
scale: 1,
+ baseScale: 1,
currentScale: 1,
- scaleValues: [0.5, 1, 1.5, 2, 3, 4],
file: null,
srMessage: '',
@@ -361,10 +361,23 @@ export default {
formattedZoom() {
return Number.parseInt(this.scale * 100, 10);
},
+ scaleValues() {
+ const defaultValues = [
+ { name: '25%', scale: 0.25 },
+ { name: '50%', scale: 0.5 },
+ { name: '75%', scale: 0.75 },
+ { name: '100%', scale: 1.0 },
+ { name: '150%', scale: 1.5 },
+ { name: '200%', scale: 2.0 },
+ { name: '300%', scale: 3.0 },
+ ];
+
+ return defaultValues.concat([{ name: this.$gettext('volle Breite'), scale: this.baseScale }]);
+ },
},
watch: {
scale(newValue) {
- let overflow = newValue > 1 ? 'auto' : 'hidden';
+ let overflow = newValue > this.baseScale ? 'auto' : 'hidden';
let container = this.$refs.container;
container.style.overflow = overflow;
this.currentScale = newValue;
@@ -407,7 +420,7 @@ export default {
if (this.currentUrl) {
let view = this;
view.pdfEventBus = new EventBus();
- view.pdfLoadingTask = getDocument(this.currentUrl).promise;
+ view.pdfLoadingTask = getDocument({ url: this.currentUrl, verbosity: 0 }).promise;
view.pdfLoadingTask.__PDFDocumentLoadingTask = true;
// Link Service
view.pdfLinkService = new PDFLinkService({
@@ -473,9 +486,16 @@ export default {
.getPage(parseInt(view.pageNum))
.then((pdfPage) => {
view.pdfPage = pdfPage;
+ const width = outerContainer.offsetWidth;
+ let pdfWidth = pdfPage.view[2];
+ if (pdfPage.rotate === 90 || pdfPage.rotate === 270) {
+ pdfWidth = pdfPage.view[3];
+ }
+ view.baseScale = (width / pdfWidth / 1.33).toFixed(2);
+ view.scale = view.baseScale;
// Creating the page view with default parameters.
let defaultViewport = pdfPage.getViewport({
- scale: 1.35,
+ scale: 1.0,
});
view.pdfBasePage = new PDFViewer({
@@ -512,8 +532,6 @@ export default {
view.pdfViewer.setPdfPage(view.pdfPage);
// Set LinkService viewer
view.pdfLinkService.setViewer(view.pdfViewer);
- // Set outer container height
- outerContainer.style.height = container.offsetHeight + 'px';
view.renderPage();
})
.catch((err) => {
@@ -610,12 +628,12 @@ export default {
this.updateSrMessage(this.$gettext('gedreht'));
},
zoomIn() {
- this.scale = this.scale < 4 ? (this.scale * 10 + 1) / 10 : this.scale;
+ this.scale = this.scale < 4 ? ((this.scale * 10 + 1) / 10).toFixed(1) : this.scale;
this.renderPage();
this.updateSrMessage(this.$gettext('vergrößert'));
},
zoomOut() {
- this.scale = this.scale > 0.1 ? (this.scale * 10 - 1) / 10 : this.scale;
+ this.scale = this.scale > 0.1 ? ((this.scale * 10 - 1) / 10).toFixed(1) : this.scale;
this.renderPage();
this.updateSrMessage(this.$gettext('verkleinert'));
},
diff --git a/resources/vue/components/courseware/blocks/CoursewareIframeBlock.vue b/resources/vue/components/courseware/blocks/CoursewareIframeBlock.vue
index be56ab4..4ff7e45 100644
--- a/resources/vue/components/courseware/blocks/CoursewareIframeBlock.vue
+++ b/resources/vue/components/courseware/blocks/CoursewareIframeBlock.vue
@@ -234,17 +234,16 @@ export default {
this.currentUrlIsValid = this.isValidUrl(this.currentUrl);
},
isValidUrl(urlString) {
- const urlPattern = new RegExp(
- '^(https?:\\/\\/)?' + // validate protocol
- '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // validate domain name
- '((\\d{1,3}\\.){3}\\d{1,3}))' + // validate OR ip (v4) address
- '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // validate port and path
- '(\\?[;&a-z\\d%_.~+=-]*)?' + // validate query string
- '(\\#[-a-z\\d_]*)?$',
- 'i'
- ); // validate fragment locator
+ if (!urlString.startsWith('http')) {
+ urlString = `${location.protocol}//${urlString}`;
+ }
- return !!urlPattern.test(urlString);
+ try {
+ const url = new URL(urlString);
+ return ['http:', 'https:'].includes(url.protocol);
+ } catch (e) {
+ return false;
+ }
},
updateUrl() {
diff --git a/resources/vue/components/courseware/blocks/CoursewareImageMapBlock.vue b/resources/vue/components/courseware/blocks/CoursewareImageMapBlock.vue
index 6d12980..4f52d4b 100644
--- a/resources/vue/components/courseware/blocks/CoursewareImageMapBlock.vue
+++ b/resources/vue/components/courseware/blocks/CoursewareImageMapBlock.vue
@@ -113,7 +113,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>
@@ -141,7 +141,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>
diff --git a/resources/vue/components/courseware/blocks/CoursewareKeyPointBlock.vue b/resources/vue/components/courseware/blocks/CoursewareKeyPointBlock.vue
index a410740..e52b608 100644
--- a/resources/vue/components/courseware/blocks/CoursewareKeyPointBlock.vue
+++ b/resources/vue/components/courseware/blocks/CoursewareKeyPointBlock.vue
@@ -38,7 +38,7 @@
v-model="currentColor"
>
<template #open-indicator="selectAttributes">
- <span v-bind="selectAttributes"><studip-icon shape="arr_1down" size="10" /></span>
+ <span v-bind="selectAttributes"><studip-icon shape="arr_1down" :size="10" /></span>
</template>
<template #no-options>
{{ $gettext('Es steht keine Auswahl zur Verfügung.') }}
@@ -57,7 +57,7 @@
{{ $gettext('Icon') }}
<studip-select :options="icons" :clearable="false" v-model="currentIcon">
<template #open-indicator="selectAttributes">
- <span v-bind="selectAttributes"><studip-icon shape="arr_1down" size="10" /></span>
+ <span v-bind="selectAttributes"><studip-icon shape="arr_1down" :size="10" /></span>
</template>
<template #no-options>
{{ $gettext('Es steht keine Auswahl zur Verfügung.') }}
diff --git a/resources/vue/components/courseware/containers/CoursewareContainerActions.vue b/resources/vue/components/courseware/containers/CoursewareContainerActions.vue
index 79379bf..875ab1f 100644
--- a/resources/vue/components/courseware/containers/CoursewareContainerActions.vue
+++ b/resources/vue/components/courseware/containers/CoursewareContainerActions.vue
@@ -3,6 +3,7 @@
<studip-action-menu
:items="menuItems"
:context="container.attributes.title"
+ :collapseAt="1"
@editContainer="editContainer"
@changeContainer="changeContainer"
@deleteContainer="deleteContainer"
diff --git a/resources/vue/components/courseware/layouts/CoursewareCompanionBox.vue b/resources/vue/components/courseware/layouts/CoursewareCompanionBox.vue
index f617e5a..f26c8d7 100644
--- a/resources/vue/components/courseware/layouts/CoursewareCompanionBox.vue
+++ b/resources/vue/components/courseware/layouts/CoursewareCompanionBox.vue
@@ -1,15 +1,9 @@
-<template>
- <div class="cw-companion-box" :class="[mood]">
- <div>
- <p v-html="msgCompanion"></p>
- <slot name="companionActions"></slot>
- </div>
- </div>
-</template>
-
<script>
export default {
name: 'courseware-companion-box',
+ render(createElement) {
+ return null;
+ },
props: {
msgCompanion: String,
mood: {
@@ -20,5 +14,40 @@ export default {
}
}
},
+ computed: {
+ msgType() {
+ let type = 'info';
+ switch (this.mood) {
+ case 'special':
+ case 'unsure':
+ type = 'warning';
+ break;
+ case 'sad':
+ type = 'error';
+ break;
+ case 'happy':
+ type = 'success';
+ break
+ case 'pointing':
+ case 'curious':
+ }
+ return type;
+ }
+ },
+ watch: {
+ msgCompanion: {
+ handler(current) {
+ if (current.trim().length === 0) {
+ return;
+ }
+ const notification = {
+ type: this.msgType,
+ message: current
+ };
+ this.globalEmit('push-system-notification', notification);
+ },
+ immediate: true
+ }
+ }
};
-</script> \ No newline at end of file
+</script>
diff --git a/resources/vue/components/courseware/layouts/CoursewareCompanionOverlay.vue b/resources/vue/components/courseware/layouts/CoursewareCompanionOverlay.vue
index ff177f7..bfd0a93 100644
--- a/resources/vue/components/courseware/layouts/CoursewareCompanionOverlay.vue
+++ b/resources/vue/components/courseware/layouts/CoursewareCompanionOverlay.vue
@@ -1,28 +1,11 @@
-<template>
- <div class="cw-companion-overlay-wrapper">
- <div
- class="cw-companion-overlay"
- :class="[showCompanion ? 'cw-companion-overlay-in' : '', showCompanion ? '' : 'cw-companion-overlay-out', styleCompanion]"
- aria-hidden="true"
- >
- <div class="cw-companion-overlay-content" v-html="msgCompanion"></div>
- <button class="cw-compantion-overlay-close" @click="hideCompanion"></button>
- </div>
- <div
- class="sr-only"
- aria-live="polite"
- role="log"
- >
- <p>{{ msgCompanion }}</p>
- </div>
- </div>
-</template>
-
<script>
import { mapActions, mapGetters } from 'vuex';
export default {
name: 'courseware-companion-overlay',
+ render(createElement) {
+ return null;
+ },
computed: {
...mapGetters({
showCompanion: 'showCompanionOverlay',
@@ -30,6 +13,24 @@ export default {
styleCompanion: 'styleCompanionOverlay',
showToolbar: 'showToolbar',
}),
+ msgType() {
+ let type = 'info';
+ switch (this.styleCompanion) {
+ case 'special':
+ case 'unsure':
+ type = 'warning';
+ break;
+ case 'sad':
+ type = 'error';
+ break;
+ case 'happy':
+ type = 'success';
+ break
+ case 'pointing':
+ case 'curious':
+ }
+ return type;
+ }
},
methods: {
...mapActions({
@@ -49,11 +50,24 @@ export default {
}
},
showToolbar(newValue, oldValue) {
- // hide companion when toolbar is closed
+ // hide companion when toolbar is closed
if (oldValue === true && newValue === false) {
this.hideCompanion();
}
+ },
+ msgCompanion: {
+ handler(current) {
+ if (current.trim().length === 0) {
+ return;
+ }
+ const notification = {
+ type: this.msgType,
+ message: current
+ };
+ this.globalEmit('push-system-notification', notification);
+ },
+ immediate: true
}
- },
+ }
};
</script>
diff --git a/resources/vue/components/courseware/structural-element/CoursewareRootContent.vue b/resources/vue/components/courseware/structural-element/CoursewareRootContent.vue
index 1a739a6..1aa8802 100644
--- a/resources/vue/components/courseware/structural-element/CoursewareRootContent.vue
+++ b/resources/vue/components/courseware/structural-element/CoursewareRootContent.vue
@@ -16,21 +16,21 @@
</div>
<div v-else class="cw-root-content-wrapper">
<div class="cw-root-content" :class="['cw-root-content-' + rootLayout]">
- <div class="cw-root-content-img" :style="image">
+ <div class="cw-root-content-img" :style="bgImage">
<section class="cw-root-content-description" :style="bgColor">
- <img v-if="imageIsSet" class="cw-root-content-description-img" :src="imageURL" />
- <template v-else>
+ <div class="cw-root-content-description-img" :src="imageURL" :style="image"></div>
+ <template v-if="!imageIsSet">
<studip-ident-image
class="cw-root-content-description-img"
- v-model="identImageCanvas"
- :showCanvas="true"
+ v-model="identImage"
:baseColor="bgColorHex"
:pattern="structuralElement.attributes.title"
/>
<studip-ident-image
- v-model="identImage"
- :width="1095"
- :height="withTOC ? 300 : 480"
+ v-model="identBgImage"
+ class="cw-root-content-description-background-img"
+ :width="4380"
+ :height="withTOC ? 1200 : 1920"
:baseColor="bgColorHex"
:pattern="structuralElement.attributes.title"
/>
@@ -46,44 +46,28 @@
</div>
<div v-if="withTOC" class="cw-root-content-toc">
<ul class="cw-tiles">
- <li
- v-for="child in childElements"
- :key="child.id"
- class="tile"
- :class="[child.attributes.payload.color]"
- >
+ <li v-for="child in childElements" :key="child.id">
<router-link :to="'/structural_element/' + child.id" :title="child.attributes.title">
- <div
- v-if="hasImage(child)"
- class="preview-image"
- :style="getChildStyle(child)"
- ></div>
- <studip-ident-image
- v-else
- :baseColor="getColor(child).hex"
- :pattern="child.attributes.title"
- :showCanvas="true"
- />
- <div class="description">
- <header
- :class="[
- child.attributes.purpose !== ''
- ? 'description-icon-' + child.attributes.purpose
- : '',
- ]"
- >
- {{ child.attributes.title || '–' }}
- </header>
- <div class="description-text-wrapper">
- <p>{{ child.attributes.payload.description }}</p>
- </div>
- <footer>
- {{ countChildChildren(child) }}
- <translate :translate-n="countChildChildren(child)" translate-plural="Seiten">
- Seite
- </translate>
- </footer>
- </div>
+ <courseware-tile
+ tag="div"
+ :color="child.attributes.payload.color"
+ :title="child.attributes.title || '–'"
+ :imageUrl="hasImage(child) ? child.relationships?.image?.meta?.['download-url'] : ''"
+ >
+ <template #description>
+ {{ child.attributes.payload.description }}
+ </template>
+ <template #footer>
+ <p class="cw-root-content-toc-tile-footer">
+ {{
+ $gettextInterpolate(
+ $ngettext('%{pages} Seite', '%{pages} Seiten', countChildChildren(child)),
+ { pages: countChildChildren(child) }
+ )
+ }}
+ </p>
+ </template>
+ </courseware-tile>
</router-link>
</li>
</ul>
@@ -93,6 +77,7 @@
<script>
import CoursewareCompanionBox from './../layouts/CoursewareCompanionBox.vue';
+import CoursewareTile from './../layouts/CoursewareTile.vue';
import StudipIdentImage from './../../StudipIdentImage.vue';
import colorMixin from '@/vue/mixins/courseware/colors.js';
import { mapActions, mapGetters } from 'vuex';
@@ -100,18 +85,19 @@ import { mapActions, mapGetters } from 'vuex';
export default {
name: 'courseware-root-content',
mixins: [colorMixin],
- props: {
- structuralElement: Object,
- canEdit: Boolean,
- },
components: {
CoursewareCompanionBox,
+ CoursewareTile,
StudipIdentImage,
},
+ props: {
+ structuralElement: Object,
+ canEdit: Boolean,
+ },
data() {
return {
identImage: '',
- identImageCanvas: '',
+ identBgImage: '',
};
},
computed: {
@@ -130,6 +116,13 @@ export default {
image() {
let style = {};
const backgroundURL = this.imageIsSet ? this.imageURL : this.identImage;
+ style.backgroundImage = 'url(' + backgroundURL + ')';
+
+ return style;
+ },
+ bgImage() {
+ let style = {};
+ const backgroundURL = this.imageIsSet ? this.imageURL : this.identBgImage;
style.backgroundImage = 'url(' + backgroundURL + ')';
style.height = this.withTOC ? '300px' : '480px';
@@ -180,7 +173,7 @@ export default {
},
addPage() {
this.showElementAddDialog(true);
- }
+ },
},
};
</script>
@@ -196,16 +189,20 @@ export default {
}
.cw-root-content-description {
display: flex;
- flex-direction: row;
- margin: 0 8em;
- padding: 2em 4px 2em 2em;
position: relative;
- top: 8em;
+ flex-direction: column;
+ margin: 0 1em;
+ padding: 1em 4px 1em 1em;
+ top: 1em;
+ gap: 10px;
.cw-root-content-description-img {
- width: 240px;
- height: fit-content;
- margin-right: 2em;
+ min-width: 135px;
+ height: 90px;
+ background-color: #fff;
+ background-size: cover;
+ background-position: center;
+ margin-right: 1em;
}
.cw-root-content-description-text {
max-height: calc(480px - 18em);
@@ -233,14 +230,68 @@ export default {
max-width: 1095px;
margin-bottom: 1em;
.cw-root-content-description {
- margin: 0 8em;
- top: 1.5em;
+ height: calc(100% - 4em);
.cw-root-content-description-text {
max-height: calc(300px - 6em);
}
}
+ .cw-root-content-toc-tile-footer {
+ line-height: 4em;
+ }
}
.cw-root-content-hint {
max-width: 1095px;
}
+
+.size-small {
+ .cw-root-content-description {
+ flex-direction: row;
+ padding: 1em 4px 1em 1em;
+
+ .cw-root-content-description-img {
+ min-width: 135px;
+ height: 90px;
+ }
+ }
+
+ .cw-root-content-default {
+ .cw-root-content-description {
+ margin: 0 4em;
+ top: 8em;
+ }
+ }
+ .cw-root-content-toc {
+ .cw-root-content-description {
+ height: calc(100% - 6em);
+ margin: 0 4em;
+ top: 1.5em;
+ }
+ }
+}
+
+.size-large {
+ .cw-root-content-description {
+ flex-direction: row;
+ padding: 2em 4px 2em 2em;
+
+ .cw-root-content-description-img {
+ min-width: 270px;
+ height: 180px;
+ }
+ }
+
+ .cw-root-content-default {
+ .cw-root-content-description {
+ margin: 0 8em;
+ top: 8em;
+ }
+ }
+ .cw-root-content-toc {
+ .cw-root-content-description {
+ height: calc(100% - 7em);
+ margin: 0 8em;
+ top: 1.5em;
+ }
+ }
+}
</style>
diff --git a/resources/vue/components/courseware/structural-element/CoursewareStructuralElementDialogImport.vue b/resources/vue/components/courseware/structural-element/CoursewareStructuralElementDialogImport.vue
index 60b404e..019bbf2 100644
--- a/resources/vue/components/courseware/structural-element/CoursewareStructuralElementDialogImport.vue
+++ b/resources/vue/components/courseware/structural-element/CoursewareStructuralElementDialogImport.vue
@@ -130,7 +130,6 @@ export default {
this.importZipFile = event.target.files[0];
this.setImportFilesProgress(0);
this.setImportStructuresProgress(0);
- this.setImportErrors([]);
},
async importCoursewareArchiv() {
this.importAborted = false;
@@ -199,6 +198,9 @@ export default {
await this.importCourseware(courseware, this.currentElement, files, this.importBehavior, null);
}
+ },
+ mounted() {
+ this.setImportErrors([]);
}
}
</script>
diff --git a/resources/vue/components/courseware/tasks/CoursewareTasksDialogDistribute.vue b/resources/vue/components/courseware/tasks/CoursewareTasksDialogDistribute.vue
index 1e076c7..7680167 100644
--- a/resources/vue/components/courseware/tasks/CoursewareTasksDialogDistribute.vue
+++ b/resources/vue/components/courseware/tasks/CoursewareTasksDialogDistribute.vue
@@ -179,7 +179,7 @@
:aria-label="
$gettextInterpolate($gettext('%{userName} auswählen'), {
userName: user.formattedname,
- })
+ }, true)
"
/>
</td>
@@ -217,7 +217,7 @@
:aria-label="
$gettextInterpolate($gettext('%{groupName} auswählen'), {
groupName: group.name,
- })
+ }, true)
"
/>
</td>
diff --git a/resources/vue/components/courseware/tasks/TaskGroupsAddSolversDialog.vue b/resources/vue/components/courseware/tasks/TaskGroupsAddSolversDialog.vue
index a843341..d7db6e1 100644
--- a/resources/vue/components/courseware/tasks/TaskGroupsAddSolversDialog.vue
+++ b/resources/vue/components/courseware/tasks/TaskGroupsAddSolversDialog.vue
@@ -44,7 +44,7 @@
:aria-label="
$gettextInterpolate($gettext('%{userName} auswählen'), {
userName: user.formattedname,
- })
+ }, true)
"
/>
</td>
@@ -77,7 +77,7 @@
:aria-label="
$gettextInterpolate($gettext('%{groupName} auswählen'), {
groupName: group.name,
- })
+ }, true)
"
/>
</td>
diff --git a/resources/vue/components/courseware/toolbar/CoursewareToolbar.vue b/resources/vue/components/courseware/toolbar/CoursewareToolbar.vue
index d31e963..dc70348 100644
--- a/resources/vue/components/courseware/toolbar/CoursewareToolbar.vue
+++ b/resources/vue/components/courseware/toolbar/CoursewareToolbar.vue
@@ -2,7 +2,7 @@
<div class="cw-toolbar-wrapper">
<div id="cw-toolbar" class="cw-toolbar" :style="toolbarStyle">
<div v-if="showTools" class="cw-toolbar-tools" :class="{ unfold: unfold, hd: isHd, wqhd: isWqhd }">
- <div class="cw-toolbar-button-wrapper">
+ <div id="cw-toolbar-nav" class="cw-toolbar-button-wrapper">
<button
class="cw-toolbar-button"
:class="{ active: activeTool === 'blockAdder' }"
@@ -35,9 +35,19 @@
<studip-icon shape="arr_2right" :size="24" />
</button>
</div>
- <courseware-toolbar-blocks v-if="activeTool === 'blockAdder'" />
- <courseware-toolbar-containers v-if="activeTool === 'containerAdder'" />
- <courseware-toolbar-clipboard v-if="activeTool === 'clipboard'" />
+ <div class="cw-toolbar-tool-wrapper">
+ <CoursewareToolbarBlocks
+ v-if="activeTool === 'blockAdder'"
+ :toolbarContentHeight="toolbarContentHeight"
+ />
+ <CoursewareToolbarContainers
+ v-if="activeTool === 'containerAdder'"
+ />
+ <CoursewareToolbarClipboard
+ v-if="activeTool === 'clipboard'"
+ :toolbarContentHeight="toolbarContentHeight"
+ />
+ </div>
</div>
<div v-else class="cw-toolbar-folded-wrapper">
<button
@@ -97,20 +107,26 @@ export default {
toolbarActive: 'toolbarActive',
hideEditLayout: 'hideEditLayout',
}),
- toolbarStyle() {
- const scrollTopStyles = window.getComputedStyle(document.getElementById('scroll-to-top'));
+ scrollTopStyles() {
+ return window.getComputedStyle(document.getElementById('scroll-to-top'));
+ },
+ toolbarHeight() {
const scrollTopHeight =
- parseInt(scrollTopStyles['height'], 10) +
- parseInt(scrollTopStyles['padding-top'], 10) +
- parseInt(scrollTopStyles['padding-bottom'], 10) +
- parseInt(scrollTopStyles['margin-bottom'], 10);
- let height = parseInt(
+ parseInt(this.scrollTopStyles['height'], 10) +
+ parseInt(this.scrollTopStyles['padding-top'], 10) +
+ parseInt(this.scrollTopStyles['padding-bottom'], 10) +
+ parseInt(this.scrollTopStyles['margin-bottom'], 10);
+ return parseInt(
Math.min(this.windowInnerHeight * 0.9, this.windowInnerHeight - this.toolbarTop - scrollTopHeight)
);
-
+ },
+ toolbarContentHeight() {
+ return this.toolbarHeight - 55;
+ },
+ toolbarStyle() {
return {
- height: height + 'px',
- minHeight: height + 'px',
+ height: this.toolbarHeight + 'px',
+ minHeight: this.toolbarHeight + 'px',
top: this.toolbarTop + 'px',
};
},
@@ -183,8 +199,8 @@ export default {
},
watch: {
- containers(oldValue, newValue) {
- if (oldValue && newValue && oldValue.length !== newValue.length) {
+ containers(newValue, oldValue) {
+ if (newValue) {
this.resetAdderStorage();
}
},
diff --git a/resources/vue/components/courseware/toolbar/CoursewareToolbarBlocks.vue b/resources/vue/components/courseware/toolbar/CoursewareToolbarBlocks.vue
index 2795e62..ceda0f0 100644
--- a/resources/vue/components/courseware/toolbar/CoursewareToolbarBlocks.vue
+++ b/resources/vue/components/courseware/toolbar/CoursewareToolbarBlocks.vue
@@ -1,85 +1,88 @@
<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 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 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 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>
-
- <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 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>
- <courseware-companion-box
- v-else
- :msgCompanion="$gettext('Es wurden keine passenden Blöcke gefunden.')"
- mood="pointing"
- />
</div>
</template>
@@ -99,6 +102,12 @@ export default {
CoursewareCompanionBox,
draggable,
},
+ props: {
+ toolbarContentHeight: {
+ type: Number,
+ required: true,
+ },
+ },
data() {
return {
searchInput: '',
@@ -132,6 +141,13 @@ export default {
{ title: this.$gettext('Biografie'), type: 'biography' },
];
},
+ toolContentStyle() {
+ const height = this.toolbarContentHeight - 115;
+
+ return {
+ height: height + 'px',
+ };
+ },
},
methods: {
...mapActions({
diff --git a/resources/vue/components/courseware/toolbar/CoursewareToolbarClipboard.vue b/resources/vue/components/courseware/toolbar/CoursewareToolbarClipboard.vue
index 98cd573..c6f1e92 100644
--- a/resources/vue/components/courseware/toolbar/CoursewareToolbarClipboard.vue
+++ b/resources/vue/components/courseware/toolbar/CoursewareToolbarClipboard.vue
@@ -1,5 +1,5 @@
<template>
- <div class="cw-toolbar-clipboard">
+ <div class="cw-toolbar-clipboard cw-toolbar-tool-content" :style="toolContentStyle">
<courseware-collapsible-box :title="$gettext('Blöcke')" :open="clipboardBlocks.length > 0">
<template v-if="clipboardBlocks.length > 0">
<div class="cw-element-inserter-wrapper">
@@ -101,7 +101,12 @@ export default {
StudipDialog,
draggable,
},
-
+ props: {
+ toolbarContentHeight: {
+ type: Number,
+ required: true,
+ },
+ },
data() {
return {
showDeleteClipboardDialog: false,
@@ -143,6 +148,11 @@ export default {
}
return '';
},
+ toolContentStyle() {
+ return {
+ height: this.toolbarContentHeight + 'px',
+ };
+ },
},
methods: {
...mapActions({
diff --git a/resources/vue/components/courseware/unit/CoursewareUnitItem.vue b/resources/vue/components/courseware/unit/CoursewareUnitItem.vue
index f09601e..dc68225 100644
--- a/resources/vue/components/courseware/unit/CoursewareUnitItem.vue
+++ b/resources/vue/components/courseware/unit/CoursewareUnitItem.vue
@@ -63,7 +63,7 @@
:question="
$gettextInterpolate($gettext('Möchten Sie das Lernmaterial %{ unitTitle } wirklich löschen?'), {
unitTitle: title,
- })
+ }, true)
"
height="200"
@confirm="executeDelete"
diff --git a/resources/vue/components/courseware/unit/CoursewareUnitItemDialogLayout.vue b/resources/vue/components/courseware/unit/CoursewareUnitItemDialogLayout.vue
index f956e47..572e171 100644
--- a/resources/vue/components/courseware/unit/CoursewareUnitItemDialogLayout.vue
+++ b/resources/vue/components/courseware/unit/CoursewareUnitItemDialogLayout.vue
@@ -1,11 +1,11 @@
<template>
- <studip-dialog
+ <studip-dialog
:title="$gettext('Darstellung')"
:confirmText="$gettext('Speichern')"
confirmClass="accept"
:closeText="$gettext('Schließen')"
closeClass="cancel"
- height="470"
+ height="540"
width="870"
@close="$emit('close')"
@confirm="storeLayout"
@@ -23,12 +23,8 @@
<label v-if="showPreviewImage">
<button class="button" @click="deleteImage">{{ $gettext('Bild löschen') }}</button>
</label>
- <courseware-companion-box
- v-if="uploadFileError"
- :msgCompanion="uploadFileError"
- mood="sad"
- />
- <label v-if="!showPreviewImage">
+ <courseware-companion-box v-if="uploadFileError" :msgCompanion="uploadFileError" mood="sad" />
+ <template v-if="!showPreviewImage">
<img
v-if="currentFile"
:src="uploadImageURL"
@@ -36,14 +32,32 @@
:alt="$gettext('Vorschaubild')"
/>
<div v-else class="cw-structural-element-image-preview-placeholder"></div>
- {{ $gettext('Bild hochladen') }}
- <input class="cw-file-input" ref="upload_image" type="file" accept="image/*" @change="checkUploadFile" />
- </label>
+ <label>
+ {{ $gettext('Bild hochladen') }}
+ <input
+ class="cw-file-input"
+ ref="upload_image"
+ type="file"
+ accept="image/*"
+ @change="checkUploadFile"
+ />
+ </label>
+ {{ $gettext('oder') }}
+ <br />
+ <button class="button" type="button" @click="showStockImageSelector = true">
+ {{ $gettext('Aus dem Bilderpool auswählen') }}
+ </button>
+ <StockImageSelector
+ v-if="showStockImageSelector"
+ @close="showStockImageSelector = false"
+ @select="onSelectStockImage"
+ />
+ </template>
</form>
<form class="default cw-unit-item-dialog-layout-content-settings" @submit.prevent="">
<label>
{{ $gettext('Titel') }}
- <input type="text" v-model="currentElement.attributes.title"/>
+ <input type="text" v-model="currentElement.attributes.title" />
</label>
<label>
{{ $gettext('Beschreibung') }}
@@ -62,13 +76,9 @@
class="cw-vs-select"
>
<template #open-indicator="selectAttributes">
- <span v-bind="selectAttributes"
- ><studip-icon shape="arr_1down" :size="10"
- /></span>
- </template>
- <template #no-options>
- {{ $gettext('Es steht keine Auswahl zur Verfügung') }}.
+ <span v-bind="selectAttributes"><studip-icon shape="arr_1down" :size="10" /></span>
</template>
+ <template #no-options> {{ $gettext('Es steht keine Auswahl zur Verfügung') }}. </template>
<template #selected-option="{ name, hex }">
<span class="vs__option-color" :style="{ 'background-color': hex }"></span
><span>{{ name }}</span>
@@ -96,19 +106,19 @@
<script>
import CoursewareCompanionBox from '../layouts/CoursewareCompanionBox.vue';
-
+import StockImageSelector from '../../stock-images/SelectorDialog.vue';
import colorMixin from '@/vue/mixins/courseware/colors.js';
import { mapActions, mapGetters } from 'vuex';
-
export default {
name: 'courseware-unit-item-dialog-layout',
components: {
- CoursewareCompanionBox
+ CoursewareCompanionBox,
+ StockImageSelector,
},
props: {
unit: Object,
- unitElement: Object
+ unitElement: Object,
},
mixins: [colorMixin],
data() {
@@ -120,18 +130,26 @@ export default {
uploadImageURL: null,
currentRootLayout: 'default',
loadingInstance: false,
- }
+ showStockImageSelector: false,
+ selectedStockImage: null,
+ };
},
computed: {
...mapGetters({
context: 'context',
instanceById: 'courseware-instances/byId',
- userId: 'userId'
+ userId: 'userId',
}),
colors() {
- return this.mixinColors.filter(color => color.darkmode);
+ return this.mixinColors.filter((color) => color.darkmode);
},
image() {
+ if (this.selectedStockImage) {
+ return this.selectedStockImage.attributes['download-urls'].small
+ }
+ if (this.uploadImageURL) {
+ return this.uploadImageURL;
+ }
return this.currentElement.relationships?.image?.meta?.['download-url'] ?? null;
},
@@ -140,15 +158,14 @@ export default {
},
instance() {
if (this.inCourseContext) {
- return this.instanceById({id: 'course_' + this.context.id + '_' + this.unit.id});
+ return this.instanceById({ id: 'course_' + this.context.id + '_' + this.unit.id });
} else {
- return this.instanceById({id: 'user_' + this.context.id + '_' + this.unit.id});
+ return this.instanceById({ id: 'user_' + this.context.id + '_' + this.unit.id });
}
-
},
inCourseContext() {
return this.context.type === 'courses';
- }
+ },
},
methods: {
...mapActions({
@@ -162,9 +179,10 @@ export default {
uploadImageForStructuralElement: 'uploadImageForStructuralElement',
deleteImageForStructuralElement: 'deleteImageForStructuralElement',
storeCoursewareSettings: 'storeCoursewareSettings',
+ setStockImageForStructuralElement: 'setStockImageForStructuralElement',
}),
async loadUnitInstance() {
- const context = {type: this.context.type, id: this.context.id, unit: this.unit.id};
+ const context = { type: this.context.type, id: this.context.id, unit: this.unit.id };
await this.loadInstance(context);
},
initData() {
@@ -192,11 +210,13 @@ export default {
this.$emit('close');
await this.loadStructuralElement(this.currentElement.id);
if (
- this.unitElement.relationships['edit-blocker'].data !== null
- && this.unitElement.relationships['edit-blocker'].data?.id !== this.userId
+ this.unitElement.relationships['edit-blocker'].data !== null &&
+ this.unitElement.relationships['edit-blocker'].data?.id !== this.userId
) {
this.companionWarning({
- info: this.$gettext('Ihre Änderungen konnten nicht gespeichert werden, die Daten werden bereits von einem anderen Nutzer bearbeitet.')
+ info: this.$gettext(
+ 'Ihre Änderungen konnten nicht gespeichert werden, die Daten werden bereits von einem anderen Nutzer bearbeitet.'
+ ),
});
return false;
} else {
@@ -207,13 +227,20 @@ export default {
this.uploadImageForStructuralElement({
structuralElement: this.currentElement,
file: this.currentFile,
- }).then(() => {
- this.loadStructuralElement(this.currentElement.id)
- }).catch((error) => {
- console.error(error);
- this.companionWarning({
- info: this.$gettext('Beim Hochladen der Bilddatei ist ein Fehler aufgetretten.')
+ })
+ .then(() => {
+ this.loadStructuralElement(this.currentElement.id);
+ })
+ .catch((error) => {
+ console.error(error);
+ this.companionWarning({
+ info: this.$gettext('Beim Hochladen der Bilddatei ist ein Fehler aufgetretten.'),
+ });
});
+ } else if (this.selectedStockImage) {
+ await this.setStockImageForStructuralElement({
+ structuralElement: this.currentElement,
+ stockImage: this.selectedStockImage,
});
} else if (this.deletingPreviewImage) {
await this.deleteImageForStructuralElement(this.currentElement);
@@ -233,13 +260,21 @@ export default {
instance: currentInstance,
});
}
- }
+ },
+ onSelectStockImage(stockImage) {
+ if (this.$refs?.upload_image) {
+ this.$refs.upload_image.value = null;
+ }
+ this.selectedStockImage = stockImage;
+ this.showStockImageSelector = false;
+ this.deletingPreviewImage = false;
+ },
},
async mounted() {
this.loadingInstance = true;
await this.loadUnitInstance();
this.loadingInstance = false;
this.initData();
- }
-}
+ },
+};
</script>
diff --git a/resources/vue/components/courseware/unit/CoursewareUnitProgress.vue b/resources/vue/components/courseware/unit/CoursewareUnitProgress.vue
index 7c9de01..31c6c01 100644
--- a/resources/vue/components/courseware/unit/CoursewareUnitProgress.vue
+++ b/resources/vue/components/courseware/unit/CoursewareUnitProgress.vue
@@ -17,7 +17,7 @@
<h1>
<a
:href="chapterUrl"
- :title="$gettextInterpolate('%{ pageTitle } öffnen', { pageTitle: selected.name })"
+ :title="$gettextInterpolate('%{ pageTitle } öffnen', { pageTitle: selected.name }, true)"
>
{{ selected.name }}
</a>