2022-10-14 21:21:17 +00:00
|
|
|
<template>
|
2022-10-28 19:08:34 +00:00
|
|
|
<div>
|
2022-11-01 05:33:17 +00:00
|
|
|
<div v-if="show" class="top-bar">
|
2022-11-01 04:59:24 +00:00
|
|
|
<NcActions :inline="1">
|
2022-10-28 19:08:34 +00:00
|
|
|
<NcActionButton
|
|
|
|
:aria-label="t('memories', 'Cancel')"
|
|
|
|
@click="clearSelection()"
|
|
|
|
>
|
|
|
|
{{ t("memories", "Cancel") }}
|
|
|
|
<template #icon> <CloseIcon :size="20" /> </template>
|
|
|
|
</NcActionButton>
|
|
|
|
</NcActions>
|
|
|
|
|
|
|
|
<div class="text">
|
|
|
|
{{
|
2022-11-03 21:47:10 +00:00
|
|
|
n("memories", "{n} selected", "{n} selected", size, {
|
|
|
|
n: size,
|
2022-10-28 19:08:34 +00:00
|
|
|
})
|
|
|
|
}}
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<NcActions :inline="1">
|
|
|
|
<NcActionButton
|
|
|
|
v-for="action of getActions()"
|
|
|
|
:key="action.name"
|
|
|
|
:aria-label="action.name"
|
|
|
|
close-after-click
|
|
|
|
@click="click(action)"
|
|
|
|
>
|
|
|
|
{{ action.name }}
|
|
|
|
<template #icon>
|
|
|
|
<component :is="action.icon" :size="20" />
|
|
|
|
</template>
|
|
|
|
</NcActionButton>
|
|
|
|
</NcActions>
|
2022-10-14 21:21:17 +00:00
|
|
|
</div>
|
2022-10-28 19:08:34 +00:00
|
|
|
|
|
|
|
<!-- Selection Modals -->
|
|
|
|
<EditDate ref="editDate" @refresh="refresh" />
|
|
|
|
<FaceMoveModal
|
|
|
|
ref="faceMoveModal"
|
|
|
|
@moved="deletePhotos"
|
|
|
|
:updateLoading="updateLoading"
|
|
|
|
/>
|
|
|
|
<AddToAlbumModal ref="addToAlbumModal" @added="clearSelection" />
|
|
|
|
</div>
|
2022-10-14 21:21:17 +00:00
|
|
|
</template>
|
|
|
|
|
|
|
|
<script lang="ts">
|
2022-11-01 05:33:17 +00:00
|
|
|
import { Component, Emit, Mixins, Prop, Watch } from "vue-property-decorator";
|
2022-10-28 19:08:34 +00:00
|
|
|
import GlobalMixin from "../mixins/GlobalMixin";
|
|
|
|
import UserConfig from "../mixins/UserConfig";
|
2022-10-18 22:52:54 +00:00
|
|
|
|
2022-10-28 19:08:34 +00:00
|
|
|
import { showError } from "@nextcloud/dialogs";
|
|
|
|
import { generateUrl } from "@nextcloud/router";
|
|
|
|
import { NcActions, NcActionButton } from "@nextcloud/vue";
|
|
|
|
import { translate as t, translatePlural as n } from "@nextcloud/l10n";
|
2022-11-03 21:47:10 +00:00
|
|
|
import { IDay, IHeadRow, IPhoto, ISelectionAction } from "../types";
|
2022-10-28 19:08:34 +00:00
|
|
|
import { getCurrentUser } from "@nextcloud/auth";
|
2022-10-14 21:21:17 +00:00
|
|
|
|
2022-10-18 22:52:54 +00:00
|
|
|
import * as dav from "../services/DavRequests";
|
2022-10-28 19:08:34 +00:00
|
|
|
import EditDate from "./modal/EditDate.vue";
|
|
|
|
import FaceMoveModal from "./modal/FaceMoveModal.vue";
|
|
|
|
import AddToAlbumModal from "./modal/AddToAlbumModal.vue";
|
|
|
|
|
|
|
|
import StarIcon from "vue-material-design-icons/Star.vue";
|
|
|
|
import DownloadIcon from "vue-material-design-icons/Download.vue";
|
|
|
|
import DeleteIcon from "vue-material-design-icons/Delete.vue";
|
|
|
|
import EditIcon from "vue-material-design-icons/ClockEdit.vue";
|
|
|
|
import ArchiveIcon from "vue-material-design-icons/PackageDown.vue";
|
|
|
|
import UnarchiveIcon from "vue-material-design-icons/PackageUp.vue";
|
|
|
|
import OpenInNewIcon from "vue-material-design-icons/OpenInNew.vue";
|
|
|
|
import CloseIcon from "vue-material-design-icons/Close.vue";
|
|
|
|
import MoveIcon from "vue-material-design-icons/ImageMove.vue";
|
|
|
|
import AlbumsIcon from "vue-material-design-icons/ImageAlbum.vue";
|
|
|
|
import AlbumRemoveIcon from "vue-material-design-icons/BookRemove.vue";
|
2022-10-14 21:21:17 +00:00
|
|
|
|
2022-10-18 22:52:54 +00:00
|
|
|
type Selection = Map<number, IPhoto>;
|
2022-10-14 21:21:17 +00:00
|
|
|
|
|
|
|
@Component({
|
2022-10-28 19:08:34 +00:00
|
|
|
components: {
|
|
|
|
NcActions,
|
|
|
|
NcActionButton,
|
|
|
|
EditDate,
|
|
|
|
FaceMoveModal,
|
|
|
|
AddToAlbumModal,
|
|
|
|
|
|
|
|
CloseIcon,
|
|
|
|
},
|
2022-10-14 21:21:17 +00:00
|
|
|
})
|
2022-11-01 04:59:24 +00:00
|
|
|
export default class SelectionManager extends Mixins(GlobalMixin, UserConfig) {
|
2022-10-28 19:08:34 +00:00
|
|
|
@Prop() public heads: { [dayid: number]: IHeadRow };
|
|
|
|
|
2022-11-01 05:33:17 +00:00
|
|
|
private show = false;
|
2022-11-03 21:47:10 +00:00
|
|
|
private size = 0;
|
2022-11-01 05:33:17 +00:00
|
|
|
private readonly selection!: Selection;
|
2022-10-28 19:08:34 +00:00
|
|
|
private readonly defaultActions: ISelectionAction[];
|
|
|
|
|
|
|
|
@Emit("refresh")
|
|
|
|
refresh() {}
|
|
|
|
|
|
|
|
@Emit("delete")
|
|
|
|
deletePhotos(photos: IPhoto[]) {}
|
|
|
|
|
|
|
|
@Emit("updateLoading")
|
|
|
|
updateLoading(delta: number) {}
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
super();
|
|
|
|
|
2022-11-01 05:33:17 +00:00
|
|
|
this.selection = new Map<number, IPhoto>();
|
|
|
|
|
2022-10-28 19:08:34 +00:00
|
|
|
// Make default actions
|
|
|
|
this.defaultActions = [
|
|
|
|
{
|
|
|
|
name: t("memories", "Delete"),
|
|
|
|
icon: DeleteIcon,
|
|
|
|
callback: this.deleteSelection.bind(this),
|
2022-10-28 21:26:56 +00:00
|
|
|
if: () => !this.routeIsAlbum(),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: t("memories", "Remove from album"),
|
|
|
|
icon: AlbumRemoveIcon,
|
|
|
|
callback: this.removeFromAlbum.bind(this),
|
|
|
|
if: () => this.routeIsAlbum(),
|
2022-10-28 19:08:34 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: t("memories", "Download"),
|
|
|
|
icon: DownloadIcon,
|
|
|
|
callback: this.downloadSelection.bind(this),
|
2022-10-29 02:08:00 +00:00
|
|
|
allowPublic: true,
|
2022-10-28 19:08:34 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: t("memories", "Favorite"),
|
|
|
|
icon: StarIcon,
|
|
|
|
callback: this.favoriteSelection.bind(this),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: t("memories", "Archive"),
|
|
|
|
icon: ArchiveIcon,
|
|
|
|
callback: this.archiveSelection.bind(this),
|
2022-10-28 21:26:56 +00:00
|
|
|
if: () =>
|
|
|
|
this.allowArchive() && !this.routeIsArchive() && !this.routeIsAlbum(),
|
2022-10-28 19:08:34 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: t("memories", "Unarchive"),
|
|
|
|
icon: UnarchiveIcon,
|
|
|
|
callback: this.archiveSelection.bind(this),
|
|
|
|
if: () => this.allowArchive() && this.routeIsArchive(),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: t("memories", "Edit Date/Time"),
|
|
|
|
icon: EditIcon,
|
|
|
|
callback: this.editDateSelection.bind(this),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: t("memories", "View in folder"),
|
|
|
|
icon: OpenInNewIcon,
|
|
|
|
callback: this.viewInFolder.bind(this),
|
2022-10-28 21:26:56 +00:00
|
|
|
if: () => this.selection.size === 1 && !this.routeIsAlbum(),
|
2022-10-28 19:08:34 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: t("memories", "Add to album"),
|
|
|
|
icon: AlbumsIcon,
|
|
|
|
callback: this.addToAlbum.bind(this),
|
2022-10-28 21:26:56 +00:00
|
|
|
if: (self: typeof this) =>
|
|
|
|
self.config_albumsEnabled && !self.routeIsAlbum(),
|
2022-10-28 19:08:34 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: t("memories", "Move to another person"),
|
|
|
|
icon: MoveIcon,
|
|
|
|
callback: this.moveSelectionToPerson.bind(this),
|
|
|
|
if: () => this.$route.name === "people",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: t("memories", "Remove from person"),
|
|
|
|
icon: CloseIcon,
|
|
|
|
callback: this.removeSelectionFromPerson.bind(this),
|
|
|
|
if: () => this.$route.name === "people",
|
|
|
|
},
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2022-11-01 05:33:17 +00:00
|
|
|
@Watch("show")
|
|
|
|
onShowChange() {
|
|
|
|
const elem = document.getElementById("content-vue");
|
|
|
|
const klass = "has-top-bar";
|
|
|
|
if (this.show) {
|
|
|
|
elem.classList.add(klass);
|
|
|
|
} else {
|
|
|
|
elem.classList.remove(klass);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private selectionChanged() {
|
|
|
|
this.show = this.selection.size > 0;
|
2022-11-03 21:47:10 +00:00
|
|
|
this.size = this.selection.size;
|
2022-11-01 05:33:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Is this fileid (or anything if not specified) selected */
|
|
|
|
public has(fileid?: number) {
|
|
|
|
if (fileid === undefined) {
|
|
|
|
return this.selection.size > 0;
|
|
|
|
}
|
|
|
|
return this.selection.has(fileid);
|
|
|
|
}
|
|
|
|
|
2022-11-03 21:47:10 +00:00
|
|
|
/** Restore selections from new day object */
|
|
|
|
public restoreDay(day: IDay) {
|
|
|
|
if (!this.has()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// FileID => Photo for new day
|
|
|
|
const dayMap = new Map<number, IPhoto>();
|
|
|
|
day.detail.forEach((photo) => {
|
|
|
|
dayMap.set(photo.fileid, photo);
|
|
|
|
});
|
|
|
|
|
|
|
|
this.selection.forEach((photo, fileid) => {
|
|
|
|
// Process this day only
|
|
|
|
if (photo.dayid !== day.dayid) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove all selections that are not in the new day
|
|
|
|
if (!dayMap.has(fileid)) {
|
|
|
|
this.selection.delete(fileid);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update the photo object
|
|
|
|
const newPhoto = dayMap.get(fileid);
|
|
|
|
this.selection.set(fileid, newPhoto);
|
|
|
|
newPhoto.flag |= this.c.FLAG_SELECTED;
|
|
|
|
});
|
|
|
|
|
|
|
|
this.selectionChanged();
|
|
|
|
}
|
|
|
|
|
2022-10-28 19:08:34 +00:00
|
|
|
/** Click on an action */
|
|
|
|
private async click(action: ISelectionAction) {
|
|
|
|
try {
|
|
|
|
this.updateLoading(1);
|
|
|
|
await action.callback(this.selection);
|
|
|
|
} catch (error) {
|
|
|
|
console.error(error);
|
|
|
|
} finally {
|
|
|
|
this.updateLoading(-1);
|
2022-10-18 22:52:54 +00:00
|
|
|
}
|
2022-10-28 19:08:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Get the actions list */
|
|
|
|
private getActions(): ISelectionAction[] {
|
2022-11-01 05:33:17 +00:00
|
|
|
return this.defaultActions.filter(
|
|
|
|
(a) => (!a.if || a.if(this)) && (!this.routeIsPublic() || a.allowPublic)
|
|
|
|
);
|
2022-10-28 19:08:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Clear all selected photos */
|
|
|
|
public clearSelection(only?: IPhoto[]) {
|
|
|
|
const heads = new Set<IHeadRow>();
|
|
|
|
const toClear = only || this.selection.values();
|
|
|
|
Array.from(toClear).forEach((photo: IPhoto) => {
|
|
|
|
photo.flag &= ~this.c.FLAG_SELECTED;
|
|
|
|
heads.add(this.heads[photo.d.dayid]);
|
|
|
|
this.selection.delete(photo.fileid);
|
2022-11-01 05:33:17 +00:00
|
|
|
this.selectionChanged();
|
2022-10-28 19:08:34 +00:00
|
|
|
});
|
|
|
|
heads.forEach(this.updateHeadSelected);
|
|
|
|
this.$forceUpdate();
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Check if the day for a photo is selected entirely */
|
|
|
|
private updateHeadSelected(head: IHeadRow) {
|
|
|
|
let selected = true;
|
|
|
|
|
|
|
|
// Check if all photos are selected
|
|
|
|
for (const row of head.day.rows) {
|
|
|
|
for (const photo of row.photos) {
|
|
|
|
if (!(photo.flag & this.c.FLAG_SELECTED)) {
|
|
|
|
selected = false;
|
|
|
|
break;
|
2022-10-19 00:01:04 +00:00
|
|
|
}
|
2022-10-28 19:08:34 +00:00
|
|
|
}
|
2022-10-19 00:01:04 +00:00
|
|
|
}
|
|
|
|
|
2022-10-28 19:08:34 +00:00
|
|
|
// Update head
|
|
|
|
head.selected = selected;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Add a photo to selection list */
|
|
|
|
public 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
|
|
|
|
) {
|
|
|
|
return; // ignore placeholders
|
2022-10-18 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
2022-10-28 19:08:34 +00:00
|
|
|
const nval = val ?? !this.selection.has(photo.fileid);
|
|
|
|
if (nval) {
|
|
|
|
photo.flag |= this.c.FLAG_SELECTED;
|
|
|
|
this.selection.set(photo.fileid, photo);
|
2022-11-01 05:33:17 +00:00
|
|
|
this.selectionChanged();
|
2022-10-28 19:08:34 +00:00
|
|
|
} else {
|
|
|
|
photo.flag &= ~this.c.FLAG_SELECTED;
|
2022-11-01 05:16:53 +00:00
|
|
|
|
|
|
|
// Only do this if the photo in the selection set is this one.
|
|
|
|
// The problem arises when there are duplicates (e.g. face rect)
|
|
|
|
// in the list, which creates an inconsistent state if we do this.
|
|
|
|
if (this.selection.get(photo.fileid) === photo) {
|
|
|
|
this.selection.delete(photo.fileid);
|
2022-11-01 05:33:17 +00:00
|
|
|
this.selectionChanged();
|
2022-11-01 05:16:53 +00:00
|
|
|
}
|
2022-10-14 21:21:17 +00:00
|
|
|
}
|
|
|
|
|
2022-10-28 19:08:34 +00:00
|
|
|
if (!noUpdate) {
|
|
|
|
this.updateHeadSelected(this.heads[photo.d.dayid]);
|
|
|
|
this.$forceUpdate();
|
2022-10-14 21:21:17 +00:00
|
|
|
}
|
2022-10-28 19:08:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Select or deselect all photos in a head */
|
|
|
|
public selectHead(head: IHeadRow) {
|
|
|
|
head.selected = !head.selected;
|
|
|
|
for (const row of head.day.rows) {
|
|
|
|
for (const photo of row.photos) {
|
|
|
|
this.selectPhoto(photo, head.selected, true);
|
|
|
|
}
|
2022-10-14 21:21:17 +00:00
|
|
|
}
|
2022-10-28 19:08:34 +00:00
|
|
|
this.$forceUpdate();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Download the currently selected files
|
|
|
|
*/
|
|
|
|
private async downloadSelection(selection: Selection) {
|
|
|
|
if (selection.size >= 100) {
|
|
|
|
if (
|
|
|
|
!confirm(
|
|
|
|
this.t(
|
|
|
|
"memories",
|
|
|
|
"You are about to download a large number of files. Are you sure?"
|
|
|
|
)
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
return;
|
|
|
|
}
|
2022-10-14 21:21:17 +00:00
|
|
|
}
|
2022-10-28 22:46:13 +00:00
|
|
|
await dav.downloadFilesByIds(Array.from(selection.values()));
|
2022-10-28 19:08:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if all files selected currently are favorites
|
|
|
|
*/
|
|
|
|
private allSelectedFavorites(selection: Selection) {
|
|
|
|
return Array.from(selection.values()).every(
|
|
|
|
(p) => p.flag & this.c.FLAG_IS_FAVORITE
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Favorite the currently selected photos
|
|
|
|
*/
|
|
|
|
private async favoriteSelection(selection: Selection) {
|
|
|
|
const val = !this.allSelectedFavorites(selection);
|
2022-10-28 22:48:10 +00:00
|
|
|
for await (const favIds of dav.favoritePhotos(
|
2022-10-28 22:46:13 +00:00
|
|
|
Array.from(selection.values()),
|
2022-10-28 19:08:34 +00:00
|
|
|
val
|
|
|
|
)) {
|
2022-10-14 21:21:17 +00:00
|
|
|
}
|
2022-10-28 19:08:34 +00:00
|
|
|
this.clearSelection();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Delete the currently selected photos
|
|
|
|
*/
|
|
|
|
private async deleteSelection(selection: Selection) {
|
|
|
|
if (selection.size >= 100) {
|
|
|
|
if (
|
|
|
|
!confirm(
|
|
|
|
this.t(
|
|
|
|
"memories",
|
|
|
|
"You are about to delete a large number of files. Are you sure?"
|
|
|
|
)
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
return;
|
|
|
|
}
|
2022-10-14 21:21:17 +00:00
|
|
|
}
|
|
|
|
|
2022-10-28 22:46:13 +00:00
|
|
|
for await (const delIds of dav.deletePhotos(
|
|
|
|
Array.from(selection.values())
|
2022-10-28 19:08:34 +00:00
|
|
|
)) {
|
2022-10-29 02:03:29 +00:00
|
|
|
const delPhotos = delIds
|
|
|
|
.filter((id) => id)
|
|
|
|
.map((id) => selection.get(id));
|
2022-10-28 19:08:34 +00:00
|
|
|
this.deletePhotos(delPhotos);
|
2022-10-14 21:21:17 +00:00
|
|
|
}
|
2022-10-28 19:08:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Open the edit date dialog
|
|
|
|
*/
|
|
|
|
private async editDateSelection(selection: Selection) {
|
|
|
|
(<any>this.$refs.editDate).open(Array.from(selection.values()));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Open the files app with the selected file (one)
|
|
|
|
* Opens a new window.
|
|
|
|
*/
|
|
|
|
private async viewInFolder(selection: Selection) {
|
|
|
|
if (selection.size !== 1) return;
|
|
|
|
|
|
|
|
const photo: IPhoto = selection.values().next().value;
|
2022-10-28 22:46:13 +00:00
|
|
|
const f = await dav.getFiles([photo]);
|
2022-10-28 19:08:34 +00:00
|
|
|
if (f.length === 0) return;
|
|
|
|
|
|
|
|
const file = f[0];
|
|
|
|
const dirPath = file.filename.split("/").slice(0, -1).join("/");
|
|
|
|
const url = generateUrl(
|
|
|
|
`/apps/files/?dir=${dirPath}&scrollto=${file.fileid}&openfile=${file.fileid}`
|
|
|
|
);
|
|
|
|
window.open(url, "_blank");
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Archive the currently selected photos
|
|
|
|
*/
|
|
|
|
private async archiveSelection(selection: Selection) {
|
|
|
|
if (selection.size >= 100) {
|
|
|
|
if (
|
|
|
|
!confirm(
|
|
|
|
this.t(
|
|
|
|
"memories",
|
|
|
|
"You are about to touch a large number of files. Are you sure?"
|
|
|
|
)
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
return;
|
|
|
|
}
|
2022-10-14 21:21:17 +00:00
|
|
|
}
|
|
|
|
|
2022-10-28 19:08:34 +00:00
|
|
|
for await (let delIds of dav.archiveFilesByIds(
|
|
|
|
Array.from(selection.keys()),
|
|
|
|
!this.routeIsArchive()
|
|
|
|
)) {
|
|
|
|
delIds = delIds.filter((x) => x);
|
|
|
|
if (delIds.length === 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
const delPhotos = delIds.map((id) => selection.get(id));
|
|
|
|
this.deletePhotos(delPhotos);
|
2022-10-14 21:21:17 +00:00
|
|
|
}
|
2022-10-28 19:08:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Archive is not allowed only on folder routes */
|
|
|
|
private allowArchive() {
|
|
|
|
return this.$route.name !== "folders";
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Is archive route */
|
|
|
|
private routeIsArchive() {
|
|
|
|
return this.$route.name === "archive";
|
|
|
|
}
|
|
|
|
|
2022-10-28 21:26:56 +00:00
|
|
|
/** Is album route */
|
|
|
|
private routeIsAlbum() {
|
|
|
|
return this.config_albumsEnabled && this.$route.name === "albums";
|
|
|
|
}
|
|
|
|
|
2022-10-29 02:08:00 +00:00
|
|
|
/** Public route that can't modify anything */
|
|
|
|
private routeIsPublic() {
|
|
|
|
return this.$route.name === "folder-share";
|
|
|
|
}
|
|
|
|
|
2022-10-28 19:08:34 +00:00
|
|
|
/**
|
|
|
|
* Move selected photos to album
|
|
|
|
*/
|
|
|
|
private async addToAlbum(selection: Selection) {
|
|
|
|
(<any>this.$refs.addToAlbumModal).open(Array.from(selection.values()));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Remove selected photos from album
|
|
|
|
*/
|
|
|
|
private async removeFromAlbum(selection: Selection) {
|
|
|
|
try {
|
|
|
|
this.updateLoading(1);
|
|
|
|
const user = this.$route.params.user;
|
|
|
|
const name = this.$route.params.name;
|
2022-10-28 22:46:13 +00:00
|
|
|
const gen = dav.removeFromAlbum(
|
|
|
|
user,
|
|
|
|
name,
|
|
|
|
Array.from(selection.values())
|
|
|
|
);
|
2022-10-28 19:08:34 +00:00
|
|
|
for await (const delIds of gen) {
|
|
|
|
const delPhotos = delIds
|
|
|
|
.filter((p) => p)
|
|
|
|
.map((id) => selection.get(id));
|
|
|
|
this.deletePhotos(delPhotos);
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
console.error(e);
|
|
|
|
showError(
|
|
|
|
e?.message || this.t("memories", "Could not remove photos from album")
|
|
|
|
);
|
|
|
|
} finally {
|
|
|
|
this.updateLoading(-1);
|
2022-10-14 21:21:17 +00:00
|
|
|
}
|
2022-10-28 19:08:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Move selected photos to another person
|
|
|
|
*/
|
|
|
|
private async moveSelectionToPerson(selection: Selection) {
|
|
|
|
if (!this.config_showFaceRect) {
|
|
|
|
showError(
|
|
|
|
this.t(
|
|
|
|
"memories",
|
|
|
|
'You must enable "Mark person in preview" to use this feature'
|
|
|
|
)
|
|
|
|
);
|
|
|
|
return;
|
2022-10-27 05:25:04 +00:00
|
|
|
}
|
2022-10-28 19:08:34 +00:00
|
|
|
(<any>this.$refs.faceMoveModal).open(Array.from(selection.values()));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Remove currently selected photos from person
|
|
|
|
*/
|
|
|
|
private async removeSelectionFromPerson(selection: Selection) {
|
|
|
|
// Make sure route is valid
|
|
|
|
const { user, name } = this.$route.params;
|
|
|
|
if (this.$route.name !== "people" || !user || !name) {
|
|
|
|
return;
|
2022-10-27 06:03:40 +00:00
|
|
|
}
|
|
|
|
|
2022-10-28 19:08:34 +00:00
|
|
|
// Check photo ownership
|
2022-10-28 23:21:08 +00:00
|
|
|
if (this.$route.params.user !== getCurrentUser()?.uid) {
|
2022-10-28 19:08:34 +00:00
|
|
|
showError(
|
|
|
|
this.t("memories", 'Only user "{user}" can update this person', {
|
|
|
|
user,
|
|
|
|
})
|
|
|
|
);
|
|
|
|
return;
|
2022-10-19 01:07:00 +00:00
|
|
|
}
|
|
|
|
|
2022-10-28 19:08:34 +00:00
|
|
|
// Run query
|
|
|
|
for await (let delIds of dav.removeFaceImages(
|
|
|
|
user,
|
|
|
|
name,
|
2022-10-28 22:46:13 +00:00
|
|
|
Array.from(selection.values())
|
2022-10-28 19:08:34 +00:00
|
|
|
)) {
|
|
|
|
const delPhotos = delIds.filter((x) => x).map((id) => selection.get(id));
|
|
|
|
this.deletePhotos(delPhotos);
|
2022-10-14 21:21:17 +00:00
|
|
|
}
|
2022-10-28 19:08:34 +00:00
|
|
|
}
|
2022-10-14 21:21:17 +00:00
|
|
|
}
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
.top-bar {
|
2022-10-28 19:08:34 +00:00
|
|
|
position: absolute;
|
|
|
|
top: 10px;
|
|
|
|
right: 60px;
|
|
|
|
padding: 8px;
|
|
|
|
width: 400px;
|
2022-11-01 04:59:24 +00:00
|
|
|
max-width: 100vw;
|
2022-10-28 19:08:34 +00:00
|
|
|
background-color: var(--color-main-background);
|
|
|
|
box-shadow: 0 0 2px gray;
|
|
|
|
border-radius: 10px;
|
2022-11-01 04:59:24 +00:00
|
|
|
opacity: 0.97;
|
2022-10-28 19:08:34 +00:00
|
|
|
display: flex;
|
|
|
|
vertical-align: middle;
|
|
|
|
z-index: 100;
|
|
|
|
|
|
|
|
> .text {
|
|
|
|
flex-grow: 1;
|
|
|
|
line-height: 40px;
|
|
|
|
padding-left: 8px;
|
|
|
|
}
|
|
|
|
|
2022-11-01 05:33:17 +00:00
|
|
|
@media (max-width: 1024px) {
|
|
|
|
// sidebar is hidden below this point
|
2022-11-01 04:59:24 +00:00
|
|
|
top: 0;
|
|
|
|
left: 0;
|
|
|
|
right: unset;
|
|
|
|
position: fixed;
|
|
|
|
width: 100vw;
|
|
|
|
border-radius: 0px;
|
|
|
|
opacity: 1;
|
|
|
|
padding-top: 3px;
|
|
|
|
padding-bottom: 3px;
|
2022-10-28 19:08:34 +00:00
|
|
|
}
|
2022-10-14 21:21:17 +00:00
|
|
|
}
|
|
|
|
</style>
|