diff options
| -rw-r--r-- | lib/models/Courseware/BlockTypes/Link.json | 9 | ||||
| -rw-r--r-- | lib/models/Courseware/BlockTypes/Link.php | 6 | ||||
| -rw-r--r-- | package.json | 1 | ||||
| -rw-r--r-- | resources/assets/stylesheets/scss/courseware/blocks/link.scss | 10 | ||||
| -rw-r--r-- | resources/vue/components/courseware/blocks/CoursewareLinkBlock.vue | 127 |
5 files changed, 129 insertions, 24 deletions
diff --git a/lib/models/Courseware/BlockTypes/Link.json b/lib/models/Courseware/BlockTypes/Link.json index 351983f..8fdd124 100644 --- a/lib/models/Courseware/BlockTypes/Link.json +++ b/lib/models/Courseware/BlockTypes/Link.json @@ -16,6 +16,15 @@ }, "title": { "type": "string" + }, + "qr-enabled": { + "type": "boolean" + }, + "qr-level": { + "type": "string" + }, + "qr-size": { + "type": "string" } }, "required": [ diff --git a/lib/models/Courseware/BlockTypes/Link.php b/lib/models/Courseware/BlockTypes/Link.php index 8e14079..9cf17f5 100644 --- a/lib/models/Courseware/BlockTypes/Link.php +++ b/lib/models/Courseware/BlockTypes/Link.php @@ -35,6 +35,9 @@ class Link extends BlockType 'unit-target' => '', 'url' => '', 'title' => '', + 'qr-enabled' => false, + 'qr-level' => 'L', + 'qr-size' => 180, ]; } @@ -76,7 +79,8 @@ class Link extends BlockType _('Hyperlink'), _('Quellenangabe'), _('Linkliste'), - _('Linksammlung') + _('Linksammlung'), + _('QR-Code'), ]; } } diff --git a/package.json b/package.json index e52d011..367c5ac 100644 --- a/package.json +++ b/package.json @@ -124,6 +124,7 @@ "postcss": "^8.4.49", "postcss-loader": "^8.1.1", "postcss-scss": "^4.0.4", + "qrcode.vue": "3.6.0", "raw-loader": "^4.0.2", "sanitize-html": "^2.7.0", "sass": "^1.29.0", diff --git a/resources/assets/stylesheets/scss/courseware/blocks/link.scss b/resources/assets/stylesheets/scss/courseware/blocks/link.scss index bfa286d..7d342bb 100644 --- a/resources/assets/stylesheets/scss/courseware/blocks/link.scss +++ b/resources/assets/stylesheets/scss/courseware/blocks/link.scss @@ -103,6 +103,14 @@ } } - } + } + } + + .cw-link-qr-code { + display: flex; + flex-direction: column; + align-items: center; + gap: 20px; + margin: 10px 0; } } diff --git a/resources/vue/components/courseware/blocks/CoursewareLinkBlock.vue b/resources/vue/components/courseware/blocks/CoursewareLinkBlock.vue index 78d17dc..5b1fe1c 100644 --- a/resources/vue/components/courseware/blocks/CoursewareLinkBlock.vue +++ b/resources/vue/components/courseware/blocks/CoursewareLinkBlock.vue @@ -11,20 +11,49 @@ > <template #content> <div v-if="currentType === 'external'"> - <a :href="currentUrl" target="_blank"> - <div class="cw-link external"> - <span class="cw-link-title">{{ currentTitle }}</span> + <template v-if="currentUrl"> + <div v-if="currentQREnabled" class="cw-link-qr-code"> + <qrcode-vue + v-if="currentUrl && currentQREnabled" + :value="currentUrl" + :size="currentQRSize ?? 180" + :level="currentQRLevel" + render-as="svg" + /> + <a :href="currentUrl" target="_blank"> + {{ currentTitle }} + </a> </div> - </a> + + <a v-else :href="currentUrl" target="_blank"> + <div class="cw-link external"> + <span class="cw-link-title">{{ currentTitle }}</span> + </div> + </a> + </template> + + <courseware-companion-box + v-if="!currentUrl && isTeacher" + mood="pointing" + :msgCompanion="$gettext('Bitte wählen Sie eine URL als Ziel aus.')" + /> </div> <div v-if="currentType === 'internal'"> - <router-link :to="{ name: 'CoursewareStructuralElement', params: { id: currentTarget } }"> + <router-link + v-if="currentTarget" + :to="{ name: 'CoursewareStructuralElement', params: { id: currentTarget } }" + > <div class="cw-link internal"> <span class="cw-link-title"> {{ currentTitle }} </span> </div> </router-link> + <courseware-companion-box + v-if="!currentTarget && isTeacher" + mood="pointing" + :msgCompanion="$gettext('Bitte wählen Sie eine Seite als Ziel aus.')" + /> </div> <div v-if="currentType === 'unit' && inCourseContext"> <a v-if="currentUnitData" :href="currentUnitData.url"> @@ -42,7 +71,7 @@ </div> </a> <courseware-companion-box - v-else + v-if="!currentUnitData && isTeacher" mood="pointing" :msgCompanion="$gettext('Bitte wählen Sie ein Lernmaterial als Ziel aus.')" /> @@ -60,29 +89,64 @@ </option> </select> </label> - <label v-show="currentType !== 'unit'"> + <label v-if="currentType !== 'unit'"> {{ $gettext('Titel') }} <input type="text" v-model="currentTitle" /> </label> - <label v-show="currentType === 'external'"> - {{ $gettext('URL') }} - <input type="text" v-model="currentUrl" @change="fixUrl" /> - </label> - <label v-show="currentType === 'internal'"> + <template v-if="currentType === 'external'"> + <label> + {{ $gettext('URL') }} + <input type="text" v-model="currentUrl" @change="fixUrl" /> + </label> + <label> + {{ $gettext('QR-Code') }} + <select v-model="currentQREnabled"> + <option value="false">{{ $gettext('Kein QR-Code') }}</option> + <option value="true">{{ $gettext('QR-Code') }}</option> + </select> + </label> + <template v-if="currentQREnabled"> + <label> + {{ $gettext('QR-Code Fehlerkorrekturlevel') }} + <select v-model="currentQRLevel"> + <option value="L">{{ $gettext('L – Niedrig (ca. 7% Wiederherstellung)') }}</option> + <option value="M">{{ $gettext('M – Mittel (ca. 15% Wiederherstellung)') }}</option> + <option value="Q">{{ $gettext('Q – Quartil (ca. 25% Wiederherstellung)') }}</option> + <option value="H">{{ $gettext('H – Hoch (ca. 30% Wiederherstellung)') }}</option> + </select> + </label> + <label> + {{ $gettext('QR-Code Größe') }} + <select v-model="currentQRSize"> + <option value="180">{{ $gettext('Klein') }}</option> + <option value="240">{{ $gettext('Mittel') }}</option> + <option value="300">{{ $gettext('Groß') }}</option> + <option value="360">{{ $gettext('Sehr groß') }}</option> + </select> + </label> + </template> + </template> + <label v-if="currentType === 'internal'"> {{ $gettext('Seite') }} - <select v-model="currentTarget"> + <select v-if="filteredStructuralElements.length > 0" v-model="currentTarget"> <option v-for="(el, index) in filteredStructuralElements" :key="index" :value="el.id"> {{ el.attributes.title }} </option> </select> + <span v-else>{{ + $gettext('Es wurde keine weitere Seite in diesem Lernmaterial gefunden.') + }}</span> </label> - <label v-show="currentType === 'unit' && inCourseContext"> + <label v-if="currentType === 'unit' && inCourseContext"> {{ $gettext('Lernmaterial') }} - <select v-model="currentUnitTarget"> + <select v-if="units.length > 0" v-model="currentUnitTarget"> <option v-for="(unit, index) in units" :key="index" :value="unit.id"> {{ unit.title }} </option> </select> + <span v-else>{{ + $gettext('Es wurde kein weiteres Lernmaterial in dieser Veranstaltung gefunden.') + }}</span> </label> </form> </template> @@ -98,12 +162,14 @@ import StudipIdentImage from './../../StudipIdentImage.vue'; import BlockComponents from './block-components.js'; import blockMixin from '@/vue/mixins/courseware/block.js'; import colorMixin from '@/vue/mixins/courseware/colors.js'; +import QrcodeVue from 'qrcode.vue'; import { mapActions, mapGetters } from 'vuex'; +import { $gettext } from '../../../../assets/javascripts/lib/gettext'; export default { name: 'courseware-link-block', mixins: [blockMixin, colorMixin], - components: Object.assign(BlockComponents, { StudipIdentImage }), + components: { ...BlockComponents, StudipIdentImage, QrcodeVue }, props: { block: Object, canEdit: Boolean, @@ -116,6 +182,9 @@ export default { currentUnitTarget: '', currentUrl: '', currentTitle: '', + currentQREnabled: false, + currentQRLevel: '', + currentQRSize: '', identimage: '', }; }, @@ -142,6 +211,15 @@ export default { title() { return this.block?.attributes?.payload?.title; }, + qrEnabled() { + return this.block?.attributes?.payload?.['qr-enabled']; + }, + qrLevel() { + return this.block?.attributes?.payload?.['qr-level']; + }, + qrSize() { + return this.block?.attributes?.payload?.['qr-size']; + }, units() { const allUnits = this.courseUnits; const units = allUnits.filter((unit) => unit.id !== this.context.unit); @@ -156,9 +234,8 @@ export default { return this.currentType === 'unit' ? this.getUnitData(this.unitById({ id: this.currentUnitTarget })) : null; }, headerImageUrl() { - const headerUrl = this.rootElement(this.unitById({ id: this.currentUnitTarget }))?.relationships?.image?.meta?.[ - 'download-url' - ]; + const headerUrl = this.rootElement(this.unitById({ id: this.currentUnitTarget }))?.relationships?.image + ?.meta?.['download-url']; return headerUrl ? headerUrl : null; }, previewImageStyle() { @@ -194,6 +271,9 @@ export default { this.currentUrl = this.url; this.fixUrl(); this.currentTitle = this.title; + this.currentQREnabled = this.qrEnabled; + this.currentQRLevel = this.qrLevel; + this.currentQRSize = this.qrSize; }, fixUrl() { @@ -249,8 +329,11 @@ export default { target: this.currentTarget, 'unit-target': this.currentUnitTarget, url: this.currentUrl, - title: this.currentTitle - } + title: this.currentTitle, + 'qr-enabled': this.currentQREnabled, + 'qr-level': this.currentQRLevel, + 'qr-size': this.currentQRSize, + }, }; this.updateBlock({ @@ -284,7 +367,7 @@ export default { }); } }, - } + }, }; </script> <style scoped lang="scss"> |
