Add "my files" using resources #3792
parent
b090544ba6
commit
fcabbcc1ef
@ -0,0 +1,75 @@ |
||||
<template> |
||||
<q-form> |
||||
<q-input |
||||
id="item_title" |
||||
v-model="item.title" |
||||
:placeholder="$t('Title')" |
||||
:error="v$.item.title.$error" |
||||
@input="v$.item.title.$touch()" |
||||
@blur="v$.item.title.$touch()" |
||||
:error-message="titleErrors" |
||||
/> |
||||
<slot></slot> |
||||
</q-form> |
||||
</template> |
||||
|
||||
<script> |
||||
import has from 'lodash/has'; |
||||
import useVuelidate from '@vuelidate/core'; |
||||
import { required } from '@vuelidate/validators'; |
||||
|
||||
export default { |
||||
name: 'PersonalFileForm', |
||||
setup () { |
||||
return { v$: useVuelidate() } |
||||
}, |
||||
props: { |
||||
values: { |
||||
type: Object, |
||||
required: true |
||||
}, |
||||
errors: { |
||||
type: Object, |
||||
default: () => {} |
||||
}, |
||||
initialValues: { |
||||
type: Object, |
||||
default: () => {} |
||||
}, |
||||
}, |
||||
data() { |
||||
return { |
||||
title: null, |
||||
parentResourceNodeId: null, |
||||
}; |
||||
}, |
||||
computed: { |
||||
item() { |
||||
return this.initialValues || this.values; |
||||
}, |
||||
titleErrors() { |
||||
const errors = []; |
||||
if (!this.v$.item.title.$dirty) return errors; |
||||
has(this.violations, 'title') && errors.push(this.violations.title); |
||||
|
||||
if (this.v$.item.title.required) { |
||||
return this.$t('Field is required') |
||||
} |
||||
|
||||
return errors; |
||||
}, |
||||
violations() { |
||||
return this.errors || {}; |
||||
} |
||||
}, |
||||
validations: { |
||||
item: { |
||||
title: { |
||||
required, |
||||
}, |
||||
parentResourceNodeId: { |
||||
}, |
||||
} |
||||
} |
||||
}; |
||||
</script> |
@ -0,0 +1,126 @@ |
||||
<template> |
||||
<q-form> |
||||
<div class="input-group mb-3"> |
||||
<div class="custom-file"> |
||||
<input |
||||
id="file_upload" |
||||
type="file" |
||||
class="custom-file-input" |
||||
ref="fileList" |
||||
multiple |
||||
placeholder="File upload" |
||||
@change="selectFile" |
||||
/> |
||||
<label |
||||
class="custom-file-label" |
||||
for="file_upload" |
||||
aria-describedby="File upload"> |
||||
Choose file |
||||
</label> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="field"> |
||||
<div |
||||
v-for="(file, index) in files" |
||||
:key="index" |
||||
:class="{ error : file.invalidMessage}" |
||||
> |
||||
<div> |
||||
{{ file.name }} |
||||
<span v-if="file.invalidMessage"> |
||||
- {{ file.invalidMessage }} |
||||
</span> |
||||
<span> |
||||
<a @click.prevent="files.splice(index, 1)" |
||||
class="delete" |
||||
> |
||||
<FontAwesomeIcon icon="trash" /> |
||||
</a> |
||||
</span> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</q-form> |
||||
</template> |
||||
|
||||
<script> |
||||
import has from 'lodash/has'; |
||||
import map from 'lodash/map'; |
||||
import useVuelidate from '@vuelidate/core'; |
||||
|
||||
export default { |
||||
name: 'PersonalFileFormUpload', |
||||
setup () { |
||||
return { v$: useVuelidate() } |
||||
}, |
||||
props: { |
||||
values: { |
||||
type: Array, |
||||
required: true |
||||
}, |
||||
parentResourceNodeId: { |
||||
type: Number |
||||
}, |
||||
resourceLinkList: { |
||||
type: String, |
||||
}, |
||||
errors: { |
||||
type: Object, |
||||
default: () => {} |
||||
}, |
||||
processFiles: { |
||||
type: Function, |
||||
required: false |
||||
}, |
||||
}, |
||||
data() { |
||||
return { |
||||
fileList:[], |
||||
files: [], |
||||
}; |
||||
}, |
||||
computed: { |
||||
titleErrors() { |
||||
const errors = []; |
||||
if (!this.$v.item.title.$dirty) return errors; |
||||
has(this.violations, 'title') && errors.push(this.violations.title); |
||||
!this.$v.item.title.required && errors.push(this.$t('Field is required')); |
||||
|
||||
return errors; |
||||
}, |
||||
violations() { |
||||
return this.errors || {}; |
||||
} |
||||
}, |
||||
methods: { |
||||
selectFile() { |
||||
const files = this.$refs.fileList.files; |
||||
|
||||
this.files = [ |
||||
...this.files, |
||||
...map(files, file => ({ |
||||
name: file.name, |
||||
size: file.size, |
||||
type: file.type, |
||||
filetype: 'file', |
||||
parentResourceNodeId: this.parentResourceNodeId, |
||||
resourceLinkList: this.resourceLinkList, |
||||
uploadFile: file, |
||||
invalidMessage: this.validate(file), |
||||
})) |
||||
] |
||||
}, |
||||
validate(file) { |
||||
if (file) { |
||||
return ''; |
||||
} |
||||
|
||||
return 'error'; |
||||
} |
||||
}, |
||||
validations: { |
||||
files: {} |
||||
} |
||||
}; |
||||
</script> |
@ -0,0 +1,9 @@ |
||||
<template> |
||||
<router-view></router-view> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
name: 'PersonalFileLayout' |
||||
} |
||||
</script> |
@ -0,0 +1,68 @@ |
||||
<template> |
||||
<div v-if="item && item['resourceLinkListFromEntity']"> |
||||
<ul> |
||||
<li |
||||
v-for="link in item['resourceLinkListFromEntity']" |
||||
> |
||||
<div v-if="link['course']"> |
||||
{{ $t('Course') }}: {{ link.course.resourceNode.title }} |
||||
</div> |
||||
|
||||
<div v-if="link['session']"> |
||||
{{ $t('Session') }}: {{ link.session.name }} |
||||
</div> |
||||
|
||||
<div v-if="link['group']"> |
||||
{{ $t('Group') }}: {{ link.session.resourceNode.title }} |
||||
</div> |
||||
|
||||
<q-separator /> |
||||
|
||||
<q-select |
||||
filled |
||||
v-model="link.visibility" |
||||
:options="visibilityList" |
||||
label="Status" |
||||
emit-value |
||||
map-options |
||||
/> |
||||
</li> |
||||
</ul> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import has from 'lodash/has'; |
||||
import useVuelidate from '@vuelidate/core'; |
||||
import { required } from '@vuelidate/validators'; |
||||
|
||||
export default { |
||||
name: 'ResourceLinkForm', |
||||
setup () { |
||||
const visibilityList = [ |
||||
{value: 2, label: 'Published'}, |
||||
{value: 0, label: 'Draft'}, |
||||
]; |
||||
return {v$: useVuelidate(), visibilityList}; |
||||
}, |
||||
props: { |
||||
values: { |
||||
type: Object, |
||||
required: true |
||||
}, |
||||
errors: { |
||||
type: Object, |
||||
default: () => {} |
||||
}, |
||||
initialValues: { |
||||
type: Object, |
||||
default: () => {} |
||||
}, |
||||
}, |
||||
computed: { |
||||
item() { |
||||
return this.initialValues || this.values; |
||||
}, |
||||
}, |
||||
}; |
||||
</script> |
@ -0,0 +1,42 @@ |
||||
export default { |
||||
path: '/resources/personal_files', |
||||
meta: { requiresAuth: true }, |
||||
name: 'personal_files', |
||||
component: () => import('../views/personalfile/Home.vue'), |
||||
children: [ |
||||
{ |
||||
name: 'personal_files', |
||||
path: ':node/', |
||||
component: () => import('../components/personalfile/Layout.vue'), |
||||
redirect: { name: 'PersonalFileList' }, |
||||
children: [ |
||||
{ |
||||
name: 'PersonalFileList', |
||||
path: '', |
||||
component: () => import('../views/personalfile/List.vue') |
||||
}, |
||||
{ |
||||
name: 'PersonalFileCreate', |
||||
path: 'new', |
||||
component: () => import('../views/personalfile/Create.vue') |
||||
}, |
||||
{ |
||||
name: 'PersonalFileUploadFile', |
||||
path: 'upload', |
||||
component: () => import('../views/personalfile/Upload.vue') |
||||
}, |
||||
{ |
||||
name: 'PersonalFileUpdate', |
||||
//path: ':id/edit',
|
||||
path: 'edit_file', |
||||
component: () => import('../views/personalfile/Update.vue') |
||||
}, |
||||
{ |
||||
name: 'PersonalFileShow', |
||||
path: 'show', |
||||
component: () => import('../views/personalfile/Show.vue') |
||||
} |
||||
] |
||||
}, |
||||
] |
||||
}; |
@ -0,0 +1,3 @@ |
||||
import makeService from './api'; |
||||
|
||||
export default makeService('personal_files'); |
@ -0,0 +1,63 @@ |
||||
<template> |
||||
<div> |
||||
<Toolbar |
||||
:handle-submit="onSendForm" |
||||
:handle-reset="resetForm" |
||||
/> |
||||
|
||||
<DocumentsForm |
||||
ref="createForm" |
||||
:values="item" |
||||
:errors="violations" |
||||
/> |
||||
<Loading :visible="isLoading" /> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import { mapActions } from 'vuex'; |
||||
import { createHelpers } from 'vuex-map-fields'; |
||||
import DocumentsForm from '../../components/personalfile/Form.vue'; |
||||
import Loading from '../../components/Loading.vue'; |
||||
import Toolbar from '../../components/Toolbar.vue'; |
||||
import CreateMixin from '../../mixins/CreateMixin'; |
||||
|
||||
const servicePrefix = 'PersonalFile'; |
||||
|
||||
const { mapFields } = createHelpers({ |
||||
getterType: 'personal_file/getField', |
||||
mutationType: 'personal_file/updateField' |
||||
}); |
||||
|
||||
export default { |
||||
name: 'PersonalFileCreate', |
||||
servicePrefix, |
||||
components: { |
||||
Loading, |
||||
Toolbar, |
||||
DocumentsForm |
||||
}, |
||||
mixins: [CreateMixin], |
||||
data() { |
||||
return { |
||||
item: {}, |
||||
type: 'folder' |
||||
}; |
||||
}, |
||||
computed: { |
||||
...mapFields(['error', 'isLoading', 'created', 'violations']) |
||||
}, |
||||
created() { |
||||
this.item.parentResourceNodeId = this.$route.params.node; |
||||
this.item.resourceLinkList = JSON.stringify([{ |
||||
gid: this.$route.query.gid, |
||||
sid: this.$route.query.sid, |
||||
c_id: this.$route.query.cid, |
||||
visibility: 2, // visible by default |
||||
}]); |
||||
}, |
||||
methods: { |
||||
...mapActions('personalfile', ['create', 'reset']) |
||||
} |
||||
}; |
||||
</script> |
@ -0,0 +1,43 @@ |
||||
<template> |
||||
<router-view></router-view> |
||||
</template> |
||||
|
||||
<script> |
||||
import { mapActions, mapGetters } from 'vuex'; |
||||
import Loading from '../../components/Loading.vue'; |
||||
import ShowMixin from '../../mixins/ShowMixin'; |
||||
import Toolbar from '../../components/Toolbar.vue'; |
||||
import isEmpty from "lodash/isEmpty"; |
||||
const servicePrefix = 'PersonalFile'; |
||||
|
||||
export default { |
||||
name: 'PersonalFileHome', |
||||
servicePrefix, |
||||
components: { |
||||
Loading, |
||||
Toolbar |
||||
}, |
||||
created() { |
||||
console.log('CREATED HOME'); |
||||
let resourceNodeId = this.currentUser.resourceNode['id']; |
||||
|
||||
console.log(resourceNodeId); |
||||
this.$router |
||||
.push({ name: `${this.$options.servicePrefix}List`, params: { node: resourceNodeId },}) |
||||
.catch(() => {}); |
||||
|
||||
}, |
||||
computed: { |
||||
// From crud.js list function |
||||
...mapGetters('resourcenode', { |
||||
resourceNode: 'getResourceNode' |
||||
}), |
||||
...mapGetters({ |
||||
'isAuthenticated': 'security/isAuthenticated', |
||||
'isAdmin': 'security/isAdmin', |
||||
'currentUser': 'security/getUser', |
||||
}), |
||||
} |
||||
|
||||
}; |
||||
</script> |
@ -0,0 +1,363 @@ |
||||
<template> |
||||
<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 label="New folder" icon="fa fa-folder-plus" class="btn btn-primary" @click="openNew" /> |
||||
<Button label="Upload" icon="fa fa-file-upload" class="btn btn-primary" @click="uploadDocumentHandler()" /> |
||||
<Button label="Delete" icon="pi pi-trash" class="btn btn-danger " @click="confirmDeleteMultiple" :disabled="!selectedItems || !selectedItems.length" /> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<DataTable |
||||
class="p-datatable-sm" |
||||
:value="items" |
||||
v-model:selection="selectedItems" |
||||
dataKey="iid" |
||||
v-model:filters="filters" |
||||
filterDisplay="menu" |
||||
:lazy="true" |
||||
:paginator="true" |
||||
:rows="10" |
||||
:totalRecords="totalItems" |
||||
:loading="isLoading" |
||||
@page="onPage($event)" |
||||
@sort="sortingChanged($event)" |
||||
paginatorTemplate="CurrentPageReport FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink RowsPerPageDropdown" |
||||
:rowsPerPageOptions="[5, 10, 20, 50]" |
||||
responsiveLayout="scroll" |
||||
currentPageReportTemplate="Showing {first} to {last} of {totalRecords}" |
||||
:globalFilterFields="['resourceNode.title', 'resourceNode.updatedAt']"> |
||||
|
||||
<span v-if="isCurrentTeacher"> |
||||
<Column selectionMode="multiple" style="width: 3rem" :exportable="false"></Column> |
||||
</span> |
||||
|
||||
<Column field="resourceNode.title" :header="$t('Title')" :sortable="true"> |
||||
<template #body="slotProps"> |
||||
<div v-if="slotProps.data && slotProps.data.resourceNode && slotProps.data.resourceNode.resourceFile"> |
||||
<ResourceFileLink :resource="slotProps.data" /> |
||||
</div> |
||||
<div v-else> |
||||
<a |
||||
v-if="slotProps.data" |
||||
@click="handleClick(slotProps.data)" |
||||
class="cursor-pointer " > |
||||
<FontAwesomeIcon |
||||
icon="folder" |
||||
size="lg" |
||||
/> |
||||
{{ slotProps.data.resourceNode.title }} |
||||
</a> |
||||
</div> |
||||
</template> |
||||
</Column> |
||||
|
||||
<Column field="resourceNode.resourceFile.size" :header="$t('Size')" :sortable="true"> |
||||
<template #body="slotProps"> |
||||
{{ |
||||
slotProps.data.resourceNode.resourceFile ? $filters.prettyBytes(slotProps.data.resourceNode.resourceFile.size) : '' |
||||
}} |
||||
</template> |
||||
</Column> |
||||
|
||||
<Column field="resourceNode.updatedAt" :header="$t('Modified')" :sortable="true"> |
||||
<template #body="slotProps"> |
||||
{{$luxonDateTime.fromISO(slotProps.data.resourceNode.updatedAt).toRelative() }} |
||||
</template> |
||||
</Column> |
||||
|
||||
<Column :exportable="false"> |
||||
<template #body="slotProps"> |
||||
<div class="flex flex-row gap-2"> |
||||
<Button icon="fa fa-info-circle" class="btn btn-primary " @click="showHandler(slotProps.data)" /> |
||||
<Button v-if="isAuthenticated" icon="pi pi-pencil" class="btn btn-primary p-mr-2" @click="editHandler(slotProps.data)" /> |
||||
<Button v-if="isAuthenticated" icon="pi pi-trash" class="btn btn-danger" @click="confirmDeleteItem(slotProps.data)" /> |
||||
</div> |
||||
</template> |
||||
</Column> |
||||
|
||||
<!-- <template #paginatorLeft>--> |
||||
<!-- <Button type="button" icon="pi pi-refresh" class="p-button-text" />--> |
||||
<!-- </template>--> |
||||
<!-- <template #paginatorRight>--> |
||||
<!-- <Button type="button" icon="pi pi-cloud" class="p-button-text" />--> |
||||
<!-- </template>--> |
||||
</DataTable> |
||||
|
||||
<Dialog v-model:visible="itemDialog" :style="{width: '450px'}" :header="$t('New folder')" :modal="true" class="p-fluid"> |
||||
<div class="p-field"> |
||||
<label for="name">{{ $t('Name') }}</label> |
||||
<InputText |
||||
autocomplete="off" |
||||
id="title" |
||||
v-model.trim="item.title" |
||||
required="true" |
||||
autofocus |
||||
:class="{'p-invalid': submitted && !item.title}" |
||||
/> |
||||
<small class="p-error" v-if="submitted && !item.title">$t('Title is required')</small> |
||||
</div> |
||||
|
||||
<template #footer> |
||||
<Button label="Cancel" icon="pi pi-times" class="p-button-text" @click="hideDialog"/> |
||||
<Button label="Save" icon="pi pi-check" class="p-button-text" @click="saveItem" /> |
||||
</template> |
||||
</Dialog> |
||||
|
||||
<Dialog v-model:visible="deleteItemDialog" :style="{width: '450px'}" header="Confirm" :modal="true"> |
||||
<div class="confirmation-content"> |
||||
<i class="pi pi-exclamation-triangle p-mr-3" style="font-size: 2rem" /> |
||||
<span v-if="item">Are you sure you want to delete <b>{{item.title}}</b>?</span> |
||||
</div> |
||||
<template #footer> |
||||
<Button label="No" icon="pi pi-times" class="p-button-text" @click="deleteItemDialog = false"/> |
||||
<Button label="Yes" icon="pi pi-check" class="p-button-text" @click="deleteItemButton" /> |
||||
</template> |
||||
</Dialog> |
||||
|
||||
<Dialog v-model:visible="deleteMultipleDialog" :style="{width: '450px'}" header="Confirm" :modal="true"> |
||||
<div class="confirmation-content"> |
||||
<i class="pi pi-exclamation-triangle p-mr-3" style="font-size: 2rem" /> |
||||
<span v-if="item">Are you sure you want to delete the selected items?</span> |
||||
</div> |
||||
<template #footer> |
||||
<Button label="No" icon="pi pi-times" class="p-button-text" @click="deleteMultipleDialog = false"/> |
||||
<Button label="Yes" icon="pi pi-check" class="p-button-text" @click="deleteMultipleItems" /> |
||||
</template> |
||||
</Dialog> |
||||
|
||||
</template> |
||||
|
||||
<script> |
||||
import { mapActions, mapGetters } from 'vuex'; |
||||
import { mapFields } from 'vuex-map-fields'; |
||||
import ListMixin from '../../mixins/ListMixin'; |
||||
import ActionCell from '../../components/ActionCell.vue'; |
||||
//import Toolbar from '../../components/Toolbar.vue'; |
||||
import ResourceFileIcon from '../../components/documents/ResourceFileIcon.vue'; |
||||
import ResourceFileLink from '../../components/documents/ResourceFileLink.vue'; |
||||
|
||||
import { useRoute } from 'vue-router' |
||||
import DataFilter from '../../components/DataFilter'; |
||||
//import DocumentsFilterForm from '../../components/personalfile/Filter'; |
||||
import { ref, reactive, onMounted, computed } from 'vue'; |
||||
import { useStore } from 'vuex'; |
||||
import isEmpty from 'lodash/isEmpty'; |
||||
import moment from "moment"; |
||||
|
||||
export default { |
||||
name: 'PersonalFileList', |
||||
servicePrefix: 'PersonalFile', |
||||
components: { |
||||
//8Toolbar, |
||||
ActionCell, |
||||
ResourceFileIcon, |
||||
ResourceFileLink, |
||||
//DocumentsFilterForm, |
||||
DataFilter |
||||
}, |
||||
mixins: [ListMixin], |
||||
data() { |
||||
return { |
||||
sortBy: 'title', |
||||
sortDesc: false, |
||||
columnsQua: [ |
||||
{align: 'left', name: 'resourceNode.title', label: this.$i18n.t('Title'), field: 'resourceNode.title', sortable: true}, |
||||
{align: 'left', name: 'resourceNode.updatedAt', label: this.$i18n.t('Modified'), field: 'resourceNode.updatedAt', sortable: true}, |
||||
{name: 'resourceNode.resourceFile.size', label: this.$i18n.t('Size'), field: 'resourceNode.resourceFile.size', sortable: true}, |
||||
{name: 'action', label: this.$i18n.t('Actions'), field: 'action', sortable: false} |
||||
], |
||||
columns: [ |
||||
{ label: this.$i18n.t('Title'), field: 'title', name: 'title', sortable: true}, |
||||
{ label: this.$i18n.t('Modified'), field: 'resourceNode.updatedAt', name: 'updatedAt', sortable: true}, |
||||
{ label: this.$i18n.t('Size'), field: 'resourceNode.resourceFile.size', name: 'size', sortable: true}, |
||||
{ label: this.$i18n.t('Actions'), name: 'action', sortable: false} |
||||
], |
||||
pageOptions: [10, 20, 50, this.$i18n.t('All')], |
||||
selected: [], |
||||
isBusy: false, |
||||
options: [], |
||||
selectedItems: [], |
||||
// prime vue |
||||
itemDialog: false, |
||||
deleteItemDialog: false, |
||||
deleteMultipleDialog: false, |
||||
item: {}, |
||||
filters: {}, |
||||
submitted: false, |
||||
}; |
||||
}, |
||||
created() { |
||||
console.log('CREATED'); |
||||
let resourceNodeId = this.currentUser.resourceNode['id']; |
||||
if (isEmpty(this.$route.params.node)) { |
||||
this.$route.params.node = resourceNodeId; |
||||
} |
||||
//this.item.parentResourceNodeId = this.$route.params.node; |
||||
this.filters['resourceNode.parent'] = resourceNodeId; |
||||
}, |
||||
mounted() { |
||||
const route = useRoute() |
||||
/*let nodeId = route.params['node']; |
||||
if (!isEmpty(nodeId)) { |
||||
this.findResourceNode('/api/resource_nodes/' + nodeId); |
||||
}*/ |
||||
console.log(this.options); |
||||
this.onUpdateOptions(this.options); |
||||
}, |
||||
computed: { |
||||
// From crud.js list function |
||||
...mapGetters('resourcenode', { |
||||
resourceNode: 'getResourceNode' |
||||
}), |
||||
...mapGetters({ |
||||
'isAuthenticated': 'security/isAuthenticated', |
||||
'isAdmin': 'security/isAdmin', |
||||
'currentUser': 'security/getUser', |
||||
}), |
||||
|
||||
...mapGetters('personalfile', { |
||||
items: 'list', |
||||
}), |
||||
|
||||
//...getters |
||||
|
||||
// From ListMixin |
||||
...mapFields('personalfile', { |
||||
deletedItem: 'deleted', |
||||
error: 'error', |
||||
isLoading: 'isLoading', |
||||
resetList: 'resetList', |
||||
totalItems: 'totalItems', |
||||
view: 'view' |
||||
}), |
||||
}, |
||||
methods: { |
||||
// prime |
||||
onPage(event) { |
||||
console.log(event); |
||||
console.log(event.page); |
||||
console.log(event.sortField); |
||||
console.log(event.sortOrder); |
||||
|
||||
this.options.itemsPerPage = event.rows; |
||||
this.options.page = event.page + 1; |
||||
this.options.sortBy = event.sortField; |
||||
this.options.sortDesc = event.sortOrder === -1; |
||||
|
||||
this.onUpdateOptions(this.options); |
||||
}, |
||||
sortingChanged(event) { |
||||
console.log('sortingChanged'); |
||||
console.log(event); |
||||
this.options.sortBy = event.sortField; |
||||
this.options.sortDesc = event.sortOrder === -1; |
||||
|
||||
this.onUpdateOptions(this.options); |
||||
// ctx.sortBy ==> Field key for sorting by (or null for no sorting) |
||||
// ctx.sortDesc ==> true if sorting descending, false otherwise |
||||
}, |
||||
|
||||
openNew() { |
||||
this.item = {}; |
||||
this.submitted = false; |
||||
this.itemDialog = true; |
||||
}, |
||||
hideDialog() { |
||||
this.itemDialog = false; |
||||
this.submitted = false; |
||||
}, |
||||
saveItem() { |
||||
this.submitted = true; |
||||
|
||||
if (this.item.title.trim()) { |
||||
if (this.item.id) { |
||||
} else { |
||||
let resourceNodeId = this.currentUser.resourceNode['id']; |
||||
if (!isEmpty(this.$route.params.node)) { |
||||
resourceNodeId = this.$route.params.node; |
||||
} |
||||
|
||||
this.item.parentResourceNodeId = resourceNodeId; |
||||
this.item.resourceLinkList = JSON.stringify([{ |
||||
gid: 0, |
||||
sid: 0, |
||||
c_id: 0, |
||||
visibility: 2, // visible by default |
||||
}]); |
||||
|
||||
this.create(this.item); |
||||
this.showMessage('Saved'); |
||||
} |
||||
|
||||
this.itemDialog = false; |
||||
this.item = {}; |
||||
} |
||||
}, |
||||
editItem(item) { |
||||
this.item = {...item}; |
||||
this.itemDialog = true; |
||||
}, |
||||
confirmDeleteItem(item) { |
||||
this.item = item; |
||||
this.deleteItemDialog = true; |
||||
}, |
||||
confirmDeleteMultiple() { |
||||
this.deleteMultipleDialog = true; |
||||
}, |
||||
deleteMultipleItems() { |
||||
console.log('deleteMultipleItems'); |
||||
console.log(this.selectedItems); |
||||
this.deleteMultipleAction(this.selectedItems); |
||||
this.onRequest({ |
||||
pagination: this.pagination, |
||||
}); |
||||
this.deleteMultipleDialog = false; |
||||
this.selectedItems = null; |
||||
//this.$toast.add({severity:'success', summary: 'Successful', detail: 'Products Deleted', life: 3000});*/ |
||||
}, |
||||
deleteItemButton() { |
||||
console.log('deleteItem'); |
||||
this.deleteItem(this.item); |
||||
//this.items = this.items.filter(val => val.iid !== this.item.iid); |
||||
this.deleteItemDialog = false; |
||||
this.item = {}; |
||||
this.onUpdateOptions(this.options); |
||||
}, |
||||
onRowSelected(items) { |
||||
this.selected = items |
||||
}, |
||||
selectAllRows() { |
||||
this.$refs.selectableTable.selectAllRows() |
||||
}, |
||||
clearSelected() { |
||||
this.$refs.selectableTable.clearSelected() |
||||
}, |
||||
async deleteSelected() { |
||||
console.log('deleteSelected'); |
||||
/*for (let i = 0; i < this.selected.length; i++) { |
||||
let item = this.selected[i]; |
||||
//this.deleteHandler(item); |
||||
this.deleteItem(item); |
||||
}*/ |
||||
|
||||
this.deleteMultipleAction(this.selected); |
||||
this.onRequest({ |
||||
pagination: this.pagination, |
||||
}); |
||||
console.log('end -- deleteSelected'); |
||||
}, |
||||
//...actions, |
||||
// From ListMixin |
||||
...mapActions('personalfile', { |
||||
getPage: 'fetchAll', |
||||
create: 'create', |
||||
deleteItem: 'del', |
||||
deleteMultipleAction: 'delMultiple' |
||||
}), |
||||
...mapActions('resourcenode', { |
||||
findResourceNode: 'findResourceNode', |
||||
}), |
||||
} |
||||
}; |
||||
</script> |
@ -0,0 +1,193 @@ |
||||
<template> |
||||
<div> |
||||
<Toolbar |
||||
v-if="item && isCurrentTeacher" |
||||
:handle-edit="editHandler" |
||||
:handle-delete="del" |
||||
> |
||||
<template slot="left"> |
||||
<!-- <v-toolbar-title v-if="item">--> |
||||
<!-- {{--> |
||||
<!-- `${$options.servicePrefix} ${item['@id']}`--> |
||||
<!-- }}--> |
||||
<!-- </v-toolbar-title>--> |
||||
</template> |
||||
</Toolbar> |
||||
|
||||
<p class="text-lg" v-if="item"> |
||||
{{ item['title'] }} |
||||
</p> |
||||
|
||||
<div v-if="item" class="flex flex-row"> |
||||
<div class="w-1/2"> |
||||
<div class ="flex justify-center" v-if="item['resourceNode']['resourceFile']"> |
||||
<div class="w-64"> |
||||
<q-img |
||||
spinner-color="primary" |
||||
v-if="item['resourceNode']['resourceFile']['image']" |
||||
:src="item['contentUrl'] + '&w=300'" |
||||
/> |
||||
<span v-else-if="item['resourceNode']['resourceFile']['video']"> |
||||
<video controls> |
||||
<source :src="item['contentUrl']" /> |
||||
</video> |
||||
</span> |
||||
<span v-else> |
||||
<q-btn |
||||
class="btn btn-primary" |
||||
:to="item['downloadUrl']" |
||||
> |
||||
<FontAwesomeIcon icon="file-download" /> |
||||
{{ $t('Download file') }} |
||||
</q-btn> |
||||
</span> |
||||
</div> |
||||
</div> |
||||
<div class ="flex justify-center" v-else> |
||||
<FontAwesomeIcon |
||||
icon="folder" |
||||
size="7x" |
||||
/> |
||||
</div> |
||||
</div> |
||||
|
||||
<span class="w-1/2"> |
||||
<q-markup-table> |
||||
<tbody> |
||||
<tr> |
||||
<td><strong>{{ $t('Author') }}</strong></td> |
||||
<td> |
||||
{{ item['resourceNode'].creator.username }} |
||||
</td> |
||||
<td></td> |
||||
<td /> |
||||
</tr> |
||||
<tr> |
||||
<td><strong>{{ $t('Comment') }}</strong></td> |
||||
<td> |
||||
{{ item['comment'] }} |
||||
</td> |
||||
</tr> |
||||
<tr> |
||||
<td><strong>{{ $t('Created at') }}</strong></td> |
||||
<td> |
||||
{{ item['resourceNode'] ? $luxonDateTime.fromISO(item['resourceNode'].createdAt).toRelative() : ''}} |
||||
</td> |
||||
<td /> |
||||
</tr> |
||||
<tr> |
||||
<td><strong>{{ $t('Updated at') }}</strong></td> |
||||
<td> |
||||
{{ item['resourceNode'] ? $luxonDateTime.fromISO(item['resourceNode'].updatedAt).toRelative() : ''}} |
||||
</td> |
||||
<td /> |
||||
</tr> |
||||
<tr v-if="item['resourceNode']['resourceFile']"> |
||||
<td><strong>{{ $t('File') }}</strong></td> |
||||
<td> |
||||
<div> |
||||
<a |
||||
class="btn btn-primary" |
||||
:href="item['downloadUrl']" |
||||
> |
||||
<FontAwesomeIcon icon="file-download" /> |
||||
{{ $t('Download file') }} |
||||
</a> |
||||
</div> |
||||
</td> |
||||
<td /> |
||||
</tr> |
||||
</tbody> |
||||
</q-markup-table> |
||||
|
||||
<hr /> |
||||
|
||||
<span v-if="item['resourceLinkListFromEntity']"> |
||||
<h2>{{ $t('Shared') }}</h2> |
||||
<span |
||||
v-for="link in item['resourceLinkListFromEntity']" |
||||
> |
||||
<q-markup-table> |
||||
<tbody> |
||||
<tr> |
||||
<td> |
||||
{{ $t('Status') }} |
||||
</td> |
||||
<td> |
||||
{{ link.visibilityName }} |
||||
</td> |
||||
</tr> |
||||
|
||||
<tr v-if="link['course']"> |
||||
<td> |
||||
{{ $t('Course') }} |
||||
</td> |
||||
<td> |
||||
{{ link.course.resourceNode.title }} |
||||
</td> |
||||
</tr> |
||||
|
||||
<tr v-if="link['session']"> |
||||
<td> |
||||
{{ $t('Session') }} |
||||
</td> |
||||
<td> |
||||
{{ link.session.name }} |
||||
</td> |
||||
</tr> |
||||
|
||||
<tr v-if="link['group']"> |
||||
<td> |
||||
{{ $t('Group') }} |
||||
</td> |
||||
<td> |
||||
{{ link.group.resourceNode.title }} |
||||
</td> |
||||
</tr> |
||||
</tbody> |
||||
</q-markup-table> |
||||
</span> |
||||
</span> |
||||
</span> |
||||
</div> |
||||
|
||||
<Loading :visible="isLoading" /> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import { mapActions, mapGetters } from 'vuex'; |
||||
import { mapFields } from 'vuex-map-fields'; |
||||
import Loading from '../../components/Loading.vue'; |
||||
import ShowMixin from '../../mixins/ShowMixin'; |
||||
import Toolbar from '../../components/Toolbar.vue'; |
||||
const servicePrefix = 'PersonalFile'; |
||||
|
||||
export default { |
||||
name: 'PersonalFileShow', |
||||
components: { |
||||
Loading, |
||||
Toolbar |
||||
}, |
||||
mixins: [ShowMixin], |
||||
computed: { |
||||
...mapFields('personalfile', { |
||||
isLoading: 'isLoading' |
||||
}), |
||||
...mapGetters('personalfile', ['find']), |
||||
...mapGetters({ |
||||
'isAuthenticated': 'security/isAuthenticated', |
||||
'isAdmin': 'security/isAdmin', |
||||
'isCurrentTeacher': 'security/isCurrentTeacher', |
||||
}), |
||||
}, |
||||
methods: { |
||||
...mapActions('personalfile', { |
||||
deleteItem: 'del', |
||||
reset: 'resetShow', |
||||
retrieve: 'loadWithQuery' |
||||
}), |
||||
}, |
||||
servicePrefix |
||||
}; |
||||
</script> |
@ -0,0 +1,68 @@ |
||||
<template> |
||||
<div v-if="!isLoading && item && isCurrentTeacher"> |
||||
<!-- :handle-delete="del"--> |
||||
<Toolbar |
||||
:handle-submit="onSendForm" |
||||
:handle-reset="resetForm" |
||||
/> |
||||
<DocumentsForm |
||||
ref="updateForm" |
||||
:values="item" |
||||
:errors="violations" |
||||
> |
||||
<ResourceLinkForm |
||||
v-if="item && !isLoading" |
||||
ref="resourceLinkForm" |
||||
:values="item" |
||||
/> |
||||
</DocumentsForm> |
||||
<Loading :visible="isLoading || deleteLoading" /> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import { mapActions, mapGetters } from 'vuex'; |
||||
import { mapFields } from 'vuex-map-fields'; |
||||
import DocumentsForm from '../../components/personalfile/Form.vue'; |
||||
import ResourceLinkForm from '../../components/personalfile/ResourceLinkForm.vue'; |
||||
import Loading from '../../components/Loading.vue'; |
||||
import Toolbar from '../../components/Toolbar.vue'; |
||||
import UpdateMixin from '../../mixins/UpdateMixin'; |
||||
|
||||
const servicePrefix = 'PersonalFile'; |
||||
|
||||
export default { |
||||
name: 'PersonalFileUpdate', |
||||
servicePrefix, |
||||
components: { |
||||
Loading, |
||||
Toolbar, |
||||
DocumentsForm, |
||||
ResourceLinkForm |
||||
}, |
||||
mixins: [UpdateMixin], |
||||
computed: { |
||||
...mapFields('personalfile', { |
||||
deleteLoading: 'isLoading', |
||||
isLoading: 'isLoading', |
||||
error: 'error', |
||||
updated: 'updated', |
||||
violations: 'violations' |
||||
}), |
||||
...mapGetters('personalfile', ['find']), |
||||
...mapGetters({ |
||||
'isCurrentTeacher': 'security/isCurrentTeacher', |
||||
}), |
||||
}, |
||||
methods: { |
||||
...mapActions('personalfile', { |
||||
createReset: 'resetCreate', |
||||
deleteItem: 'del', |
||||
delReset: 'resetDelete', |
||||
retrieve: 'load', |
||||
update: 'update', |
||||
updateReset: 'resetUpdate' |
||||
}) |
||||
} |
||||
}; |
||||
</script> |
@ -0,0 +1,138 @@ |
||||
<template> |
||||
<div> |
||||
<DocumentsForm |
||||
ref="createForm" |
||||
:values="files" |
||||
:parentResourceNodeId="parentResourceNodeId" |
||||
:resourceLinkList="resourceLinkList" |
||||
:errors="violations" |
||||
:process-files="processFiles" |
||||
/> |
||||
|
||||
<Toolbar |
||||
:handle-submit="onUploadForm" |
||||
/> |
||||
<Loading :visible="isLoading" /> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import {mapActions, mapGetters} from 'vuex'; |
||||
import { createHelpers } from 'vuex-map-fields'; |
||||
import DocumentsForm from '../../components/personalfile/FormUpload.vue'; |
||||
import Loading from '../../components/Loading.vue'; |
||||
import Toolbar from '../../components/Toolbar.vue'; |
||||
import UploadMixin from '../../mixins/UploadMixin'; |
||||
import { ref, onMounted } from 'vue' |
||||
import isEmpty from 'lodash/isEmpty'; |
||||
|
||||
const servicePrefix = 'PersonalFile'; |
||||
|
||||
const { mapFields } = createHelpers({ |
||||
getterType: 'personalfile/getField', |
||||
mutationType: 'personalfile/updateField' |
||||
}); |
||||
|
||||
export default { |
||||
name: 'PersonalFileUploadFile', |
||||
servicePrefix, |
||||
components: { |
||||
Loading, |
||||
Toolbar, |
||||
DocumentsForm |
||||
}, |
||||
setup() { |
||||
const createForm = ref(null); |
||||
|
||||
return { |
||||
createForm |
||||
} |
||||
}, |
||||
mixins: [UploadMixin], |
||||
data() { |
||||
return { |
||||
files : [], |
||||
parentResourceNodeId: 0, |
||||
resourceLinkList: '', |
||||
}; |
||||
}, |
||||
computed: { |
||||
...mapFields(['error', 'isLoading', 'created', 'violations']), |
||||
...mapGetters({ |
||||
'isAuthenticated': 'security/isAuthenticated', |
||||
'isAdmin': 'security/isAdmin', |
||||
'currentUser': 'security/getUser', |
||||
}), |
||||
}, |
||||
created() { |
||||
console.log('created'); |
||||
let nodeId = this.$route.params.node; |
||||
if (isEmpty(nodeId)) { |
||||
nodeId = this.currentUser.resourceNode['id'] |
||||
} |
||||
|
||||
console.log(nodeId) |
||||
this.parentResourceNodeId = Number(nodeId); |
||||
this.resourceLinkList = JSON.stringify([{ |
||||
gid: this.$route.query.gid, |
||||
sid: this.$route.query.sid, |
||||
c_id: this.$route.query.cid, |
||||
visibility: 2, |
||||
}]); |
||||
this.files = []; |
||||
}, |
||||
methods: { |
||||
async processFiles(files) { |
||||
/*this.files = [ |
||||
...this.files, |
||||
...map(files, file => ({ |
||||
title: file.name, |
||||
name: file.name, |
||||
size: file.size, |
||||
type: file.type, |
||||
filetype: 'file', |
||||
parentResourceNodeId: this.parentResourceNodeId, |
||||
resourceLinkList: this.resourceLinkList, |
||||
uploadFile: file, |
||||
invalidMessage: this.validate(file), |
||||
})) |
||||
];*/ |
||||
|
||||
return new Promise((resolve) => { |
||||
for (let i = 0; i < files.length; i++) { |
||||
files[i].title = files[i].name; |
||||
files[i].parentResourceNodeId = this.parentResourceNodeId; |
||||
files[i].resourceLinkList = this.resourceLinkList; |
||||
files[i].uploadFile = files[i]; |
||||
this.createFile(files[i]); |
||||
} |
||||
|
||||
resolve(files); |
||||
/*console.log(file); |
||||
file.title = file.name; |
||||
file.parentResourceNodeId = this.parentResourceNodeId; |
||||
file.resourceLinkList = this.resourceLinkList; |
||||
file.uploadFile = file; |
||||
this.create(file); |
||||
resolve(file);*/ |
||||
|
||||
|
||||
/*for (let i = 0; i < this.files.length; i++) { |
||||
this.create(this.files[i]); |
||||
} |
||||
resolve(true);*/ |
||||
}).then(() => { |
||||
this.files = []; |
||||
}); |
||||
}, |
||||
validate(file) { |
||||
if (file) { |
||||
return ''; |
||||
} |
||||
|
||||
return 'error'; |
||||
}, |
||||
...mapActions('personalfile', ['uploadMany', 'create', 'createFile']) |
||||
} |
||||
}; |
||||
</script> |
@ -0,0 +1,23 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
namespace Chamilo\CoreBundle\Controller\Api; |
||||
|
||||
use Chamilo\CoreBundle\Entity\PersonalFile; |
||||
use Symfony\Component\HttpFoundation\Request; |
||||
|
||||
class CreatePersonalFileAction extends BaseResourceFileAction |
||||
{ |
||||
public function __invoke(Request $request): PersonalFile |
||||
{ |
||||
error_log('CreatePersonalFileAction __invoke'); |
||||
|
||||
$document = new PersonalFile(); |
||||
$this->handleCreateRequest($document, $request); |
||||
|
||||
return $document; |
||||
} |
||||
} |
@ -0,0 +1,25 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
namespace Chamilo\CoreBundle\Controller\Api; |
||||
|
||||
use Chamilo\CoreBundle\Entity\PersonalFile; |
||||
use Chamilo\CourseBundle\Repository\CDocumentRepository; |
||||
use Symfony\Component\HttpFoundation\Request; |
||||
|
||||
class UpdatePersonalFileAction extends BaseResourceFileAction |
||||
{ |
||||
public function __invoke(PersonalFile $document, Request $request, CDocumentRepository $repo): PersonalFile |
||||
{ |
||||
error_log('UpdatePersonalFileAction __invoke'); |
||||
|
||||
$this->handleUpdateRequest($document, $repo, $request); |
||||
|
||||
error_log('Finish update resource node file action'); |
||||
|
||||
return $document; |
||||
} |
||||
} |
Loading…
Reference in new issue