diff --git a/appinfo/routes.php b/appinfo/routes.php
index 7c63c51b..e11adb3c 100644
--- a/appinfo/routes.php
+++ b/appinfo/routes.php
@@ -15,6 +15,14 @@ return [
['name' => 'page#videos', 'url' => '/videos', 'verb' => 'GET'],
['name' => 'page#archive', 'url' => '/archive', 'verb' => 'GET'],
['name' => 'page#thisday', 'url' => '/thisday', 'verb' => 'GET'],
+ ['name' => 'page#tags', 'url' => '/tags/{name}', 'verb' => 'GET',
+ 'requirements' => [
+ 'name' => '.*',
+ ],
+ 'defaults' => [
+ 'name' => '',
+ ]
+ ],
// API
['name' => 'api#days', 'url' => '/api/days', 'verb' => 'GET'],
diff --git a/lib/Controller/PageController.php b/lib/Controller/PageController.php
index 3f62dfa7..c89a385f 100644
--- a/lib/Controller/PageController.php
+++ b/lib/Controller/PageController.php
@@ -104,4 +104,12 @@ class PageController extends Controller {
public function thisday() {
return $this->main();
}
+
+ /**
+ * @NoAdminRequired
+ * @NoCSRFRequired
+ */
+ public function tags() {
+ return $this->main();
+ }
}
diff --git a/src/App.vue b/src/App.vue
index 55e702dd..41fa8437 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -27,6 +27,10 @@
:title="t('memories', 'On this day')">
+
+
+
@@ -74,6 +78,7 @@ import Star from 'vue-material-design-icons/Star.vue'
import Video from 'vue-material-design-icons/Video.vue'
import ArchiveIcon from 'vue-material-design-icons/PackageDown.vue';
import CalendarIcon from 'vue-material-design-icons/Calendar.vue';
+import TagsIcon from 'vue-material-design-icons/Tag.vue';
@Component({
components: {
@@ -92,6 +97,7 @@ import CalendarIcon from 'vue-material-design-icons/Calendar.vue';
Video,
ArchiveIcon,
CalendarIcon,
+ TagsIcon,
},
})
export default class App extends Mixins(GlobalMixin) {
diff --git a/src/components/Tag.vue b/src/components/Tag.vue
new file mode 100644
index 00000000..5385414a
--- /dev/null
+++ b/src/components/Tag.vue
@@ -0,0 +1,159 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/components/Timeline.vue b/src/components/Timeline.vue
index 9033c822..e73dca7e 100644
--- a/src/components/Timeline.vue
+++ b/src/components/Timeline.vue
@@ -42,6 +42,12 @@
:data="photo"
:rowHeight="rowHeight"
:key="photo.fileid" />
+
+
+
(generateUrl(this.appendQuery(url), params))).data;
}
@@ -873,9 +891,9 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
photo.flag |= this.c.FLAG_IS_FAVORITE;
delete photo.isfavorite;
}
- if (photo.isfolder) {
- photo.flag |= this.c.FLAG_IS_FOLDER;
- delete photo.isfolder;
+ if (photo.istag) {
+ photo.flag |= this.c.FLAG_IS_TAG;
+ delete photo.istag;
}
// Move to next index of photo
diff --git a/src/mixins/GlobalMixin.ts b/src/mixins/GlobalMixin.ts
index 44718c85..492108a4 100644
--- a/src/mixins/GlobalMixin.ts
+++ b/src/mixins/GlobalMixin.ts
@@ -1,27 +1,12 @@
import { Component, Vue } from 'vue-property-decorator';
import { translate as t, translatePlural as n } from '@nextcloud/l10n'
+import { constants } from '../services/Utils';
@Component
export default class GlobalMixin extends Vue {
public readonly t = t;
public readonly n = n;
- public readonly c = {
- FLAG_PLACEHOLDER: 1 << 0,
- FLAG_LOADED: 1 << 1,
- FLAG_LOAD_FAIL: 1 << 2,
- FLAG_IS_VIDEO: 1 << 3,
- FLAG_IS_FAVORITE: 1 << 4,
- FLAG_IS_FOLDER: 1 << 5,
- FLAG_SELECTED: 1 << 6,
- FLAG_LEAVING: 1 << 7,
- FLAG_EXIT_LEFT: 1 << 8,
- FLAG_ENTER_RIGHT: 1 << 9,
- FLAG_FORCE_RELOAD: 1 << 10,
- }
-
- public readonly TagDayID = {
- START: -(1 << 30),
- FOLDERS: -(1 << 30) + 1,
- }
+ public readonly c = constants.c;
+ public readonly TagDayID = constants.TagDayID;
}
\ No newline at end of file
diff --git a/src/router.ts b/src/router.ts
index 3a7345ad..b65ded14 100644
--- a/src/router.ts
+++ b/src/router.ts
@@ -46,16 +46,16 @@
base: generateUrl('/apps/memories'),
linkActiveClass: 'active',
routes: [
- {
- path: '/',
- component: Timeline,
- name: 'timeline',
- props: route => ({
- rootTitle: t('memories', 'Timeline'),
- }),
- },
+ {
+ path: '/',
+ component: Timeline,
+ name: 'timeline',
+ props: route => ({
+ rootTitle: t('memories', 'Timeline'),
+ }),
+ },
- {
+ {
path: '/folders/:path*',
component: Timeline,
name: 'folders',
@@ -99,5 +99,14 @@
rootTitle: t('memories', 'On this day'),
}),
},
+
+ {
+ path: '/tags/:name*',
+ component: Timeline,
+ name: 'tags',
+ props: route => ({
+ rootTitle: t('memories', 'Tags'),
+ }),
+ },
],
})
\ No newline at end of file
diff --git a/src/services/DavRequests.ts b/src/services/DavRequests.ts
index 181d9baf..fbaee789 100644
--- a/src/services/DavRequests.ts
+++ b/src/services/DavRequests.ts
@@ -4,7 +4,8 @@ import { encodePath } from '@nextcloud/paths'
import { showError } from '@nextcloud/dialogs'
import { translate as t, translatePlural as n } from '@nextcloud/l10n'
import { genFileInfo } from './FileUtils'
-import { IDay, IFileInfo, IPhoto } from '../types';
+import { IDay, IFileInfo, IPhoto, ITag } from '../types';
+import { constants, hashCode } from './Utils';
import axios from '@nextcloud/axios'
import client from './DavClient';
@@ -374,7 +375,7 @@ export async function downloadFilesByIds(fileIds: number[]) {
* Get the onThisDay data
* Query for last 120 years; should be enough
*/
-export async function getOnThisDayData() {
+export async function getOnThisDayData(): Promise {
const diffs: { [dayId: number]: number } = {};
const now = new Date();
const nowUTC = new Date(now.getTime() - now.getTimezoneOffset() * 60000);
@@ -424,4 +425,34 @@ export async function getOnThisDayData() {
}
return ans;
+}
+
+/**
+ * Get list of tags and convert to Days response
+ */
+ export async function getTagsData(): Promise {
+ // Query for photos
+ let data: {
+ count: number;
+ name: string;
+ }[] = [];
+ try {
+ const res = await axios.get(generateUrl('/apps/memories/api/tags'));
+ data = res.data;
+ } catch (e) {
+ throw e;
+ }
+
+ // Convert to days response
+ return [{
+ dayid: constants.TagDayID.TAGS,
+ count: data.length,
+ detail: data.map((tag) => ({
+ name: tag.name,
+ count: tag.count,
+ fileid: hashCode(tag.name),
+ flag: constants.c.FLAG_IS_TAG,
+ istag: true,
+ } as ITag)),
+ }]
}
\ No newline at end of file
diff --git a/src/services/Utils.ts b/src/services/Utils.ts
index 8127ba3b..0a0c399f 100644
--- a/src/services/Utils.ts
+++ b/src/services/Utils.ts
@@ -25,4 +25,43 @@ export function getLongDateStr(date: Date, skipYear=false, time=false) {
hour: time ? 'numeric' : undefined,
minute: time ? 'numeric' : undefined,
});
+}
+
+/**
+ * Returns a hash code from a string
+ * @param {String} str The string to hash.
+ * @return {Number} A 32bit integer
+ * @see http://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/
+ */
+ export function hashCode(str: string): number {
+ let hash = 0;
+ for (let i = 0, len = str.length; i < len; i++) {
+ let chr = str.charCodeAt(i);
+ hash = (hash << 5) - hash + chr;
+ hash |= 0; // Convert to 32bit integer
+ }
+ return hash;
+}
+
+export const constants = {
+ c: {
+ FLAG_PLACEHOLDER: 1 << 0,
+ FLAG_LOADED: 1 << 1,
+ FLAG_LOAD_FAIL: 1 << 2,
+ FLAG_IS_VIDEO: 1 << 3,
+ FLAG_IS_FAVORITE: 1 << 4,
+ FLAG_IS_FOLDER: 1 << 5,
+ FLAG_IS_TAG: 1 << 6,
+ FLAG_SELECTED: 1 << 7,
+ FLAG_LEAVING: 1 << 8,
+ FLAG_EXIT_LEFT: 1 << 9,
+ FLAG_ENTER_RIGHT: 1 << 10,
+ FLAG_FORCE_RELOAD: 1 << 11,
+ },
+
+ TagDayID: {
+ START: -(1 << 30),
+ FOLDERS: -(1 << 30) + 1,
+ TAGS: -(1 << 30) + 2,
+ },
}
\ No newline at end of file
diff --git a/src/types.ts b/src/types.ts
index 380eb9e9..b076581c 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -47,6 +47,8 @@ export type IPhoto = {
isfavorite?: boolean;
/** Is this a folder */
isfolder?: boolean;
+ /** Is this a tag */
+ istag?: boolean;
/** Optional datetaken epoch */
datetaken?: number;
}
@@ -60,6 +62,13 @@ export interface IFolder extends IPhoto {
name: string;
}
+export interface ITag extends IPhoto {
+ /** Name of tag */
+ name: string;
+ /** Number of images in this tag */
+ count: number;
+}
+
export type IRow = {
/** Vue Recycler identifier */
id?: number;