Internal: Fix file manager pagination and tab visibility issues - refs BT#21647

pull/5748/head
christianbeeznst 1 year ago
parent a2e5ba4e74
commit a2a81ea705
  1. 2
      assets/css/app.scss
  2. 2
      assets/css/scss/_documents.scss
  3. 26
      assets/vue/components/basecomponents/BaseTinyEditor.vue
  4. 10
      assets/vue/components/filemanager/CourseDocuments.vue
  5. 12
      assets/vue/components/filemanager/PersonalFiles.vue
  6. 45
      assets/vue/composables/useFileManager.js
  7. 11
      assets/vue/views/filemanager/List.vue
  8. 2
      public/main/inc/lib/formvalidator/Element/DateTimePicker.php
  9. 2
      public/main/ticket/tickets.php
  10. 2
      src/CoreBundle/Component/Editor/CkEditor/CkEditor.php
  11. 3
      src/CoreBundle/Entity/ResourceFile.php
  12. 8
      src/CoreBundle/Entity/ResourceNode.php

@ -813,7 +813,7 @@ form .field {
.thumbnail-item {
width: 150px;
padding: 10px;
padding: 5px;
border: 1px solid #ccc;
border-radius: 5px;
text-align: center;

@ -42,7 +42,7 @@
}
.filemanager-container .thumbnails {
@apply flex flex-wrap gap-2.5 justify-center mb-12;
@apply flex flex-wrap gap-2.5 justify-center;
}
.filemanager-container .thumbnail-item {

@ -170,6 +170,7 @@ if (props.fullPage) {
const editorConfig = computed(() => ({
...defaultEditorConfig,
...props.editorConfig,
file_picker_callback: filePickerCallback,
}))
watch(modelValue, (newValue) => {
@ -189,19 +190,16 @@ async function filePickerCallback(callback, value, meta) {
window.addEventListener("message", function (event) {
let data = event.data
if (data.url) {
url = data.url
callback(url)
callback(data.url)
}
})
// tinymce is already in the global scope, set by backend and php
window.tinymce.activeEditor.windowManager.openUrl({
url: url,
title: "File manager",
title: "File Manager",
onMessage: (api, message) => {
if (message.mceAction === "fileSelected") {
const fileUrl = message.content
callback(fileUrl)
callback(message.content.url)
api.close()
}
},
@ -218,19 +216,11 @@ function getUrlForTinyEditor() {
}).href
}
let nodeId = course.value.resourceNode ? course.value.resourceNode.id : null
if (!nodeId) {
console.error("Resource node ID is not available.")
return
}
let queryParams = { cid: course.value.id, sid: 0, gid: 0, filetype: 'file' }
return router.resolve({
name: "DocumentForHtmlEditor",
params: {
node: nodeId,
},
query: route.query,
name: 'FileManagerList',
params: { node: parentResourceNodeId.value },
query: queryParams,
}).href
}
</script>

@ -102,6 +102,11 @@
<div class="thumbnail-title">{{ file.resourceNode.title }}</div>
</div>
</div>
<div v-if="totalPages > 1" class="flex justify-center mt-4 space-x-4">
<button class="btn btn--plain px-4 py-2 rounded-md hover:bg-blue-600 disabled:bg-gray-300" :disabled="filters.page === 1" @click="previousPage">Previous</button>
<span class="text-gray-700 font-semibold">Page {{ filters.page }} of {{ totalPages }}</span>
<button class="btn btn--plain px-4 py-2 rounded-md hover:bg-blue-600 disabled:bg-gray-300" :disabled="filters.page === totalPages" @click="nextPage">Next</button>
</div>
<BaseContextMenu :visible="contextMenuVisible" :position="contextMenuPosition" @close="contextMenuVisible = false">
<ul>
<li @click="selectFile(contextMenuFile)">
@ -216,7 +221,10 @@ const {
uploadDocumentHandler,
onMountedCallback,
isAuthenticated,
selectFile
selectFile,
nextPage,
previousPage,
totalPages
} = useFileManager('documents', '/api/documents', 'CourseDocumentsUploadFile', true);
onMountedCallback();

@ -53,7 +53,7 @@
</template>
</Column>
<Column :header="$t('Size')" :sortable="true" field="resourceNode.firstResourceFile.size">
<Column :header="$t('Size')" field="resourceNode.firstResourceFile.size">
<template #body="slotProps">
{{ slotProps.data.resourceNode.firstResourceFile ? prettyBytes(slotProps.data.resourceNode.firstResourceFile.size) : ""
}}
@ -103,6 +103,11 @@
<div class="thumbnail-title">{{ file.resourceNode.title }}</div>
</div>
</div>
<div v-if="totalPages > 1" class="flex justify-center mt-4 space-x-4">
<button class="btn btn--plain px-4 py-2 rounded-md hover:bg-blue-600 disabled:bg-gray-300" :disabled="filters.page === 1" @click="previousPage">Previous</button>
<span class="text-gray-700 font-semibold">Page {{ filters.page }} of {{ totalPages }}</span>
<button class="btn btn--plain px-4 py-2 rounded-md hover:bg-blue-600 disabled:bg-gray-300" :disabled="filters.page === totalPages" @click="nextPage">Next</button>
</div>
<BaseContextMenu :visible="contextMenuVisible" :position="contextMenuPosition" @close="contextMenuVisible = false">
<ul>
<li @click="selectFile(contextMenuFile)">
@ -217,7 +222,10 @@ const {
uploadDocumentHandler,
onMountedCallback,
isAuthenticated,
selectFile
selectFile,
nextPage,
previousPage,
totalPages
} = useFileManager('personalfile', '/api/personal_files', 'FileManagerUploadFile');
onMountedCallback();

@ -31,7 +31,7 @@ export function useFileManager(entity, apiEndpoint, uploadRoute, isCourseDocumen
const itemToDelete = ref(null);
const item = ref({});
const submitted = ref(false);
const filters = ref({ shared: 0, loadNode: 1 });
const filters = ref({ shared: 0, loadNode: 1, itemsPerPage: 10, page: 1, sortBy: '', sortDesc: false });
const viewMode = ref('thumbnails');
const contextMenuVisible = ref(false);
const contextMenuPosition = ref({ x: 0, y: 0 });
@ -48,7 +48,12 @@ export function useFileManager(entity, apiEndpoint, uploadRoute, isCourseDocumen
};
const onUpdateOptions = async () => {
let flattenedFilters = flattenFilters({
if (!filters.value) {
filters.value = { shared: 0, loadNode: 1 };
}
const flattenedFilters = flattenFilters({
...filters.value,
cid: route.query.cid || '',
sid: route.query.sid || '',
@ -58,16 +63,15 @@ export function useFileManager(entity, apiEndpoint, uploadRoute, isCourseDocumen
const params = {
...flattenedFilters,
page: 1,
itemsPerPage: 10,
sortBy: '',
sortDesc: false,
page: filters.value.page || 1,
itemsPerPage: filters.value.itemsPerPage || 10,
[`order[${filters.value.sortBy}]`]: filters.value.sortDesc ? 'desc' : 'asc',
};
isLoading.value = true;
try {
const response = await fetch(`${apiEndpoint}?page=${params.page}&rows=${params.itemsPerPage}&sortBy=${params.sortBy}&sortDesc=${params.sortDesc}&shared=${params.shared}&loadNode=${params.loadNode}&resourceNode.parent=${params['resourceNode.parent']}&cid=${params.cid}&sid=${params.sid}&gid=${params.gid}&type=${params.type}`, {
const response = await fetch(`${apiEndpoint}?${new URLSearchParams(params).toString()}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
@ -260,7 +264,7 @@ export function useFileManager(entity, apiEndpoint, uploadRoute, isCourseDocumen
};
const onFilesPage = (event) => {
filters.value.itemsPerPage = event.rows;
filters.value.itemsPerPage = event.rows || 10;
filters.value.page = event.page + 1;
filters.value.sortBy = event.sortField;
filters.value.sortDesc = event.sortOrder === -1;
@ -268,7 +272,7 @@ export function useFileManager(entity, apiEndpoint, uploadRoute, isCourseDocumen
};
const sortingFilesChanged = (event) => {
filters.value.sortBy = event.sortField;
filters.value.sortBy = event.sortField || '';
filters.value.sortDesc = event.sortOrder === -1;
onUpdateOptions();
};
@ -337,6 +341,24 @@ export function useFileManager(entity, apiEndpoint, uploadRoute, isCourseDocumen
dialog.value = true;
};
const totalPages = computed(() => {
return Math.ceil(totalFiles.value / filters.value.itemsPerPage);
});
const nextPage = () => {
if (filters.value.page < totalPages.value) {
filters.value.page++;
onUpdateOptions();
}
};
const previousPage = () => {
if (filters.value.page > 1) {
filters.value.page--;
onUpdateOptions();
}
};
return {
files,
totalFiles,
@ -383,6 +405,9 @@ export function useFileManager(entity, apiEndpoint, uploadRoute, isCourseDocumen
isAuthenticated,
selectFile,
showHandler,
editHandler
editHandler,
nextPage,
previousPage,
totalPages
};
}

@ -12,7 +12,7 @@
{{ t('Personal Files') }}
</button>
<button
v-if="isAllowedToEdit"
v-if="isAllowedToEdit && courseIsSet"
class="px-4 py-2 -mb-px font-semibold border-b-2"
:class="{
'border-blue-500 text-blue-600': activeTab === 'documents',
@ -28,7 +28,7 @@
<PersonalFiles />
</div>
<div v-if="activeTab === 'documents' && isAllowedToEdit" class="mt-4">
<div v-if="activeTab === 'documents' && isAllowedToEdit && courseIsSet" class="mt-4">
<CourseDocuments />
</div>
</div>
@ -41,6 +41,8 @@ import PersonalFiles from "../../components/filemanager/PersonalFiles.vue"
import CourseDocuments from "../../components/filemanager/CourseDocuments.vue"
import { checkIsAllowedToEdit } from "../../composables/userPermissions"
import { useI18n } from "vue-i18n"
import { useCidReqStore } from "../../store/cidReq"
import { storeToRefs } from 'pinia'
const route = useRoute()
const router = useRouter()
@ -50,6 +52,10 @@ const isAllowedToEdit = ref(false)
const isLoading = ref(true)
const { t } = useI18n()
const cidReqStore = useCidReqStore()
const { course } = storeToRefs(cidReqStore)
const courseIsSet = ref(false)
const changeTab = (tab) => {
activeTab.value = tab
router.replace({ query: { ...route.query, tab } })
@ -63,6 +69,7 @@ watch(route, (newRoute) => {
onMounted(async () => {
isAllowedToEdit.value = await checkIsAllowedToEdit()
courseIsSet.value = !!course.value
isLoading.value = false
})
</script>

@ -134,7 +134,7 @@ class DateTimePicker extends HTML_QuickForm_text
}
$courseInfo = api_get_course_info();
if (isset($courseInfo)) {
if (!empty($courseInfo)) {
$locale = $courseInfo['language'];
}

@ -236,7 +236,7 @@ if (!empty($projectId)) {
if ('true' === api_get_setting('ticket_allow_student_add') || api_is_platform_admin()) {
$actionRight = Display::url(
Display::getMdiIcon(ActionIcon::ADD, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('Add')),
api_get_path(WEB_CODE_PATH).'ticket/new_ticket.php?project_id='.$projectId.'&'.api_get_cidReq(),
api_get_path(WEB_CODE_PATH).'ticket/new_ticket.php?project_id='.$projectId,
['title' => get_lang('Add')]
);
}

@ -194,7 +194,7 @@ class CkEditor extends Editor
*
* @return string javaScript function as string
*/
private function getFileManagerPicker($onlyPersonalfiles = true): string
private function getFileManagerPicker(bool $onlyPersonalfiles = true): string
{
$user = api_get_user_entity();
$course = api_get_course_entity();

@ -71,6 +71,7 @@ use Vich\UploaderBundle\Mapping\Annotation as Vich;
'document:read',
'media_object_read',
'message:read',
'personal_file:read'
],
]
)]
@ -100,7 +101,7 @@ class ResourceFile implements Stringable
#[Groups(['resource_file:read', 'resource_node:read', 'document:read'])]
#[ORM\Column(type: 'simple_array', nullable: true)]
protected ?array $dimensions;
#[Groups(['resource_file:read', 'resource_node:read', 'document:read', 'message:read'])]
#[Groups(['resource_file:read', 'resource_node:read', 'document:read', 'message:read', 'personal_file:read'])]
#[ORM\Column(type: 'integer')]
protected ?int $size = 0;

@ -59,16 +59,18 @@ use Symfony\Component\Validator\Constraints as Assert;
'groups' => [
'resource_node:read',
'document:read',
'personal_file:read'
],
],
denormalizationContext: [
'groups' => [
'resource_node:write',
'document:write',
'personal_file:write'
],
]
)]
#[ApiFilter(filterClass: OrderFilter::class, properties: ['id', 'title', 'createdAt', 'updatedAt'])]
#[ApiFilter(filterClass: OrderFilter::class, properties: ['id', 'title', 'createdAt', 'updatedAt', 'firstResourceFile.size'])]
#[ApiFilter(filterClass: PropertyFilter::class)]
#[ApiFilter(filterClass: SearchFilter::class, properties: ['title' => 'partial'])]
class ResourceNode implements Stringable
@ -186,7 +188,7 @@ class ResourceNode implements Stringable
*
* @var Collection<int, ResourceFile>
*/
#[Groups(['resource_node:read', 'resource_node:write', 'document:read', 'document:write', 'message:read'])]
#[Groups(['resource_node:read', 'resource_node:write', 'document:read', 'document:write', 'message:read', 'personal_file:read'])]
#[ORM\OneToMany(
mappedBy: 'resourceNode',
targetEntity: ResourceFile::class,
@ -648,7 +650,7 @@ class ResourceNode implements Stringable
return $this;
}
#[Groups(['resource_node:read', 'document:read', 'message:read'])]
#[Groups(['resource_node:read', 'document:read', 'message:read', 'personal_file:read'])]
public function getFirstResourceFile(): ?ResourceFile
{
return $this->resourceFiles->first() ?: null;

Loading…
Cancel
Save