diff options
author | John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> | 2019-11-22 15:01:42 +0300 |
---|---|---|
committer | John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> | 2019-11-22 15:01:42 +0300 |
commit | cff7fd88f115beb4d33ddc2be32d7a580a44742e (patch) | |
tree | ccfec8efbab6ccfd4f6738649aea89e8c2e67b46 /src | |
parent | 9051ca63c5211f84582d4cc9f0634a4e20102390 (diff) |
Timeline
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
Diffstat (limited to 'src')
-rw-r--r-- | src/router/index.js | 3 | ||||
-rw-r--r-- | src/services/AlbumContent.js | 2 | ||||
-rw-r--r-- | src/services/FileList.js | 2 | ||||
-rw-r--r-- | src/services/FolderInfo.js | 2 | ||||
-rw-r--r-- | src/services/PhotoSearch.js | 97 | ||||
-rw-r--r-- | src/services/TaggedImages.js | 11 | ||||
-rw-r--r-- | src/store/files.js | 2 | ||||
-rw-r--r-- | src/store/index.js | 2 | ||||
-rw-r--r-- | src/store/timeline.js | 58 | ||||
-rw-r--r-- | src/views/Albums.vue | 2 | ||||
-rw-r--r-- | src/views/Tags.vue | 4 | ||||
-rw-r--r-- | src/views/Timeline.vue | 144 |
12 files changed, 275 insertions, 54 deletions
diff --git a/src/router/index.js b/src/router/index.js index 2310dc27..802aabac 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -25,6 +25,7 @@ import Router from 'vue-router' import Vue from 'vue' import Albums from '../views/Albums' +import Timeline from '../views/Timeline' import Tags from '../views/Tags' Vue.use(Router) @@ -38,7 +39,7 @@ export default new Router({ routes: [ { path: '/', - component: Albums, + component: Timeline, name: 'root', }, { diff --git a/src/services/AlbumContent.js b/src/services/AlbumContent.js index 9bbacbc8..a35fcfd1 100644 --- a/src/services/AlbumContent.js +++ b/src/services/AlbumContent.js @@ -38,7 +38,7 @@ export default async function(path = '/', options = {}) { // fetch listing const response = await axios.get(prefixPath + path, options) - const list = response.data.map(data => genFileInfo(data, prefixPath)) + const list = response.data.map(data => genFileInfo(data)) // filter all the files and folders let folder = {} diff --git a/src/services/FileList.js b/src/services/FileList.js index adbab9b2..82a47887 100644 --- a/src/services/FileList.js +++ b/src/services/FileList.js @@ -36,7 +36,7 @@ import { genFileInfo } from '../utils/fileUtils' * @param {Object} [options] optional options for axios * @returns {Array} the file list */ -export default async function(path, options) { +export default async function(path, options = {}) { options = Object.assign({ method: 'PROPFIND', diff --git a/src/services/FolderInfo.js b/src/services/FolderInfo.js index b58ddb79..bc9bfb55 100644 --- a/src/services/FolderInfo.js +++ b/src/services/FolderInfo.js @@ -43,5 +43,5 @@ export default async function(path) { details: true, }) - return genFileInfo(response.data, prefixPath) + return genFileInfo(response.data) } diff --git a/src/services/PhotoSearch.js b/src/services/PhotoSearch.js index 8d3789ea..e2e34ca2 100644 --- a/src/services/PhotoSearch.js +++ b/src/services/PhotoSearch.js @@ -20,7 +20,6 @@ * */ -import { generateRemoteUrl } from '@nextcloud/router' import { getCurrentUser } from '@nextcloud/auth' import client from './DavClient' import { genFileInfo } from '../utils/fileUtils' @@ -28,53 +27,69 @@ import { genFileInfo } from '../utils/fileUtils' /** * List files from a folder and filter out unwanted mimes * + * @param {string} [path] not used + * @param {Object} [options] used for the cancellable requests * @returns {Array} the file list */ -export default async function() { - // fetch listing - const response = await client.customRequest('', { +export default async function(path = '', options) { + + const prefixPath = `/files/${getCurrentUser().uid}` + + options = Object.assign({ method: 'SEARCH', headers: { 'content-Type': 'text/xml', }, - url: generateRemoteUrl(`dav`), data: `<?xml version="1.0" encoding="UTF-8"?> -<d:searchrequest xmlns:d="DAV:" - xmlns:oc="http://owncloud.org/ns" - xmlns:nc="http://nextcloud.org/ns" - xmlns:ocs="http://open-collaboration-services.org/ns"> - <d:basicsearch> - <d:select> - <d:prop> - <d:getlastmodified /> - <d:getetag /> - <d:getcontenttype /> - <oc:fileid /> - <d:getcontentlength /> - <nc:has-preview /> - <oc:favorite /> - <d:resourcetype /> - </d:prop> - </d:select> - <d:from> - <d:scope> - <d:href>/files/${getCurrentUser().uid}/</d:href> - <d:depth>infinity</d:depth> - </d:scope> - </d:from> - <d:where> - <d:like> - <d:prop> - <d:getcontenttype/> - </d:prop> - <d:literal>image/jpeg</d:literal> - </d:like> - </d:where> - <d:orderby/> - </d:basicsearch> -</d:searchrequest>` }) + <d:searchrequest xmlns:d="DAV:" + xmlns:oc="http://owncloud.org/ns" + xmlns:nc="http://nextcloud.org/ns" + xmlns:ocs="http://open-collaboration-services.org/ns"> + <d:basicsearch> + <d:select> + <d:prop> + <d:getlastmodified /> + <d:getetag /> + <d:getcontenttype /> + <oc:fileid /> + <d:getcontentlength /> + <nc:has-preview /> + <oc:favorite /> + <d:resourcetype /> + </d:prop> + </d:select> + <d:from> + <d:scope> + <d:href>${prefixPath}</d:href> + <d:depth>infinity</d:depth> + </d:scope> + </d:from> + <d:where> + <d:like> + <d:prop> + <d:getcontenttype/> + </d:prop> + <d:literal>image/jpeg</d:literal> + </d:like> + <d:eq> + <d:prop> + <oc:owner-id/> + </d:prop> + <d:literal>admin</d:literal> + </d:eq> + </d:where> + <d:orderby/> + </d:basicsearch> + </d:searchrequest>`, + deep: true, + details: true, + }, options) + + const response = await client.getDirectoryContents('', options) - const entry = response.data - console.info(entry) + return response.data + .map(data => genFileInfo(data)) + // remove prefix path from full file path + .map(data => Object.assign({}, data, { filename: data.filename.replace(prefixPath, '') })) } diff --git a/src/services/TaggedImages.js b/src/services/TaggedImages.js index fe812264..5fe4b3ef 100644 --- a/src/services/TaggedImages.js +++ b/src/services/TaggedImages.js @@ -55,9 +55,7 @@ import client from './DavClient' */ export default async function(id, options = {}) { - const prefixPath = `/files/${getCurrentUser().uid}` - - const response = await client.getDirectoryContents(prefixPath, Object.assign({}, { + options = Object.assign({ method: 'REPORT', data: `<?xml version="1.0"?> <oc:filter-files @@ -90,10 +88,13 @@ export default async function(id, options = {}) { </oc:filter-rules> </oc:filter-files>`, details: true, - }, options)) + }, options) + + const prefixPath = `/files/${getCurrentUser().uid}` + const response = await client.getDirectoryContents(prefixPath, options) return response.data - .map(data => genFileInfo(data, prefixPath)) + .map(data => genFileInfo(data)) // remove prefix path from full file path .map(data => Object.assign({}, data, { filename: data.filename.replace(prefixPath, '') })) } diff --git a/src/store/files.js b/src/store/files.js index 2dc5fa8d..e74d0714 100644 --- a/src/store/files.js +++ b/src/store/files.js @@ -27,7 +27,7 @@ const state = { const mutations = { /** - * Increment the number of contacts accepted + * Append or update given files * * @param {Object} state the store mutations * @param {Array} files the store mutations diff --git a/src/store/index.js b/src/store/index.js index e48270b7..1ce9b647 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -26,6 +26,7 @@ import Vuex, { Store } from 'vuex' import files from './files' import folders from './folders' import systemtags from './systemtags' +import timeline from './timeline' Vue.use(Vuex) export default new Store({ @@ -33,6 +34,7 @@ export default new Store({ files, folders, systemtags, + timeline, }, strict: process.env.NODE_ENV !== 'production', diff --git a/src/store/timeline.js b/src/store/timeline.js new file mode 100644 index 00000000..c343d764 --- /dev/null +++ b/src/store/timeline.js @@ -0,0 +1,58 @@ +/** + * @copyright Copyright (c) 2019 John Molakvoæ <skjnldsv@protonmail.com> + * + * @author John Molakvoæ <skjnldsv@protonmail.com> + * + * @license GNU AGPL version 3 or any later version + * + * 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/>. + * + */ + +const state = { + timeline: [], +} + +const mutations = { + /** + * Update timeline files list + * + * @param {Object} state the store mutations + * @param {Array} files the store mutations + */ + updateTimeline(state, files) { + state.timeline = files + .map(file => file.fileid) + .filter(id => id >= 0) + }, +} + +const getters = { + timeline: state => state.timeline, +} + +const actions = { + /** + * Update timeline files list + * + * @param {Object} context the store mutations + * @param {Number[]} files list of files ids + */ + updateTimeline(context, files = []) { + // we want all the FileInfo! Folders included! + context.commit('updateTimeline', files) + }, +} + +export default { state, mutations, getters, actions } diff --git a/src/views/Albums.vue b/src/views/Albums.vue index 1063b6bb..54ff735c 100644 --- a/src/views/Albums.vue +++ b/src/views/Albums.vue @@ -155,7 +155,7 @@ export default { }, beforeDestroy() { - this.cancelRequest() + this.cancelRequest('Changed view') }, methods: { diff --git a/src/views/Tags.vue b/src/views/Tags.vue index 44827e8f..30407b55 100644 --- a/src/views/Tags.vue +++ b/src/views/Tags.vue @@ -142,7 +142,7 @@ export default { }, beforeDestroy() { - this.cancelRequest() + this.cancelRequest('Changed view') }, async beforeMount() { @@ -161,7 +161,7 @@ export default { methods: { async fetchRootContent() { // cancel any pending requests - this.cancelRequest() + this.cancelRequest('Changed folder') // close any potential opened viewer OCA.Viewer.close() diff --git a/src/views/Timeline.vue b/src/views/Timeline.vue new file mode 100644 index 00000000..61f85ac0 --- /dev/null +++ b/src/views/Timeline.vue @@ -0,0 +1,144 @@ +<!-- + - @copyright Copyright (c) 2019 John Molakvoæ <skjnldsv@protonmail.com> + - + - @author John Molakvoæ <skjnldsv@protonmail.com> + - + - @license GNU AGPL version 3 or any later version + - + - 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--> + <EmptyContent v-if="error === 404" illustration-name="folder"> + {{ t('photos', 'This folder does not exists') }} + </EmptyContent> + <EmptyContent v-else-if="error"> + {{ t('photos', 'An error occurred') }} + </EmptyContent> + <EmptyContent v-else-if="!loading && isEmpty" illustration-name="empty"> + {{ t('photos', 'No photos in here') }} + </EmptyContent> + + <!-- Folder content --> + <Grid v-else> + <File v-for="file in fileList" :key="file.fileid" v-bind="file" /> + </Grid> +</template> + +<script> +import { mapGetters } from 'vuex' + +import getPhotos from '../services/PhotoSearch' + +import EmptyContent from './EmptyContent' +import File from '../components/File' +import Grid from '../components/Grid' + +import cancelableRequest from '../utils/CancelableRequest' + +export default { + name: 'Timeline', + components: { + EmptyContent, + File, + Grid, + }, + props: { + loading: { + type: Boolean, + required: true, + }, + }, + + data() { + return { + error: null, + cancelRequest: () => {}, + } + }, + + computed: { + // global lists + ...mapGetters([ + 'files', + 'timeline', + ]), + + fileList() { + return this.timeline + .map(id => this.files[id]) + .filter(file => !!file) + }, + + // is current folder empty? + isEmpty() { + return this.fileList.length === 0 + }, + }, + + async beforeMount() { + this.fetchFolderContent() + }, + + beforeDestroy() { + this.cancelRequest() + }, + + methods: { + async fetchFolderContent() { + // cancel any pending requests + this.cancelRequest('Changed view') + + // close any potential opened viewer + OCA.Viewer.close() + + // if we don't already have some cached data let's show a loader + if (this.timeline.length === 0) { + this.$emit('update:loading', true) + } + this.error = null + + // init cancellable request + const { request, cancel } = cancelableRequest(getPhotos) + this.cancelRequest = cancel + + try { + // get content and current folder info + const files = await request() + this.$store.dispatch('updateTimeline', files) + this.$store.dispatch('appendFiles', files) + } catch (error) { + if (error.response && error.response.status) { + if (error.response.status === 404) { + this.error = 404 + setTimeout(() => { + this.$router.push({ name: this.$route.name }) + }, 3000) + } else { + this.error = error + } + } + // cancelled request, moving on... + console.error('Error fetching timeline', error) + } finally { + // done loading even with errors + this.$emit('update:loading', false) + } + }, + }, + +} +</script> |