+
@@ -11,7 +11,7 @@
import { defineComponent } from "vue";
import FolderTopMatter from "./FolderTopMatter.vue";
-import TagTopMatter from "./TagTopMatter.vue";
+import ClusterTopMatter from "./ClusterTopMatter.vue";
import FaceTopMatter from "./FaceTopMatter.vue";
import AlbumTopMatter from "./AlbumTopMatter.vue";
@@ -21,7 +21,7 @@ export default defineComponent({
name: "TopMatter",
components: {
FolderTopMatter,
- TagTopMatter,
+ ClusterTopMatter,
FaceTopMatter,
AlbumTopMatter,
},
@@ -47,21 +47,16 @@ export default defineComponent({
switch (this.$route.name) {
case "folders":
return TopMatterType.FOLDER;
+ case "albums":
+ return TopMatterType.ALBUM;
case "tags":
- return this.$route.params.name
- ? TopMatterType.TAG
- : TopMatterType.NONE;
+ case "places":
+ return TopMatterType.CLUSTER;
case "recognize":
case "facerecognition":
return this.$route.params.name
? TopMatterType.FACE
- : TopMatterType.NONE;
- case "albums":
- return TopMatterType.ALBUM;
- case "places":
- return this.$route.params.name
- ? TopMatterType.TAG
- : TopMatterType.NONE;
+ : TopMatterType.CLUSTER;
default:
return TopMatterType.NONE;
}
@@ -70,3 +65,46 @@ export default defineComponent({
},
});
+
+
diff --git a/src/global.scss b/src/global.scss
index b76196e0..1a09081b 100644
--- a/src/global.scss
+++ b/src/global.scss
@@ -94,13 +94,21 @@ body.has-viewer header {
}
// Hide scrollbar
-.recycler::-webkit-scrollbar {
- display: none;
- width: 0 !important;
-}
-.recycler {
+@mixin hide-scrollbar {
scrollbar-width: none;
-ms-overflow-style: none;
+ &::-webkit-scrollbar {
+ display: none;
+ width: 0 !important;
+ }
+}
+.hide-scrollbar {
+ @include hide-scrollbar;
+}
+.hide-scrollbar-mobile {
+ @media (max-width: 768px) {
+ @include hide-scrollbar;
+ }
}
// Make metadata tab scrollbar thin
diff --git a/src/router.ts b/src/router.ts
index e30aafb4..f48faedc 100644
--- a/src/router.ts
+++ b/src/router.ts
@@ -4,6 +4,7 @@ import Router from "vue-router";
import Vue from "vue";
import Timeline from "./components/Timeline.vue";
import SplitTimeline from "./components/SplitTimeline.vue";
+import ClusterView from "./components/ClusterView.vue";
Vue.use(Router);
@@ -52,7 +53,7 @@ export default new Router({
{
path: "/albums/:user?/:name?",
- component: Timeline,
+ component: ClusterView,
name: "albums",
props: (route) => ({
rootTitle: t("memories", "Albums"),
@@ -79,7 +80,7 @@ export default new Router({
{
path: "/recognize/:user?/:name?",
- component: Timeline,
+ component: ClusterView,
name: "recognize",
props: (route) => ({
rootTitle: t("memories", "People"),
@@ -88,7 +89,7 @@ export default new Router({
{
path: "/facerecognition/:user?/:name?",
- component: Timeline,
+ component: ClusterView,
name: "facerecognition",
props: (route) => ({
rootTitle: t("memories", "People"),
@@ -97,7 +98,7 @@ export default new Router({
{
path: "/places/:name*",
- component: Timeline,
+ component: ClusterView,
name: "places",
props: (route) => ({
rootTitle: t("memories", "Places"),
@@ -106,7 +107,7 @@ export default new Router({
{
path: "/tags/:name*",
- component: Timeline,
+ component: ClusterView,
name: "tags",
props: (route) => ({
rootTitle: t("memories", "Tags"),
diff --git a/src/services/API.ts b/src/services/API.ts
index 86f034b0..1c6e6737 100644
--- a/src/services/API.ts
+++ b/src/services/API.ts
@@ -1,4 +1,5 @@
import { generateUrl } from "@nextcloud/router";
+import { ClusterTypes } from "../types";
const BASE = "/apps/memories/api";
@@ -65,7 +66,7 @@ export class API {
return tok(gen(`${BASE}/days/{id}`, { id }));
}
- static DAYS_FILTER(query: any, filter: DaysFilterType, value: string = '1') {
+ static DAYS_FILTER(query: any, filter: DaysFilterType, value: string = "1") {
query[filter] = value;
}
@@ -74,25 +75,20 @@ export class API {
}
static ALBUM_DOWNLOAD(user: string, name: string) {
- return gen(`${BASE}/clusters/albums/download?name={user}/{name}`, { user, name });
+ return gen(`${BASE}/clusters/albums/download?name={user}/{name}`, {
+ user,
+ name,
+ });
}
static PLACE_LIST() {
return gen(`${BASE}/clusters/places`);
}
- static PLACE_PREVIEW(place: number | string) {
- return gen(`${BASE}/clusters/places/preview/{place}`, { place });
- }
-
static TAG_LIST() {
return gen(`${BASE}/clusters/tags`);
}
- static TAG_PREVIEW(tag: string) {
- return gen(`${BASE}/clusters/tags/preview/{tag}`, { tag });
- }
-
static TAG_SET(fileid: string | number) {
return gen(`${BASE}/tags/set/{fileid}`, { fileid });
}
@@ -101,11 +97,8 @@ export class API {
return gen(`${BASE}/clusters/${app}`);
}
- static FACE_PREVIEW(
- app: "recognize" | "facerecognition",
- face: string | number
- ) {
- return gen(`${BASE}/clusters/${app}/preview/{face}`, { face });
+ static CLUSTER_PREVIEW(backend: ClusterTypes, name: string | number) {
+ return gen(`${BASE}/clusters/${backend}/preview/{name}`, { name });
}
static ARCHIVE(fileid: number) {
diff --git a/src/services/Utils.ts b/src/services/Utils.ts
index 920ef656..b8aff66f 100644
--- a/src/services/Utils.ts
+++ b/src/services/Utils.ts
@@ -212,27 +212,6 @@ export function convertFlags(photo: IPhoto) {
photo.flag |= constants.c.FLAG_IS_FOLDER;
delete photo.isfolder;
}
- if (photo.isface) {
- const app = photo.isface;
- if (app === "recognize") {
- photo.flag |= constants.c.FLAG_IS_FACE_RECOGNIZE;
- } else if (app === "facerecognition") {
- photo.flag |= constants.c.FLAG_IS_FACE_RECOGNITION;
- }
- delete photo.isface;
- }
- if (photo.isplace) {
- photo.flag |= constants.c.FLAG_IS_PLACE;
- delete photo.isplace;
- }
- if (photo.istag) {
- photo.flag |= constants.c.FLAG_IS_TAG;
- delete photo.istag;
- }
- if (photo.isalbum) {
- photo.flag |= constants.c.FLAG_IS_ALBUM;
- delete photo.isalbum;
- }
}
/**
@@ -325,13 +304,8 @@ export const constants = {
FLAG_IS_VIDEO: 1 << 2,
FLAG_IS_FAVORITE: 1 << 3,
FLAG_IS_FOLDER: 1 << 4,
- FLAG_IS_ALBUM: 1 << 5,
- FLAG_IS_FACE_RECOGNIZE: 1 << 6,
- FLAG_IS_FACE_RECOGNITION: 1 << 7,
- FLAG_IS_PLACE: 1 << 8,
- FLAG_IS_TAG: 1 << 9,
- FLAG_SELECTED: 1 << 10,
- FLAG_LEAVING: 1 << 11,
+ FLAG_SELECTED: 1 << 5,
+ FLAG_LEAVING: 1 << 6,
},
TagDayID: TagDayID,
diff --git a/src/services/dav/albums.ts b/src/services/dav/albums.ts
index 4301196e..9f78d352 100644
--- a/src/services/dav/albums.ts
+++ b/src/services/dav/albums.ts
@@ -1,9 +1,8 @@
import * as base from "./base";
import { getCurrentUser } from "@nextcloud/auth";
import { showError } from "@nextcloud/dialogs";
-import { translate as t, translatePlural as n } from "@nextcloud/l10n";
-import { IAlbum, IDay, IFileInfo, IPhoto, ITag } from "../../types";
-import { constants } from "../Utils";
+import { translate as t } from "@nextcloud/l10n";
+import { IAlbum, IFileInfo, IPhoto } from "../../types";
import { API } from "../API";
import axios from "@nextcloud/axios";
import client from "../DavClient";
@@ -22,21 +21,12 @@ export function getAlbumPath(user: string, name: string) {
}
/**
- * Get list of albums and convert to Days response
+ * Get list of albums.
* @param type Type of albums to get; 1 = personal, 2 = shared, 3 = all
* @param sortOrder Sort order; 1 = by date, 2 = by name
*/
-export async function getAlbumsData(
- type: 1 | 2 | 3,
- sortOrder: 1 | 2
-): Promise
{
- let data: IAlbum[] = [];
- try {
- const res = await axios.get(API.ALBUM_LIST(type));
- data = res.data;
- } catch (e) {
- throw e;
- }
+export async function getAlbums(type: 1 | 2 | 3, sortOrder: 1 | 2) {
+ const data = (await axios.get(API.ALBUM_LIST(type))).data;
// Response is already sorted by date, sort otherwise
if (sortOrder === 2) {
@@ -45,23 +35,7 @@ export async function getAlbumsData(
);
}
- // Convert to days response
- return [
- {
- dayid: constants.TagDayID.ALBUMS,
- count: data.length,
- detail: data.map(
- (album) =>
- ({
- ...album,
- fileid: album.album_id,
- flag: constants.c.FLAG_IS_TAG & constants.c.FLAG_IS_ALBUM,
- istag: true,
- isalbum: true,
- } as ITag)
- ),
- },
- ];
+ return data;
}
/**
diff --git a/src/services/dav/face.ts b/src/services/dav/face.ts
index fd1df0ca..31d1e003 100644
--- a/src/services/dav/face.ts
+++ b/src/services/dav/face.ts
@@ -2,47 +2,13 @@ import axios from "@nextcloud/axios";
import { showError } from "@nextcloud/dialogs";
import { translate as t } from "@nextcloud/l10n";
import { generateUrl } from "@nextcloud/router";
-import { IDay, IPhoto } from "../../types";
+import { IFace, IPhoto } from "../../types";
import { API } from "../API";
-import { constants } from "../Utils";
import client from "../DavClient";
import * as base from "./base";
-/**
- * Get list of tags and convert to Days response
- */
-export async function getPeopleData(
- app: "recognize" | "facerecognition"
-): Promise {
- // Query for photos
- let data: {
- id: number;
- count: number;
- name: string;
- }[] = [];
- try {
- const res = await axios.get(API.FACE_LIST(app));
- data = res.data;
- } catch (e) {
- throw e;
- }
-
- // Convert to days response
- return [
- {
- dayid: constants.TagDayID.FACES,
- count: data.length,
- detail: data.map(
- (face) =>
- ({
- ...face,
- fileid: face.id,
- istag: true,
- isface: app,
- } as any)
- ),
- },
- ];
+export async function getFaceList(app: "recognize" | "facerecognition") {
+ return (await axios.get(API.FACE_LIST(app))).data;
}
export async function updatePeopleFaceRecognition(
diff --git a/src/services/dav/places.ts b/src/services/dav/places.ts
index e053c98b..8f6770ba 100644
--- a/src/services/dav/places.ts
+++ b/src/services/dav/places.ts
@@ -1,41 +1,7 @@
-import { IDay, IPhoto, ITag } from "../../types";
-import { constants } from "../Utils";
+import { ICluster } from "../../types";
import { API } from "../API";
import axios from "@nextcloud/axios";
-/**
- * Get list of tags and convert to Days response
- */
-export async function getPlacesData(): Promise {
- // Query for photos
- let data: {
- osm_id: number;
- count: number;
- name: string;
- }[] = [];
- try {
- const res = await axios.get(API.PLACE_LIST());
- data = res.data;
- } catch (e) {
- throw e;
- }
-
- // Convert to days response
- return [
- {
- dayid: constants.TagDayID.TAGS,
- count: data.length,
- detail: data.map(
- (tag) =>
- ({
- ...tag,
- id: tag.osm_id,
- fileid: tag.osm_id,
- flag: constants.c.FLAG_IS_TAG,
- istag: true,
- isplace: true,
- } as ITag)
- ),
- },
- ];
+export async function getPlaces() {
+ return (await axios.get(API.PLACE_LIST())).data;
}
diff --git a/src/services/dav/tags.ts b/src/services/dav/tags.ts
index 59606abd..728c9955 100644
--- a/src/services/dav/tags.ts
+++ b/src/services/dav/tags.ts
@@ -1,39 +1,10 @@
-import { IDay, IPhoto, ITag } from "../../types";
-import { constants, hashCode } from "../Utils";
+import { ICluster } from "../../types";
import { API } from "../API";
import axios from "@nextcloud/axios";
/**
- * Get list of tags and convert to Days response
+ * Get list of tags.
*/
-export async function getTagsData(): Promise {
- // Query for photos
- let data: {
- id: number;
- count: number;
- name: string;
- }[] = [];
- try {
- const res = await axios.get(API.TAG_LIST());
- data = res.data;
- } catch (e) {
- throw e;
- }
-
- // Convert to days response
- return [
- {
- dayid: constants.TagDayID.TAGS,
- count: data.length,
- detail: data.map(
- (tag) =>
- ({
- ...tag,
- fileid: hashCode(tag.name),
- flag: constants.c.FLAG_IS_TAG,
- istag: true,
- } as ITag)
- ),
- },
- ];
+export async function getTags() {
+ return (await axios.get(API.TAG_LIST())).data;
}
diff --git a/src/services/strings.ts b/src/services/strings.ts
new file mode 100644
index 00000000..117f7d14
--- /dev/null
+++ b/src/services/strings.ts
@@ -0,0 +1,70 @@
+import { loadState } from "@nextcloud/initial-state";
+import { translate as t } from "@nextcloud/l10n";
+
+const config_facerecognitionEnabled = Boolean(
+ loadState("memories", "facerecognitionEnabled", "")
+);
+
+export function emptyDescription(routeName: string): string {
+ switch (routeName) {
+ case "timeline":
+ return t(
+ "memories",
+ "Upload some photos and make sure the timeline path is configured"
+ );
+ case "favorites":
+ return t("memories", "Mark photos as favorite to find them easily");
+ case "thisday":
+ return t("memories", "Memories from past years will appear here");
+ case "facerecognition":
+ return config_facerecognitionEnabled
+ ? t("memories", "You will find your friends soon. Please be patient")
+ : t(
+ "memories",
+ "Face Recognition is disabled. Enable in settings to find your friends"
+ );
+ case "videos":
+ return t("memories", "Your videos will appear here");
+ case "albums":
+ return t("memories", "Create an album to get started");
+ case "archive":
+ return t(
+ "memories",
+ "Archive photos you don't want to see in your timeline"
+ );
+ case "tags":
+ return t("memories", "Tag photos to find them easily");
+ case "recognize":
+ return t("memories", "Recognize is still working on your photos");
+ case "places":
+ return t("memories", "Places you have been to will appear here");
+ default:
+ return "";
+ }
+}
+
+export function viewName(routeName: string): string {
+ switch (routeName) {
+ case "timeline":
+ return t("memories", "Your Timeline");
+ case "favorites":
+ return t("memories", "Favorites");
+ case "recognize":
+ case "facerecognition":
+ return t("memories", "People");
+ case "videos":
+ return t("memories", "Videos");
+ case "albums":
+ return t("memories", "Albums");
+ case "archive":
+ return t("memories", "Archive");
+ case "thisday":
+ return t("memories", "On this day");
+ case "tags":
+ return t("memories", "Tags");
+ case "places":
+ return t("memories", "Places");
+ default:
+ return "";
+ }
+}
diff --git a/src/types.ts b/src/types.ts
index 308a7ab7..b811efaa 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -117,18 +117,28 @@ export interface IFolder extends IPhoto {
name: string;
}
-export interface ITag extends IPhoto {
- /** Name of tag */
- name: string;
- /** Number of images in this tag */
+export type ClusterTypes =
+ | "tags"
+ | "albums"
+ | "places"
+ | "recognize"
+ | "facerecognition";
+
+export interface ICluster {
+ /** A unique identifier for the cluster */
+ cluster_id: number | string;
+ /** Type of cluster */
+ cluster_type: ClusterTypes;
+ /** Number of images in this cluster */
count: number;
- /** User for face if face */
- user_id?: string;
- /** Cache of previews */
- previews?: IPhoto[];
+ /** Name of cluster */
+ name: string;
+
+ /** Preview loading failed */
+ previewError?: boolean;
}
-export interface IAlbum extends ITag {
+export interface IAlbum extends ICluster {
/** ID of album */
album_id: number;
/** Owner of album */
@@ -141,6 +151,11 @@ export interface IAlbum extends ITag {
last_added_photo: number;
}
+export interface IFace extends ICluster {
+ /** User for face */
+ user_id: string;
+}
+
export interface IFaceRect {
w: number;
h: number;
@@ -211,7 +226,7 @@ export type TopMatter = {
export enum TopMatterType {
NONE = 0,
FOLDER = 1,
- TAG = 2,
+ CLUSTER = 2,
FACE = 3,
ALBUM = 4,
}