From 024c0fd808a0434b86f159df23eb0daf79ad0989 Mon Sep 17 00:00:00 2001 From: Jo Van Bulck Date: Wed, 17 Jul 2024 22:27:52 +0200 Subject: [PATCH 1/8] albums: show whether owned album is shared with other users Signed-off-by: Jo Van Bulck --- lib/Db/AlbumsQuery.php | 8 ++++++++ src/components/frame/Cluster.vue | 2 ++ src/components/modal/AlbumsList.vue | 2 ++ src/typings/cluster.d.ts | 2 ++ 4 files changed, 14 insertions(+) diff --git a/lib/Db/AlbumsQuery.php b/lib/Db/AlbumsQuery.php index 2a198789d..2a688772b 100644 --- a/lib/Db/AlbumsQuery.php +++ b/lib/Db/AlbumsQuery.php @@ -88,12 +88,20 @@ public function getList( // FETCH all albums $albums = $query->executeQuery()->fetchAll(); + // Additionally fetch all shared album ids + $queryShared = $this->connection->getQueryBuilder(); + $queryShared->select('album_id')->from($this->collaboratorsTable()); + $shared_ids = array_map(function($row) { + return (int)$row['album_id']; + }, $queryShared->executeQuery()->fetchAll()); + // Post process foreach ($albums as &$row) { $row['cluster_id'] = $row['user'].'/'.$row['name']; $row['album_id'] = (int) $row['album_id']; $row['created'] = (int) $row['created']; $row['last_added_photo'] = (int) $row['last_added_photo']; + $row['shared'] = in_array($row['album_id'], $shared_ids, true); } return $albums; diff --git a/src/components/frame/Cluster.vue b/src/components/frame/Cluster.vue index 2eab8e87b..96a743e86 100644 --- a/src/components/frame/Cluster.vue +++ b/src/components/frame/Cluster.vue @@ -98,6 +98,8 @@ export default defineComponent({ user: this.data.user_display || this.data.user, }); text = `${text} / ${sharer}`; + } else if (this.data.shared) { + text += ' | ' + this.t('memories', 'Shared Album'); } return text; diff --git a/src/components/modal/AlbumsList.vue b/src/components/modal/AlbumsList.vue index b1aa1e112..804e67bee 100644 --- a/src/components/modal/AlbumsList.vue +++ b/src/components/modal/AlbumsList.vue @@ -115,6 +115,8 @@ export default defineComponent({ this.t('memories', 'Shared by {user}', { user: album.user_display || album.user, }); + } else if (album.shared) { + text += ' | ' + this.t('memories', 'Shared Album'); } return text; diff --git a/src/typings/cluster.d.ts b/src/typings/cluster.d.ts index bc4658082..47dd214ff 100644 --- a/src/typings/cluster.d.ts +++ b/src/typings/cluster.d.ts @@ -46,6 +46,8 @@ declare module '@typings' { last_added_photo_etag: string; /** Record ID of the latest update */ update_id: number; + /** Album is shared with other users */ + shared: boolean; } export interface IFace extends ICluster { From bf6616876c925659a4d10a6a3ea2264cb949853c Mon Sep 17 00:00:00 2001 From: Jo Van Bulck Date: Wed, 17 Jul 2024 22:32:33 +0200 Subject: [PATCH 2/8] albums: display avatars and location field in contents view Signed-off-by: Jo Van Bulck --- .../top-matter/AlbumDynamicTopMatter.vue | 98 +++++++++++++++++++ .../top-matter/DynamicTopMatter.vue | 4 +- 2 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 src/components/top-matter/AlbumDynamicTopMatter.vue diff --git a/src/components/top-matter/AlbumDynamicTopMatter.vue b/src/components/top-matter/AlbumDynamicTopMatter.vue new file mode 100644 index 000000000..2648f9bb8 --- /dev/null +++ b/src/components/top-matter/AlbumDynamicTopMatter.vue @@ -0,0 +1,98 @@ + + + + + diff --git a/src/components/top-matter/DynamicTopMatter.vue b/src/components/top-matter/DynamicTopMatter.vue index 2e41ab4f6..e671b53f0 100644 --- a/src/components/top-matter/DynamicTopMatter.vue +++ b/src/components/top-matter/DynamicTopMatter.vue @@ -10,10 +10,10 @@ import { defineComponent, type Component } from 'vue'; import UserMixin from '@mixins/UserConfig'; +import AlbumDynamicTopMatter from './AlbumDynamicTopMatter.vue'; import FolderDynamicTopMatter from './FolderDynamicTopMatter.vue'; import PlacesDynamicTopMatterVue from './PlacesDynamicTopMatter.vue'; import OnThisDay from './OnThisDay.vue'; - import * as strings from '@services/strings'; // Auto-hide top header on public shares if redundant @@ -40,6 +40,8 @@ export default defineComponent({ return FolderDynamicTopMatter; } else if (this.routeIsPlaces) { return PlacesDynamicTopMatterVue; + } else if (this.routeIsAlbums) { + return AlbumDynamicTopMatter; } else if (this.routeIsBase && this.config.enable_top_memories) { return OnThisDay; } From 30fc91dd361fa0cda82c3b5514242be82ed46394 Mon Sep 17 00:00:00 2001 From: Varun Patil Date: Fri, 20 Sep 2024 12:16:15 -0700 Subject: [PATCH 3/8] albums: refactor fetching shared flag Signed-off-by: Varun Patil --- lib/ClustersBackend/AlbumsBackend.php | 6 ++++++ lib/Db/AlbumsQuery.php | 22 ++++++++++++++-------- src/components/frame/Cluster.vue | 18 +----------------- src/components/modal/AlbumsList.vue | 15 ++------------- src/services/dav/albums.ts | 23 ++++++++++++++++++++++- 5 files changed, 45 insertions(+), 39 deletions(-) diff --git a/lib/ClustersBackend/AlbumsBackend.php b/lib/ClustersBackend/AlbumsBackend.php index 79a803793..ee457954f 100644 --- a/lib/ClustersBackend/AlbumsBackend.php +++ b/lib/ClustersBackend/AlbumsBackend.php @@ -121,8 +121,14 @@ public function getClustersInternal(int $fileid = 0): array ); }; + // Transformation to select the shared flag + $sharedFlag = function (IQueryBuilder &$query): void { + $this->albumsQuery->transformSharedFlag($query); + }; + // Transformations to apply to own albums $transformOwned = [ + $sharedFlag, $materialize, $ownCover, $materialize, $etag('last_added_photo'), $etag('cover'), ]; diff --git a/lib/Db/AlbumsQuery.php b/lib/Db/AlbumsQuery.php index 2a688772b..ac4b0d88b 100644 --- a/lib/Db/AlbumsQuery.php +++ b/lib/Db/AlbumsQuery.php @@ -88,20 +88,12 @@ public function getList( // FETCH all albums $albums = $query->executeQuery()->fetchAll(); - // Additionally fetch all shared album ids - $queryShared = $this->connection->getQueryBuilder(); - $queryShared->select('album_id')->from($this->collaboratorsTable()); - $shared_ids = array_map(function($row) { - return (int)$row['album_id']; - }, $queryShared->executeQuery()->fetchAll()); - // Post process foreach ($albums as &$row) { $row['cluster_id'] = $row['user'].'/'.$row['name']; $row['album_id'] = (int) $row['album_id']; $row['created'] = (int) $row['created']; $row['last_added_photo'] = (int) $row['last_added_photo']; - $row['shared'] = in_array($row['album_id'], $shared_ids, true); } return $albums; @@ -308,6 +300,20 @@ public function getAlbumPhotos(int $albumId, ?int $limit, ?int $fileid): array return $result; } + /** + * Query transformation to add a "shared" flag to the list + * of albums (whether the album has any shared collaborators). + */ + public function transformSharedFlag(IQueryBuilder &$query): void + { + $sSq = $query->getConnection()->getQueryBuilder(); + $sSq->select($sSq->expr()->literal(1)) + ->from($this->collaboratorsTable(), 'pc') + ->where($sSq->expr()->eq('pc.album_id', 'pa.album_id')) + ; + $query->selectAlias(SQL::exists($query, $sSq), 'shared'); + } + /** * Get the various collaborator IDs that a user has. * This includes the groups the user is in and the user itself. diff --git a/src/components/frame/Cluster.vue b/src/components/frame/Cluster.vue index 96a743e86..413fd6310 100644 --- a/src/components/frame/Cluster.vue +++ b/src/components/frame/Cluster.vue @@ -86,23 +86,7 @@ export default defineComponent({ subtitle() { if (dav.clusterIs.album(this.data)) { - let text: string; - if (this.data.count === 0) { - text = this.t('memories', 'No items'); - } else { - text = this.n('memories', '{n} item', '{n} items', this.data.count, { n: this.data.count }); - } - - if (this.data.user !== utils.uid) { - const sharer = this.t('memories', 'Shared by {user}', { - user: this.data.user_display || this.data.user, - }); - text = `${text} / ${sharer}`; - } else if (this.data.shared) { - text += ' | ' + this.t('memories', 'Shared Album'); - } - - return text; + return dav.getAlbumSubtitle(this.data); } return String(); diff --git a/src/components/modal/AlbumsList.vue b/src/components/modal/AlbumsList.vue index aea347fb3..e61ebb6d8 100644 --- a/src/components/modal/AlbumsList.vue +++ b/src/components/modal/AlbumsList.vue @@ -37,6 +37,7 @@ import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'; const NcListItem = () => import('@nextcloud/vue/dist/Components/NcListItem.js'); import * as utils from '@services/utils'; +import * as dav from '@services/dav'; import type { IAlbum, IPhoto } from '@typings'; @@ -107,19 +108,7 @@ export default defineComponent({ }, getSubtitle(album: IAlbum) { - let text = this.n('memories', '%n item', '%n items', album.count); - - if (album.user !== utils.uid) { - text += - ' / ' + - this.t('memories', 'Shared by {user}', { - user: album.user_display || album.user, - }); - } else if (album.shared) { - text += ' | ' + this.t('memories', 'Shared Album'); - } - - return text; + return dav.getAlbumSubtitle(album); }, }, }); diff --git a/src/services/dav/albums.ts b/src/services/dav/albums.ts index 39d7e05c4..e57feb256 100644 --- a/src/services/dav/albums.ts +++ b/src/services/dav/albums.ts @@ -4,7 +4,7 @@ import axios from '@nextcloud/axios'; import { showError } from '@nextcloud/dialogs'; import { getLanguage } from '@nextcloud/l10n'; -import { translate as t } from '@services/l10n'; +import { translate as t, translatePlural as n } from '@services/l10n'; import { API } from '@services/API'; import client from '@services/dav/client'; import staticConfig from '@services/static-config'; @@ -258,3 +258,24 @@ export function getAlbumFileInfos(photos: IPhoto[], albumUser: string, albumName } as IFileInfo; }); } + +export function getAlbumSubtitle(album: IAlbum) { + let text: string; + if (album.count === 0) { + text = t('memories', 'No items'); + } else { + text = n('memories', '{n} item', '{n} items', album.count, { n: album.count }); + } + + if (album.user !== utils.uid) { + const sharer = t('memories', 'Shared by {user}', { + user: album.user_display || album.user, + }); + text = `${text} | ${sharer}`; + } else if (album.shared) { + const shared = t('memories', 'Shared Album'); + text = `${text} | ${shared}`; + } + + return text; +} From f6ce584b55c75424c15f26551907a58783eec7b7 Mon Sep 17 00:00:00 2001 From: Varun Patil Date: Fri, 20 Sep 2024 12:44:41 -0700 Subject: [PATCH 4/8] albums: fix link display Signed-off-by: Varun Patil --- .../top-matter/AlbumDynamicTopMatter.vue | 64 ++++++++----------- src/services/dav/albums.ts | 17 ++++- 2 files changed, 44 insertions(+), 37 deletions(-) diff --git a/src/components/top-matter/AlbumDynamicTopMatter.vue b/src/components/top-matter/AlbumDynamicTopMatter.vue index 2648f9bb8..d6328618d 100644 --- a/src/components/top-matter/AlbumDynamicTopMatter.vue +++ b/src/components/top-matter/AlbumDynamicTopMatter.vue @@ -1,19 +1,26 @@ @@ -23,44 +30,24 @@ import { defineComponent } from 'vue'; import * as dav from '@services/dav'; -import MapMarkerOutlineIcon from 'vue-material-design-icons/MapMarkerOutline.vue'; const NcAvatar = () => import('@nextcloud/vue/dist/Components/NcAvatar.js'); -type Collaborator = { - id: string; - label: string; -}; +import MapMarkerOutlineIcon from 'vue-material-design-icons/MapMarkerOutline.vue'; +import LinkIcon from 'vue-material-design-icons/Link.vue'; export default defineComponent({ name: 'AlbumDynamicTopMatter', components: { - MapMarkerOutlineIcon, NcAvatar, + MapMarkerOutlineIcon, + LinkIcon, }, data: () => ({ - album: null as any, + album: null as dav.IDavAlbum | null, }), - computed: { - - collaborators(): string[] { - if (this.album) { - return [this.$route.params.user, ...this.album.collaborators.map((c: Collaborator) => c.id)]; - } - return []; - }, - - /** Get view subtitle for dynamic top matter */ - viewsubTitle(): string { - if (this.album) { - return this.album.location ?? String(); - } - return String(); - }, - }, - methods: { async refresh(): Promise { try { @@ -93,6 +80,11 @@ export default defineComponent({ line-height: 1.2em; margin-top: 0.5em; padding-left: 10px; + + :deep .avatardiv { + margin-right: 2px; + vertical-align: bottom; + } } } diff --git a/src/services/dav/albums.ts b/src/services/dav/albums.ts index e57feb256..8f9c00008 100644 --- a/src/services/dav/albums.ts +++ b/src/services/dav/albums.ts @@ -12,6 +12,15 @@ import * as utils from '@services/utils'; import type { IAlbum, IFileInfo, IPhoto } from '@typings'; +export type IDavAlbum = { + location: string; + collaborators: { + id: string; + label: string; + type: number; + }[]; +}; + /** * Get DAV path for album */ @@ -190,7 +199,7 @@ export async function updateAlbum(album: any, { albumName, properties }: any) { * @param user Owner of album * @param name Name of album (or ID) */ -export async function getAlbum(user: string, name: string, extraProps = {}) { +export async function getAlbum(user: string, name: string, extraProps = {}): Promise { const req = ` { + return (a.type ?? -1) - (b.type ?? -1); + }); + return album; } From 3cd19afe5929807df187cf33f04a42159b70886c Mon Sep 17 00:00:00 2001 From: Varun Patil Date: Fri, 20 Sep 2024 12:51:39 -0700 Subject: [PATCH 5/8] albums: get rid of useless query Signed-off-by: Varun Patil --- src/components/top-matter/AlbumDynamicTopMatter.vue | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/components/top-matter/AlbumDynamicTopMatter.vue b/src/components/top-matter/AlbumDynamicTopMatter.vue index d6328618d..b22f6463d 100644 --- a/src/components/top-matter/AlbumDynamicTopMatter.vue +++ b/src/components/top-matter/AlbumDynamicTopMatter.vue @@ -28,6 +28,7 @@