refactor: tag frame to cluster
Signed-off-by: Varun Patil <varunpatil@ucla.edu>pull/563/head
parent
92781df0c1
commit
7326ee8ec0
|
@ -0,0 +1,137 @@
|
|||
<template>
|
||||
<div v-if="noParams" class="container" :class="{ 'icon-loading': loading }">
|
||||
<TopMatter />
|
||||
|
||||
<EmptyContent v-if="items.length === 0 && !loading" />
|
||||
|
||||
<RecycleScroller
|
||||
class="grid-recycler hide-scrollbar-mobile"
|
||||
:class="{ empty: !items.length }"
|
||||
ref="recycler"
|
||||
:items="items"
|
||||
:skipHover="true"
|
||||
:itemSize="itemSize"
|
||||
:gridItems="gridItems"
|
||||
:updateInterval="100"
|
||||
key-field="cluster_id"
|
||||
@resize="resize"
|
||||
>
|
||||
<template v-slot="{ item }">
|
||||
<div class="grid-item fill-block" :key="item.cluster_id">
|
||||
<Cluster :data="item" @click="click(item)" :link="link" />
|
||||
</div>
|
||||
</template>
|
||||
</RecycleScroller>
|
||||
</div>
|
||||
|
||||
<Timeline v-else />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
import UserConfig from "../mixins/UserConfig";
|
||||
import TopMatter from "./top-matter/TopMatter.vue";
|
||||
import Cluster from "./frame/Cluster.vue";
|
||||
import Timeline from "./Timeline.vue";
|
||||
import EmptyContent from "./top-matter/EmptyContent.vue";
|
||||
|
||||
import * as dav from "../services/DavRequests";
|
||||
|
||||
import { ICluster } from "../types";
|
||||
|
||||
export default defineComponent({
|
||||
name: "ClusterView",
|
||||
|
||||
components: {
|
||||
TopMatter,
|
||||
Cluster,
|
||||
Timeline,
|
||||
EmptyContent,
|
||||
},
|
||||
|
||||
mixins: [UserConfig],
|
||||
|
||||
data: () => ({
|
||||
items: [] as ICluster[],
|
||||
itemSize: 200,
|
||||
gridItems: 5,
|
||||
loading: 0,
|
||||
}),
|
||||
|
||||
props: {
|
||||
link: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
noParams() {
|
||||
return !this.$route.params.name && !this.$route.params.user;
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.routeChange(this.$route);
|
||||
this.resize();
|
||||
},
|
||||
|
||||
watch: {
|
||||
async $route(to: any, from?: any) {
|
||||
this.routeChange(to, from);
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
async routeChange(to: any, from?: any) {
|
||||
try {
|
||||
this.items = [];
|
||||
this.loading++;
|
||||
|
||||
if (to.name === "albums") {
|
||||
this.items = await dav.getAlbums(3, this.config_albumListSort);
|
||||
} else if (to.name === "tags") {
|
||||
this.items = await dav.getTags();
|
||||
} else if (to.name === "recognize" || to.name === "facerecognition") {
|
||||
this.items = await dav.getFaceList(to.name);
|
||||
} else if (to.name === "places") {
|
||||
this.items = await dav.getPlaces();
|
||||
}
|
||||
} finally {
|
||||
this.loading--;
|
||||
}
|
||||
},
|
||||
|
||||
click(item: ICluster) {
|
||||
this.$emit("click", item);
|
||||
},
|
||||
|
||||
resize() {
|
||||
const w = (<any>this.$refs.recycler).$el.clientWidth;
|
||||
this.gridItems = Math.max(Math.floor(w / 200), 3);
|
||||
this.itemSize = Math.floor(w / this.gridItems);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.container {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.grid-recycler {
|
||||
flex: 1;
|
||||
max-height: 100%;
|
||||
overflow-y: scroll !important;
|
||||
&.empty {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
.grid-item {
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
|
@ -528,8 +528,7 @@ export default defineComponent({
|
|||
selectPhoto(photo: IPhoto, val?: boolean, noUpdate?: boolean) {
|
||||
if (
|
||||
photo.flag & this.c.FLAG_PLACEHOLDER ||
|
||||
photo.flag & this.c.FLAG_IS_FOLDER ||
|
||||
photo.flag & this.c.FLAG_IS_TAG
|
||||
photo.flag & this.c.FLAG_IS_FOLDER
|
||||
) {
|
||||
return; // ignore placeholders
|
||||
}
|
||||
|
|
|
@ -8,22 +8,12 @@
|
|||
<TopMatter ref="topmatter" />
|
||||
|
||||
<!-- No content found and nothing is loading -->
|
||||
<NcEmptyContent
|
||||
title="Nothing to show here"
|
||||
:description="emptyViewDescription"
|
||||
v-if="loading === 0 && list.length === 0"
|
||||
>
|
||||
<template #icon>
|
||||
<PeopleIcon v-if="routeIsPeople" />
|
||||
<ArchiveIcon v-else-if="routeIsArchive" />
|
||||
<ImageMultipleIcon v-else />
|
||||
</template>
|
||||
</NcEmptyContent>
|
||||
<EmptyContent v-if="loading === 0 && list.length === 0" />
|
||||
|
||||
<!-- Main recycler view for rows -->
|
||||
<RecycleScroller
|
||||
ref="recycler"
|
||||
class="recycler"
|
||||
class="recycler hide-scrollbar"
|
||||
:class="{ empty: list.length === 0 }"
|
||||
:items="list"
|
||||
:emit-update="true"
|
||||
|
@ -83,8 +73,6 @@
|
|||
>
|
||||
<Folder v-if="photo.flag & c.FLAG_IS_FOLDER" :data="photo" />
|
||||
|
||||
<Tag v-else-if="photo.flag & c.FLAG_IS_TAG" :data="photo" />
|
||||
|
||||
<Photo
|
||||
v-else
|
||||
:data="photo"
|
||||
|
@ -137,7 +125,6 @@ import { defineComponent } from "vue";
|
|||
import axios from "@nextcloud/axios";
|
||||
import { showError } from "@nextcloud/dialogs";
|
||||
import { subscribe, unsubscribe } from "@nextcloud/event-bus";
|
||||
import NcEmptyContent from "@nextcloud/vue/dist/Components/NcEmptyContent";
|
||||
|
||||
import { getLayout } from "../services/Layout";
|
||||
import { IDay, IFolder, IHeadRow, IPhoto, IRow, IRowType } from "../types";
|
||||
|
@ -145,20 +132,19 @@ import { IDay, IFolder, IHeadRow, IPhoto, IRow, IRowType } from "../types";
|
|||
import UserConfig from "../mixins/UserConfig";
|
||||
import Folder from "./frame/Folder.vue";
|
||||
import Photo from "./frame/Photo.vue";
|
||||
import Tag from "./frame/Tag.vue";
|
||||
import ScrollerManager from "./ScrollerManager.vue";
|
||||
import SelectionManager from "./SelectionManager.vue";
|
||||
import Viewer from "./viewer/Viewer.vue";
|
||||
|
||||
import EmptyContent from "./top-matter/EmptyContent.vue";
|
||||
import OnThisDay from "./top-matter/OnThisDay.vue";
|
||||
import TopMatter from "./top-matter/TopMatter.vue";
|
||||
|
||||
import * as dav from "../services/DavRequests";
|
||||
import * as utils from "../services/Utils";
|
||||
import * as strings from "../services/strings";
|
||||
|
||||
import PeopleIcon from "vue-material-design-icons/AccountMultiple.vue";
|
||||
import CheckCircle from "vue-material-design-icons/CheckCircle.vue";
|
||||
import ImageMultipleIcon from "vue-material-design-icons/ImageMultiple.vue";
|
||||
import ArchiveIcon from "vue-material-design-icons/PackageDown.vue";
|
||||
import { API, DaysFilterType } from "../services/API";
|
||||
|
||||
const SCROLL_LOAD_DELAY = 100; // Delay in loading data when scrolling
|
||||
|
@ -170,19 +156,15 @@ export default defineComponent({
|
|||
|
||||
components: {
|
||||
Folder,
|
||||
Tag,
|
||||
Photo,
|
||||
TopMatter,
|
||||
EmptyContent,
|
||||
OnThisDay,
|
||||
TopMatter,
|
||||
SelectionManager,
|
||||
ScrollerManager,
|
||||
Viewer,
|
||||
NcEmptyContent,
|
||||
|
||||
CheckCircle,
|
||||
ArchiveIcon,
|
||||
PeopleIcon,
|
||||
ImageMultipleIcon,
|
||||
},
|
||||
|
||||
mixins: [UserConfig],
|
||||
|
@ -279,54 +261,7 @@ export default defineComponent({
|
|||
},
|
||||
/** Get view name for dynamic top matter */
|
||||
viewName(): string {
|
||||
switch (this.$route.name) {
|
||||
case "timeline":
|
||||
return this.t("memories", "Your Timeline");
|
||||
case "favorites":
|
||||
return this.t("memories", "Favorites");
|
||||
case "recognize":
|
||||
case "facerecognition":
|
||||
return this.t("memories", "People");
|
||||
case "videos":
|
||||
return this.t("memories", "Videos");
|
||||
case "albums":
|
||||
return this.t("memories", "Albums");
|
||||
case "archive":
|
||||
return this.t("memories", "Archive");
|
||||
case "thisday":
|
||||
return this.t("memories", "On this day");
|
||||
case "tags":
|
||||
return this.t("memories", "Tags");
|
||||
case "places":
|
||||
return this.t("memories", "Places");
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
},
|
||||
emptyViewDescription(): string {
|
||||
switch (this.$route.name) {
|
||||
case "facerecognition":
|
||||
if (this.config_facerecognitionEnabled)
|
||||
return this.t(
|
||||
"memories",
|
||||
"You will find your friends soon. Please, be patient."
|
||||
);
|
||||
else
|
||||
return this.t(
|
||||
"memories",
|
||||
"Face Recognition is disabled. Enable in settings to find your friends."
|
||||
);
|
||||
case "timeline":
|
||||
case "favorites":
|
||||
case "recognize":
|
||||
case "videos":
|
||||
case "albums":
|
||||
case "archive":
|
||||
case "thisday":
|
||||
case "tags":
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
return strings.viewName(this.$route.name);
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -681,7 +616,11 @@ export default defineComponent({
|
|||
|
||||
// Map Bounds
|
||||
if (this.$route.name === "map" && this.$route.query.b) {
|
||||
API.DAYS_FILTER(query, DaysFilterType.MAP_BOUNDS, <string>this.$route.query.b);
|
||||
API.DAYS_FILTER(
|
||||
query,
|
||||
DaysFilterType.MAP_BOUNDS,
|
||||
<string>this.$route.query.b
|
||||
);
|
||||
}
|
||||
|
||||
// Month view
|
||||
|
@ -739,14 +678,6 @@ export default defineComponent({
|
|||
let data: IDay[] = [];
|
||||
if (this.$route.name === "thisday") {
|
||||
data = await dav.getOnThisDayData();
|
||||
} else if (this.$route.name === "albums" && !this.$route.params.name) {
|
||||
data = await dav.getAlbumsData(3, this.config_albumListSort);
|
||||
} else if (this.routeIsPeople && !this.$route.params.name) {
|
||||
data = await dav.getPeopleData(this.$route.name as any);
|
||||
} else if (this.$route.name === "places" && !this.$route.params.name) {
|
||||
data = await dav.getPlacesData();
|
||||
} else if (this.$route.name === "tags" && !this.$route.params.name) {
|
||||
data = await dav.getTagsData();
|
||||
} else if (dav.isSingleItem()) {
|
||||
data = await dav.getSingleItemData();
|
||||
this.$router.replace(utils.getViewerRoute(data[0]!.detail[0]));
|
||||
|
@ -1043,9 +974,7 @@ export default defineComponent({
|
|||
return {
|
||||
width: p.w || this.rowHeight,
|
||||
height: p.h || this.rowHeight,
|
||||
forceSquare: Boolean(
|
||||
(p.flag & this.c.FLAG_IS_FOLDER) | (p.flag & this.c.FLAG_IS_TAG)
|
||||
),
|
||||
forceSquare: Boolean(p.flag & this.c.FLAG_IS_FOLDER),
|
||||
};
|
||||
}),
|
||||
{
|
||||
|
@ -1419,13 +1348,7 @@ export default defineComponent({
|
|||
}
|
||||
}
|
||||
|
||||
/** Static and dynamic top matter */
|
||||
.top-matter {
|
||||
padding-top: 4px;
|
||||
@include phone {
|
||||
padding-left: 40px;
|
||||
}
|
||||
}
|
||||
/** Dynamic top matter */
|
||||
.recycler-before {
|
||||
width: 100%;
|
||||
> .text {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<template>
|
||||
<router-link
|
||||
draggable="false"
|
||||
class="tag fill-block"
|
||||
:class="{ face, error }"
|
||||
class="cluster fill-block"
|
||||
:class="{ error }"
|
||||
:to="target"
|
||||
@click.native="openTag(data)"
|
||||
@click.native="click"
|
||||
>
|
||||
<div class="bbl">
|
||||
<NcCounterBubble> {{ data.count }} </NcCounterBubble>
|
||||
|
@ -32,29 +32,30 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent, PropType } from "vue";
|
||||
|
||||
import { IAlbum, ITag } from "../../types";
|
||||
import { IAlbum, ICluster, IFace } from "../../types";
|
||||
import { getPreviewUrl } from "../../services/FileUtils";
|
||||
import { getCurrentUser } from "@nextcloud/auth";
|
||||
|
||||
import NcCounterBubble from "@nextcloud/vue/dist/Components/NcCounterBubble";
|
||||
|
||||
import { constants } from "../../services/Utils";
|
||||
import { API } from "../../services/API";
|
||||
|
||||
import Vue from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "Tag",
|
||||
name: "Cluster",
|
||||
components: {
|
||||
NcCounterBubble,
|
||||
},
|
||||
|
||||
props: {
|
||||
data: {
|
||||
type: Object as PropType<ITag>,
|
||||
type: Object as PropType<ICluster>,
|
||||
required: true,
|
||||
},
|
||||
noNavigate: {
|
||||
link: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -65,15 +66,7 @@ export default defineComponent({
|
|||
return getPreviewUrl(mock, true, 512);
|
||||
}
|
||||
|
||||
if (this.face) {
|
||||
return API.FACE_PREVIEW(this.faceApp, this.face.fileid);
|
||||
}
|
||||
|
||||
if (this.place) {
|
||||
return API.PLACE_PREVIEW(this.place.fileid);
|
||||
}
|
||||
|
||||
return API.TAG_PREVIEW(this.data.name);
|
||||
return API.CLUSTER_PREVIEW(this.data.cluster_type, this.data.cluster_id);
|
||||
},
|
||||
|
||||
title() {
|
||||
|
@ -93,35 +86,28 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
tag() {
|
||||
return !this.face && !this.place && !this.album ? this.data : null;
|
||||
return this.data.cluster_type === "tags" && this.data;
|
||||
},
|
||||
|
||||
face() {
|
||||
return this.data.flag & constants.c.FLAG_IS_FACE_RECOGNIZE ||
|
||||
this.data.flag & constants.c.FLAG_IS_FACE_RECOGNITION
|
||||
? this.data
|
||||
: null;
|
||||
},
|
||||
|
||||
faceApp() {
|
||||
return this.data.flag & constants.c.FLAG_IS_FACE_RECOGNITION
|
||||
? "facerecognition"
|
||||
: "recognize";
|
||||
return (
|
||||
(this.data.cluster_type === "recognize" ||
|
||||
this.data.cluster_type === "facerecognition") &&
|
||||
(this.data as IFace)
|
||||
);
|
||||
},
|
||||
|
||||
place() {
|
||||
return this.data.flag & constants.c.FLAG_IS_PLACE ? this.data : null;
|
||||
return this.data.cluster_type === "places" && this.data;
|
||||
},
|
||||
|
||||
album() {
|
||||
return this.data.flag & constants.c.FLAG_IS_ALBUM
|
||||
? <IAlbum>this.data
|
||||
: null;
|
||||
return this.data.cluster_type === "albums" && (this.data as IAlbum);
|
||||
},
|
||||
|
||||
/** Target URL to navigate to */
|
||||
target() {
|
||||
if (this.noNavigate) return {};
|
||||
if (!this.link) return {};
|
||||
|
||||
if (this.album) {
|
||||
const user = this.album.user;
|
||||
|
@ -130,13 +116,13 @@ export default defineComponent({
|
|||
}
|
||||
|
||||
if (this.face) {
|
||||
const name = this.face.name || this.face.fileid.toString();
|
||||
const name = this.face.name || this.face.cluster_id.toString();
|
||||
const user = this.face.user_id;
|
||||
return { name: this.faceApp, params: { name, user } };
|
||||
return { name: this.data.cluster_type, params: { name, user } };
|
||||
}
|
||||
|
||||
if (this.place) {
|
||||
const id = this.place.fileid.toString();
|
||||
const id = this.place.cluster_id;
|
||||
const placeName = this.place.name || id;
|
||||
const name = `${id}-${placeName}`;
|
||||
return { name: "places", params: { name } };
|
||||
|
@ -147,30 +133,24 @@ export default defineComponent({
|
|||
|
||||
error() {
|
||||
return (
|
||||
Boolean(this.data.flag & this.c.FLAG_LOAD_FAIL) ||
|
||||
Boolean(this.data.previewError) ||
|
||||
Boolean(this.album && this.album.last_added_photo <= 0)
|
||||
);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* Open tag event
|
||||
* Unless noNavigate is set, the tag will be opened
|
||||
*/
|
||||
openTag(tag: ITag) {
|
||||
this.$emit("open", tag);
|
||||
},
|
||||
|
||||
/** Mark as loading failed */
|
||||
failed() {
|
||||
this.data.flag |= this.c.FLAG_LOAD_FAIL;
|
||||
Vue.set(this.data, "previewError", true);
|
||||
},
|
||||
click() {
|
||||
this.$emit("click", this.data);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tag,
|
||||
.cluster,
|
||||
.name,
|
||||
.bubble,
|
||||
img {
|
||||
|
@ -178,7 +158,7 @@ img {
|
|||
}
|
||||
|
||||
// Get rid of color of the bubble
|
||||
.tag .bbl :deep .counter-bubble__counter {
|
||||
.cluster .bbl :deep .counter-bubble__counter {
|
||||
color: unset !important;
|
||||
}
|
||||
|
||||
|
@ -201,7 +181,7 @@ img {
|
|||
display: block;
|
||||
}
|
||||
|
||||
.tag.error > & {
|
||||
.cluster.error > & {
|
||||
color: unset;
|
||||
}
|
||||
|
|
@ -6,15 +6,14 @@
|
|||
:value.sync="search"
|
||||
:label="t('memories', 'Search')"
|
||||
:placeholder="t('memories', 'Search')"
|
||||
@input="searchChanged"
|
||||
>
|
||||
<Magnify :size="16" />
|
||||
</NcTextField>
|
||||
</div>
|
||||
|
||||
<div v-if="detail">
|
||||
<div class="photo" v-for="photo of detail" :key="photo.fileid">
|
||||
<Tag :data="photo" :noNavigate="true" @open="clickFace" />
|
||||
<div v-if="list">
|
||||
<div class="photo" v-for="photo of filteredList" :key="photo.cluster_id">
|
||||
<Cluster :data="photo" :link="false" @click="clickFace" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
|
@ -25,8 +24,8 @@
|
|||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import { IPhoto, ITag } from "../../types";
|
||||
import Tag from "../frame/Tag.vue";
|
||||
import { ICluster, IFace } from "../../types";
|
||||
import Cluster from "../frame/Cluster.vue";
|
||||
|
||||
import NcTextField from "@nextcloud/vue/dist/Components/NcTextField";
|
||||
|
||||
|
@ -38,7 +37,7 @@ import Magnify from "vue-material-design-icons/Magnify.vue";
|
|||
export default defineComponent({
|
||||
name: "FaceList",
|
||||
components: {
|
||||
Tag,
|
||||
Cluster,
|
||||
NcTextField,
|
||||
Magnify,
|
||||
},
|
||||
|
@ -46,9 +45,8 @@ export default defineComponent({
|
|||
data: () => ({
|
||||
user: "",
|
||||
name: "",
|
||||
fullDetail: null as ITag[] | null,
|
||||
detail: null as ITag[] | null,
|
||||
fuse: null as Fuse<ITag>,
|
||||
list: null as ICluster[] | null,
|
||||
fuse: null as Fuse<ICluster>,
|
||||
search: "",
|
||||
}),
|
||||
|
||||
|
@ -62,6 +60,13 @@ export default defineComponent({
|
|||
this.refreshParams();
|
||||
},
|
||||
|
||||
computed: {
|
||||
filteredList() {
|
||||
if (!this.list || !this.search || !this.fuse) return this.list || [];
|
||||
return this.fuse.search(this.search).map((r) => r.item);
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
close() {
|
||||
this.$emit("close");
|
||||
|
@ -70,43 +75,22 @@ export default defineComponent({
|
|||
async refreshParams() {
|
||||
this.user = <string>this.$route.params.user || "";
|
||||
this.name = <string>this.$route.params.name || "";
|
||||
this.detail = null;
|
||||
this.fullDetail = null;
|
||||
this.list = null;
|
||||
this.search = "";
|
||||
|
||||
let data = [];
|
||||
let flags = this.c.FLAG_IS_TAG;
|
||||
if (this.$route.name === "recognize") {
|
||||
data = await dav.getPeopleData("recognize");
|
||||
flags |= this.c.FLAG_IS_FACE_RECOGNIZE;
|
||||
} else {
|
||||
data = await dav.getPeopleData("facerecognition");
|
||||
flags |= this.c.FLAG_IS_FACE_RECOGNITION;
|
||||
this.list = (await dav.getFaceList(this.$route.name as any)).filter(
|
||||
(c: IFace) => {
|
||||
const clusterName = String(c.name || c.cluster_id);
|
||||
return c.user_id === this.user && clusterName !== this.name;
|
||||
}
|
||||
let detail = data[0].detail;
|
||||
detail.forEach((photo: IPhoto) => {
|
||||
photo.flag = flags;
|
||||
});
|
||||
detail = detail.filter((photo: ITag) => {
|
||||
const pname = photo.name || photo.fileid.toString();
|
||||
return photo.user_id === this.user && pname !== this.name;
|
||||
});
|
||||
);
|
||||
|
||||
this.detail = detail;
|
||||
this.fullDetail = detail;
|
||||
this.fuse = new Fuse(detail, { keys: ["name"] });
|
||||
this.fuse = new Fuse(this.list, { keys: ["name"] });
|
||||
},
|
||||
|
||||
async clickFace(face: ITag) {
|
||||
async clickFace(face: IFace) {
|
||||
this.$emit("select", face);
|
||||
},
|
||||
|
||||
searchChanged() {
|
||||
if (!this.detail) return;
|
||||
this.detail = this.search
|
||||
? this.fuse.search(this.search).map((r) => r.item)
|
||||
: this.fullDetail;
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -35,8 +35,8 @@ const NcProgressBar = () =>
|
|||
|
||||
import { showError } from "@nextcloud/dialogs";
|
||||
import { getCurrentUser } from "@nextcloud/auth";
|
||||
import { IFileInfo, ITag } from "../../types";
|
||||
import Tag from "../frame/Tag.vue";
|
||||
import { IFileInfo, IFace } from "../../types";
|
||||
import Cluster from "../frame/Cluster.vue";
|
||||
import FaceList from "./FaceList.vue";
|
||||
|
||||
import Modal from "./Modal.vue";
|
||||
|
@ -50,7 +50,7 @@ export default defineComponent({
|
|||
NcTextField,
|
||||
NcProgressBar,
|
||||
Modal,
|
||||
Tag,
|
||||
Cluster,
|
||||
FaceList,
|
||||
},
|
||||
|
||||
|
@ -79,11 +79,11 @@ export default defineComponent({
|
|||
this.show = true;
|
||||
},
|
||||
|
||||
async clickFace(face: ITag) {
|
||||
async clickFace(face: IFace) {
|
||||
const user = this.$route.params.user || "";
|
||||
const name = this.$route.params.name || "";
|
||||
|
||||
const newName = face.name || face.fileid.toString();
|
||||
const newName = String(face.name || face.cluster_id);
|
||||
if (
|
||||
!confirm(
|
||||
this.t(
|
||||
|
|
|
@ -24,8 +24,8 @@ const NcTextField = () => import("@nextcloud/vue/dist/Components/NcTextField");
|
|||
|
||||
import { showError } from "@nextcloud/dialogs";
|
||||
import { getCurrentUser } from "@nextcloud/auth";
|
||||
import { IPhoto, ITag } from "../../types";
|
||||
import Tag from "../frame/Tag.vue";
|
||||
import { IPhoto, IFace } from "../../types";
|
||||
import Cluster from "../frame/Cluster.vue";
|
||||
import FaceList from "./FaceList.vue";
|
||||
|
||||
import Modal from "./Modal.vue";
|
||||
|
@ -38,7 +38,7 @@ export default defineComponent({
|
|||
NcButton,
|
||||
NcTextField,
|
||||
Modal,
|
||||
Tag,
|
||||
Cluster,
|
||||
FaceList,
|
||||
},
|
||||
|
||||
|
@ -86,11 +86,11 @@ export default defineComponent({
|
|||
this.$emit("moved", list);
|
||||
},
|
||||
|
||||
async clickFace(face: ITag) {
|
||||
async clickFace(face: IFace) {
|
||||
const user = this.$route.params.user || "";
|
||||
const name = this.$route.params.name || "";
|
||||
|
||||
const newName = face.name || face.fileid.toString();
|
||||
const newName = String(face.name || face.cluster_id);
|
||||
|
||||
if (
|
||||
!confirm(
|
||||
|
|
|
@ -206,26 +206,3 @@ export default defineComponent({
|
|||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.top-matter {
|
||||
display: flex;
|
||||
vertical-align: middle;
|
||||
|
||||
.name {
|
||||
font-size: 1.3em;
|
||||
font-weight: 400;
|
||||
line-height: 40px;
|
||||
padding-left: 3px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.right-actions {
|
||||
margin-right: 40px;
|
||||
z-index: 50;
|
||||
@media (max-width: 768px) {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<template>
|
||||
<div v-if="name" class="tag-top-matter">
|
||||
<NcActions>
|
||||
<div class="top-matter">
|
||||
<NcActions v-if="name">
|
||||
<NcActionButton :aria-label="t('memories', 'Back')" @click="back()">
|
||||
{{ t("memories", "Back") }}
|
||||
<template #icon> <BackIcon :size="20" /> </template>
|
||||
</NcActionButton>
|
||||
</NcActions>
|
||||
<span class="name">{{ name }}</span>
|
||||
<span class="name">{{ name || viewname }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -15,6 +15,7 @@ import { defineComponent } from "vue";
|
|||
|
||||
import NcActions from "@nextcloud/vue/dist/Components/NcActions";
|
||||
import NcActionButton from "@nextcloud/vue/dist/Components/NcActionButton";
|
||||
import * as strings from "../../services/strings";
|
||||
|
||||
import BackIcon from "vue-material-design-icons/ArrowLeft.vue";
|
||||
|
||||
|
@ -26,48 +27,27 @@ export default defineComponent({
|
|||
BackIcon,
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
name: "",
|
||||
}),
|
||||
|
||||
watch: {
|
||||
$route: function (from: any, to: any) {
|
||||
this.createMatter();
|
||||
},
|
||||
computed: {
|
||||
viewname(): string {
|
||||
return strings.viewName(this.$route.name);
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.createMatter();
|
||||
name(): string | null {
|
||||
switch (this.$route.name) {
|
||||
case "tags":
|
||||
return this.$route.params.name;
|
||||
case "places":
|
||||
return this.$route.params.name?.split("-").slice(1).join("-");
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
createMatter() {
|
||||
this.name = <string>this.$route.params.name || "";
|
||||
|
||||
if (this.$route.name === "places") {
|
||||
this.name = this.name.split("-").slice(1).join("-");
|
||||
}
|
||||
},
|
||||
|
||||
back() {
|
||||
this.$router.push({ name: this.$route.name });
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tag-top-matter {
|
||||
.name {
|
||||
font-size: 1.3em;
|
||||
font-weight: 400;
|
||||
line-height: 42px;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
button {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,53 @@
|
|||
<template>
|
||||
<NcEmptyContent
|
||||
:title="t('memories', 'Nothing to show here')"
|
||||
:description="emptyViewDescription"
|
||||
>
|
||||
<template #icon>
|
||||
<PeopleIcon v-if="routeIsPeople" />
|
||||
<ArchiveIcon v-else-if="routeIsArchive" />
|
||||
<ImageMultipleIcon v-else />
|
||||
</template>
|
||||
</NcEmptyContent>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
import NcEmptyContent from "@nextcloud/vue/dist/Components/NcEmptyContent";
|
||||
|
||||
import PeopleIcon from "vue-material-design-icons/AccountMultiple.vue";
|
||||
import ImageMultipleIcon from "vue-material-design-icons/ImageMultiple.vue";
|
||||
import ArchiveIcon from "vue-material-design-icons/PackageDown.vue";
|
||||
|
||||
import * as strings from "../../services/strings";
|
||||
|
||||
export default defineComponent({
|
||||
name: "EmptyContent",
|
||||
|
||||
components: {
|
||||
NcEmptyContent,
|
||||
|
||||
PeopleIcon,
|
||||
ArchiveIcon,
|
||||
ImageMultipleIcon,
|
||||
},
|
||||
|
||||
computed: {
|
||||
emptyViewDescription(): string {
|
||||
return strings.emptyDescription(this.$route.name);
|
||||
},
|
||||
|
||||
routeIsPeople(): boolean {
|
||||
return (
|
||||
this.$route.name === "recognize" ||
|
||||
this.$route.name === "facerecognition"
|
||||
);
|
||||
},
|
||||
|
||||
routeIsArchive(): boolean {
|
||||
return this.$route.name === "archive";
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -119,26 +119,3 @@ export default defineComponent({
|
|||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.face-top-matter {
|
||||
display: flex;
|
||||
vertical-align: middle;
|
||||
|
||||
.name {
|
||||
font-size: 1.3em;
|
||||
font-weight: 400;
|
||||
line-height: 40px;
|
||||
padding-left: 3px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.right-actions {
|
||||
margin-right: 40px;
|
||||
z-index: 50;
|
||||
@media (max-width: 768px) {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -131,23 +131,8 @@ export default defineComponent({
|
|||
|
||||
<style lang="scss" scoped>
|
||||
.top-matter {
|
||||
display: flex;
|
||||
vertical-align: middle;
|
||||
|
||||
.breadcrumb {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.right-actions {
|
||||
margin-right: 40px;
|
||||
z-index: 50;
|
||||
@media (max-width: 768px) {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
:deep span {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div class="top-matter" v-if="type">
|
||||
<div class="top-matter-container" v-if="type">
|
||||
<FolderTopMatter v-if="type === 1" />
|
||||
<TagTopMatter v-else-if="type === 2" />
|
||||
<ClusterTopMatter v-else-if="type === 2" />
|
||||
<FaceTopMatter v-else-if="type === 3" />
|
||||
<AlbumTopMatter v-else-if="type === 4" />
|
||||
</div>
|
||||
|
@ -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({
|
|||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.top-matter-container {
|
||||
padding-top: 4px;
|
||||
@media (max-width: 768px) {
|
||||
padding-left: 40px;
|
||||
}
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
:deep .name {
|
||||
font-size: 1.3em;
|
||||
font-weight: 400;
|
||||
line-height: 42px;
|
||||
vertical-align: top;
|
||||
flex-grow: 1;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
:deep button + .name {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
:deep .right-actions {
|
||||
margin-right: 40px;
|
||||
z-index: 50;
|
||||
@media (max-width: 768px) {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
span {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
:deep button {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -94,13 +94,21 @@ body.has-viewer header {
|
|||
}
|
||||
|
||||
// Hide scrollbar
|
||||
.recycler::-webkit-scrollbar {
|
||||
@mixin hide-scrollbar {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0 !important;
|
||||
}
|
||||
.recycler {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
.hide-scrollbar {
|
||||
@include hide-scrollbar;
|
||||
}
|
||||
.hide-scrollbar-mobile {
|
||||
@media (max-width: 768px) {
|
||||
@include hide-scrollbar;
|
||||
}
|
||||
}
|
||||
|
||||
// Make metadata tab scrollbar thin
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<IDay[]> {
|
||||
let data: IAlbum[] = [];
|
||||
try {
|
||||
const res = await axios.get<typeof data>(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<IAlbum[]>(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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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<IDay[]> {
|
||||
// Query for photos
|
||||
let data: {
|
||||
id: number;
|
||||
count: number;
|
||||
name: string;
|
||||
}[] = [];
|
||||
try {
|
||||
const res = await axios.get<typeof data>(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<IFace[]>(API.FACE_LIST(app))).data;
|
||||
}
|
||||
|
||||
export async function updatePeopleFaceRecognition(
|
||||
|
|
|
@ -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<IDay[]> {
|
||||
// Query for photos
|
||||
let data: {
|
||||
osm_id: number;
|
||||
count: number;
|
||||
name: string;
|
||||
}[] = [];
|
||||
try {
|
||||
const res = await axios.get<typeof data>(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<ICluster[]>(API.PLACE_LIST())).data;
|
||||
}
|
||||
|
|
|
@ -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<IDay[]> {
|
||||
// Query for photos
|
||||
let data: {
|
||||
id: number;
|
||||
count: number;
|
||||
name: string;
|
||||
}[] = [];
|
||||
try {
|
||||
const res = await axios.get<typeof data>(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<ICluster[]>(API.TAG_LIST())).data;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
import { loadState } from "@nextcloud/initial-state";
|
||||
import { translate as t } from "@nextcloud/l10n";
|
||||
|
||||
const config_facerecognitionEnabled = Boolean(
|
||||
loadState("memories", "facerecognitionEnabled", <string>"")
|
||||
);
|
||||
|
||||
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 "";
|
||||
}
|
||||
}
|
35
src/types.ts
35
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,
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue