Merge pull request #5919 from christianbeeznest/ofaj-22165

Documents: Enable document move icon in actions column - refs BT#22165
pull/5922/head
Nicolas Ducoulombier 10 months ago committed by GitHub
commit 6580e559e8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 3
      assets/vue/components/basecomponents/ChamiloIcons.js
  2. 98
      assets/vue/views/documents/DocumentsList.vue
  3. 12
      src/CoreBundle/Controller/Api/BaseResourceFileAction.php
  4. 21
      src/CourseBundle/Entity/CDocument.php

@ -33,7 +33,8 @@ export const chamiloIconToClass = {
"cog": "mdi mdi-cog", "cog": "mdi mdi-cog",
"confirm": "mdi mdi-check", "confirm": "mdi mdi-check",
"save": "mdi mdi-content-save", "save": "mdi mdi-content-save",
"cursor-move": "", "cursor-move": "mdi mdi-cursor-move",
"folder-move": "mdi mdi-folder-move",
"customize": "mdi mdi-format-paint", "customize": "mdi mdi-format-paint",
"delete": "mdi mdi-delete", "delete": "mdi mdi-delete",
"delete-multiple-user": "mdi mdi-account-multiple-minus-outline", "delete-multiple-user": "mdi mdi-account-multiple-minus-outline",

@ -174,6 +174,13 @@
<Column :exportable="false"> <Column :exportable="false">
<template #body="slotProps"> <template #body="slotProps">
<div class="flex flex-row justify-end gap-2"> <div class="flex flex-row justify-end gap-2">
<BaseButton
v-if="canEdit(slotProps.data)"
icon="folder-move"
size="small"
type="secondary"
@click="openMoveDialog(slotProps.data)"
/>
<BaseButton <BaseButton
icon="information" icon="information"
size="small" size="small"
@ -255,6 +262,22 @@
/> />
</BaseToolbar> </BaseToolbar>
<BaseDialogConfirmCancel
v-model:is-visible="isMoveDialogVisible"
:title="t('Move document')"
@confirm-clicked="moveDocument"
@cancel-clicked="isMoveDialogVisible = false"
>
<p>{{ t("Select the destination folder") }}</p>
<Dropdown
v-model="selectedFolder"
:options="folders"
optionLabel="label"
optionValue="value"
placeholder="Select a folder"
/>
</BaseDialogConfirmCancel>
<BaseDialogConfirmCancel <BaseDialogConfirmCancel
v-model:is-visible="isNewFolderDialogVisible" v-model:is-visible="isNewFolderDialogVisible"
:cancel-label="t('Cancel')" :cancel-label="t('Cancel')"
@ -420,6 +443,8 @@ const { isImage, isHtml } = useFileUtils()
const { relativeDatetime } = useFormatDate() const { relativeDatetime } = useFormatDate()
const isAllowedToEdit = ref(false) const isAllowedToEdit = ref(false)
const folders = ref([])
const selectedFolder = ref(null)
const { const {
showNewDocumentButton, showNewDocumentButton,
@ -445,6 +470,7 @@ const isFileUsageDialogVisible = ref(false)
const isRecordAudioDialogVisible = ref(false) const isRecordAudioDialogVisible = ref(false)
const submitted = ref(false) const submitted = ref(false)
const isMoveDialogVisible = ref(false)
filters.value.loadNode = 1 filters.value.loadNode = 1
@ -501,6 +527,7 @@ onMounted(async () => {
await loadDefaultCertificate() await loadDefaultCertificate()
onUpdateOptions(options.value) onUpdateOptions(options.value)
await loadAllFolders()
}) })
watch( watch(
@ -745,6 +772,77 @@ function recordedAudioNotSaved(error) {
console.error(error) console.error(error)
} }
function openMoveDialog(document) {
item.value = document
isMoveDialogVisible.value = true
}
async function fetchFolders(nodeId = null, parentPath = '') {
const foldersList = [{
label: 'Root',
value: nodeId || route.params.node || route.query.node || 'root-node-id',
}]
try {
let nodesToFetch = [{ id: nodeId || route.params.node || route.query.node, path: parentPath }]
let depth = 0
const maxDepth = 5
while (nodesToFetch.length > 0 && depth < maxDepth) {
const currentNode = nodesToFetch.shift()
const response = await axios.get("/api/documents", {
params: {
filetype: "folder",
"resourceNode.parent": currentNode.id,
cid: route.query.cid,
sid: route.query.sid,
},
})
response.data["hydra:member"].forEach(folder => {
const fullPath = `${currentNode.path}/${folder.title}`
foldersList.push({
label: fullPath,
value: folder.resourceNode?.id || folder.resourceNodeId || folder["@id"],
})
if (folder.resourceNode && folder.resourceNode.id) {
nodesToFetch.push({ id: folder.resourceNode.id, path: fullPath })
}
})
depth++
}
return foldersList
} catch (error) {
console.error("Error fetching folders:", error.message || error)
return []
}
}
async function loadAllFolders() {
folders.value = await fetchFolders()
}
async function moveDocument() {
try {
const response = await axios.put(`/api/documents/${item.value.iid}/move`, {
parentResourceNodeId: selectedFolder.value,
})
notification.showSuccessNotification(t("Document moved successfully"))
isMoveDialogVisible.value = false
onUpdateOptions(options.value)
} catch (error) {
console.error("Error moving document:", error.response || error)
notification.showErrorNotification(t("Error moving the document"))
}
}
async function selectAsDefaultCertificate(certificate) { async function selectAsDefaultCertificate(certificate) {
try { try {
const response = await axios.patch(`/gradebook/set_default_certificate/${cid}/${certificate.iid}`) const response = await axios.patch(`/gradebook/set_default_certificate/${cid}/${certificate.iid}`)

@ -10,6 +10,7 @@ use Chamilo\CoreBundle\Component\Utils\CreateUploadedFile;
use Chamilo\CoreBundle\Entity\AbstractResource; use Chamilo\CoreBundle\Entity\AbstractResource;
use Chamilo\CoreBundle\Entity\Course; use Chamilo\CoreBundle\Entity\Course;
use Chamilo\CoreBundle\Entity\ResourceLink; use Chamilo\CoreBundle\Entity\ResourceLink;
use Chamilo\CoreBundle\Entity\ResourceNode;
use Chamilo\CoreBundle\Entity\ResourceRight; use Chamilo\CoreBundle\Entity\ResourceRight;
use Chamilo\CoreBundle\Entity\Session; use Chamilo\CoreBundle\Entity\Session;
use Chamilo\CoreBundle\Entity\User; use Chamilo\CoreBundle\Entity\User;
@ -396,13 +397,15 @@ class BaseResourceFileAction
$resourceLinkList = []; $resourceLinkList = [];
if (!empty($contentData)) { if (!empty($contentData)) {
$contentData = json_decode($contentData, true); $contentData = json_decode($contentData, true);
if (isset($contentData['parentResourceNodeId']) && count($contentData) === 1) {
$parentResourceNodeId = (int) $contentData['parentResourceNodeId'];
}
$title = $contentData['title'] ?? ''; $title = $contentData['title'] ?? '';
$content = $contentData['contentFile'] ?? ''; $content = $contentData['contentFile'] ?? '';
$resourceLinkList = $contentData['resourceLinkListFromEntity'] ?? []; $resourceLinkList = $contentData['resourceLinkListFromEntity'] ?? [];
} else { } else {
$title = $request->get('title'); $title = $request->get('title');
$content = $request->request->get('contentFile'); $content = $request->request->get('contentFile');
// $comment = $request->request->get('comment');
} }
$repo->setResourceName($resource, $title); $repo->setResourceName($resource, $title);
@ -447,6 +450,13 @@ class BaseResourceFileAction
$repo->copyVisibilityToChildren($resource->getResourceNode(), $link); $repo->copyVisibilityToChildren($resource->getResourceNode(), $link);
} }
if (!empty($parentResourceNodeId)) {
$parentResourceNode = $em->getRepository(ResourceNode::class)->find($parentResourceNodeId);
if ($parentResourceNode) {
$resourceNode->setParent($parentResourceNode);
}
}
$resourceNode->setUpdatedAt(new DateTime()); $resourceNode->setUpdatedAt(new DateTime());
return $resource; return $resource;

@ -24,6 +24,7 @@ use Chamilo\CoreBundle\Entity\AbstractResource;
use Chamilo\CoreBundle\Entity\GradebookCategory; use Chamilo\CoreBundle\Entity\GradebookCategory;
use Chamilo\CoreBundle\Entity\Listener\ResourceListener; use Chamilo\CoreBundle\Entity\Listener\ResourceListener;
use Chamilo\CoreBundle\Entity\ResourceInterface; use Chamilo\CoreBundle\Entity\ResourceInterface;
use Chamilo\CoreBundle\Entity\ResourceNode;
use Chamilo\CoreBundle\Entity\ResourceShowCourseResourcesInSessionInterface; use Chamilo\CoreBundle\Entity\ResourceShowCourseResourcesInSessionInterface;
use Chamilo\CoreBundle\Filter\CidFilter; use Chamilo\CoreBundle\Filter\CidFilter;
use Chamilo\CoreBundle\Filter\SidFilter; use Chamilo\CoreBundle\Filter\SidFilter;
@ -53,6 +54,12 @@ use Symfony\Component\Validator\Constraints as Assert;
security: "is_granted('EDIT', object.resourceNode)", security: "is_granted('EDIT', object.resourceNode)",
deserialize: false deserialize: false
), ),
new Put(
uriTemplate: '/documents/{iid}/move',
controller: UpdateDocumentFileAction::class,
security: "is_granted('EDIT', object.resourceNode)",
deserialize: true
),
new Get(security: "is_granted('VIEW', object.resourceNode)"), new Get(security: "is_granted('VIEW', object.resourceNode)"),
new Delete(security: "is_granted('DELETE', object.resourceNode)"), new Delete(security: "is_granted('DELETE', object.resourceNode)"),
new Post( new Post(
@ -300,4 +307,18 @@ class CDocument extends AbstractResource implements ResourceInterface, ResourceS
return $this; return $this;
} }
#[Groups(['document:read', 'document:fullPath'])]
public function getFullPath(): string
{
$pathParts = [$this->getTitle()];
$parent = $this->getParent();
while ($parent instanceof ResourceNode) {
array_unshift($pathParts, $parent->getTitle());
$parent = $parent->getParent();
}
return implode('/', $pathParts);
}
} }

Loading…
Cancel
Save