mirror of https://github.com/nextcloud/photos
325 lines
8.2 KiB
Vue
325 lines
8.2 KiB
Vue
<!--
|
|
- @copyright Copyright (c) 2019 John Molakvoæ <skjnldsv@protonmail.com>
|
|
-
|
|
- @author John Molakvoæ <skjnldsv@protonmail.com>
|
|
- @author Corentin Mors <medias@pixelswap.fr>
|
|
- @author Louis Chemineau <louis@chmn.me>
|
|
-
|
|
- @license AGPL-3.0-or-later
|
|
-
|
|
- This program is free software: you can redistribute it and/or modify
|
|
- it under the terms of the GNU Affero General Public License as
|
|
- published by the Free Software Foundation, either version 3 of the
|
|
- License, or (at your option) any later version.
|
|
-
|
|
- This program is distributed in the hope that it will be useful,
|
|
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
- GNU Affero General Public License for more details.
|
|
-
|
|
- You should have received a copy of the GNU Affero General Public License
|
|
- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
-
|
|
-->
|
|
|
|
<template>
|
|
<!-- Errors handlers -->
|
|
<NcEmptyContent v-if="errorFetchingFiles">
|
|
{{ t('photos', 'An error occurred') }}
|
|
</NcEmptyContent>
|
|
|
|
<div v-else class="timeline">
|
|
<!-- Header -->
|
|
<HeaderNavigation key="navigation"
|
|
:loading="loadingCount > 0"
|
|
:path="'/'"
|
|
:title="rootTitle"
|
|
:root-title="rootTitle"
|
|
@refresh="resetFetchFilesState">
|
|
<div class="timeline__header__left">
|
|
<!-- TODO: UploadPicker -->
|
|
<NcActions v-if="selectedFileIds.length === 0"
|
|
:force-menu="true"
|
|
:menu-name="t('photos', 'Add')">
|
|
<template #icon>
|
|
<Plus />
|
|
</template>
|
|
<NcActionButton :close-after-click="true"
|
|
:aria-label="t('photos', 'Create new album')"
|
|
@click="showAlbumCreationForm = true">
|
|
{{ t('photos', 'Create new album') }}
|
|
<template #icon>
|
|
<PlusBoxMultiple />
|
|
</template>
|
|
</NcActionButton>
|
|
</NcActions>
|
|
|
|
<template v-else>
|
|
<NcButton :close-after-click="true"
|
|
type="primary"
|
|
:aria-label="t('photos', 'Add to album')"
|
|
@click="showAlbumPicker = true">
|
|
<template #icon>
|
|
<Plus />
|
|
</template>
|
|
<template v-if="!isMobile">
|
|
{{ t('photos', 'Add to album') }}
|
|
</template>
|
|
</NcButton>
|
|
|
|
<NcButton v-if="selectedFileIds.length > 0"
|
|
:aria-label="t('photos', 'Unselect all')"
|
|
@click="resetSelection">
|
|
<template #icon>
|
|
<Close />
|
|
</template>
|
|
<template v-if="!isMobile">
|
|
{{ t('photos', 'Unselect all') }}
|
|
</template>
|
|
</NcButton>
|
|
|
|
<NcActions :aria-label="t('photos', 'Open actions menu')">
|
|
<ActionDownload :selected-file-ids="selectedFileIds" :title="t('photos', 'Download selected files')">
|
|
<Download slot="icon" />
|
|
</ActionDownload>
|
|
|
|
<ActionFavorite :selected-file-ids="selectedFileIds" />
|
|
|
|
<NcActionButton :close-after-click="true"
|
|
:aria-label="t('photos', 'Delete selection')"
|
|
@click="deleteSelection">
|
|
{{ t('photos', 'Delete selection') }}
|
|
<template #icon>
|
|
<Delete />
|
|
</template>
|
|
</NcActionButton>
|
|
</NcActions>
|
|
</template>
|
|
</div>
|
|
</HeaderNavigation>
|
|
|
|
<FilesListViewer ref="filesListViewer"
|
|
:container-element="appContent"
|
|
class="timeline__file-list"
|
|
:file-ids-by-section="fileIdsByMonth"
|
|
:sections="monthsList"
|
|
:loading="loadingFiles"
|
|
:base-height="isMobile ? 120 : 200"
|
|
:empty-message="t('photos', 'No photos or videos in here')"
|
|
@need-content="getContent">
|
|
<template slot-scope="{file, isHeader, distance}">
|
|
<h2 v-if="isHeader"
|
|
:id="`file-picker-section-header-${file.id}`"
|
|
class="section-header">
|
|
<b>{{ file.id | dateMonth }}</b>
|
|
{{ file.id | dateYear }}
|
|
</h2>
|
|
<File v-else
|
|
:file="files[file.id]"
|
|
:allow-selection="true"
|
|
:selected="selection[file.id] === true"
|
|
:distance="distance"
|
|
@click="openViewer"
|
|
@select-toggled="onFileSelectToggle" />
|
|
</template>
|
|
</FilesListViewer>
|
|
|
|
<NcModal v-if="showAlbumCreationForm"
|
|
key="albumCreationForm"
|
|
@close="showAlbumCreationForm = false">
|
|
<h2 class="timeline__heading">{{ t('photos', 'New album') }}</h2>
|
|
<AlbumForm @done="showAlbumCreationForm = false" />
|
|
</NcModal>
|
|
|
|
<NcModal v-if="showAlbumPicker"
|
|
key="albumPicker"
|
|
@close="showAlbumPicker = false">
|
|
<AlbumPicker @album-picked="addSelectionToAlbum" />
|
|
</NcModal>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { mapActions, mapGetters } from 'vuex'
|
|
import Plus from 'vue-material-design-icons/Plus.vue'
|
|
import Delete from 'vue-material-design-icons/Delete.vue'
|
|
import PlusBoxMultiple from 'vue-material-design-icons/PlusBoxMultiple.vue'
|
|
import Download from 'vue-material-design-icons/Download.vue'
|
|
import Close from 'vue-material-design-icons/Close.vue'
|
|
|
|
import { NcModal, NcActions, NcActionButton, NcButton, NcEmptyContent, isMobile } from '@nextcloud/vue'
|
|
import moment from '@nextcloud/moment'
|
|
|
|
import { allMimes } from '../services/AllowedMimes.js'
|
|
import FetchFilesMixin from '../mixins/FetchFilesMixin.js'
|
|
import FilesByMonthMixin from '../mixins/FilesByMonthMixin.js'
|
|
import FilesSelectionMixin from '../mixins/FilesSelectionMixin.js'
|
|
import FilesListViewer from '../components/FilesListViewer.vue'
|
|
import File from '../components/File.vue'
|
|
import AlbumForm from '../components/Albums/AlbumForm.vue'
|
|
import AlbumPicker from '../components/Albums/AlbumPicker.vue'
|
|
import ActionFavorite from '../components/Actions/ActionFavorite.vue'
|
|
import ActionDownload from '../components/Actions/ActionDownload.vue'
|
|
import HeaderNavigation from '../components/HeaderNavigation.vue'
|
|
import { translate } from '@nextcloud/l10n'
|
|
|
|
export default {
|
|
name: 'Timeline',
|
|
components: {
|
|
Delete,
|
|
PlusBoxMultiple,
|
|
Download,
|
|
Close,
|
|
Plus,
|
|
NcEmptyContent,
|
|
NcModal,
|
|
NcActions,
|
|
NcActionButton,
|
|
NcButton,
|
|
AlbumForm,
|
|
AlbumPicker,
|
|
FilesListViewer,
|
|
File,
|
|
ActionFavorite,
|
|
ActionDownload,
|
|
HeaderNavigation,
|
|
},
|
|
|
|
filters: {
|
|
/**
|
|
* @param {string} date - In the following format: YYYYMM
|
|
*/
|
|
dateMonth(date) {
|
|
return moment(date, 'YYYYMM').format('MMMM')
|
|
},
|
|
/**
|
|
* @param {string} date - In the following format: YYYYMM
|
|
*/
|
|
dateYear(date) {
|
|
return moment(date, 'YYYYMM').format('YYYY')
|
|
},
|
|
},
|
|
|
|
mixins: [
|
|
FetchFilesMixin,
|
|
FilesSelectionMixin,
|
|
FilesByMonthMixin,
|
|
isMobile,
|
|
],
|
|
|
|
beforeRouteLeave(to, from, next) {
|
|
window.scrollTo(0, 0)
|
|
next()
|
|
},
|
|
|
|
props: {
|
|
onlyFavorites: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
mimesType: {
|
|
type: Array,
|
|
default: () => allMimes,
|
|
},
|
|
onThisDay: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
rootTitle: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
},
|
|
|
|
data() {
|
|
return {
|
|
loadingCount: 0,
|
|
showAlbumCreationForm: false,
|
|
showAlbumPicker: false,
|
|
appContent: document.getElementById('app-content-vue'),
|
|
}
|
|
},
|
|
|
|
computed: {
|
|
...mapGetters([
|
|
'files',
|
|
]),
|
|
},
|
|
|
|
methods: {
|
|
...mapActions([
|
|
'deleteFiles',
|
|
'addFilesToCollection',
|
|
]),
|
|
|
|
getContent() {
|
|
this.fetchFiles('', {
|
|
mimesType: this.mimesType,
|
|
onThisDay: this.onThisDay,
|
|
onlyFavorites: this.onlyFavorites,
|
|
})
|
|
},
|
|
|
|
openViewer(fileId) {
|
|
const file = this.files[fileId]
|
|
OCA.Viewer.open({
|
|
fileInfo: file,
|
|
list: Object.values(this.fileIdsByMonth).flat().map(fileId => this.files[fileId]),
|
|
loadMore: file.loadMore ? async () => await file.loadMore(true) : () => [],
|
|
canLoop: file.canLoop,
|
|
})
|
|
},
|
|
|
|
openUploader() {
|
|
// TODO: finish when implementing upload
|
|
},
|
|
|
|
async addSelectionToAlbum(album) {
|
|
this.showAlbumPicker = false
|
|
await this.addFilesToCollection({ collectionFileName: album.filename, fileIdsToAdd: this.selectedFileIds })
|
|
},
|
|
|
|
async deleteSelection() {
|
|
// Need to store the file ids so it is not changed before the deleteFiles call.
|
|
const fileIds = this.selectedFileIds
|
|
this.onUncheckFiles(fileIds)
|
|
this.fetchedFileIds = this.fetchedFileIds.filter(fileid => !fileIds.includes(fileid))
|
|
await this.deleteFiles(fileIds)
|
|
},
|
|
|
|
t: translate,
|
|
},
|
|
}
|
|
</script>
|
|
<style lang="scss" scoped>
|
|
.timeline {
|
|
display: flex;
|
|
flex-direction: column;
|
|
|
|
&__header {
|
|
&__left {
|
|
display: flex;
|
|
gap: 4px;
|
|
}
|
|
}
|
|
|
|
&__heading {
|
|
padding: calc(var(--default-grid-baseline) * 4);
|
|
margin-bottom: 0px;
|
|
padding-bottom: 0px;
|
|
}
|
|
|
|
&__file-list {
|
|
padding: 0 64px;
|
|
|
|
@media only screen and (max-width: 1200px) {
|
|
padding: 0 4px;
|
|
}
|
|
|
|
:deep .files-list-viewer__section-header {
|
|
top: var(--photos-navigation-height);
|
|
}
|
|
}
|
|
}
|
|
</style>
|