Skip to content

Learnpath: Add automatic switch to next step in learning path, only if video - refs #2512 #6419

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion assets/vue/composables/datatableList.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function useDatatableList(servicePrefix) {
if (!isEmpty(route.query.filetype) && route.query.filetype === "certificate") {
filters.value.filetype = "certificate"
} else {
filters.value.filetype = ["file", "folder"]
filters.value.filetype = ["file", "folder", "video"]
}

let params = { ...filters.value }
Expand Down
130 changes: 86 additions & 44 deletions assets/vue/views/documents/AddVariation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,29 @@
/>
</SectionHeader>

<div v-if="originalFile" class="bg-gray-100 p-4 rounded-md shadow-md">
<h3 class="text-lg font-semibold">{{ t('Original File') }}</h3>
<p><strong>{{ t('Title:') }}</strong> {{ originalFile.originalName }}</p>
<p><strong>{{ t('Format:') }}</strong> {{ originalFile.mimeType }}</p>
<p><strong>{{ t('Size:') }}</strong> {{ prettyBytes(originalFile.size) }}</p>
<div
v-if="originalFile"
class="bg-gray-100 p-4 rounded-md shadow-md"
>
<h3 class="text-lg font-semibold">{{ t("Original File") }}</h3>
<p>
<strong>{{ t("Title:") }}</strong> {{ originalFile.originalName }}
</p>
<p>
<strong>{{ t("Format:") }}</strong> {{ originalFile.mimeType }}
</p>
<p>
<strong>{{ t("Size:") }}</strong> {{ prettyBytes(originalFile.size) }}
</p>
</div>

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

<form @submit.prevent="uploadVariation" class="flex flex-col space-y-4">
<form
@submit.prevent="uploadVariation"
class="flex flex-col space-y-4"
>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<BaseFileUpload
@file-selected="onFileSelected"
Expand Down Expand Up @@ -52,37 +64,69 @@
</div>

<div>
<h3 class="text-xl font-bold mb-4">{{ t('Current Variations') }}</h3>
<DataTable :value="variations" class="w-full">
<Column field="title" :header="t('Title')" />
<Column field="mimeType" :header="t('Format')" />
<Column field="size" :header="t('Size')">
<h3 class="text-xl font-bold mb-4">{{ t("Current Variations") }}</h3>
<DataTable
:value="variations"
class="w-full"
>
<Column
field="title"
:header="t('Title')"
/>
<Column
field="mimeType"
:header="t('Format')"
/>
<Column
field="size"
:header="t('Size')"
>
<template #body="slotProps">
{{ prettyBytes(slotProps.data.size) }}
</template>
</Column>
<Column field="updatedAt" :header="t('Updated At')" />
<Column field="url" :header="t('URL')">
<Column
field="updatedAt"
:header="t('Updated At')"
/>
<Column
field="url"
:header="t('URL')"
>
<template #body="slotProps">
<video
v-if="slotProps.data.mimeType.startsWith('video/')"
controls
class="max-w-xs"
>
<source :src="slotProps.data.path" />
</video>
<a
v-else
:href="slotProps.data.path"
target="_blank"
class="text-blue-500 hover:underline"
>
{{ t('View') }}
{{ t("View") }}
</a>
</template>
</Column>
<Column field="creator" :header="t('Creator')" />
<Column field="accessUrl" :header="t('Associated URL')">
<Column
field="creator"
:header="t('Creator')"
/>
<Column
field="accessUrl"
:header="t('Associated URL')"
>
<template #body="slotProps">
<span>
{{ slotProps.data.url ? slotProps.data.url : t('Default (No URL)') }}
{{ slotProps.data.url ? slotProps.data.url : t("Default (No URL)") }}
</span>
</template>
</Column>
<Column>
<template #header>{{ t('Actions') }}</template>
<template #header>{{ t("Actions") }}</template>
<template #body="slotProps">
<BaseButton
:label="t('Delete')"
Expand All @@ -99,15 +143,15 @@

<script setup>
import { ref, onMounted, computed } from "vue"
import { useRoute, useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import axios from 'axios'
import DataTable from 'primevue/datatable'
import Column from 'primevue/column'
import { useRoute, useRouter } from "vue-router"
import { useI18n } from "vue-i18n"
import axios from "axios"
import DataTable from "primevue/datatable"
import Column from "primevue/column"
import SectionHeader from "../../components/layout/SectionHeader.vue"
import BaseButton from "../../components/basecomponents/BaseButton.vue"
import BaseFileUpload from "../../components/basecomponents/BaseFileUpload.vue"
import prettyBytes from 'pretty-bytes'
import prettyBytes from "pretty-bytes"
import { useCidReq } from "../../composables/cidReq"
import { useSecurityStore } from "../../store/securityStore"

Expand All @@ -126,7 +170,7 @@ const isAdmin = computed(() => securityStore.isAdmin)

onMounted(async () => {
if (!isAdmin.value) {
await router.push({ name: 'DocumentsList' })
await router.push({ name: "DocumentsList" })
return
}

Expand All @@ -137,7 +181,7 @@ onMounted(async () => {

async function fetchVariations() {
if (!originalFile.value?.resourceNode?.id) {
console.error('ResourceNodeId is undefined. Cannot fetch variations.')
console.error("ResourceNodeId is undefined. Cannot fetch variations.")
return
}

Expand All @@ -146,24 +190,22 @@ async function fetchVariations() {
const response = await axios.get(`/r/resource_files/${resourceNodeId}/variants`)
variations.value = response.data
} catch (error) {
console.error('Error fetching variations:', error)
console.error("Error fetching variations:", error)
}
}

async function fetchAccessUrls() {
try {
const response = await axios.get('/api/access_urls')
if (Array.isArray(response.data['hydra:member'])) {
const response = await axios.get("/api/access_urls")
if (Array.isArray(response.data["hydra:member"])) {
const currentAccessUrlId = window.access_url_id

accessUrls.value = response.data['hydra:member'].filter(
(url) => url.id !== currentAccessUrlId
)
accessUrls.value = response.data["hydra:member"].filter((url) => url.id !== currentAccessUrlId)
} else {
accessUrls.value = []
}
} catch (error) {
console.error('Error fetching access URLs:', error)
console.error("Error fetching access URLs:", error)
accessUrls.value = []
}
}
Expand All @@ -173,42 +215,42 @@ async function fetchOriginalFile() {
const response = await axios.get(`/api/resource_files/${resourceFileId}`)
originalFile.value = response.data
} catch (error) {
console.error('Error fetching original file:', error)
console.error("Error fetching original file:", error)
}
}

async function uploadVariant(file, resourceNodeId, accessUrlId) {
if (!resourceNodeId) {
console.error('ResourceNodeId is undefined. Check originalFile:', originalFile.value)
console.error("ResourceNodeId is undefined. Check originalFile:", originalFile.value)
return
}

const formData = new FormData()
formData.append('file', file)
formData.append('resourceNodeId', resourceNodeId)
formData.append("file", file)
formData.append("resourceNodeId", resourceNodeId)
if (accessUrlId) {
formData.append('accessUrlId', accessUrlId)
formData.append("accessUrlId", accessUrlId)
}

try {
const response = await axios.post('/api/resource_files/add_variant', formData)
console.log('Variant uploaded or updated successfully:', response.data)
const response = await axios.post("/api/resource_files/add_variant", formData)
console.log("Variant uploaded or updated successfully:", response.data)

await fetchVariations()
file.value = null
selectedAccessUrl.value = null
} catch (error) {
console.error('Error uploading variant:', error)
console.error("Error uploading variant:", error)
}
}

async function deleteVariant(variantId) {
try {
await axios.delete(`/r/resource_files/${variantId}/delete_variant`)
console.log('Variant deleted successfully.')
console.log("Variant deleted successfully.")
await fetchVariations()
} catch (error) {
console.error('Error deleting variant:', error)
console.error("Error deleting variant:", error)
}
}

Expand Down
5 changes: 4 additions & 1 deletion assets/vue/views/documents/CreateFile.vue
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ export default {
},
mixins: [CreateMixin],
data() {
const filetype = this.$route.query.filetype === "certificate" ? "certificate" : "file"
const allowedFiletypes = ["file", "video", "audio", "certificate"]
const filetypeQuery = this.$route.query.filetype
const filetype = allowedFiletypes.includes(filetypeQuery) ? filetypeQuery : "file"

const finalTags = this.getCertificateTags()
return {
item: {
Expand Down
2 changes: 1 addition & 1 deletion assets/vue/views/documents/DocumentShow.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
</video>

<iframe
v-if="'text/html' === item.resourceNode.firstResourceFile.mimeType"
v-else-if="'text/html' === item.resourceNode.firstResourceFile.mimeType"
:src="item['contentUrl']"
/>
</div>
Expand Down
42 changes: 29 additions & 13 deletions assets/vue/views/documents/DocumentsList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -183,12 +183,15 @@
@click="openMoveDialog(slotProps.data)"
/>
<BaseButton
:disabled="slotProps.data.filetype !== 'file'"
:title="slotProps.data.filetype !== 'file' ? t('Replace (files only)') : t('Replace file')"
:disabled="!(slotProps.data.filetype === 'file' || slotProps.data.filetype === 'video')"
:title="getReplaceButtonTitle(slotProps.data)"
icon="file-swap"
size="small"
type="secondary"
@click="slotProps.data.filetype === 'file' && openReplaceDialog(slotProps.data)"
@click="
(slotProps.data.filetype === 'file' || slotProps.data.filetype === 'video') &&
openReplaceDialog(slotProps.data)
"
/>
<BaseButton
:title="t('Information')"
Expand Down Expand Up @@ -486,7 +489,9 @@ const router = useRouter()
const securityStore = useSecurityStore()

const platformConfigStore = usePlatformConfig()
const allowAccessUrlFiles = computed(() => "false" !== platformConfigStore.getSetting("course.access_url_specific_files"))
const allowAccessUrlFiles = computed(
() => "false" !== platformConfigStore.getSetting("course.access_url_specific_files"),
)

const { t } = useI18n()
const { filters, options, onUpdateOptions, deleteItem } = useDatatableList("Documents")
Expand Down Expand Up @@ -570,6 +575,7 @@ const documentToReplace = ref(null)
onMounted(async () => {
isAllowedToEdit.value = await checkIsAllowedToEdit(true, true, true)
filters.value.loadNode = 1
filters.value.filetype = ["file", "folder", "video"]

// Set resource node.
let nodeId = route.params.node
Expand Down Expand Up @@ -610,7 +616,7 @@ const showBackButtonIfNotRootFolder = computed(() => {
function goToAddVariation(item) {
const resourceFileId = item.resourceNode.firstResourceFile.id
router.push({
name: 'DocumentsAddVariation',
name: "DocumentsAddVariation",
params: { resourceFileId, node: route.params.node },
query: { cid, sid, gid },
})
Expand Down Expand Up @@ -677,6 +683,16 @@ function confirmDeleteItem(itemToDelete) {
isDeleteItemDialogVisible.value = true
}

function getReplaceButtonTitle(item) {
if (item.filetype === "file") {
return t("Replace file")
}
if (item.filetype === "video") {
return t("Replace video")
}
return t("Replace (files or videos only)")
}

async function downloadSelectedItems() {
if (!selectedItems.value.length) {
notification.showErrorNotification(t("No items selected."))
Expand All @@ -688,8 +704,8 @@ async function downloadSelectedItems() {
try {
const response = await axios.post(
"/api/documents/download-selected",
{ ids: selectedItems.value.map(item => item.iid) },
{ responseType: "blob" }
{ ids: selectedItems.value.map((item) => item.iid) },
{ responseType: "blob" },
)

const url = window.URL.createObjectURL(new Blob([response.data]))
Expand All @@ -705,7 +721,7 @@ async function downloadSelectedItems() {
console.error("Error downloading selected items:", error)
notification.showErrorNotification(t("Error downloading selected items."))
} finally {
isDownloading.value = false;
isDownloading.value = false
}
}

Expand Down Expand Up @@ -884,19 +900,19 @@ async function replaceDocument() {
return
}

if (documentToReplace.value.filetype !== 'file') {
notification.showErrorNotification(t("Only files can be replaced."))
if (!(documentToReplace.value.filetype === "file" || documentToReplace.value.filetype === "video")) {
notification.showErrorNotification(t("Only files or videos can be replaced."))
return
}

const formData = new FormData()
console.log(selectedReplaceFile.value)
formData.append('file', selectedReplaceFile.value)
formData.append("file", selectedReplaceFile.value)

try {
await axios.post(`/api/documents/${documentToReplace.value.iid}/replace`, formData, {
headers: {
'Content-Type': 'multipart/form-data',
"Content-Type": "multipart/form-data",
},
})
notification.showSuccessNotification(t("File replaced"))
Expand All @@ -911,7 +927,7 @@ async function replaceDocument() {
async function fetchFolders(nodeId = null, parentPath = "") {
const foldersList = [
{
label: t('Documents'),
label: t("Documents"),
value: nodeId || route.params.node || route.query.node || "root-node-id",
},
]
Expand Down
Loading
Loading