diff --git a/lib/Controller/ApiController.php b/lib/Controller/ApiController.php index 907c9b96..9de9d270 100644 --- a/lib/Controller/ApiController.php +++ b/lib/Controller/ApiController.php @@ -691,6 +691,12 @@ class ApiController extends Controller { $transforms = []; + // Add extra information, basename and mimetype + if (!$aggregateOnly && ($fields = $this->request->getParam('fields'))) { + $fields = explode(',', $fields); + $transforms[] = [$this->timelineQuery, 'transformExtraFields', $fields]; + } + // Filter only favorites if ($this->request->getParam('fav')) { $transforms[] = [$this->timelineQuery, 'transformFavoriteFilter']; diff --git a/lib/Db/TimelineQuery.php b/lib/Db/TimelineQuery.php index 4c7f4511..6eea23db 100644 --- a/lib/Db/TimelineQuery.php +++ b/lib/Db/TimelineQuery.php @@ -43,6 +43,18 @@ class TimelineQuery return $sql; } + public function transformExtraFields(IQueryBuilder &$query, string $uid, array &$fields) + { + if (\in_array('basename', $fields, true)) { + $query->addSelect('f.name AS basename'); + } + + if (\in_array('mimetype', $fields, true)) { + $query->join('f', 'mimetypes', 'mimetypes', $query->expr()->eq('f.mimetype', 'mimetypes.id')); + $query->addSelect('mimetypes.mimetype'); + } + } + public function getInfoById(int $id): array { $qb = $this->connection->getQueryBuilder(); diff --git a/src/components/SelectionManager.vue b/src/components/SelectionManager.vue index 1d22810a..82d3650e 100644 --- a/src/components/SelectionManager.vue +++ b/src/components/SelectionManager.vue @@ -108,17 +108,17 @@ export default class SelectionHandler extends Mixins(GlobalMixin, UserConfig) { // Make default actions this.defaultActions = [ - { - // This is at the top because otherwise it is confusing - name: t("memories", "Remove from album"), - icon: AlbumRemoveIcon, - callback: this.removeFromAlbum.bind(this), - if: () => this.$route.name === "albums", - }, { name: t("memories", "Delete"), icon: DeleteIcon, callback: this.deleteSelection.bind(this), + if: () => !this.routeIsAlbum(), + }, + { + name: t("memories", "Remove from album"), + icon: AlbumRemoveIcon, + callback: this.removeFromAlbum.bind(this), + if: () => this.routeIsAlbum(), }, { name: t("memories", "Download"), @@ -134,7 +134,8 @@ export default class SelectionHandler extends Mixins(GlobalMixin, UserConfig) { name: t("memories", "Archive"), icon: ArchiveIcon, callback: this.archiveSelection.bind(this), - if: () => this.allowArchive() && !this.routeIsArchive(), + if: () => + this.allowArchive() && !this.routeIsArchive() && !this.routeIsAlbum(), }, { name: t("memories", "Unarchive"), @@ -151,13 +152,14 @@ export default class SelectionHandler extends Mixins(GlobalMixin, UserConfig) { name: t("memories", "View in folder"), icon: OpenInNewIcon, callback: this.viewInFolder.bind(this), - if: () => this.selection.size === 1, + if: () => this.selection.size === 1 && !this.routeIsAlbum(), }, { name: t("memories", "Add to album"), icon: AlbumsIcon, callback: this.addToAlbum.bind(this), - if: (self: any) => self.config_albumsEnabled, + if: (self: typeof this) => + self.config_albumsEnabled && !self.routeIsAlbum(), }, { name: t("memories", "Move to another person"), @@ -402,6 +404,11 @@ export default class SelectionHandler extends Mixins(GlobalMixin, UserConfig) { return this.$route.name === "archive"; } + /** Is album route */ + private routeIsAlbum() { + return this.config_albumsEnabled && this.$route.name === "albums"; + } + /** * Move selected photos to album */ diff --git a/src/components/Timeline.vue b/src/components/Timeline.vue index b4c5bb09..49e0c180 100644 --- a/src/components/Timeline.vue +++ b/src/components/Timeline.vue @@ -219,7 +219,8 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) { /** Nextcloud viewer proxy */ private viewerManager = new ViewerManager( this.deleteFromViewWithAnimation.bind(this), - this.updateLoading.bind(this) + this.updateLoading.bind(this), + this.$route ); mounted() { @@ -530,6 +531,7 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) { "album", `${this.$route.params.user}/${this.$route.params.name}` ); + query.set("fields", "basename,mimetype"); } // Create query string and append to URL diff --git a/src/services/Viewer.ts b/src/services/Viewer.ts index 17323a09..5f40d6c3 100644 --- a/src/services/Viewer.ts +++ b/src/services/Viewer.ts @@ -2,6 +2,7 @@ import { IFileInfo, IPhoto } from "../types"; import { showError } from "@nextcloud/dialogs"; import { subscribe } from "@nextcloud/event-bus"; import { translate as t, translatePlural as n } from "@nextcloud/l10n"; +import { Route } from "vue-router"; import * as dav from "./DavRequests"; // Key to store sidebar state @@ -13,7 +14,8 @@ export class ViewerManager { constructor( ondelete: (photos: IPhoto[]) => void, - private updateLoading: (delta: number) => void + private updateLoading: (delta: number) => void, + private $route: Route ) { subscribe("files:file:deleted", ({ fileid }: { fileid: number }) => { const photo = this.photoMap.get(fileid); @@ -23,7 +25,7 @@ export class ViewerManager { public async open(photo: IPhoto, list?: IPhoto[]) { list = list || photo.d?.detail; - if (!list) return; + if (!list?.length) return; // Repopulate map this.photoMap.clear(); @@ -36,7 +38,14 @@ export class ViewerManager { const ids = list.map((p) => p.fileid); try { this.updateLoading(1); - fileInfos = await dav.getFiles(ids); + + if (this.$route.name === "albums") { + const user = this.$route.params.user; + const name = this.$route.params.name; + fileInfos = dav.getAlbumFileInfos(list, user, name); + } else { + fileInfos = await dav.getFiles(ids); + } } catch (e) { console.error("Failed to load fileInfos", e); showError("Failed to load fileInfos"); @@ -66,7 +75,7 @@ export class ViewerManager { // Open Nextcloud viewer globalThis.OCA.Viewer.open({ - path: fInfo.filename, // path + fileInfo: fInfo, list: fileInfos, // file list canLoop: false, // don't loop onClose: () => { diff --git a/src/services/dav/albums.ts b/src/services/dav/albums.ts index fd864a83..cc14827b 100644 --- a/src/services/dav/albums.ts +++ b/src/services/dav/albums.ts @@ -3,7 +3,7 @@ import { getCurrentUser } from "@nextcloud/auth"; import { generateUrl } from "@nextcloud/router"; import { showError } from "@nextcloud/dialogs"; import { translate as t, translatePlural as n } from "@nextcloud/l10n"; -import { IAlbum, IDay, ITag } from "../../types"; +import { IAlbum, IDay, IFileInfo, IPhoto, ITag } from "../../types"; import { constants } from "../Utils"; import axios from "@nextcloud/axios"; import client from "../DavClient"; @@ -256,3 +256,32 @@ export async function renameAlbum( return album; } } + +/** Get fileinfo objects from album photos */ +export function getAlbumFileInfos( + photos: IPhoto[], + albumUser: string, + albumName: string +): IFileInfo[] { + const uid = getCurrentUser()?.uid; + const collection = + albumUser === uid + ? `/photos/${uid}/albums/${albumName}` + : `/photos/${uid}/sharedalbums/${albumName} (${albumUser})`; + + return photos.map((photo) => { + const basename = + albumUser === uid + ? `${photo.fileid}-${(photo).basename}` + : `${photo.fileid}-${albumName} (${albumUser})`; + + return { + fileid: photo.fileid, + filename: `${collection}/${basename}`, + basename: basename, + mime: (photo).mimetype, + hasPreview: true, + etag: photo.etag, + }; + }); +} diff --git a/src/types.ts b/src/types.ts index f0de0773..7285294a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -6,7 +6,7 @@ export type IFileInfo = { /** Full file name, e.g. /pi/test/Qx0dq7dvEXA.jpg */ filename: string; /** Original file name, e.g. /files/admin/pi/test/Qx0dq7dvEXA.jpg */ - originalFilename: string; + originalFilename?: string; /** Base name of file e.g. Qx0dq7dvEXA.jpg */ basename: string; /** Etag identifier */ @@ -14,7 +14,7 @@ export type IFileInfo = { /** File has preview available */ hasPreview: boolean; /** File is marked favorite */ - favorite: boolean; + favorite?: boolean; /** Vue flags */ flag?: number; };