albums: initial commit
parent
d489ffbc1d
commit
294b3b8a0c
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -120,6 +120,16 @@ class PageController extends Controller
|
||||||
return $this->main();
|
return $this->main();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*
|
||||||
|
* @NoCSRFRequired
|
||||||
|
*/
|
||||||
|
public function albums()
|
||||||
|
{
|
||||||
|
return $this->main();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @NoAdminRequired
|
* @NoAdminRequired
|
||||||
*
|
*
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
18
src/App.vue
18
src/App.vue
|
@ -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() {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -82,6 +82,15 @@
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: '/albums',
|
||||||
|
component: Timeline,
|
||||||
|
name: 'albums',
|
||||||
|
props: route => ({
|
||||||
|
rootTitle: t('memories', 'Albums'),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
path: '/archive',
|
path: '/archive',
|
||||||
component: Timeline,
|
component: Timeline,
|
||||||
|
|
|
@ -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 [];
|
||||||
|
}
|
Loading…
Reference in New Issue