You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
401 lines
11 KiB
401 lines
11 KiB
<template>
|
|
<div class="filemanager-container">
|
|
<div
|
|
v-if="isAuthenticated"
|
|
class="q-card"
|
|
>
|
|
<div class="p-4 flex flex-row gap-1 mb-2">
|
|
<div class="flex flex-row gap-2">
|
|
<Button
|
|
class="btn btn--primary"
|
|
icon="fa fa-folder-plus"
|
|
label="New folder"
|
|
@click="openNewDialog"
|
|
/>
|
|
<Button
|
|
class="btn btn--primary"
|
|
icon="fa fa-file-upload"
|
|
label="Upload"
|
|
@click="uploadDocumentHandler"
|
|
/>
|
|
<Button
|
|
v-if="selectedFiles.length"
|
|
class="btn btn--danger"
|
|
icon="pi pi-trash"
|
|
label="Delete"
|
|
@click="confirmDeleteMultiple"
|
|
/>
|
|
<Button
|
|
:icon="viewModeIcon"
|
|
class="btn btn--primary"
|
|
@click="toggleViewMode"
|
|
/>
|
|
<Button
|
|
v-if="previousFolders.length"
|
|
class="btn btn--primary"
|
|
icon="pi pi-arrow-left"
|
|
label="Back"
|
|
@click="goBack"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div class="breadcrumbs">
|
|
<span
|
|
v-for="(folder, index) in previousFolders"
|
|
:key="index"
|
|
>
|
|
<span>{{ folder.title }}</span> /
|
|
</span>
|
|
<span>{{ currentFolderTitle }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="viewMode === 'list'">
|
|
<DataTable
|
|
v-model:filters="filters"
|
|
v-model:selection="selectedFiles"
|
|
:global-filter-fields="['resourceNode.title', 'resourceNode.updatedAt']"
|
|
:lazy="true"
|
|
:loading="isLoading"
|
|
:paginator="true"
|
|
:rows="10"
|
|
:rows-per-page-options="[5, 10, 20, 50]"
|
|
:total-records="totalFiles"
|
|
:value="files"
|
|
class="p-datatable-sm"
|
|
current-page-report-template="Showing {first} to {last} of {totalRecords}"
|
|
data-key="iid"
|
|
filter-display="menu"
|
|
paginator-template="CurrentPageReport FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink RowsPerPageDropdown"
|
|
responsive-layout="scroll"
|
|
@page="onFilesPage"
|
|
@sort="sortingFilesChanged"
|
|
>
|
|
<Column
|
|
:header="$t('Title')"
|
|
:sortable="true"
|
|
field="resourceNode.title"
|
|
>
|
|
<template #body="slotProps">
|
|
<div>
|
|
<span
|
|
v-if="!slotProps.data.resourceNode.firstResourceFile"
|
|
@click="handleClickFile(slotProps.data)"
|
|
>
|
|
{{ slotProps.data.resourceNode.title }} folder
|
|
</span>
|
|
<span v-else>
|
|
{{ slotProps.data.resourceNode.title }}
|
|
</span>
|
|
</div>
|
|
</template>
|
|
</Column>
|
|
|
|
<Column
|
|
:header="$t('Size')"
|
|
:sortable="true"
|
|
field="resourceNode.firstResourceFile.size"
|
|
>
|
|
<template #body="slotProps">
|
|
{{
|
|
slotProps.data.resourceNode.firstResourceFile
|
|
? prettyBytes(slotProps.data.resourceNode.firstResourceFile.size)
|
|
: ""
|
|
}}
|
|
</template>
|
|
</Column>
|
|
|
|
<Column
|
|
:header="$t('Modified')"
|
|
:sortable="true"
|
|
field="resourceNode.updatedAt"
|
|
>
|
|
<template #body="slotProps">
|
|
{{ relativeDatetime(slotProps.data.resourceNode.updatedAt) }}
|
|
</template>
|
|
</Column>
|
|
|
|
<Column :exportable="false">
|
|
<template #body="slotProps">
|
|
<div class="flex flex-row gap-2">
|
|
<Button
|
|
v-if="isAuthenticated"
|
|
class="btn btn--danger"
|
|
icon="pi pi-trash"
|
|
@click="confirmDeleteItem(slotProps.data)"
|
|
/>
|
|
</div>
|
|
</template>
|
|
</Column>
|
|
|
|
<Column :exportable="false">
|
|
<template #body="slotProps">
|
|
<div class="flex flex-row gap-2">
|
|
<Button
|
|
v-if="slotProps.data.resourceNode.firstResourceFile"
|
|
class="p-button-sm p-button p-mr-2"
|
|
label="Select"
|
|
@click="returnToEditor(slotProps.data)"
|
|
/>
|
|
</div>
|
|
</template>
|
|
</Column>
|
|
</DataTable>
|
|
</div>
|
|
|
|
<div v-else>
|
|
<div class="thumbnails">
|
|
<div
|
|
v-for="file in files"
|
|
:key="file.iid"
|
|
class="thumbnail-item"
|
|
@click="handleClickFile(file)"
|
|
@contextmenu.prevent="showContextMenu($event, file)"
|
|
>
|
|
<div class="thumbnail-icon">
|
|
<template v-if="isImage(file)">
|
|
<img
|
|
:alt="file.resourceNode.title"
|
|
:src="getFileUrl(file)"
|
|
:title="file.resourceNode.title"
|
|
class="thumbnail-image"
|
|
/>
|
|
</template>
|
|
<template v-else>
|
|
<span
|
|
:class="['mdi', getIcon(file)]"
|
|
class="mdi-icon"
|
|
></span>
|
|
</template>
|
|
</div>
|
|
<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
|
|
:disabled="filters.page === 1"
|
|
class="btn btn--plain px-4 py-2 rounded-md hover:bg-blue-600 disabled:bg-gray-300"
|
|
@click="previousPage"
|
|
>
|
|
Previous
|
|
</button>
|
|
<span class="text-gray-700 font-semibold">Page {{ filters.page }} of {{ totalPages }}</span>
|
|
<button
|
|
:disabled="filters.page === totalPages"
|
|
class="btn btn--plain px-4 py-2 rounded-md hover:bg-blue-600 disabled:bg-gray-300"
|
|
@click="nextPage"
|
|
>
|
|
Next
|
|
</button>
|
|
</div>
|
|
<BaseContextMenu
|
|
:position="contextMenuPosition"
|
|
:visible="contextMenuVisible"
|
|
@close="contextMenuVisible = false"
|
|
>
|
|
<ul>
|
|
<li @click="selectFile(contextMenuFile)">
|
|
<span class="mdi mdi-file-check-outline"></span>
|
|
Select
|
|
</li>
|
|
<li @click="confirmDeleteItem(contextMenuFile)">
|
|
<span class="mdi mdi-delete-outline"></span>
|
|
Delete
|
|
</li>
|
|
</ul>
|
|
</BaseContextMenu>
|
|
</div>
|
|
|
|
<Dialog
|
|
v-model:visible="dialog"
|
|
:header="$t('New folder')"
|
|
:modal="true"
|
|
:style="{ width: '450px' }"
|
|
class="p-fluid"
|
|
>
|
|
<div class="p-field">
|
|
<label for="title">{{ $t("Name") }}</label>
|
|
<InputText
|
|
id="title"
|
|
v-model.trim="item.title"
|
|
:class="{ 'p-invalid': submitted && !item.title }"
|
|
autocomplete="off"
|
|
autofocus
|
|
required
|
|
/>
|
|
<small
|
|
v-if="submitted && !item.title"
|
|
class="p-error"
|
|
>{{ $t("Title is required") }}</small
|
|
>
|
|
</div>
|
|
<template #footer>
|
|
<Button
|
|
class="p-button-text"
|
|
icon="pi pi-times"
|
|
label="Cancel"
|
|
@click="hideDialog"
|
|
/>
|
|
<Button
|
|
class="p-button-text"
|
|
icon="pi pi-check"
|
|
label="Save"
|
|
@click="saveItem"
|
|
/>
|
|
</template>
|
|
</Dialog>
|
|
|
|
<Dialog
|
|
v-model:visible="deleteDialog"
|
|
:modal="true"
|
|
:style="{ width: '450px' }"
|
|
header="Confirm"
|
|
>
|
|
<div class="confirmation-content">
|
|
<i
|
|
class="pi pi-exclamation-triangle p-mr-3"
|
|
style="font-size: 2rem"
|
|
></i>
|
|
<span
|
|
>Are you sure you want to delete <b>{{ itemToDelete?.title }}</b
|
|
>?</span
|
|
>
|
|
</div>
|
|
<template #footer>
|
|
<Button
|
|
class="p-button-text"
|
|
icon="pi pi-times"
|
|
label="No"
|
|
@click="deleteDialog = false"
|
|
/>
|
|
<Button
|
|
class="p-button-text"
|
|
icon="pi pi-check"
|
|
label="Yes"
|
|
@click="deleteItemButton"
|
|
/>
|
|
</template>
|
|
</Dialog>
|
|
|
|
<Dialog
|
|
v-model:visible="deleteMultipleDialog"
|
|
:modal="true"
|
|
:style="{ width: '450px' }"
|
|
header="Confirm"
|
|
>
|
|
<div class="confirmation-content">
|
|
<i
|
|
class="pi pi-exclamation-triangle p-mr-3"
|
|
style="font-size: 2rem"
|
|
></i>
|
|
<span>{{ $t("Are you sure you want to delete the selected items?") }}</span>
|
|
</div>
|
|
<template #footer>
|
|
<Button
|
|
class="p-button-text"
|
|
icon="pi pi-times"
|
|
label="No"
|
|
@click="deleteMultipleDialog = false"
|
|
/>
|
|
<Button
|
|
class="p-button-text"
|
|
icon="pi pi-check"
|
|
label="Yes"
|
|
@click="deleteMultipleItems"
|
|
/>
|
|
</template>
|
|
</Dialog>
|
|
|
|
<Dialog
|
|
v-model:visible="detailsDialogVisible"
|
|
:header="selectedItem.title || 'Item Details'"
|
|
:modal="true"
|
|
:style="{ width: '50%' }"
|
|
>
|
|
<div v-if="Object.keys(selectedItem).length > 0">
|
|
<p><strong>Title:</strong> {{ selectedItem.title }}</p>
|
|
<p><strong>Modified:</strong> {{ relativeDatetime(selectedItem.resourceNode.updatedAt) }}</p>
|
|
<p><strong>Size:</strong> {{ prettyBytes(selectedItem.resourceNode.firstResourceFile.size) }}</p>
|
|
<p>
|
|
<strong>URL:</strong>
|
|
<a
|
|
:href="selectedItem.contentUrl"
|
|
target="_blank"
|
|
>Open File</a
|
|
>
|
|
</p>
|
|
</div>
|
|
<template #footer>
|
|
<Button
|
|
class="p-button-text"
|
|
label="Close"
|
|
@click="closeDetailsDialog"
|
|
/>
|
|
</template>
|
|
</Dialog>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { useFileManager } from "../../composables/useFileManager"
|
|
import { useI18n } from "vue-i18n"
|
|
import { useFormatDate } from "../../composables/formatDate"
|
|
import BaseContextMenu from "../basecomponents/BaseContextMenu.vue"
|
|
import prettyBytes from "pretty-bytes"
|
|
|
|
const { t } = useI18n()
|
|
const { relativeDatetime } = useFormatDate()
|
|
|
|
const {
|
|
files,
|
|
totalFiles,
|
|
isLoading,
|
|
selectedFiles,
|
|
dialog,
|
|
deleteDialog,
|
|
deleteMultipleDialog,
|
|
detailsDialogVisible,
|
|
selectedItem,
|
|
itemToDelete,
|
|
item,
|
|
submitted,
|
|
filters,
|
|
viewMode,
|
|
contextMenuVisible,
|
|
contextMenuPosition,
|
|
contextMenuFile,
|
|
previousFolders,
|
|
currentFolderTitle,
|
|
handleClickFile,
|
|
goBack,
|
|
returnToEditor,
|
|
toggleViewMode,
|
|
viewModeIcon,
|
|
isImage,
|
|
getFileUrl,
|
|
getIcon,
|
|
showContextMenu,
|
|
openNewDialog,
|
|
hideDialog,
|
|
saveItem,
|
|
confirmDeleteItem,
|
|
confirmDeleteMultiple,
|
|
deleteMultipleItems,
|
|
deleteItemButton,
|
|
onFilesPage,
|
|
sortingFilesChanged,
|
|
closeDetailsDialog,
|
|
uploadDocumentHandler,
|
|
onMountedCallback,
|
|
isAuthenticated,
|
|
selectFile,
|
|
nextPage,
|
|
previousPage,
|
|
totalPages,
|
|
} = useFileManager("documents", "/api/documents", "CourseDocumentsUploadFile", true)
|
|
|
|
onMountedCallback()
|
|
</script>
|
|
|