From b501e72f31bbaf90d89e3c55ef2438b002244d0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Molakvo=C3=A6=20=28skjnldsv=29?= Date: Wed, 11 Dec 2019 20:38:14 +0100 Subject: Grid virtual scroller MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: John Molakvoæ (skjnldsv) --- package-lock.json | 80 ++++++++++---------- package.json | 4 +- src/Photos.vue | 8 ++ src/assets/grid-sizes.js | 15 +++- src/components/File.vue | 5 +- src/components/Grid.vue | 3 +- src/components/GridRow.vue | 77 +++++++++++++++++++ src/components/VirtualGrid.vue | 168 +++++++++++++++++++++++++++++++++++++++++ src/mixins/GridConfig.js | 46 +++++++++++ src/services/DavRequest.js | 19 +++-- src/services/GridConfig.js | 51 +++++++++++++ src/services/PhotoSearch.js | 26 ++++--- src/services/TaggedImages.js | 21 +----- src/store/timeline.js | 4 +- src/utils/ArrayChunk.js | 39 ++++++++++ src/utils/fileUtils.js | 2 +- src/utils/numberUtil.js | 30 -------- src/utils/numberUtils.js | 30 ++++++++ src/views/Timeline.vue | 91 ++++++++++++++++++++-- 19 files changed, 597 insertions(+), 122 deletions(-) create mode 100644 src/components/GridRow.vue create mode 100644 src/components/VirtualGrid.vue create mode 100644 src/mixins/GridConfig.js create mode 100644 src/services/GridConfig.js create mode 100644 src/utils/ArrayChunk.js delete mode 100644 src/utils/numberUtil.js create mode 100644 src/utils/numberUtils.js diff --git a/package-lock.json b/package-lock.json index cf0851a8..78af04cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1703,22 +1703,6 @@ "@babel/helper-plugin-utils": "^7.0.0" } }, - "@babel/polyfill": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@babel/polyfill/-/polyfill-7.7.0.tgz", - "integrity": "sha512-/TS23MVvo34dFmf8mwCisCbWGrfhbiWZSwBo6HkADTBhUa2Q/jWltyY/tpofz/b6/RIhqaqQcquptCirqIhOaQ==", - "requires": { - "core-js": "^2.6.5", - "regenerator-runtime": "^0.13.2" - }, - "dependencies": { - "core-js": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", - "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==" - } - } - }, "@babel/preset-env": { "version": "7.7.6", "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.7.6.tgz", @@ -1856,6 +1840,13 @@ "requires": { "@nextcloud/event-bus": "^0.2.0", "core-js": "3.2.1" + }, + "dependencies": { + "core-js": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.2.1.tgz", + "integrity": "sha512-Qa5XSVefSVPRxy2XfUC13WbvqkxhkwB3ve+pgCQveNgYzbM/UxZeu1dcOX/xr4UmfUd+muuvsaxilQzCyUurMw==" + } } }, "@nextcloud/axios": { @@ -1907,6 +1898,13 @@ "integrity": "sha512-iLdyxluCehsRibR4R/nH3O8T9CcGoAaW3eWEdQW2qPtn6eEiBXASek5nWhXa5hko1GvE7koYia4FoTWuL85/Ng==", "requires": { "core-js": "3.2.1" + }, + "dependencies": { + "core-js": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.2.1.tgz", + "integrity": "sha512-Qa5XSVefSVPRxy2XfUC13WbvqkxhkwB3ve+pgCQveNgYzbM/UxZeu1dcOX/xr4UmfUd+muuvsaxilQzCyUurMw==" + } } }, "@nextcloud/router": { @@ -3012,13 +3010,6 @@ "integrity": "sha512-fpZ81yYfzentuieinmGnphk0pLkOTMm6MZdVqwd77ROvhko6iujLNGrHH5E7utq3ygWklwfmwuG+A7P+NpqT6w==", "dev": true }, - "cdav-library": { - "version": "git+https://github.com/nextcloud/cdav-library.git#964c26c33c348315424d313b4ba9fc3aab58fed6", - "from": "git+https://github.com/nextcloud/cdav-library.git#964c26c33c348315424d313b4ba9fc3aab58fed6", - "requires": { - "@babel/polyfill": "^7.7.0" - } - }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -3405,9 +3396,9 @@ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" }, "core-js": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.2.1.tgz", - "integrity": "sha512-Qa5XSVefSVPRxy2XfUC13WbvqkxhkwB3ve+pgCQveNgYzbM/UxZeu1dcOX/xr4UmfUd+muuvsaxilQzCyUurMw==" + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.5.0.tgz", + "integrity": "sha512-Ifh3kj78gzQ7NAoJXeTu+XwzDld0QRIwjBLRqAMhuLhP3d2Av5wmgE9ycfnvK6NAEjTkQ1sDPeoEZAWO3Hx1Uw==" }, "core-js-compat": { "version": "3.4.8", @@ -3617,6 +3608,11 @@ "integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=", "dev": true }, + "debounce": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.0.tgz", + "integrity": "sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg==" + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -8920,9 +8916,9 @@ "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" }, "serialize-javascript": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.9.1.tgz", - "integrity": "sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz", + "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==", "dev": true }, "set-blocking": { @@ -9147,9 +9143,9 @@ } }, "source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", + "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", "dev": true, "requires": { "buffer-from": "^1.0.0", @@ -9315,9 +9311,9 @@ } }, "stream-shift": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", - "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", "dev": true }, "string-width": { @@ -10044,9 +10040,9 @@ } }, "terser": { - "version": "4.3.9", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.3.9.tgz", - "integrity": "sha512-NFGMpHjlzmyOtPL+fDw3G7+6Ueh/sz4mkaUYa4lJCxOPTNzd0Uj0aZJOmsDYoSQyfuVoWDMSWTPU3huyOm2zdA==", + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.4.2.tgz", + "integrity": "sha512-Uufrsvhj9O1ikwgITGsZ5EZS6qPokUOkCegS7fYOdGTv+OA90vndUbU6PEjr5ePqHfNUbGyMO7xyIZv2MhsALQ==", "dev": true, "requires": { "commander": "^2.20.0", @@ -10063,16 +10059,16 @@ } }, "terser-webpack-plugin": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.1.tgz", - "integrity": "sha512-ZXmmfiwtCLfz8WKZyYUuuHf3dMYEjg8NrjHMb0JqHVHVOSkzp3cW2/XG1fP3tRhqEqSzMwzzRQGtAPbs4Cncxg==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz", + "integrity": "sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==", "dev": true, "requires": { "cacache": "^12.0.2", "find-cache-dir": "^2.1.0", "is-wsl": "^1.1.0", "schema-utils": "^1.0.0", - "serialize-javascript": "^1.7.0", + "serialize-javascript": "^2.1.2", "source-map": "^0.6.1", "terser": "^4.1.2", "webpack-sources": "^1.4.0", diff --git a/package.json b/package.json index 9b1b9c9a..5763db91 100644 --- a/package.json +++ b/package.json @@ -38,9 +38,11 @@ "@nextcloud/router": "^0.1.0", "@nextcloud/vue": "^1.2.2", "camelcase": "^5.3.1", - "cdav-library": "git+https://github.com/nextcloud/cdav-library.git", + "core-js": "^3.5.0", + "debounce": "^1.2.0", "path-posix": "^1.0.0", "qs": "^6.9.1", + "regenerator-runtime": "^0.13.3", "url-parse": "^1.4.7", "vue": "^2.6.10", "vue-router": "^3.1.3", diff --git a/src/Photos.vue b/src/Photos.vue index acd5057a..50766d90 100644 --- a/src/Photos.vue +++ b/src/Photos.vue @@ -73,6 +73,14 @@ export default { } diff --git a/src/components/VirtualGrid.vue b/src/components/VirtualGrid.vue new file mode 100644 index 00000000..26501a0d --- /dev/null +++ b/src/components/VirtualGrid.vue @@ -0,0 +1,168 @@ + + + + + + diff --git a/src/mixins/GridConfig.js b/src/mixins/GridConfig.js new file mode 100644 index 00000000..e7b18817 --- /dev/null +++ b/src/mixins/GridConfig.js @@ -0,0 +1,46 @@ +/** + * @copyright Copyright (c) 2018 John Molakvoæ + * + * @author John Molakvoæ + * + * @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 . + * + */ + +import getGridConfig from '../services/GridConfig' + +/** + * Get the current used grid config + */ +export default { + data() { + return { + gridConfig: {}, + } + }, + + created() { + getGridConfig.$on('changed', val => { + this.gridConfig = val + }) + console.debug('Current grid config', getGridConfig.gridConfig) + this.gridConfig = getGridConfig.gridConfig + }, + + beforeDestroy() { + getGridConfig.$off('changed', this.gridConfig) + }, +} diff --git a/src/services/DavRequest.js b/src/services/DavRequest.js index 9a2ca05a..79c7c586 100644 --- a/src/services/DavRequest.js +++ b/src/services/DavRequest.js @@ -19,20 +19,23 @@ * along with this program. If not, see . * */ +const props = ` + + + + + + + + ` +export { props } export default ` - - - - - - - - + ${props} ` diff --git a/src/services/GridConfig.js b/src/services/GridConfig.js new file mode 100644 index 00000000..c3ee14b7 --- /dev/null +++ b/src/services/GridConfig.js @@ -0,0 +1,51 @@ +/** + * @copyright Copyright (c) 2019 John Molakvoæ + * + * @author John Molakvoæ + * + * @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 . + * + */ + +import Vue from 'vue' +import { sizes } from '../assets/grid-sizes' + +export default new Vue({ + data() { + return { + gridConfig: sizes.max, + } + }, + watch: { + gridConfig(val) { + this.$emit('changed', val) + }, + }, + created() { + window.addEventListener('resize', this.handleWindowResize) + this.handleWindowResize() + }, + beforeDestroy() { + window.removeEventListener('resize', this.handleWindowResize) + }, + methods: { + handleWindowResize() { + // find the first grid size that fit the current window width + const currentSize = Object.keys(sizes).find(size => size > document.documentElement.clientWidth) + this.gridConfig = sizes[currentSize] + }, + }, +}) diff --git a/src/services/PhotoSearch.js b/src/services/PhotoSearch.js index 06d8dda3..1050ff8f 100644 --- a/src/services/PhotoSearch.js +++ b/src/services/PhotoSearch.js @@ -24,15 +24,25 @@ import { genFileInfo } from '../utils/fileUtils' import { getCurrentUser } from '@nextcloud/auth' import allowedMimes from './AllowedMimes' import client from './DavClient' +import { props } from './DavRequest' +import { sizes } from '../assets/grid-sizes' /** * List files from a folder and filter out unwanted mimes * * @param {boolean} [onlyFavorites=false] not used * @param {Object} [options] used for the cancellable requests + * @param {number} [options.page=0] which page to start (starts at 0) + * @param {number} [options.perPage] how many to display per page default is 5 times the max number per line from the grid-sizes config file + * @param {boolean} [options.full=false] get full data of the files * @returns {Array} the file list */ export default async function(onlyFavorites = false, options = {}) { + // default function options + options = Object.assign({}, { + page: 0, // start at the first page + perPage: sizes.max.count * 10, // ten rows of the max width + }, options) const prefixPath = `/files/${getCurrentUser().uid}` @@ -65,18 +75,12 @@ export default async function(onlyFavorites = false, options = {}) { - - - - - - - - + ${props} @@ -104,7 +108,11 @@ export default async function(onlyFavorites = false, options = {}) { - + + + ${options.perPage} + ${options.page * options.perPage} + `, deep: true, diff --git a/src/services/TaggedImages.js b/src/services/TaggedImages.js index 5fe4b3ef..4488dc88 100644 --- a/src/services/TaggedImages.js +++ b/src/services/TaggedImages.js @@ -46,6 +46,8 @@ import { genFileInfo } from '../utils/fileUtils' import { getCurrentUser } from '@nextcloud/auth' import client from './DavClient' +import { props } from './DavRequest' + /** * Get tagged files based on provided tag id * @@ -64,24 +66,7 @@ export default async function(id, options = {}) { xmlns:nc="http://nextcloud.org/ns" xmlns:ocs="http://open-collaboration-services.org/ns"> - - - - - - - - - - - - - - - - - - + ${props} ${id} diff --git a/src/store/timeline.js b/src/store/timeline.js index c343d764..35d6ff2b 100644 --- a/src/store/timeline.js +++ b/src/store/timeline.js @@ -32,9 +32,9 @@ const mutations = { * @param {Array} files the store mutations */ updateTimeline(state, files) { - state.timeline = files + state.timeline.push(...files .map(file => file.fileid) - .filter(id => id >= 0) + .filter(id => id >= 0)) }, } diff --git a/src/utils/ArrayChunk.js b/src/utils/ArrayChunk.js new file mode 100644 index 00000000..ff212228 --- /dev/null +++ b/src/utils/ArrayChunk.js @@ -0,0 +1,39 @@ +/** + * @copyright Copyright (c) 2019 John Molakvoæ + * + * @author John Molakvoæ + * + * @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 . + * + */ + +const arrayRange = function(n) { + // Array.range(5) --> [0,1,2,3,4] + return Array.apply(null, Array(n)).map((x, i) => i) +} + +/** + * Split an array into chunks + * + * @param {Array} arr an array to split + * @param {number} count lenght of the chunk + * @returns {Array} + */ +export default function(arr = [], count = 5) { + return arrayRange(Math.ceil(arr.length / count)).map((x, i) => + arr.slice(i * count, i * count + count) + ) +} diff --git a/src/utils/fileUtils.js b/src/utils/fileUtils.js index a04b9945..50e6168d 100644 --- a/src/utils/fileUtils.js +++ b/src/utils/fileUtils.js @@ -20,7 +20,7 @@ * */ import camelcase from 'camelcase' -import { isNumber } from './numberUtil' +import { isNumber } from './numberUtils' /** * Get an url encoded path diff --git a/src/utils/numberUtil.js b/src/utils/numberUtil.js deleted file mode 100644 index 0c3a96e5..00000000 --- a/src/utils/numberUtil.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * @copyright Copyright (c) 2019 John Molakvoæ - * - * @author John Molakvoæ - * - * @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 . - * - */ - -const isNumber = function(num) { - if (!num) { - return false - } - return Number(num).toString() === num.toString() -} - -export { isNumber } diff --git a/src/utils/numberUtils.js b/src/utils/numberUtils.js new file mode 100644 index 00000000..0c3a96e5 --- /dev/null +++ b/src/utils/numberUtils.js @@ -0,0 +1,30 @@ +/** + * @copyright Copyright (c) 2019 John Molakvoæ + * + * @author John Molakvoæ + * + * @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 . + * + */ + +const isNumber = function(num) { + if (!num) { + return false + } + return Number(num).toString() === num.toString() +} + +export { isNumber } diff --git a/src/views/Timeline.vue b/src/views/Timeline.vue index df47f1ea..51533763 100644 --- a/src/views/Timeline.vue +++ b/src/views/Timeline.vue @@ -33,29 +33,35 @@ - - - +