From ac3b581bcc522dab8729705c754b4bcc1520c384 Mon Sep 17 00:00:00 2001 From: Ron Lucke <rlucke@latitude> Date: Fri, 9 Sep 2022 10:25:24 +0200 Subject: [PATCH 1/3] fix #874 --- .../Routes/Files/FileRefsCreateByUpload.php | 7 + lib/models/Courseware/BlockTypes/Folder.php | 47 +++- .../assets/stylesheets/scss/courseware.scss | 71 ++++- .../courseware/CoursewareAudioBlock.vue | 38 +-- .../courseware/CoursewareCanvasBlock.vue | 24 +- .../courseware/CoursewareFolderBlock.vue | 254 ++++++++++++++---- .../vue/store/courseware/courseware.module.js | 16 +- 7 files changed, 368 insertions(+), 89 deletions(-) diff --git a/lib/classes/JsonApi/Routes/Files/FileRefsCreateByUpload.php b/lib/classes/JsonApi/Routes/Files/FileRefsCreateByUpload.php index 3bb00d1b9ed..8c18ca1aaae 100644 --- a/lib/classes/JsonApi/Routes/Files/FileRefsCreateByUpload.php +++ b/lib/classes/JsonApi/Routes/Files/FileRefsCreateByUpload.php @@ -22,8 +22,15 @@ class FileRefsCreateByUpload extends NonJsonApiController throw new AuthorizationFailedException(); } + $term_id = $request->getParsedBody()['term-id']; + $fileRef = $this->handleUpload($request, $folder); + if ($term_id) { + $fileRef->content_terms_of_use_id = $term_id; + $fileRef->store(); + } + return $this->redirectToFileRef($response, $fileRef); } } diff --git a/lib/models/Courseware/BlockTypes/Folder.php b/lib/models/Courseware/BlockTypes/Folder.php index ef0bd2eb512..86380f23c4c 100644 --- a/lib/models/Courseware/BlockTypes/Folder.php +++ b/lib/models/Courseware/BlockTypes/Folder.php @@ -38,9 +38,54 @@ class Folder extends BlockType ]; } + /** + * Returns the decoded payload of the block associated with this instance. + * + * @return mixed the decoded payload + */ + public function getPayload() + { + $user = \User::findCurrent(); + $payload = $this->decodePayloadString($this->block['payload']); + + $typedFolder = \Folder::find($payload['folder_id'])->getTypedFolder(); + $payload['folder-type'] = $typedFolder->folder_type; + $payload['files'] = []; + + foreach ($typedFolder->getFiles() as $folderFile) { + $file['id'] = $folderFile->id; + $file['attributes'] = [ + 'name' => $folderFile->name, + 'mime-type' => $folderFile->mime_type, + 'filesize' => (int) $folderFile->size, + 'mkdate' => date('c', $folderFile->mkdate), + ]; + $file['relationships'] = [ + 'owner' => [ + 'data' => ['type' => 'users', 'id' => $folderFile->user_id], + 'meta' => ['name' => $folderFile->getFileRef()->getAuthorName()] + ] + ]; + $file['meta'] = [ + 'download-url' => $folderFile->getDownloadURL(), + ]; + + if ($this->filePermission($typedFolder, $file, $user)) { + array_push($payload['files'], $file); + } + } + + return $payload; + } + + private function filePermission($typedFolder, $file, $user): bool + { + return $typedFolder->folder_type !== 'HomeworkFolder' || $user->id === $file['relationships']['owner']['data']['id'] || $typedFolder->isReadable($user->id); + } + /** - * get all files related to this bloc. + * get all files related to this block. * * @return \FileRef[] list of file references realted to this block */ diff --git a/resources/assets/stylesheets/scss/courseware.scss b/resources/assets/stylesheets/scss/courseware.scss index 6f8aadba176..b48113675be 100644 --- a/resources/assets/stylesheets/scss/courseware.scss +++ b/resources/assets/stylesheets/scss/courseware.scss @@ -3674,16 +3674,23 @@ i f r a m e b l o c k e n d /* * * * * * * * * * * * f o l d e r b l o c k * * * * * * * * * * * */ +.cw-block-folder-info { + border: solid thin $content-color-40; + padding: 10px 10px 0 10px; + overflow: hidden; + border-bottom: none; +} .cw-block-folder-list { - border: solid thin #ccc; - padding: 4px; + border: solid thin $content-color-40; + padding: 0; list-style: none; .cw-block-folder-file-item { list-style: none; + padding: 10px; &:not(:last-child) { - border-bottom: solid thin #ccc; + border-bottom: solid thin $content-color-40; } a { display: block; @@ -3701,17 +3708,28 @@ f o l d e r b l o c k width: 24px; margin: 1em; } +} +.cw-block-folder-upload { + border: solid thin $content-color-40; + padding: 1em 10px; + border-top: none; + + .cw-file-input { + width: calc(100% - 148px); + vertical-align: middle; + } } // for folder and download block .cw-block-file-info { @include background-icon(file, clickable, 24); background-repeat: no-repeat; - display: inline-block; + display: block; padding-left: 26px; - margin: 1em; line-height: 24px; - color: $base-color; + width: calc(100% - 48px); + overflow: hidden; + text-overflow: ellipsis; &.cw-block-file-icon-empty { color: $black; @@ -3723,35 +3741,74 @@ f o l d e r b l o c k } &.cw-block-file-icon-audio { @include background-icon(file-audio, clickable, 24); + &.download-disabled { + @include background-icon(file-audio, info, 24); + } } &.cw-block-file-icon-pic { @include background-icon(file-pic, clickable, 24); + &.download-disabled { + @include background-icon(file-pic, info, 24); + } } &.cw-block-file-icon-video { @include background-icon(file-video, clickable, 24); + &.download-disabled { + @include background-icon(file-video, info, 24); + } } &.cw-block-file-icon-pdf { @include background-icon(file-pdf, clickable, 24); + &.download-disabled { + @include background-icon(file-pdf, info, 24); + } } &.cw-block-file-icon-word { @include background-icon(file-word, clickable, 24); + &.download-disabled { + @include background-icon(file-word, info, 24); + } } &.cw-block-file-icon-spreadsheet { @include background-icon(file-excel, clickable, 24); + &.download-disabled { + @include background-icon(file-excel, info, 24); + } } &.cw-block-file-icon-text { @include background-icon(file-text, clickable, 24); + &.download-disabled { + @include background-icon(file-text, info, 24); + } } &.cw-block-file-icon-ppt { @include background-icon(file-ppt, clickable, 24); + &.download-disabled { + @include background-icon(file-ppt, info, 24); + } } &.cw-block-file-icon-archive { @include background-icon(file-archive, clickable, 24); + &.download-disabled { + @include background-icon(file-archive, info, 24); + } } &.cw-block-file-icon-file { @include background-icon(file, clickable, 24); + &.download-disabled { + @include background-icon(file, info, 24); + } } } +.cw-block-file-owner, +.cw-block-file-mkdate { + display: block; + padding-left: 26px; + color: $dark-gray-color; + width: calc(100% - 48px); + overflow: hidden; + text-overflow: ellipsis; +} /* * * * * * * * * * * * * * * f o l d e r b l o c k e n d * * * * * * * * * * * * * * */ @@ -4766,7 +4823,7 @@ cw tiles end .cw-file-input { width: stretch; border: solid thin $base-color; - font-size: 14px; + font-size: 13px; cursor: pointer; &::file-selector-button { diff --git a/resources/vue/components/courseware/CoursewareAudioBlock.vue b/resources/vue/components/courseware/CoursewareAudioBlock.vue index 7a946fa2088..218f11dc666 100644 --- a/resources/vue/components/courseware/CoursewareAudioBlock.vue +++ b/resources/vue/components/courseware/CoursewareAudioBlock.vue @@ -587,28 +587,32 @@ export default { async storeRecording() { let view = this; let user = this.usersById({id: this.userId}); - let file = {}; let blob = new Blob(view.chunks, {type: 'audio/webm; codecs:vp9' }); - file.attributes = {}; - file.attributes.name = (user.attributes["formatted-name"]).replace(/\s+/g, '_') + '.webm'; - let fileObj = false; - try { - fileObj = await this.createFile({ - file: file, - filedata: blob, - folder: {id: this.currentFolderId} - }); - } - catch(e) { - this.companionError({ - info: this.$gettext('Es ist ein Fehler aufgetreten! Die Aufnahme konnte nicht gespeichert werden.') - }); - console.debug(e); - } + let file = { + attributes: { + name: (user.attributes["formatted-name"]).replace(/\s+/g, '_') + '.webm' + }, + relationships: { + 'terms-of-use': { + data: { + id: 'SELFMADE_NONPUB' + } + } + } + }; + let fileObj = await this.createFile({ + file: file, + filedata: blob, + folder: {id: this.currentFolderId} + }); if(fileObj && fileObj.type === 'file-refs') { this.companionSuccess({ info: this.$gettext('Die Aufnahme wurde erfolgreich im Dateibereich abgelegt.') }); + } else { + this.companionError({ + info: this.$gettext('Es ist ein Fehler aufgetreten! Die Aufnahme konnte nicht gespeichert werden.') + }); } this.newRecording = false; this.getFolderFiles(); diff --git a/resources/vue/components/courseware/CoursewareCanvasBlock.vue b/resources/vue/components/courseware/CoursewareCanvasBlock.vue index 79363d51fc8..de474cee2f9 100644 --- a/resources/vue/components/courseware/CoursewareCanvasBlock.vue +++ b/resources/vue/components/courseware/CoursewareCanvasBlock.vue @@ -574,24 +574,20 @@ export default { file.attributes.name = (user.attributes["formatted-name"]).replace(/\s+/g, '_') + '_' + this.block.attributes.title + '_' + this.block.id; } - let img = false; - try { - img = await this.createFile({ - file: file, - filedata: imageBlob, - folder: {id: this.currentUploadFolderId} - }); - } - catch(e) { - this.companionError({ - info: this.$gettext('Es ist ein Fehler aufgetretten! Das Bild konnte nicht gespeichert werden.') - }); - console.log(e); - } + let img = await this.createFile({ + file: file, + filedata: imageBlob, + folder: {id: this.currentUploadFolderId} + }); + if(img && img.type === 'file-refs') { this.companionSuccess({ info: this.$gettext('Das Bild wurde erfolgreich im Dateibereich abgelegt.') }); + } else { + this.companionError({ + info: this.$gettext('Es ist ein Fehler aufgetretten! Das Bild konnte nicht gespeichert werden.') + }); } }, }, diff --git a/resources/vue/components/courseware/CoursewareFolderBlock.vue b/resources/vue/components/courseware/CoursewareFolderBlock.vue index a1b97490d09..888d95dc814 100644 --- a/resources/vue/components/courseware/CoursewareFolderBlock.vue +++ b/resources/vue/components/courseware/CoursewareFolderBlock.vue @@ -10,36 +10,118 @@ > <template #content> <div v-if="currentTitle !== ''" class="cw-block-title">{{ currentTitle }}</div> + <div v-if="isHomework" class="cw-block-folder-info"> + <p> + {{ $gettext('Dieser Ordner ist ein Hausaufgabenordner. Es können nur Dateien eingestellt werden.') }} + </p> + <p v-if="!isTeacher"> + {{ $gettext('Sie selbst haben folgende Dateien in diesen Ordner eingestellt') }}: + </p> + </div> <ul class="cw-block-folder-list"> <li v-for="file in files" :key="file.id" class="cw-block-folder-file-item"> - <a target="_blank" :download="file.name" :href="file.download_url"> - <span class="cw-block-file-info" :class="['cw-block-file-icon-' + file.icon]"> - {{ file.name }} + <a v-if="downloadEnabled" target="_blank" :download="file.attributes.name" :href="file.meta['download-url']"> + <span class="cw-block-file-info" :class="['cw-block-file-icon-' + getIcon(file.attributes['mime-type'])]"> + {{ file.attributes.name }} </span> - <span class="cw-block-folder-download-icon"></span> + <template v-if="isTeacher && isHomework"> + <span class="cw-block-file-owner"> + {{ file.relationships.owner.meta.name }} + </span> + <span class="cw-block-file-mkdate"> + {{ getFormattedDate(file.attributes.mkdate) }} + </span> + </template> </a> + <template v-else> + <span class="cw-block-file-info download-disabled" :class="['cw-block-file-icon-' + getIcon(file.attributes['mime-type'])]"> + {{ file.attributes.name }} + </span> + <span class="cw-block-file-mkdate"> + {{ getFormattedDate(file.attributes.mkdate) }} + </span> + </template> </li> <li v-if="files.length === 0"> <span class="cw-block-file-info cw-block-file-icon-empty"> - <translate>Dieser Ordner ist leer</translate> + {{ $gettext('Dieser Ordner ist leer') }} </span> </li> </ul> + <div v-if="uploadEnabled" class="cw-block-folder-upload"> + <form class="default" @submit.prevent=""> + <label> + {{ $gettext('Dateien zum Hochladen auswählen') }} + <input class="cw-file-input" ref="uploadFile" type="file" @change="displayTermSelector"/> + <button class="button" @click="prepareUpload"> + {{ $gettext('Datei hochladen') }} + </button> + </label> + </form> + <studip-dialog + v-if="showTermSelector" + width="780" + height="510" + :title="$gettext('Lizenz auswählen')" + :confirmText="$gettext('Speichern')" + confirmClass="accept" + :closeText="$gettext('Lizenzauswahl abbrechen')" + closeClass="cancel" + @close="showTermSelector = false" + @confirm="selectTerm" + > + <template v-slot:dialogContent> + <form class="default" @submit.prevent=""> + <div style="margin-bottom: 1ex;"> + {{ $gettext('Bereitgestellte Dateien können heruntergeladen und ggf. weiterverbreitet werden. Dabei ist das Urheberrecht sowohl beim Hochladen der Datei als auch bei der Nutzung zu beachten. Bitte geben Sie daher an, um welche Art von Bereitstellung es sich handelt. Diese Angabe dient mehreren Zwecken: Beim Herunterladen wird ein Hinweis angezeigt, welche Nutzung der Datei zulässig ist. Beim Hochladen stellt die Angabe eine Entscheidungshilfe dar, damit Sie sichergehen können, dass die Datei tatsächlich bereitgestellt werden darf.') }} + </div> + <fieldset class="select_terms_of_use"> + <template v-for="term in termsOfUse"> + <input + type="radio" + name="content_terms_of_use_id" + :value="term.id" + v-model="selectedTerm" + :id="'content_terms_of_use-' + term.id" + :checked="selectedTerm === term.id" + :aria-description="term.description"> + /> + <label @click="selectedTerm = term.id"> + <div class="icon"> + <studip-icon :shape="term.attributes.icon" size="32"/> + </div> + <div class="text"> + {{ term.attributes.name }} + </div> + <studip-icon shape="arr_1down" size="24" class="arrow" /> + <studip-icon shape="check-circle" size="24" class="check" /> + </label> + <div class="terms_of_use_description"> + <div class="description"> + {{ term.attributes.description }} + </div> + </div> + </template> + </fieldset> + </form> + </template> + </studip-dialog> + </div> </template> <template v-if="canEdit" #edit> <form class="default" @submit.prevent=""> <label> - <translate>Überschrift</translate> + {{ $gettext('Überschrift') }} <input type="text" v-model="currentTitle" /> </label> <label> - <translate>Ordner</translate> - <courseware-folder-chooser v-model="currentFolderId" allowUserFolders /> + {{ $gettext('Ordner') }} + <courseware-folder-chooser v-model="currentFolderId" allowUserFolders allowHomeworkFolders /> </label> </form> </template> <template #info> - <p><translate>Informationen zum Dateiordner-Block</translate></p> + <p>{{ $gettext('Informationen zum Dateiordner-Block') }}</p> </template> </courseware-default-block> </div> @@ -48,6 +130,7 @@ <script> import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue'; import CoursewareFolderChooser from './CoursewareFolderChooser.vue'; +import StudipDialog from '../StudipDialog.vue'; import { mapActions, mapGetters } from 'vuex'; @@ -56,6 +139,7 @@ export default { components: { CoursewareDefaultBlock, CoursewareFolderChooser, + StudipDialog }, props: { block: Object, @@ -66,66 +150,71 @@ export default { return { currentTitle: '', currentFolderId: '', - currentFileType: '', - files: [], + currentFolderType: '', + showTermSelector: false, + selectedTerm: null, + termSelected: false }; }, computed: { ...mapGetters({ - relatedFileRefs: 'file-refs/related', - urlHelper: 'urlHelper', - relatedTermOfUse: 'terms-of-use/related', + folderById: 'folders/byId', + termsOfUse: 'terms-of-use/all' }), folderType() { return this.block?.attributes?.payload?.type; }, + storedFolderType() { + return this.block?.attributes?.payload?.folder_type; + }, + folderTypeHasChanged() { + return this.folderType === this.storedFolderType; + }, folderId() { return this.block?.attributes?.payload?.folder_id; }, title() { return this.block?.attributes?.payload?.title; }, + files() { + return this.block?.attributes?.payload?.files; + }, + isHomework() { + return this.folderType === 'HomeworkFolder'; + }, + uploadEnabled() { + return !this.isTeacher && this.isHomework; + }, + downloadEnabled() { + return this.isTeacher || !this.isHomework; + } }, mounted() { + this.loadTermsOfUse(); this.initCurrentData(); }, methods: { ...mapActions({ - loadRelatedFileRefs: 'file-refs/loadRelated', + loadFolder: 'folders/loadById', + loadBlock: 'courseware-blocks/loadById', updateBlock: 'updateBlockInContainer', + createFile: 'createFile', + companionWarning: 'companionWarning', + companionSuccess: 'companionSuccess', + companionError: 'companionError', + loadTermsOfUse: 'terms-of-use/loadAll' }), - initCurrentData() { + async initCurrentData() { this.currentTitle = this.title; this.currentFolderId = this.folderId; - this.currentFolderType = this.folderType; - }, - async getFolderFiles() { - const parent = { type: 'folders', id: `${this.currentFolderId}` }; - const relationship = 'file-refs'; - const options = { include: 'terms-of-use' }; - await this.loadRelatedFileRefs({ parent, relationship, options }); - const fileRefs = this.relatedFileRefs({ parent, relationship }) ?? []; - this.processFiles(fileRefs); - }, - processFiles(files) { - this.files = files - .filter((file) => { - if (this.relatedTermOfUse({parent: file, relationship: 'terms-of-use'}).attributes['download-condition'] !== 0) { - return false; - } else { - return true; - } - }) - .map(({ id, attributes }) => ({ - id, - name: attributes.name, - icon: this.getIcon(attributes['mime-type']), - download_url: this.urlHelper.getURL( - `sendfile.php/`, - { type: 0, file_id: id, file_name: attributes.name }, - true - ), - })); + if (this.$refs?.uploadFile) { + this.$refs.uploadFile.value = null; + } + }, + async setCurrentFolderType() { + await this.loadFolder({ id: this.currentFolderId }); + const folder = this.folderById({ id: this.currentFolderId }); + this.currentFolderType = folder?.attributes['folder-type']; }, getIcon(mimeType) { let icon = 'file'; @@ -171,12 +260,22 @@ export default { return icon; }, + getFormattedDate(unformattedDate) { + const date = new Date(unformattedDate); + const localeDate = date.toLocaleDateString("de-DE", { + year: "numeric", + month: "2-digit", + day: "2-digit", + }); + + return `${localeDate} ${date.getHours()}:${(date.getMinutes() < 10 ? '0' : '') + date.getMinutes()}:${(date.getSeconds() < 10 ? '0' : '') + date.getSeconds()}`; + }, storeBlock() { let attributes = {}; attributes.payload = {}; attributes.payload.title = this.currentTitle; attributes.payload.folder_id = this.currentFolderId; - attributes.payload.type = this.currentFileType; + attributes.payload.type = this.currentFolderType; this.updateBlock({ attributes: attributes, @@ -184,10 +283,71 @@ export default { containerId: this.block.relationships.container.data.id, }); }, + displayTermSelector() { + this.showTermSelector = true; + }, + selectTerm() { + this.termSelected = true; + this.showTermSelector = false; + }, + prepareUpload() { + if (this.termSelected) { + this.uploadFile(); + } else { + this.displayTermSelector(); + } + }, + async uploadFile() { + const userFile = this.$refs?.uploadFile?.files[0]; + if (!userFile) { + this.companionWarning({ + info: this.$gettext('Bitte wählen Sie eine Datei aus.') + }); + return; + } + + let file = { + attributes: { + name: userFile.name.replace(/\s/g, '_') + }, + relationships: { + 'terms-of-use': { + data: { + id: this.selectedTerm + } + } + } + }; + let fileObj = await this.createFile({ + file: file, + filedata: userFile, + folder: { id: this.currentFolderId } + }); + if (fileObj && fileObj.type === 'file-refs') { + this.companionSuccess({ + info: this.$gettext('Die Datei wurde erfolgreich im Dateibereich abgelegt.') + }); + } else { + if (this.folderType !== 'HomeworkFolder') { + this.companionError({ + info: this.$gettext('Es ist ein Fehler aufgetretten.') + }); + } + } + this.reload(); + }, + async reload() { + await this.loadBlock({ id: this.block.id }); + this.termSelected = false; + this.selectedTerm = null; + this.initCurrentData(); + } }, watch: { currentFolderId() { - this.getFolderFiles(); + if (this.canEdit) { + this.setCurrentFolderType(); + } }, }, }; diff --git a/resources/vue/store/courseware/courseware.module.js b/resources/vue/store/courseware/courseware.module.js index 85b7d11ca85..a211103ffcf 100644 --- a/resources/vue/store/courseware/courseware.module.js +++ b/resources/vue/store/courseware/courseware.module.js @@ -286,8 +286,12 @@ export const actions = { }, async createFile(context, { file, filedata, folder }) { + const termId = file.relationships['terms-of-use'].data.id; const formData = new FormData(); formData.append('file', filedata, file.attributes.name); + if (termId) { + formData.append('term-id', termId); + } const url = `folders/${folder.id}/file-refs`; let request = await state.httpClient.post(url, formData, { @@ -295,10 +299,16 @@ export const actions = { 'Content-Type': 'multipart/form-data', }, }); + let response = null; + try { + response = await state.httpClient.get(request.headers.location); + } + catch(e) { + console.debug(e); + response = null; + } - return state.httpClient.get(request.headers.location).then((response) => { - return response.data.data; - }); + return response ? response.data.data : response; }, async createRootFolder({ dispatch, rootGetters }, { context, folder }) { -- GitLab From 38030b53b1306eb1ab7b0f82c4fcf84d2bb861b7 Mon Sep 17 00:00:00 2001 From: Ron Lucke <lucke@elan-ev.de> Date: Mon, 12 Sep 2022 11:59:55 +0200 Subject: [PATCH 2/3] fix layout and folder not found issue --- lib/models/Courseware/BlockTypes/Folder.php | 49 ++++++++++--------- .../assets/stylesheets/scss/courseware.scss | 20 ++++---- .../courseware/CoursewareDownloadBlock.vue | 9 +++- .../courseware/CoursewareFolderBlock.vue | 27 ++++++---- 4 files changed, 63 insertions(+), 42 deletions(-) diff --git a/lib/models/Courseware/BlockTypes/Folder.php b/lib/models/Courseware/BlockTypes/Folder.php index 86380f23c4c..b3a0767b0d6 100644 --- a/lib/models/Courseware/BlockTypes/Folder.php +++ b/lib/models/Courseware/BlockTypes/Folder.php @@ -48,30 +48,35 @@ class Folder extends BlockType $user = \User::findCurrent(); $payload = $this->decodePayloadString($this->block['payload']); - $typedFolder = \Folder::find($payload['folder_id'])->getTypedFolder(); - $payload['folder-type'] = $typedFolder->folder_type; + $folder = \Folder::find($payload['folder_id']); + $payload['folder-type'] = null; $payload['files'] = []; - foreach ($typedFolder->getFiles() as $folderFile) { - $file['id'] = $folderFile->id; - $file['attributes'] = [ - 'name' => $folderFile->name, - 'mime-type' => $folderFile->mime_type, - 'filesize' => (int) $folderFile->size, - 'mkdate' => date('c', $folderFile->mkdate), - ]; - $file['relationships'] = [ - 'owner' => [ - 'data' => ['type' => 'users', 'id' => $folderFile->user_id], - 'meta' => ['name' => $folderFile->getFileRef()->getAuthorName()] - ] - ]; - $file['meta'] = [ - 'download-url' => $folderFile->getDownloadURL(), - ]; - - if ($this->filePermission($typedFolder, $file, $user)) { - array_push($payload['files'], $file); + if ($folder) { + $typedFolder = $folder->getTypedFolder(); + $payload['folder-type'] = $typedFolder->folder_type; + + foreach ($typedFolder->getFiles() as $folderFile) { + $file['id'] = $folderFile->id; + $file['attributes'] = [ + 'name' => $folderFile->name, + 'mime-type' => $folderFile->mime_type, + 'filesize' => (int) $folderFile->size, + 'mkdate' => date('c', $folderFile->mkdate), + ]; + $file['relationships'] = [ + 'owner' => [ + 'data' => ['type' => 'users', 'id' => $folderFile->user_id], + 'meta' => ['name' => $folderFile->getFileRef()->getAuthorName()] + ] + ]; + $file['meta'] = [ + 'download-url' => $folderFile->getDownloadURL(), + ]; + + if ($this->filePermission($typedFolder, $file, $user)) { + array_push($payload['files'], $file); + } } } diff --git a/resources/assets/stylesheets/scss/courseware.scss b/resources/assets/stylesheets/scss/courseware.scss index b48113675be..f7ee74ea7f6 100644 --- a/resources/assets/stylesheets/scss/courseware.scss +++ b/resources/assets/stylesheets/scss/courseware.scss @@ -3687,7 +3687,6 @@ f o l d e r b l o c k .cw-block-folder-file-item { list-style: none; - padding: 10px; &:not(:last-child) { border-bottom: solid thin $content-color-40; @@ -3719,15 +3718,15 @@ f o l d e r b l o c k vertical-align: middle; } } - // for folder and download block +// for folder and download block .cw-block-file-info { @include background-icon(file, clickable, 24); background-repeat: no-repeat; display: block; - padding-left: 26px; - line-height: 24px; - width: calc(100% - 48px); + padding: 16px 16px 16px 40px; + background-position: 10px 16px; + width: calc(100% - 56px); overflow: hidden; text-overflow: ellipsis; @@ -3800,12 +3799,16 @@ f o l d e r b l o c k } } } +.cw-block-file-details { + margin-top: -16px; + padding-left: 40px; + padding-bottom: 16px; + color: $dark-gray-color; +} .cw-block-file-owner, .cw-block-file-mkdate { display: block; - padding-left: 26px; - color: $dark-gray-color; - width: calc(100% - 48px); + width: calc(100% - 56px); overflow: hidden; text-overflow: ellipsis; } @@ -3820,7 +3823,6 @@ d o w n l o a d b l o c k .cw-block-download { .cw-block-download-content { border: solid thin $content-color-40; - padding: 4px; .cw-block-download-file-item { a { display: block; diff --git a/resources/vue/components/courseware/CoursewareDownloadBlock.vue b/resources/vue/components/courseware/CoursewareDownloadBlock.vue index 2094cfa7133..1f7a78beb90 100644 --- a/resources/vue/components/courseware/CoursewareDownloadBlock.vue +++ b/resources/vue/components/courseware/CoursewareDownloadBlock.vue @@ -19,11 +19,16 @@ {{ currentSuccess }} </div> <div class="cw-block-download-file-item"> - <a target="_blank" :download="currentFile.name" :href="currentFile.download_url" @click="handleDownload"> + <a + target="_blank" + :download="currentFile.name" + :title="$gettext('Datei herunterladen')" + :href="currentFile.download_url" + @click="handleDownload" + > <span class="cw-block-file-info" :class="['cw-block-file-icon-' + currentFile.icon]"> {{ currentFile.name }} </span> - <span class="cw-block-download-download-icon"></span> </a> </div> </div> diff --git a/resources/vue/components/courseware/CoursewareFolderBlock.vue b/resources/vue/components/courseware/CoursewareFolderBlock.vue index 888d95dc814..09555ae2675 100644 --- a/resources/vue/components/courseware/CoursewareFolderBlock.vue +++ b/resources/vue/components/courseware/CoursewareFolderBlock.vue @@ -20,26 +20,34 @@ </div> <ul class="cw-block-folder-list"> <li v-for="file in files" :key="file.id" class="cw-block-folder-file-item"> - <a v-if="downloadEnabled" target="_blank" :download="file.attributes.name" :href="file.meta['download-url']"> + <a + v-if="downloadEnabled" + target="_blank" + :download="file.attributes.name" + :title="$gettext('Datei herunterladen')" + :href="file.meta['download-url']" + > <span class="cw-block-file-info" :class="['cw-block-file-icon-' + getIcon(file.attributes['mime-type'])]"> {{ file.attributes.name }} </span> - <template v-if="isTeacher && isHomework"> + <div v-if="isTeacher && isHomework" class="cw-block-file-details"> <span class="cw-block-file-owner"> {{ file.relationships.owner.meta.name }} </span> <span class="cw-block-file-mkdate"> {{ getFormattedDate(file.attributes.mkdate) }} </span> - </template> + </div> </a> <template v-else> <span class="cw-block-file-info download-disabled" :class="['cw-block-file-icon-' + getIcon(file.attributes['mime-type'])]"> {{ file.attributes.name }} </span> - <span class="cw-block-file-mkdate"> - {{ getFormattedDate(file.attributes.mkdate) }} - </span> + <div class="cw-block-file-details"> + <span class="cw-block-file-mkdate"> + {{ getFormattedDate(file.attributes.mkdate) }} + </span> + </div> </template> </li> <li v-if="files.length === 0"> @@ -84,9 +92,10 @@ v-model="selectedTerm" :id="'content_terms_of_use-' + term.id" :checked="selectedTerm === term.id" - :aria-description="term.description"> + :aria-description="term.description" + :key="term.id" /> - <label @click="selectedTerm = term.id"> + <label @click="selectedTerm = term.id" :key="term.id"> <div class="icon"> <studip-icon :shape="term.attributes.icon" size="32"/> </div> @@ -96,7 +105,7 @@ <studip-icon shape="arr_1down" size="24" class="arrow" /> <studip-icon shape="check-circle" size="24" class="check" /> </label> - <div class="terms_of_use_description"> + <div class="terms_of_use_description" :key="term.id"> <div class="description"> {{ term.attributes.description }} </div> -- GitLab From 867e84a222d9ce0fad706578c0a416312cc50d05 Mon Sep 17 00:00:00 2001 From: Ron Lucke <lucke@elan-ev.de> Date: Mon, 12 Sep 2022 13:41:23 +0200 Subject: [PATCH 3/3] final polish --- .../JsonApi/Schemas/ContentTermsOfUse.php | 1 + .../courseware/CoursewareFolderBlock.vue | 32 +++++++++---------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/lib/classes/JsonApi/Schemas/ContentTermsOfUse.php b/lib/classes/JsonApi/Schemas/ContentTermsOfUse.php index b827788e1fc..23c929437a9 100644 --- a/lib/classes/JsonApi/Schemas/ContentTermsOfUse.php +++ b/lib/classes/JsonApi/Schemas/ContentTermsOfUse.php @@ -19,6 +19,7 @@ class ContentTermsOfUse extends SchemaProvider 'name' => (string) $resource['name'], 'description' => mb_strlen($resource['description']) ? (string) $resource['description'] : null, 'icon' => $resource['icon'], + 'is-default' => (bool) $resource['is_default'], 'download-condition' => (int) $resource['download_condition'], 'mkdate' => date('c', $resource['mkdate']), 'chdate' => date('c', $resource['chdate']), diff --git a/resources/vue/components/courseware/CoursewareFolderBlock.vue b/resources/vue/components/courseware/CoursewareFolderBlock.vue index 09555ae2675..2d1f355f5b5 100644 --- a/resources/vue/components/courseware/CoursewareFolderBlock.vue +++ b/resources/vue/components/courseware/CoursewareFolderBlock.vue @@ -61,7 +61,7 @@ <label> {{ $gettext('Dateien zum Hochladen auswählen') }} <input class="cw-file-input" ref="uploadFile" type="file" @change="displayTermSelector"/> - <button class="button" @click="prepareUpload"> + <button class="button" @click="uploadFile"> {{ $gettext('Datei hochladen') }} </button> </label> @@ -93,9 +93,9 @@ :id="'content_terms_of_use-' + term.id" :checked="selectedTerm === term.id" :aria-description="term.description" - :key="term.id" + :key="term.id + '_input'" /> - <label @click="selectedTerm = term.id" :key="term.id"> + <label @click="selectedTerm = term.id" :key="term.id + 'label'"> <div class="icon"> <studip-icon :shape="term.attributes.icon" size="32"/> </div> @@ -105,7 +105,7 @@ <studip-icon shape="arr_1down" size="24" class="arrow" /> <studip-icon shape="check-circle" size="24" class="check" /> </label> - <div class="terms_of_use_description" :key="term.id"> + <div class="terms_of_use_description" :key="term.id + '_description'"> <div class="description"> {{ term.attributes.description }} </div> @@ -162,7 +162,6 @@ export default { currentFolderType: '', showTermSelector: false, selectedTerm: null, - termSelected: false }; }, computed: { @@ -198,8 +197,8 @@ export default { return this.isTeacher || !this.isHomework; } }, - mounted() { - this.loadTermsOfUse(); + async mounted() { + await this.loadTermsOfUse(); this.initCurrentData(); }, methods: { @@ -219,6 +218,7 @@ export default { if (this.$refs?.uploadFile) { this.$refs.uploadFile.value = null; } + this.selectedTerm = this.getDefaultTerm(); }, async setCurrentFolderType() { await this.loadFolder({ id: this.currentFolderId }); @@ -296,15 +296,8 @@ export default { this.showTermSelector = true; }, selectTerm() { - this.termSelected = true; this.showTermSelector = false; - }, - prepareUpload() { - if (this.termSelected) { - this.uploadFile(); - } else { - this.displayTermSelector(); - } + this.uploadFile(); }, async uploadFile() { const userFile = this.$refs?.uploadFile?.files[0]; @@ -347,9 +340,14 @@ export default { }, async reload() { await this.loadBlock({ id: this.block.id }); - this.termSelected = false; - this.selectedTerm = null; this.initCurrentData(); + }, + getDefaultTerm() { + const defaultTerm = this.termsOfUse.filter(term => term.attributes['is-default'])[0]; + if (defaultTerm) { + return defaultTerm.id; + } + return null; } }, watch: { -- GitLab