albums: initial commit

old-stable24
Varun Patil 2022-10-26 15:12:46 -07:00
parent d489ffbc1d
commit 294b3b8a0c
9 changed files with 124 additions and 3 deletions

View File

@ -9,6 +9,7 @@ return [
], ],
['name' => 'page#favorites', 'url' => '/favorites', 'verb' => 'GET'], ['name' => 'page#favorites', 'url' => '/favorites', 'verb' => 'GET'],
['name' => 'page#videos', 'url' => '/videos', 'verb' => 'GET'], ['name' => 'page#videos', 'url' => '/videos', 'verb' => 'GET'],
['name' => 'page#albums', 'url' => '/albums', 'verb' => 'GET'],
['name' => 'page#archive', 'url' => '/archive', 'verb' => 'GET'], ['name' => 'page#archive', 'url' => '/archive', 'verb' => 'GET'],
['name' => 'page#thisday', 'url' => '/thisday', 'verb' => 'GET'], ['name' => 'page#thisday', 'url' => '/thisday', 'verb' => 'GET'],
['name' => 'page#people', 'url' => '/people/{name}', 'verb' => 'GET', ['name' => 'page#people', 'url' => '/people/{name}', 'verb' => 'GET',
@ -24,11 +25,17 @@ return [
['name' => 'api#days', 'url' => '/api/days', 'verb' => 'GET'], ['name' => 'api#days', 'url' => '/api/days', 'verb' => 'GET'],
['name' => 'api#dayPost', 'url' => '/api/days', 'verb' => 'POST'], ['name' => 'api#dayPost', 'url' => '/api/days', 'verb' => 'POST'],
['name' => 'api#day', 'url' => '/api/days/{id}', 'verb' => 'GET'], ['name' => 'api#day', 'url' => '/api/days/{id}', 'verb' => 'GET'],
['name' => 'api#tags', 'url' => '/api/tags', 'verb' => 'GET'], ['name' => 'api#tags', 'url' => '/api/tags', 'verb' => 'GET'],
['name' => 'api#albums', 'url' => '/api/albums', 'verb' => 'GET'],
['name' => 'api#faces', 'url' => '/api/faces', 'verb' => 'GET'], ['name' => 'api#faces', 'url' => '/api/faces', 'verb' => 'GET'],
['name' => 'api#facePreview', 'url' => '/api/faces/preview/{id}', 'verb' => 'GET'], ['name' => 'api#facePreview', 'url' => '/api/faces/preview/{id}', 'verb' => 'GET'],
['name' => 'api#imageInfo', 'url' => '/api/info/{id}', 'verb' => 'GET'], ['name' => 'api#imageInfo', 'url' => '/api/info/{id}', 'verb' => 'GET'],
['name' => 'api#imageEdit', 'url' => '/api/edit/{id}', 'verb' => 'PATCH'], ['name' => 'api#imageEdit', 'url' => '/api/edit/{id}', 'verb' => 'PATCH'],
['name' => 'api#archive', 'url' => '/api/archive/{id}', 'verb' => 'PATCH'], ['name' => 'api#archive', 'url' => '/api/archive/{id}', 'verb' => 'PATCH'],
// Config API // Config API

View File

@ -253,6 +253,30 @@ class ApiController extends Controller
return new JSONResponse($list, Http::STATUS_OK); return new JSONResponse($list, Http::STATUS_OK);
} }
/**
* @NoAdminRequired
* @NoCSRFRequired
*
* Get list of albums with counts of images
*/
public function albums(): JSONResponse
{
$user = $this->userSession->getUser();
if (null === $user) {
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
}
// Check tags enabled for this user
if (!$this->albumsIsEnabled()) {
return new JSONResponse(['message' => 'Albums not enabled for user'], Http::STATUS_PRECONDITION_FAILED);
}
// Run actual query
$list = $this->timelineQuery->getAlbums($user->getUID());
return new JSONResponse($list, Http::STATUS_OK);
}
/** /**
* @NoAdminRequired * @NoAdminRequired
* *
@ -737,6 +761,14 @@ class ApiController extends Controller
return $folder; return $folder;
} }
/**
* Check if albums are enabled for this user.
*/
private function albumsIsEnabled(): bool
{
return $this->appManager->isEnabledForUser('photos');
}
/** /**
* Check if tags is enabled for this user. * Check if tags is enabled for this user.
*/ */

View File

@ -120,6 +120,16 @@ class PageController extends Controller
return $this->main(); return $this->main();
} }
/**
* @NoAdminRequired
*
* @NoCSRFRequired
*/
public function albums()
{
return $this->main();
}
/** /**
* @NoAdminRequired * @NoAdminRequired
* *

View File

@ -12,6 +12,7 @@ class TimelineQuery
use TimelineQueryFaces; use TimelineQueryFaces;
use TimelineQueryFilters; use TimelineQueryFilters;
use TimelineQueryTags; use TimelineQueryTags;
use TimelineQueryAlbums;
protected IDBConnection $connection; protected IDBConnection $connection;

View File

@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace OCA\Memories\Db;
use OCP\IDBConnection;
trait TimelineQueryAlbums
{
protected IDBConnection $connection;
public function getAlbums(string $uid)
{
$query = $this->connection->getQueryBuilder();
// SELECT everything from albums
$query->select('*')->from('photos_albums', 'pa')->where(
$query->expr()->eq('user', $query->createNamedParameter($uid)),
);
// GROUP and ORDER by
$query->orderBy('pa.created', 'DESC');
$query->addOrderBy('pa.album_id', 'DESC'); // tie-breaker
// FETCH all albums
$albums = $query->executeQuery()->fetchAll();
// Post process
foreach ($albums as &$row) {
$row['album_id'] = (int) $row['id'];
$row['created'] = (int) $row['count'];
$row['last_added_photo'] = (int) $row['last_added_photo'];
}
return $albums;
}
}

View File

@ -23,6 +23,10 @@
:title="t('memories', 'Videos')"> :title="t('memories', 'Videos')">
<Video slot="icon" :size="20" /> <Video slot="icon" :size="20" />
</NcAppNavigationItem> </NcAppNavigationItem>
<NcAppNavigationItem :to="{name: 'albums'}"
:title="t('memories', 'Albums')" v-if="showAlbums">
<AlbumIcon slot="icon" :size="20" />
</NcAppNavigationItem>
<NcAppNavigationItem :to="{name: 'people'}" <NcAppNavigationItem :to="{name: 'people'}"
:title="t('memories', 'People')" v-if="showPeople"> :title="t('memories', 'People')" v-if="showPeople">
<PeopleIcon slot="icon" :size="20" /> <PeopleIcon slot="icon" :size="20" />
@ -78,6 +82,7 @@ import ImageMultiple from 'vue-material-design-icons/ImageMultiple.vue'
import FolderIcon from 'vue-material-design-icons/Folder.vue' import FolderIcon from 'vue-material-design-icons/Folder.vue'
import Star from 'vue-material-design-icons/Star.vue' import Star from 'vue-material-design-icons/Star.vue'
import Video from 'vue-material-design-icons/Video.vue' import Video from 'vue-material-design-icons/Video.vue'
import AlbumIcon from 'vue-material-design-icons/ImageAlbum.vue';
import ArchiveIcon from 'vue-material-design-icons/PackageDown.vue'; import ArchiveIcon from 'vue-material-design-icons/PackageDown.vue';
import CalendarIcon from 'vue-material-design-icons/Calendar.vue'; import CalendarIcon from 'vue-material-design-icons/Calendar.vue';
import PeopleIcon from 'vue-material-design-icons/AccountBoxMultiple.vue'; import PeopleIcon from 'vue-material-design-icons/AccountBoxMultiple.vue';
@ -100,6 +105,7 @@ import MapIcon from 'vue-material-design-icons/Map.vue';
FolderIcon, FolderIcon,
Star, Star,
Video, Video,
AlbumIcon,
ArchiveIcon, ArchiveIcon,
CalendarIcon, CalendarIcon,
PeopleIcon, PeopleIcon,
@ -110,6 +116,11 @@ import MapIcon from 'vue-material-design-icons/Map.vue';
export default class App extends Mixins(GlobalMixin, UserConfig) { export default class App extends Mixins(GlobalMixin, UserConfig) {
// Outer element // Outer element
get ncVersion() {
const version = (<any>window.OC).config.version.split('.');
return Number(version[0]);
}
get showPeople() { get showPeople() {
return this.config_recognizeEnabled || getCurrentUser()?.isAdmin; return this.config_recognizeEnabled || getCurrentUser()?.isAdmin;
} }
@ -118,9 +129,12 @@ export default class App extends Mixins(GlobalMixin, UserConfig) {
return this.config_timelinePath === 'EMPTY'; return this.config_timelinePath === 'EMPTY';
} }
get showAlbums() {
return this.ncVersion >= 25; // todo: and photos enabled
}
get removeOuterGap() { get removeOuterGap() {
const version = (<any>window.OC).config.version.split('.'); return this.ncVersion >= 25;
return (Number(version[0]) >= 25);
} }
async beforeMount() { async beforeMount() {

View File

@ -500,6 +500,7 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
case 'favorites': return this.t('memories', 'Favorites'); case 'favorites': return this.t('memories', 'Favorites');
case 'people': return this.t('memories', 'People'); case 'people': return this.t('memories', 'People');
case 'videos': return this.t('memories', 'Videos'); case 'videos': return this.t('memories', 'Videos');
case 'albums': return this.t('memories', 'Albums');
case 'archive': return this.t('memories', 'Archive'); case 'archive': return this.t('memories', 'Archive');
case 'thisday': return this.t('memories', 'On this day'); case 'thisday': return this.t('memories', 'On this day');
case 'tags': return this.t('memories', 'Tags'); case 'tags': return this.t('memories', 'Tags');
@ -555,6 +556,8 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
data = await dav.getTagsData(); data = await dav.getTagsData();
} else if (this.$route.name === 'people' && !this.$route.params.name) { } else if (this.$route.name === 'people' && !this.$route.params.name) {
data = await dav.getPeopleData(); data = await dav.getPeopleData();
} else if (this.$route.name === 'albums') {
data = await dav.getAlbumsData();
} else { } else {
// Try the cache // Try the cache
try { try {

View File

@ -82,6 +82,15 @@
}), }),
}, },
{
path: '/albums',
component: Timeline,
name: 'albums',
props: route => ({
rootTitle: t('memories', 'Albums'),
}),
},
{ {
path: '/archive', path: '/archive',
component: Timeline, component: Timeline,

View File

@ -552,3 +552,10 @@ export async function* removeFaceImages(user: string, name: string, fileIds: num
yield* runInParallel(calls, 10); yield* runInParallel(calls, 10);
} }
/**
* Get list of albums and convert to Days response
*/
export async function getAlbumsData(): Promise<IDay[]> {
return [];
}