Skip to content

Commit dce1054

Browse files
Learnpath: Add automatic switch to next step in learning path, only if video - refs #2512
1 parent 35185c5 commit dce1054

File tree

20 files changed

+543
-169
lines changed

20 files changed

+543
-169
lines changed

assets/vue/composables/datatableList.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export function useDatatableList(servicePrefix) {
3636
if (!isEmpty(route.query.filetype) && route.query.filetype === "certificate") {
3737
filters.value.filetype = "certificate"
3838
} else {
39-
filters.value.filetype = ["file", "folder"]
39+
filters.value.filetype = ["file", "folder", "video"]
4040
}
4141

4242
let params = { ...filters.value }

assets/vue/views/documents/AddVariation.vue

Lines changed: 86 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,29 @@
99
/>
1010
</SectionHeader>
1111

12-
<div v-if="originalFile" class="bg-gray-100 p-4 rounded-md shadow-md">
13-
<h3 class="text-lg font-semibold">{{ t('Original File') }}</h3>
14-
<p><strong>{{ t('Title:') }}</strong> {{ originalFile.originalName }}</p>
15-
<p><strong>{{ t('Format:') }}</strong> {{ originalFile.mimeType }}</p>
16-
<p><strong>{{ t('Size:') }}</strong> {{ prettyBytes(originalFile.size) }}</p>
12+
<div
13+
v-if="originalFile"
14+
class="bg-gray-100 p-4 rounded-md shadow-md"
15+
>
16+
<h3 class="text-lg font-semibold">{{ t("Original File") }}</h3>
17+
<p>
18+
<strong>{{ t("Title:") }}</strong> {{ originalFile.originalName }}
19+
</p>
20+
<p>
21+
<strong>{{ t("Format:") }}</strong> {{ originalFile.mimeType }}
22+
</p>
23+
<p>
24+
<strong>{{ t("Size:") }}</strong> {{ prettyBytes(originalFile.size) }}
25+
</p>
1726
</div>
1827

1928
<div class="space-y-6">
20-
<h3 class="text-xl font-bold">{{ t('Upload New Variation') }}</h3>
29+
<h3 class="text-xl font-bold">{{ t("Upload New Variation") }}</h3>
2130

22-
<form @submit.prevent="uploadVariation" class="flex flex-col space-y-4">
31+
<form
32+
@submit.prevent="uploadVariation"
33+
class="flex flex-col space-y-4"
34+
>
2335
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
2436
<BaseFileUpload
2537
@file-selected="onFileSelected"
@@ -52,37 +64,69 @@
5264
</div>
5365

5466
<div>
55-
<h3 class="text-xl font-bold mb-4">{{ t('Current Variations') }}</h3>
56-
<DataTable :value="variations" class="w-full">
57-
<Column field="title" :header="t('Title')" />
58-
<Column field="mimeType" :header="t('Format')" />
59-
<Column field="size" :header="t('Size')">
67+
<h3 class="text-xl font-bold mb-4">{{ t("Current Variations") }}</h3>
68+
<DataTable
69+
:value="variations"
70+
class="w-full"
71+
>
72+
<Column
73+
field="title"
74+
:header="t('Title')"
75+
/>
76+
<Column
77+
field="mimeType"
78+
:header="t('Format')"
79+
/>
80+
<Column
81+
field="size"
82+
:header="t('Size')"
83+
>
6084
<template #body="slotProps">
6185
{{ prettyBytes(slotProps.data.size) }}
6286
</template>
6387
</Column>
64-
<Column field="updatedAt" :header="t('Updated At')" />
65-
<Column field="url" :header="t('URL')">
88+
<Column
89+
field="updatedAt"
90+
:header="t('Updated At')"
91+
/>
92+
<Column
93+
field="url"
94+
:header="t('URL')"
95+
>
6696
<template #body="slotProps">
97+
<video
98+
v-if="slotProps.data.mimeType.startsWith('video/')"
99+
controls
100+
class="max-w-xs"
101+
>
102+
<source :src="slotProps.data.path" />
103+
</video>
67104
<a
105+
v-else
68106
:href="slotProps.data.path"
69107
target="_blank"
70108
class="text-blue-500 hover:underline"
71109
>
72-
{{ t('View') }}
110+
{{ t("View") }}
73111
</a>
74112
</template>
75113
</Column>
76-
<Column field="creator" :header="t('Creator')" />
77-
<Column field="accessUrl" :header="t('Associated URL')">
114+
<Column
115+
field="creator"
116+
:header="t('Creator')"
117+
/>
118+
<Column
119+
field="accessUrl"
120+
:header="t('Associated URL')"
121+
>
78122
<template #body="slotProps">
79123
<span>
80-
{{ slotProps.data.url ? slotProps.data.url : t('Default (No URL)') }}
124+
{{ slotProps.data.url ? slotProps.data.url : t("Default (No URL)") }}
81125
</span>
82126
</template>
83127
</Column>
84128
<Column>
85-
<template #header>{{ t('Actions') }}</template>
129+
<template #header>{{ t("Actions") }}</template>
86130
<template #body="slotProps">
87131
<BaseButton
88132
:label="t('Delete')"
@@ -99,15 +143,15 @@
99143

100144
<script setup>
101145
import { ref, onMounted, computed } from "vue"
102-
import { useRoute, useRouter } from 'vue-router'
103-
import { useI18n } from 'vue-i18n'
104-
import axios from 'axios'
105-
import DataTable from 'primevue/datatable'
106-
import Column from 'primevue/column'
146+
import { useRoute, useRouter } from "vue-router"
147+
import { useI18n } from "vue-i18n"
148+
import axios from "axios"
149+
import DataTable from "primevue/datatable"
150+
import Column from "primevue/column"
107151
import SectionHeader from "../../components/layout/SectionHeader.vue"
108152
import BaseButton from "../../components/basecomponents/BaseButton.vue"
109153
import BaseFileUpload from "../../components/basecomponents/BaseFileUpload.vue"
110-
import prettyBytes from 'pretty-bytes'
154+
import prettyBytes from "pretty-bytes"
111155
import { useCidReq } from "../../composables/cidReq"
112156
import { useSecurityStore } from "../../store/securityStore"
113157
@@ -126,7 +170,7 @@ const isAdmin = computed(() => securityStore.isAdmin)
126170
127171
onMounted(async () => {
128172
if (!isAdmin.value) {
129-
await router.push({ name: 'DocumentsList' })
173+
await router.push({ name: "DocumentsList" })
130174
return
131175
}
132176
@@ -137,7 +181,7 @@ onMounted(async () => {
137181
138182
async function fetchVariations() {
139183
if (!originalFile.value?.resourceNode?.id) {
140-
console.error('ResourceNodeId is undefined. Cannot fetch variations.')
184+
console.error("ResourceNodeId is undefined. Cannot fetch variations.")
141185
return
142186
}
143187
@@ -146,24 +190,22 @@ async function fetchVariations() {
146190
const response = await axios.get(`/r/resource_files/${resourceNodeId}/variants`)
147191
variations.value = response.data
148192
} catch (error) {
149-
console.error('Error fetching variations:', error)
193+
console.error("Error fetching variations:", error)
150194
}
151195
}
152196
153197
async function fetchAccessUrls() {
154198
try {
155-
const response = await axios.get('/api/access_urls')
156-
if (Array.isArray(response.data['hydra:member'])) {
199+
const response = await axios.get("/api/access_urls")
200+
if (Array.isArray(response.data["hydra:member"])) {
157201
const currentAccessUrlId = window.access_url_id
158202
159-
accessUrls.value = response.data['hydra:member'].filter(
160-
(url) => url.id !== currentAccessUrlId
161-
)
203+
accessUrls.value = response.data["hydra:member"].filter((url) => url.id !== currentAccessUrlId)
162204
} else {
163205
accessUrls.value = []
164206
}
165207
} catch (error) {
166-
console.error('Error fetching access URLs:', error)
208+
console.error("Error fetching access URLs:", error)
167209
accessUrls.value = []
168210
}
169211
}
@@ -173,42 +215,42 @@ async function fetchOriginalFile() {
173215
const response = await axios.get(`/api/resource_files/${resourceFileId}`)
174216
originalFile.value = response.data
175217
} catch (error) {
176-
console.error('Error fetching original file:', error)
218+
console.error("Error fetching original file:", error)
177219
}
178220
}
179221
180222
async function uploadVariant(file, resourceNodeId, accessUrlId) {
181223
if (!resourceNodeId) {
182-
console.error('ResourceNodeId is undefined. Check originalFile:', originalFile.value)
224+
console.error("ResourceNodeId is undefined. Check originalFile:", originalFile.value)
183225
return
184226
}
185227
186228
const formData = new FormData()
187-
formData.append('file', file)
188-
formData.append('resourceNodeId', resourceNodeId)
229+
formData.append("file", file)
230+
formData.append("resourceNodeId", resourceNodeId)
189231
if (accessUrlId) {
190-
formData.append('accessUrlId', accessUrlId)
232+
formData.append("accessUrlId", accessUrlId)
191233
}
192234
193235
try {
194-
const response = await axios.post('/api/resource_files/add_variant', formData)
195-
console.log('Variant uploaded or updated successfully:', response.data)
236+
const response = await axios.post("/api/resource_files/add_variant", formData)
237+
console.log("Variant uploaded or updated successfully:", response.data)
196238
197239
await fetchVariations()
198240
file.value = null
199241
selectedAccessUrl.value = null
200242
} catch (error) {
201-
console.error('Error uploading variant:', error)
243+
console.error("Error uploading variant:", error)
202244
}
203245
}
204246
205247
async function deleteVariant(variantId) {
206248
try {
207249
await axios.delete(`/r/resource_files/${variantId}/delete_variant`)
208-
console.log('Variant deleted successfully.')
250+
console.log("Variant deleted successfully.")
209251
await fetchVariations()
210252
} catch (error) {
211-
console.error('Error deleting variant:', error)
253+
console.error("Error deleting variant:", error)
212254
}
213255
}
214256

assets/vue/views/documents/CreateFile.vue

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,10 @@ export default {
5757
},
5858
mixins: [CreateMixin],
5959
data() {
60-
const filetype = this.$route.query.filetype === "certificate" ? "certificate" : "file"
60+
const allowedFiletypes = ["file", "video", "audio", "certificate"]
61+
const filetypeQuery = this.$route.query.filetype
62+
const filetype = allowedFiletypes.includes(filetypeQuery) ? filetypeQuery : "file"
63+
6164
const finalTags = this.getCertificateTags()
6265
return {
6366
item: {

assets/vue/views/documents/DocumentShow.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
</video>
3131

3232
<iframe
33-
v-if="'text/html' === item.resourceNode.firstResourceFile.mimeType"
33+
v-else-if="'text/html' === item.resourceNode.firstResourceFile.mimeType"
3434
:src="item['contentUrl']"
3535
/>
3636
</div>

assets/vue/views/documents/DocumentsList.vue

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -183,12 +183,15 @@
183183
@click="openMoveDialog(slotProps.data)"
184184
/>
185185
<BaseButton
186-
:disabled="slotProps.data.filetype !== 'file'"
187-
:title="slotProps.data.filetype !== 'file' ? t('Replace (files only)') : t('Replace file')"
186+
:disabled="!(slotProps.data.filetype === 'file' || slotProps.data.filetype === 'video')"
187+
:title="getReplaceButtonTitle(slotProps.data)"
188188
icon="file-swap"
189189
size="small"
190190
type="secondary"
191-
@click="slotProps.data.filetype === 'file' && openReplaceDialog(slotProps.data)"
191+
@click="
192+
(slotProps.data.filetype === 'file' || slotProps.data.filetype === 'video') &&
193+
openReplaceDialog(slotProps.data)
194+
"
192195
/>
193196
<BaseButton
194197
:title="t('Information')"
@@ -486,7 +489,9 @@ const router = useRouter()
486489
const securityStore = useSecurityStore()
487490
488491
const platformConfigStore = usePlatformConfig()
489-
const allowAccessUrlFiles = computed(() => "false" !== platformConfigStore.getSetting("course.access_url_specific_files"))
492+
const allowAccessUrlFiles = computed(
493+
() => "false" !== platformConfigStore.getSetting("course.access_url_specific_files"),
494+
)
490495
491496
const { t } = useI18n()
492497
const { filters, options, onUpdateOptions, deleteItem } = useDatatableList("Documents")
@@ -570,6 +575,7 @@ const documentToReplace = ref(null)
570575
onMounted(async () => {
571576
isAllowedToEdit.value = await checkIsAllowedToEdit(true, true, true)
572577
filters.value.loadNode = 1
578+
filters.value.filetype = ["file", "folder", "video"]
573579
574580
// Set resource node.
575581
let nodeId = route.params.node
@@ -610,7 +616,7 @@ const showBackButtonIfNotRootFolder = computed(() => {
610616
function goToAddVariation(item) {
611617
const resourceFileId = item.resourceNode.firstResourceFile.id
612618
router.push({
613-
name: 'DocumentsAddVariation',
619+
name: "DocumentsAddVariation",
614620
params: { resourceFileId, node: route.params.node },
615621
query: { cid, sid, gid },
616622
})
@@ -677,6 +683,16 @@ function confirmDeleteItem(itemToDelete) {
677683
isDeleteItemDialogVisible.value = true
678684
}
679685
686+
function getReplaceButtonTitle(item) {
687+
if (item.filetype === "file") {
688+
return t("Replace file")
689+
}
690+
if (item.filetype === "video") {
691+
return t("Replace video")
692+
}
693+
return t("Replace (files or videos only)")
694+
}
695+
680696
async function downloadSelectedItems() {
681697
if (!selectedItems.value.length) {
682698
notification.showErrorNotification(t("No items selected."))
@@ -688,8 +704,8 @@ async function downloadSelectedItems() {
688704
try {
689705
const response = await axios.post(
690706
"/api/documents/download-selected",
691-
{ ids: selectedItems.value.map(item => item.iid) },
692-
{ responseType: "blob" }
707+
{ ids: selectedItems.value.map((item) => item.iid) },
708+
{ responseType: "blob" },
693709
)
694710
695711
const url = window.URL.createObjectURL(new Blob([response.data]))
@@ -705,7 +721,7 @@ async function downloadSelectedItems() {
705721
console.error("Error downloading selected items:", error)
706722
notification.showErrorNotification(t("Error downloading selected items."))
707723
} finally {
708-
isDownloading.value = false;
724+
isDownloading.value = false
709725
}
710726
}
711727
@@ -884,19 +900,19 @@ async function replaceDocument() {
884900
return
885901
}
886902
887-
if (documentToReplace.value.filetype !== 'file') {
888-
notification.showErrorNotification(t("Only files can be replaced."))
903+
if (!(documentToReplace.value.filetype === "file" || documentToReplace.value.filetype === "video")) {
904+
notification.showErrorNotification(t("Only files or videos can be replaced."))
889905
return
890906
}
891907
892908
const formData = new FormData()
893909
console.log(selectedReplaceFile.value)
894-
formData.append('file', selectedReplaceFile.value)
910+
formData.append("file", selectedReplaceFile.value)
895911
896912
try {
897913
await axios.post(`/api/documents/${documentToReplace.value.iid}/replace`, formData, {
898914
headers: {
899-
'Content-Type': 'multipart/form-data',
915+
"Content-Type": "multipart/form-data",
900916
},
901917
})
902918
notification.showSuccessNotification(t("File replaced"))
@@ -911,7 +927,7 @@ async function replaceDocument() {
911927
async function fetchFolders(nodeId = null, parentPath = "") {
912928
const foldersList = [
913929
{
914-
label: t('Documents'),
930+
label: t("Documents"),
915931
value: nodeId || route.params.node || route.query.node || "root-node-id",
916932
},
917933
]

0 commit comments

Comments
 (0)