Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/photos.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>2019-11-07 21:45:14 +0300
committerJohn Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>2019-11-07 21:45:14 +0300
commitae28cc9b2d79568ae069e3f25ed29791986791d0 (patch)
tree64142d5c99b2b0c392bc8e22a4ea536fd5c9f816
parent9c94a3e10fb3345ed57df27f44d51dde2f7ff733 (diff)
Added navigation, albums, init tags
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
-rw-r--r--appinfo/app.php24
-rw-r--r--css/icons.scss27
-rw-r--r--img/app.svg2
-rw-r--r--img/photos.svg1
-rw-r--r--lib/AppInfo/Application.php4
-rw-r--r--lib/Controller/PageController.php5
-rw-r--r--package-lock.json26
-rw-r--r--package.json2
-rw-r--r--src/Photos.vue (renamed from src/Gallery.vue)20
-rw-r--r--src/components/Folder.vue27
-rw-r--r--src/components/Grid.vue87
-rw-r--r--src/components/Navigation.vue16
-rw-r--r--src/main.js6
-rw-r--r--src/router/index.js68
-rw-r--r--src/services/DavClient.js3
-rw-r--r--src/services/FileList.js24
-rw-r--r--src/services/FolderInfo.js14
-rw-r--r--src/services/PhotoSearch.js6
-rw-r--r--src/services/SystemTags.js56
-rw-r--r--src/store/index.js2
-rw-r--r--src/store/systemtags.js99
-rw-r--r--src/utils/ParseFile.js37
-rw-r--r--src/views/Albums.vue (renamed from src/views/Grid.vue)60
-rw-r--r--src/views/Tags.vue111
-rw-r--r--templates/main.php23
-rw-r--r--webpack.common.js36
26 files changed, 659 insertions, 127 deletions
diff --git a/appinfo/app.php b/appinfo/app.php
new file mode 100644
index 00000000..12482fcd
--- /dev/null
+++ b/appinfo/app.php
@@ -0,0 +1,24 @@
+<?php
+/**
+ * @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/>.
+ *
+ */
+use OCA\Photos\AppInfo\Application;
+\OC::$server->query(Application::class);
diff --git a/css/icons.scss b/css/icons.scss
new file mode 100644
index 00000000..88e42707
--- /dev/null
+++ b/css/icons.scss
@@ -0,0 +1,27 @@
+/**
+ * @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/>.
+ *
+ */
+
+.icon-folder.icon-dark {
+ @include icon-color('folder', 'filetypes', $color-black, 1, true);
+}
+
+@include icon-black-white('photos', 'photos', 1);
diff --git a/img/app.svg b/img/app.svg
index 0779a5dd..04aaca9e 100644
--- a/img/app.svg
+++ b/img/app.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="32" width="32" version="1.0"><path fill="#fff" d="M2.69 4a.9.9 0 00-.69.88v22.25c0 .46.42.87.88.87h26.25c.45 0 .87-.42.87-.87V5.22C30 4.55 29.47 4 28.97 4zM4 6h24v10l-2-2-6 8-6-6-8 8H4zm5 2a3 3 0 100 6 3 3 0 000-6z"/></svg> \ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" height="32" width="32" version="1"><path d="M2.8 4a1.3 1.3 0 00-1.3 1.3v22.4c0 .6.7 1.3 1.3 1.3h26.4c.6 0 1.3-.7 1.3-1.3V5.3c0-.6-.7-1.3-1.3-1.3zm.7 2h25v19h-25z" fill="#fff"/><circle cx="8.5" cy="11.2" r="3" fill="#fff"/><path d="M26.4 14.5l-4.7 6.2L20 23l-1.6-1.8-4.5-4.6-6 5.7-4.7 4.3h26.2v-8.7z" fill="#fff"/></svg> \ No newline at end of file
diff --git a/img/photos.svg b/img/photos.svg
new file mode 100644
index 00000000..90c3fdb6
--- /dev/null
+++ b/img/photos.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="32" width="32" version="1"><g transform="translate(-11.5 2.5)"><path d="M20.5 7.5a1 1 0 00-1 1v17c0 .5.6 1 1 1h20c.5 0 1-.5 1-1v-17c0-.4-.5-1-1-1zM21 9h19v14.5H21z"/><circle cx="24.8" cy="13" r="2.3"/><path d="M38.4 15.5L35 20.2 33.6 22l-1.2-1.4-3.5-3.5-4.5 4.3-3.6 3.3h19.9v-6.6zM14.5 2.5a1 1 0 00-1 1v17c0 .5.6 1 1 1h6v-3H15V4h19v3.5h1.5v-4c0-.4-.5-1-1-1h-20z"/></g></svg> \ No newline at end of file
diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php
index 52139b4d..97ab98b4 100644
--- a/lib/AppInfo/Application.php
+++ b/lib/AppInfo/Application.php
@@ -28,9 +28,9 @@ use OCP\AppFramework\App;
class Application extends App {
- const appID = 'photos';
+ const APP_ID = 'photos';
public function __construct() {
- parent::__construct(self::appID);
+ parent::__construct(self::APP_ID);
}
}
diff --git a/lib/Controller/PageController.php b/lib/Controller/PageController.php
index 7f9538a6..8529441e 100644
--- a/lib/Controller/PageController.php
+++ b/lib/Controller/PageController.php
@@ -30,6 +30,7 @@ use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IRequest;
+use OCP\Util;
class PageController extends Controller {
@@ -58,6 +59,10 @@ class PageController extends Controller {
$this->eventDispatcher->dispatch(LoadSidebar::class, new LoadSidebar());
$this->eventDispatcher->dispatch(LoadViewer::class, new LoadViewer());
+
+ Util::addScript('photos', 'photos');
+ Util::addStyle('photos', 'icons');
+
$response = new TemplateResponse($this->appName, 'main');
return $response;
}
diff --git a/package-lock.json b/package-lock.json
index c53e7e30..39a3b402 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,5 +1,5 @@
{
- "name": "gallery",
+ "name": "photos",
"version": "19.0.0",
"lockfileVersion": 1,
"requires": true,
@@ -872,18 +872,18 @@
}
},
"@nextcloud/vue": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@nextcloud/vue/-/vue-1.0.0.tgz",
- "integrity": "sha512-jYggwGf9so7g9uWP59cLspSo62uN7qX0+T096T/QBxyiEhoa+yspf9+Py/RIDylLacceKPDLTU4AdxELj63ZYQ==",
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@nextcloud/vue/-/vue-1.1.0.tgz",
+ "integrity": "sha512-SGWrNTalT/59vElPnPJZ0xQb9sui1yq5fKRiIA2w81NlAK9JQGq+ADeqdVb8sciVnLGobQJ1qERhpo3pIyOhaQ==",
"requires": {
"@babel/polyfill": "^7.4.4",
"@nextcloud/axios": "^0.4.0",
"escape-html": "^1.0.3",
"hammerjs": "^2.0.8",
"md5": "^2.2.1",
+ "v-click-outside": "^2.1.4",
"v-tooltip": "^2.0.0-rc.33",
"vue": "^2.6.7",
- "vue-click-outside": "^1.0.7",
"vue-color": "^2.7.0",
"vue-multiselect": "^2.1.3",
"vue-visible": "^1.0.2",
@@ -9307,6 +9307,11 @@
"integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==",
"dev": true
},
+ "v-click-outside": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/v-click-outside/-/v-click-outside-2.1.5.tgz",
+ "integrity": "sha512-VPNCOTZK6WZy73lcWc+R7IW1uaBFEO3/Csrs5CzWVOdvE30V8Y1+BE/BtTlcEmeDGx0eqdE7bSCg55Jj37PMJg=="
+ },
"v-tooltip": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/v-tooltip/-/v-tooltip-2.0.2.tgz",
@@ -9382,11 +9387,6 @@
"resolved": "https://registry.npmjs.org/vue/-/vue-2.6.10.tgz",
"integrity": "sha512-ImThpeNU9HbdZL3utgMCq0oiMzAkt1mcgy3/E6zWC/G6AaQoeuFdsl9nDhTDU3X1R6FK7nsIUuRACVcjI+A2GQ=="
},
- "vue-click-outside": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/vue-click-outside/-/vue-click-outside-1.0.7.tgz",
- "integrity": "sha1-zdKxYF48SUR4TheU6uShKg9wC9Y="
- },
"vue-color": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/vue-color/-/vue-color-2.7.0.tgz",
@@ -9506,9 +9506,9 @@
"integrity": "sha512-yaX2its9XAJKGuQqf7LsiZHHSkxsIK8rmCOQOvEGEoF41blKRK8qr9my4qYoD6ikdLss4n8tKqYBecmaY0+WJg=="
},
"vue2-datepicker": {
- "version": "2.13.2",
- "resolved": "https://registry.npmjs.org/vue2-datepicker/-/vue2-datepicker-2.13.2.tgz",
- "integrity": "sha512-bgtCdSTpFJogL37A5n2HnNPkyKVi0WTiM2+H+fYTHVYbRpSyNaPQ1Kj86A6tx3T14cv6qq4Oo8MrCxXiarDx2w==",
+ "version": "2.13.3",
+ "resolved": "https://registry.npmjs.org/vue2-datepicker/-/vue2-datepicker-2.13.3.tgz",
+ "integrity": "sha512-kAiTpCLlDC88mMTW5OqhlME0ZSB1fJKlHbKSEryPIi3lRJWHn4BlRSvGUTnSmBUr/5Qidma7Pxei9vih9Luicw==",
"requires": {
"fecha": "^2.3.3"
}
diff --git a/package.json b/package.json
index 825f4348..abc6819f 100644
--- a/package.json
+++ b/package.json
@@ -35,7 +35,7 @@
"@nextcloud/axios": "^0.5.0",
"@nextcloud/l10n": "^0.2.1",
"@nextcloud/router": "^0.1.0",
- "@nextcloud/vue": "^1.0.0",
+ "@nextcloud/vue": "^1.1.0",
"cdav-library": "git+https://github.com/nextcloud/cdav-library.git",
"path-posix": "^1.0.0",
"qs": "^6.9.0",
diff --git a/src/Gallery.vue b/src/Photos.vue
index cb9a8882..ec26f323 100644
--- a/src/Gallery.vue
+++ b/src/Photos.vue
@@ -22,6 +22,17 @@
<template>
<Content app-name="photos">
+ <AppNavigation>
+ <AppNavigationItem :to="{name: 'root'}"
+ class="app-navigation__photos"
+ :title="t('photos', 'Your photos')"
+ icon="icon-photos" />
+ <AppNavigationItem to="/favorites" :title="t('photos', 'Favorites')" icon="icon-favorite" />
+ <AppNavigationItem :to="{name: 'albums'}" :title="t('photos', 'Your albums')" icon="icon-files-dark" />
+ <AppNavigationItem :to="{name: 'shared'}" :title="t('photos', 'Shared albums')" icon="icon-share" />
+ <AppNavigationItem :to="{name: 'tags'}" :title="t('photos', 'Tags')" icon="icon-tag" />
+ <AppNavigationItem :to="{name: 'maps'}" :title="t('photos', 'Locations')" icon="icon-address" />
+ </AppNavigation>
<AppContent :class="{ 'icon-loading': loading }">
<router-view v-show="!loading" :loading.sync="loading" />
@@ -34,6 +45,8 @@
<script>
import Content from '@nextcloud/vue/dist/Components/Content'
import AppContent from '@nextcloud/vue/dist/Components/AppContent'
+import AppNavigation from '@nextcloud/vue/dist/Components/AppNavigation'
+import AppNavigationItem from '@nextcloud/vue/dist/Components/AppNavigationItem'
import svgplaceholder from './assets/img-placeholder.svg'
export default {
@@ -41,6 +54,8 @@ export default {
components: {
Content,
AppContent,
+ AppNavigation,
+ AppNavigationItem,
},
data: function() {
return {
@@ -50,3 +65,8 @@ export default {
},
}
</script>
+<style lang="scss" scoped>
+.app-navigation__photos::v-deep .app-navigation-entry-icon.icon-photos {
+ background-size: 20px;
+}
+</style>
diff --git a/src/components/Folder.vue b/src/components/Folder.vue
index 5dec6b5a..de2db5a6 100644
--- a/src/components/Folder.vue
+++ b/src/components/Folder.vue
@@ -23,7 +23,7 @@
<template>
<router-link :class="{'folder--clear': isEmpty}"
class="folder"
- :to="folder.filename"
+ :to="to"
:aria-label="ariaLabel">
<transition name="fade">
<div v-show="loaded"
@@ -39,8 +39,8 @@
</transition>
<div
class="folder-name">
- <span :class="{'icon-white': !isEmpty}"
- class="folder-name__icon icon-folder"
+ <span :class="[!isEmpty ? 'icon-white' : 'icon-dark', icon]"
+ class="folder-name__icon"
role="img" />
<p :id="ariaUuid" class="folder-name__name">
{{ folder.basename }}
@@ -66,6 +66,10 @@ export default {
type: Object,
required: true,
},
+ icon: {
+ type: String,
+ default: 'icon-folder',
+ },
},
data() {
@@ -106,6 +110,20 @@ export default {
ariaLabel() {
return t('photos', 'Open the "{name}" sub-directory', { name: this.folder.basename })
},
+
+ /**
+ * We do not want encoded slashes when browsing by folder
+ * so we generate a new valid route object, get the final url back
+ * decode it and use it as a direct string, which vue-router
+ * does not encode afterwards
+ */
+ to() {
+ const route = Object.assign({}, this.$route, {
+ // always remove first slash
+ params: { path: this.folder.filename.substr(1) }
+ });
+ return decodeURIComponent(this.$router.resolve(route).resolved.path)
+ },
},
async created() {
@@ -215,6 +233,9 @@ $name-height: 1.2rem;
.folder {
// if no img, let's display the folder icon as default black
&--clear {
+ .folder-name__icon {
+ opacity: .3;
+ }
.folder-name__name {
color: var(--color-main-text);
text-shadow: 0 0 8px var(--color-main-background);
diff --git a/src/components/Grid.vue b/src/components/Grid.vue
new file mode 100644
index 00000000..85023f5a
--- /dev/null
+++ b/src/components/Grid.vue
@@ -0,0 +1,87 @@
+<!--
+ - @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>
+ <!-- Folder content -->
+ <transition-group
+ class="photos-grid"
+ role="grid"
+ name="list"
+ tag="div">
+ <slot />
+ <div key="footer" role="none" class="photos-grid__footer-spacer" />
+ </transition-group>
+</template>
+
+<script>
+export default {
+ name: 'Grid',
+}
+</script>
+
+<style scoped lang="scss">
+.photos-grid {
+ display: grid;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ grid-template-columns: repeat(10, 1fr);
+ position: relative;
+
+ // always put one more row of grid for the spacer
+ &__footer-spacer {
+ // always add one row, so placing it on the first
+ // column will always add one more
+ grid-column: 1;
+ // same height as the width
+ padding-bottom: 100%;
+ }
+}
+
+.list-move {
+ transition: transform var(--animation-quick);
+}
+
+// TODO: use mixins/GridSizes as soon as node-sass supports it
+// needs node-sass 5.0 (with libsass 3.6)
+// https://github.com/sass/node-sass/pull/2312
+$previous: 0;
+@each $size, $config in get('sizes') {
+ $count: map-get($config, 'count');
+ $marginTop: map-get($config, 'marginTop');
+ $marginW: map-get($config, 'marginW');
+
+ // if this is the last entry, only use min-width
+ $rule: '(min-width: #{$previous}px) and (max-width: #{$size}px)';
+ @if $size == 'max' {
+ $rule: '(min-width: #{$previous}px)';
+ }
+
+ @media #{$rule} {
+ .photos-grid {
+ padding: #{$marginTop}px #{$marginW}px #{$marginW}px #{$marginW}px;
+ grid-template-columns: repeat($count, 1fr);
+ }
+ }
+ $previous: $size;
+}
+</style>
diff --git a/src/components/Navigation.vue b/src/components/Navigation.vue
index facbdf10..df1c3f0f 100644
--- a/src/components/Navigation.vue
+++ b/src/components/Navigation.vue
@@ -89,11 +89,25 @@ export default {
}
return t('photos', 'Back to {folder}', { folder: this.parentName })
},
+
+ /**
+ * We do not want encoded slashes when browsing by folder
+ * so we generate a new valid route object, get the final url back
+ * decode it and use it as a direct string, which vue-router
+ * does not encode afterwards
+ */
+ to() {
+ const route = Object.assign({}, this.$route, {
+ // always remove first slash
+ params: { path: this.parentPath.substr(1) }
+ });
+ return decodeURIComponent(this.$router.resolve(route).resolved.path)
+ },
},
methods: {
folderUp() {
- this.$router.push(this.parentPath)
+ this.$router.push(this.to)
},
},
}
diff --git a/src/main.js b/src/main.js
index 595ecb95..5e4a96d3 100644
--- a/src/main.js
+++ b/src/main.js
@@ -26,7 +26,7 @@ import { sync } from 'vuex-router-sync'
import { translate, translatePlural } from '@nextcloud/l10n'
import Vue from 'vue'
-import Gallery from './Gallery'
+import Photos from './Photos'
import router from './router'
import store from './store'
@@ -49,8 +49,8 @@ Vue.prototype.n = translatePlural
export default new Vue({
el: '#content',
// eslint-disable-next-line vue/match-component-file-name
- name: 'GalleryRoot',
+ name: 'PhotosRoot',
router,
store,
- render: h => h(Gallery),
+ render: h => h(Photos),
})
diff --git a/src/router/index.js b/src/router/index.js
index 80dcdd5e..df464138 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -24,10 +24,17 @@ import { generateUrl } from '@nextcloud/router'
import Router from 'vue-router'
import Vue from 'vue'
-import Grid from '../views/Grid'
+import Albums from '../views/Albums'
+import Tags from '../views/Tags'
Vue.use(Router)
+// shortcut to properly format the path prop
+const props = route => ({
+ // always lead current path with a slash
+ path: `/${route.params.path ? route.params.path : ''}`,
+})
+
export default new Router({
mode: 'history',
// if index.php is in the url AND we got this far, then it's working:
@@ -37,20 +44,65 @@ export default new Router({
routes: [
{
path: '/',
- component: Grid,
- props: route => ({
- // always lead current path with a slash
- path: `/${route.params.path ? route.params.path : ''}`,
- }),
+ component: Albums,
name: 'root',
+ },
+ {
+ path: '/albums',
+ component: Albums,
+ name: 'albums',
+ props,
+ children: [
+ {
+ path: ':path*',
+ name: 'path',
+ component: Albums,
+ },
+ ],
+ },
+ {
+ path: '/shared',
+ component: Albums,
+ name: 'shared',
+ props,
+ children: [
+ {
+ path: ':path*',
+ name: 'path',
+ component: Albums,
+ },
+ ],
+ },
+ {
+ path: '/favorites',
+ component: Tags,
+ name: 'favorites',
+ props,
children: [
{
path: ':path*',
name: 'path',
- component: Grid,
+ component: Tags,
},
],
},
- { path: '*', redirect: { name: 'root' } },
+ {
+ path: '/tags',
+ component: Tags,
+ name: 'tags',
+ props,
+ children: [
+ {
+ path: ':path*',
+ name: 'path',
+ component: Tags,
+ },
+ ],
+ },
+ {
+ path: '/maps',
+ name: 'maps',
+ redirect: '',
+ },
],
})
diff --git a/src/services/DavClient.js b/src/services/DavClient.js
index acdfdf1b..c91b57e8 100644
--- a/src/services/DavClient.js
+++ b/src/services/DavClient.js
@@ -24,14 +24,13 @@ import webdav from 'webdav'
import axios from '@nextcloud/axios'
import parseUrl from 'url-parse'
import { generateRemoteUrl } from '@nextcloud/router'
-import { getCurrentUser } from '@nextcloud/auth'
// force our axios
const patcher = webdav.getPatcher()
patcher.patch('request', axios)
// init webdav client
-const remote = generateRemoteUrl(`dav/files/${getCurrentUser().uid}`)
+const remote = generateRemoteUrl(`dav`)
const client = webdav.createClient(remote)
export const remotePath = parseUrl(remote).pathname
diff --git a/src/services/FileList.js b/src/services/FileList.js
index 2ee84328..80e9df6d 100644
--- a/src/services/FileList.js
+++ b/src/services/FileList.js
@@ -20,12 +20,14 @@
*
*/
+import { getCurrentUser } from '@nextcloud/auth'
import { getSingleValue, getValueForKey, parseXML, propsToStat } from 'webdav/dist/interface/dav'
import { handleResponseCode, processResponsePayload } from 'webdav/dist/response'
import { normaliseHREF, normalisePath } from 'webdav/dist/url'
import client, { remotePath } from './DavClient'
import pathPosix from 'path-posix'
import request from './DavRequest'
+import parseFile from '../utils/ParseFile'
/**
* List files from a folder and filter out unwanted mimes
@@ -46,6 +48,8 @@ export default async function(path, options) {
details: true,
}, options)
+ const prefixPath = `/files/${getCurrentUser().uid}`
+
/**
* Fetch listing
*
@@ -54,7 +58,7 @@ export default async function(path, options) {
* see https://github.com/perry-mitchell/webdav-client/blob/baf858a4856d44ae19ac12cb10c469b3e6c41ae4/source/interface/directoryContents.js#L11
*/
let response = null
- const { data } = await client.customRequest(path, options)
+ const { data } = await client.customRequest(prefixPath + path, options)
.then(handleResponseCode)
.then(res => {
response = res
@@ -64,14 +68,7 @@ export default async function(path, options) {
.then(result => getDirectoryFiles(result, remotePath, options.details))
.then(files => processResponsePayload(response, files, options.details))
- const list = data
- .map(entry => {
- return Object.assign({
- id: parseInt(entry.props.fileid),
- isFavorite: entry.props.favorite !== '0',
- hasPreview: entry.props['has-preview'] !== 'false',
- }, entry)
- })
+ const list = data.map(data => parseFile(data, prefixPath))
// filter all the files and folders
let folder = {}
@@ -91,6 +88,15 @@ export default async function(path, options) {
return { folder, folders, files }
}
+/**
+ * Modified function to include the root requested folder
+ * Into the returned data
+ *
+ * @param {Object} result the request result
+ * @param {string} serverBasePath server base path
+ * @param {boolean} isDetailed detailed request
+ * @returns {Array}
+ */
function getDirectoryFiles(result, serverBasePath, isDetailed = false) {
const serverBase = pathPosix.join(serverBasePath, '/')
// Extract the response items (directory contents)
diff --git a/src/services/FolderInfo.js b/src/services/FolderInfo.js
index b95d7c67..6d65b57a 100644
--- a/src/services/FolderInfo.js
+++ b/src/services/FolderInfo.js
@@ -20,8 +20,10 @@
*
*/
+import { getCurrentUser } from '@nextcloud/auth'
import client from './DavClient'
import request from './DavRequest'
+import parseFile from '../utils/ParseFile'
/**
* List files from a folder and filter out unwanted mimes
@@ -33,17 +35,13 @@ export default async function(path) {
// getDirectoryContents doesn't accept / for root
const fixedPath = path === '/' ? '' : path
+ const prefixPath = `/files/${getCurrentUser().uid}`
+
// fetch listing
- const response = await client.stat(fixedPath, {
+ const response = await client.stat(prefixPath + fixedPath, {
data: request,
details: true,
})
- const entry = response.data
- return Object.assign({
- id: parseInt(entry.props.fileid),
- isFavorite: entry.props.favorite !== '0',
- hasPreview: entry.props['has-preview'] !== 'false',
- }, entry)
-
+ return parseFile(response.data, prefixPath)
}
diff --git a/src/services/PhotoSearch.js b/src/services/PhotoSearch.js
index b780ba2c..e4546bb4 100644
--- a/src/services/PhotoSearch.js
+++ b/src/services/PhotoSearch.js
@@ -20,8 +20,10 @@
*
*/
-import client from './DavClient'
+import { generateRemoteUrl } from '@nextcloud/router'
import { getCurrentUser } from '@nextcloud/auth'
+import client from './DavClient'
+import parseFile from '../utils/ParseFile'
/**
* List files from a folder and filter out unwanted mimes
@@ -35,7 +37,7 @@ export default async function() {
headers: {
'content-Type': 'text/xml',
},
- url: '/remote.php/dav/',
+ url: generateRemoteUrl(`dav`),
data: `<?xml version="1.0" encoding="UTF-8"?>
<d:searchrequest xmlns:d="DAV:"
xmlns:oc="http://owncloud.org/ns"
diff --git a/src/services/SystemTags.js b/src/services/SystemTags.js
new file mode 100644
index 00000000..a8ce55eb
--- /dev/null
+++ b/src/services/SystemTags.js
@@ -0,0 +1,56 @@
+/**
+ * @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/>.
+ *
+ */
+
+import client from './DavClient'
+import { generateRemoteUrl } from '@nextcloud/router'
+
+/**
+ * List files from a folder and filter out unwanted mimes
+ *
+ * @returns {Array} the file list
+ */
+export default async function() {
+ const response = await client.getDirectoryContents('/systemtags/', {
+ data: `<?xml version="1.0"?>
+ <d:propfind xmlns:d="DAV:"
+ xmlns:oc="http://owncloud.org/ns">
+ <d:prop>
+ <oc:id />
+ <oc:display-name />
+ <oc:user-visible />
+ <oc:user-assignable />
+ <oc:can-assign />
+ </d:prop>
+ </d:propfind>`,
+ details: true,
+ })
+
+ console.info(response)
+
+ const entry = response.data
+ return Object.assign({
+ id: parseInt(entry.props.fileid),
+ isFavorite: entry.props.favorite !== '0',
+ hasPreview: entry.props['has-preview'] !== 'false',
+ }, entry)
+
+}
diff --git a/src/store/index.js b/src/store/index.js
index d7593570..e48270b7 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -25,12 +25,14 @@ import Vuex, { Store } from 'vuex'
import files from './files'
import folders from './folders'
+import systemtags from './systemtags'
Vue.use(Vuex)
export default new Store({
modules: {
files,
folders,
+ systemtags,
},
strict: process.env.NODE_ENV !== 'production',
diff --git a/src/store/systemtags.js b/src/store/systemtags.js
new file mode 100644
index 00000000..8f213e9e
--- /dev/null
+++ b/src/store/systemtags.js
@@ -0,0 +1,99 @@
+/**
+ * @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/>.
+ *
+ */
+import Vue from 'vue'
+
+const state = {
+ paths: {},
+ tags: {},
+}
+
+const mutations = {
+ /**
+ * Index folders paths and ids
+ *
+ * @param {Object} state vuex state
+ * @param {Object} data destructuring object
+ * @param {number} data.id current folder id
+ * @param {Array} data.files list of files
+ */
+ updateTags(state, { id, files }) {
+ if (files.length > 0) {
+ // sort by last modified
+ const list = files.sort((a, b) => {
+ return new Date(b.lastmod).getTime() - new Date(a.lastmod).getTime()
+ })
+
+ // Set folder list
+ Vue.set(state.tags, id, list.map(file => file.id))
+ }
+ },
+
+ /**
+ * Index folders paths and ids
+ *
+ * @param {Object} state vuex state
+ * @param {Object} data destructuring object
+ * @param {string} data.path path of this folder
+ * @param {number} data.id id of this folder
+ */
+ addPath(state, { path, id }) {
+ Vue.set(state.paths, path, id)
+ },
+}
+
+const getters = {
+ tags: state => state.tags,
+ tag: state => id => state.tags[id],
+ tagId: state => path => state.paths[path],
+}
+
+const actions = {
+ /**
+ * Update files and folders
+ *
+ * @param {Object} context vuex context
+ * @param {Object} data destructuring object
+ * @param {number} data.id current folder id
+ * @param {Array} data.files list of files
+ * @param {Array} data.folders list of folders
+ */
+ updateTags(context, { id, files, folders }) {
+ context.commit('updateTags', { id, files })
+
+ // then add each folders path indexes
+ folders.forEach(folder => context.commit('addPath', { path: folder.filename, id: folder.id }))
+ },
+
+ /**
+ * Index folders paths and ids
+ *
+ * @param {Object} context vuex context
+ * @param {Object} data destructuring object
+ * @param {string} data.path path of this folder
+ * @param {number} data.id id of this folder
+ */
+ addPath(context, { path, id }) {
+ context.commit('addPath', { path, id })
+ },
+}
+
+export default { state, mutations, getters, actions }
diff --git a/src/utils/ParseFile.js b/src/utils/ParseFile.js
new file mode 100644
index 00000000..8d9d9baf
--- /dev/null
+++ b/src/utils/ParseFile.js
@@ -0,0 +1,37 @@
+/**
+ * @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/>.
+ *
+ */
+
+/**
+ * Format a file into a usable fileinfo object
+ *
+ * @param {Object} fileData file data returned by the webdav lib
+ * @param {String} prefixPath path to substract from the files
+ * @returns {Object}
+ */
+export default function(fileData, prefixPath = '') {
+ const filename = fileData.filename.replace(prefixPath, '/').replace(/^\/\//, '/')
+ return Object.assign({
+ id: parseInt(fileData.props.fileid),
+ isFavorite: fileData.props.favorite !== '0',
+ hasPreview: fileData.props['has-preview'] !== 'false',
+ }, fileData, { filename })
+}
diff --git a/src/views/Grid.vue b/src/views/Albums.vue
index 849fd99b..98642e3d 100644
--- a/src/views/Grid.vue
+++ b/src/views/Albums.vue
@@ -33,16 +33,11 @@
</EmptyContent>
<!-- Folder content -->
- <transition-group v-else
- class="photos-grid"
- role="grid"
- name="list"
- tag="div">
+ <Grid v-else>
<Navigation v-if="folder" key="navigation" v-bind="folder" />
<Folder v-for="dir in folderList" :key="dir.id" :folder="dir" />
<File v-for="file in fileList" :key="file.id" v-bind="file" />
- <div key="footer" role="none" class="photos-grid__footer-spacer" />
- </transition-group>
+ </Grid>
</template>
<script>
@@ -55,16 +50,18 @@ import getPictures from '../services/FileList'
import EmptyContent from './EmptyContent'
import Folder from '../components/Folder'
import File from '../components/File'
+import Grid from '../components/Grid'
import Navigation from '../components/Navigation'
import cancelableRequest from '../utils/CancelableRequest'
export default {
- name: 'Grid',
+ name: 'Albums',
components: {
EmptyContent,
File,
Folder,
+ Grid,
Navigation,
},
props: {
@@ -205,50 +202,3 @@ export default {
}
</script>
-
-<style lang="scss">
-.photos-grid {
- display: grid;
- align-items: center;
- justify-content: center;
- gap: 8px;
- grid-template-columns: repeat(10, 1fr);
- position: relative;
- // always put one more row of grid for the spacer
- &__footer-spacer {
- // always add one row, so placing it on the first
- // column will always add one more
- grid-column: 1;
- // same height as the width
- padding-bottom: 100%;
- }
-}
-
-.list-move {
- transition: transform var(--animation-quick);
-}
-
-// TODO: use mixins/GridSizes as soon as node-sass supports it
-// needs node-sass 5.0 (with libsass 3.6)
-// https://github.com/sass/node-sass/pull/2312
-$previous: 0;
-@each $size, $config in get('sizes') {
- $count: map-get($config, 'count');
- $marginTop: map-get($config, 'marginTop');
- $marginW: map-get($config, 'marginW');
-
- // if this is the last entry, only use min-width
- $rule: '(min-width: #{$previous}px) and (max-width: #{$size}px)';
- @if $size == 'max' {
- $rule: '(min-width: #{$previous}px)';
- }
-
- @media #{$rule} {
- .photos-grid {
- padding: #{$marginTop}px #{$marginW}px #{$marginW}px #{$marginW}px;
- grid-template-columns: repeat($count, 1fr);
- }
- }
- $previous: $size;
-}
-</style>
diff --git a/src/views/Tags.vue b/src/views/Tags.vue
new file mode 100644
index 00000000..8180b2bc
--- /dev/null
+++ b/src/views/Tags.vue
@@ -0,0 +1,111 @@
+<!--
+ - @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', 'This folder does not contain pictures') }}
+ </EmptyContent> -->
+
+ <!-- Folder content -->
+ <!-- <Grid v-else>
+ <Navigation v-if="folder" key="navigation" v-bind="folder" />
+ <Folder v-for="dir in folderList" :key="dir.id" :folder="dir" />
+ <File v-for="file in fileList" :key="file.id" v-bind="file" />
+ </Grid> -->
+ <span>Test</span>
+</template>
+
+<script>
+import { mapGetters } from 'vuex'
+
+import getSystemTags from '../services/SystemTags'
+
+import EmptyContent from './EmptyContent'
+import Folder from '../components/Folder'
+import File from '../components/File'
+import Grid from '../components/Grid'
+import Navigation from '../components/Navigation'
+
+import cancelableRequest from '../utils/CancelableRequest'
+
+export default {
+ name: 'Tags',
+ components: {
+ EmptyContent,
+ File,
+ Folder,
+ Grid,
+ Navigation,
+ },
+ props: {
+ path: {
+ type: String,
+ default: '/',
+ },
+ loading: {
+ type: Boolean,
+ required: true,
+ },
+ },
+
+ data() {
+ return {
+ error: null,
+ cancelRequest: () => {},
+ }
+ },
+
+ computed: {
+ // global lists
+ ...mapGetters([
+ 'files',
+ 'tags',
+ ]),
+ },
+
+ watch: {
+ path(path) {
+ console.debug('changed:', path)
+ this.fetchFolderContent()
+ },
+ },
+
+ async beforeMount() {
+ console.debug('beforemount: GRID')
+ this.fetchFolderContent()
+ },
+
+ methods: {
+ async fetchFolderContent() {
+ await getSystemTags()
+ },
+ },
+
+}
+</script>
diff --git a/templates/main.php b/templates/main.php
index 94e41675..1adb9af1 100644
--- a/templates/main.php
+++ b/templates/main.php
@@ -1,4 +1,25 @@
<?php
-script('photos', 'photos');
+/**
+ * @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/>.
+ *
+ */
?>
+
<div id="content"></div>
diff --git a/webpack.common.js b/webpack.common.js
index b9bbc35a..47979290 100644
--- a/webpack.common.js
+++ b/webpack.common.js
@@ -14,13 +14,13 @@ module.exports = {
path: path.resolve(__dirname, './js'),
publicPath: '/js/',
filename: `${appName}.js`,
- chunkFilename: 'chunks/[name]-[hash].js'
+ chunkFilename: 'chunks/[name]-[hash].js',
},
module: {
rules: [
{
test: /\.css$/,
- use: ['vue-style-loader', 'css-loader', 'postcss-loader']
+ use: ['vue-style-loader', 'css-loader', 'postcss-loader'],
},
{
test: /\.scss$/,
@@ -32,34 +32,34 @@ module.exports = {
loader: 'sass-loader',
options: {
functions: {
- 'get($keys)': SassGetGridConfig
- }
- }
- }
- ]
+ 'get($keys)': SassGetGridConfig,
+ },
+ },
+ },
+ ],
},
{
test: /\.(js|vue)$/,
use: 'eslint-loader',
exclude: /node_modules/,
- enforce: 'pre'
+ enforce: 'pre',
},
{
test: /\.vue$/,
loader: 'vue-loader',
- exclude: /node_modules/
+ exclude: /node_modules/,
},
{
test: /\.js$/,
loader: 'babel-loader',
- exclude: /node_modules(?!(\/|\\)(hot-patcher|webdav)(\/|\\))/
+ exclude: /node_modules(?!(\/|\\)(hot-patcher|webdav)(\/|\\))/,
},
{
test: /\.svg$/,
// illustrations
- loader: 'svg-inline-loader'
- }
- ]
+ loader: 'svg-inline-loader',
+ },
+ ],
},
plugins: [
new VueLoaderPlugin(),
@@ -68,12 +68,12 @@ module.exports = {
new ModuleReplaceWebpackPlugin({
modules: [{
test: /request.js/,
- replace: './src/patchedRequest.js'
- }]
- })
+ replace: './src/patchedRequest.js',
+ }],
+ }),
],
resolve: {
extensions: ['*', '.js', '.vue'],
- symlinks: false
- }
+ symlinks: false,
+ },
}