|
|
|
@ -33,7 +33,7 @@
|
|
|
|
|
|
|
|
|
|
<!-- Link to file -->
|
|
|
|
|
<td class="files-list__row-name">
|
|
|
|
|
<a ref="name" v-bind="linkTo">
|
|
|
|
|
<a ref="name" v-bind="linkTo" @click="execDefaultAction">
|
|
|
|
|
<!-- Icon or preview -->
|
|
|
|
|
<span class="files-list__row-icon">
|
|
|
|
|
<FolderIcon v-if="source.type === 'folder'" />
|
|
|
|
@ -49,6 +49,13 @@
|
|
|
|
|
:style="{ backgroundImage: mimeIconUrl }" />
|
|
|
|
|
|
|
|
|
|
<FileIcon v-else />
|
|
|
|
|
|
|
|
|
|
<!-- Favorite icon -->
|
|
|
|
|
<span v-if="isFavorite"
|
|
|
|
|
class="files-list__row-icon-favorite"
|
|
|
|
|
:aria-label="t('files', 'Favorite')">
|
|
|
|
|
<StarIcon aria-hidden="true" :size="20" />
|
|
|
|
|
</span>
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
|
|
<!-- File name -->
|
|
|
|
@ -64,6 +71,8 @@
|
|
|
|
|
<!-- Menu actions -->
|
|
|
|
|
<NcActions v-if="active"
|
|
|
|
|
ref="actionsMenu"
|
|
|
|
|
:boundaries-element="boundariesElement"
|
|
|
|
|
:container="boundariesElement"
|
|
|
|
|
:disabled="source._loading"
|
|
|
|
|
:force-title="true"
|
|
|
|
|
:inline="enabledInlineActions.length"
|
|
|
|
@ -84,7 +93,8 @@
|
|
|
|
|
<!-- Size -->
|
|
|
|
|
<td v-if="isSizeAvailable"
|
|
|
|
|
:style="{ opacity: sizeOpacity }"
|
|
|
|
|
class="files-list__row-size">
|
|
|
|
|
class="files-list__row-size"
|
|
|
|
|
@click="execDefaultAction">
|
|
|
|
|
<span>{{ size }}</span>
|
|
|
|
|
</td>
|
|
|
|
|
|
|
|
|
@ -92,7 +102,8 @@
|
|
|
|
|
<td v-for="column in columns"
|
|
|
|
|
:key="column.id"
|
|
|
|
|
:class="`files-list__row-${currentView?.id}-${column.id}`"
|
|
|
|
|
class="files-list__row-column-custom">
|
|
|
|
|
class="files-list__row-column-custom"
|
|
|
|
|
@click="execDefaultAction">
|
|
|
|
|
<CustomElementRender v-if="active"
|
|
|
|
|
:current-view="currentView"
|
|
|
|
|
:render="column.render"
|
|
|
|
@ -115,9 +126,11 @@ import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
|
|
|
|
|
import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
|
|
|
|
|
import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js'
|
|
|
|
|
import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
|
|
|
|
|
import StarIcon from 'vue-material-design-icons/Star.vue'
|
|
|
|
|
import Vue from 'vue'
|
|
|
|
|
|
|
|
|
|
import { getFileActions } from '../services/FileAction.ts'
|
|
|
|
|
import { hashCode } from '../utils/hashUtils.ts'
|
|
|
|
|
import { isCachedPreview } from '../services/PreviewService.ts'
|
|
|
|
|
import { useActionsMenuStore } from '../store/actionsmenu.ts'
|
|
|
|
|
import { useFilesStore } from '../store/files.ts'
|
|
|
|
@ -144,6 +157,7 @@ export default Vue.extend({
|
|
|
|
|
NcActions,
|
|
|
|
|
NcCheckboxRadioSwitch,
|
|
|
|
|
NcLoadingIcon,
|
|
|
|
|
StarIcon,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
props: {
|
|
|
|
@ -192,6 +206,7 @@ export default Vue.extend({
|
|
|
|
|
return {
|
|
|
|
|
backgroundFailed: false,
|
|
|
|
|
backgroundImage: '',
|
|
|
|
|
boundariesElement: document.querySelector('.app-content > .files-list'),
|
|
|
|
|
loading: '',
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
@ -204,7 +219,6 @@ export default Vue.extend({
|
|
|
|
|
currentView() {
|
|
|
|
|
return this.$navigation.active
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
columns() {
|
|
|
|
|
// Hide columns if the list is too small
|
|
|
|
|
if (this.filesListWidth < 512) {
|
|
|
|
@ -217,7 +231,6 @@ export default Vue.extend({
|
|
|
|
|
// Remove any trailing slash but leave root slash
|
|
|
|
|
return (this.$route?.query?.dir || '/').replace(/^(.+)\/$/, '$1')
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
fileid() {
|
|
|
|
|
return this.source?.fileid?.toString?.()
|
|
|
|
|
},
|
|
|
|
@ -225,6 +238,7 @@ export default Vue.extend({
|
|
|
|
|
return this.source.attributes.displayName
|
|
|
|
|
|| this.source.basename
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
size() {
|
|
|
|
|
const size = parseInt(this.source.size, 10) || 0
|
|
|
|
|
if (typeof size !== 'number' || size < 0) {
|
|
|
|
@ -232,7 +246,6 @@ export default Vue.extend({
|
|
|
|
|
}
|
|
|
|
|
return formatFileSize(size, true)
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
sizeOpacity() {
|
|
|
|
|
const size = parseInt(this.source.size, 10) || 0
|
|
|
|
|
if (!size || size < 0) {
|
|
|
|
@ -247,6 +260,15 @@ export default Vue.extend({
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
linkTo() {
|
|
|
|
|
if (this.enabledDefaultActions.length > 0) {
|
|
|
|
|
const action = this.enabledDefaultActions[0]
|
|
|
|
|
const displayName = action.displayName([this.source], this.currentView)
|
|
|
|
|
return {
|
|
|
|
|
title: displayName,
|
|
|
|
|
role: 'button',
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.source.type === 'folder') {
|
|
|
|
|
const to = { ...this.$route, query: { dir: join(this.dir, this.source.basename) } }
|
|
|
|
|
return {
|
|
|
|
@ -272,7 +294,6 @@ export default Vue.extend({
|
|
|
|
|
cropPreviews() {
|
|
|
|
|
return this.userConfig.crop_image_previews
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
previewUrl() {
|
|
|
|
|
try {
|
|
|
|
|
const url = new URL(window.location.origin + this.source.attributes.previewUrl)
|
|
|
|
@ -280,13 +301,12 @@ export default Vue.extend({
|
|
|
|
|
url.searchParams.set('x', '32')
|
|
|
|
|
url.searchParams.set('y', '32')
|
|
|
|
|
// Handle cropping
|
|
|
|
|
url.searchParams.set('a', this.cropPreviews === true ? '1' : '0')
|
|
|
|
|
url.searchParams.set('a', this.cropPreviews === true ? '0' : '1')
|
|
|
|
|
return url.href
|
|
|
|
|
} catch (e) {
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
mimeIconUrl() {
|
|
|
|
|
const mimeType = this.source.mime || 'application/octet-stream'
|
|
|
|
|
const mimeIconUrl = window.OC?.MimeType?.getIconUrl?.(mimeType)
|
|
|
|
@ -301,29 +321,38 @@ export default Vue.extend({
|
|
|
|
|
.filter(action => !action.enabled || action.enabled([this.source], this.currentView))
|
|
|
|
|
.sort((a, b) => (a.order || 0) - (b.order || 0))
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
enabledInlineActions() {
|
|
|
|
|
if (this.filesListWidth < 768) {
|
|
|
|
|
return []
|
|
|
|
|
}
|
|
|
|
|
return this.enabledActions.filter(action => action?.inline?.(this.source, this.currentView))
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
enabledMenuActions() {
|
|
|
|
|
if (this.filesListWidth < 768) {
|
|
|
|
|
// If we have a default action, do not render the first one
|
|
|
|
|
if (this.enabledDefaultActions.length > 0) {
|
|
|
|
|
return this.enabledActions.slice(1)
|
|
|
|
|
}
|
|
|
|
|
return this.enabledActions
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
const actions = [
|
|
|
|
|
...this.enabledInlineActions,
|
|
|
|
|
...this.enabledActions.filter(action => !action.inline),
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
uniqueId() {
|
|
|
|
|
return this.hashCode(this.source.source)
|
|
|
|
|
},
|
|
|
|
|
// If we have a default action, do not render the first one
|
|
|
|
|
if (this.enabledDefaultActions.length > 0) {
|
|
|
|
|
return actions.slice(1)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return actions
|
|
|
|
|
},
|
|
|
|
|
enabledDefaultActions() {
|
|
|
|
|
return [
|
|
|
|
|
...this.enabledActions.filter(action => action.default),
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
openedMenu: {
|
|
|
|
|
get() {
|
|
|
|
|
return this.actionsMenuStore.opened === this.uniqueId
|
|
|
|
@ -332,6 +361,14 @@ export default Vue.extend({
|
|
|
|
|
this.actionsMenuStore.opened = opened ? this.uniqueId : null
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
uniqueId() {
|
|
|
|
|
return hashCode(this.source.source)
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
isFavorite() {
|
|
|
|
|
return this.source.attributes.favorite === 1
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
watch: {
|
|
|
|
@ -457,16 +494,6 @@ export default Vue.extend({
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
hashCode(str) {
|
|
|
|
|
let hash = 0
|
|
|
|
|
for (let i = 0, len = str.length; i < len; i++) {
|
|
|
|
|
const chr = str.charCodeAt(i)
|
|
|
|
|
hash = (hash << 5) - hash + chr
|
|
|
|
|
hash |= 0 // Convert to 32bit integer
|
|
|
|
|
}
|
|
|
|
|
return hash
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async onActionClick(action) {
|
|
|
|
|
const displayName = action.displayName([this.source], this.currentView)
|
|
|
|
|
try {
|
|
|
|
@ -475,6 +502,12 @@ export default Vue.extend({
|
|
|
|
|
Vue.set(this.source, '_loading', true)
|
|
|
|
|
|
|
|
|
|
const success = await action.exec(this.source, this.currentView)
|
|
|
|
|
|
|
|
|
|
// If the action returns null, we stay silent
|
|
|
|
|
if (success === null) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (success) {
|
|
|
|
|
showSuccess(this.t('files', '"{displayName}" action executed successfully', { displayName }))
|
|
|
|
|
return
|
|
|
|
@ -489,6 +522,14 @@ export default Vue.extend({
|
|
|
|
|
Vue.set(this.source, '_loading', false)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
execDefaultAction(event) {
|
|
|
|
|
if (this.enabledDefaultActions.length > 0) {
|
|
|
|
|
event.preventDefault()
|
|
|
|
|
event.stopPropagation()
|
|
|
|
|
// Execute the first default action if any
|
|
|
|
|
this.enabledDefaultActions[0].exec(this.source, this.currentView)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
onSelectionChange(selection) {
|
|
|
|
|
const newSelectedIndex = this.index
|
|
|
|
|