albums: initial commit
parent
d489ffbc1d
commit
294b3b8a0c
|
@ -9,6 +9,7 @@ return [
|
|||
],
|
||||
['name' => 'page#favorites', 'url' => '/favorites', '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#thisday', 'url' => '/thisday', '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#dayPost', 'url' => '/api/days', 'verb' => 'POST'],
|
||||
['name' => 'api#day', 'url' => '/api/days/{id}', '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#facePreview', 'url' => '/api/faces/preview/{id}', 'verb' => 'GET'],
|
||||
|
||||
['name' => 'api#imageInfo', 'url' => '/api/info/{id}', 'verb' => 'GET'],
|
||||
['name' => 'api#imageEdit', 'url' => '/api/edit/{id}', 'verb' => 'PATCH'],
|
||||
|
||||
['name' => 'api#archive', 'url' => '/api/archive/{id}', 'verb' => 'PATCH'],
|
||||
|
||||
// Config API
|
||||
|
|
|
@ -253,6 +253,30 @@ class ApiController extends Controller
|
|||
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
|
||||
*
|
||||
|
@ -737,6 +761,14 @@ class ApiController extends Controller
|
|||
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.
|
||||
*/
|
||||
|
|
|
@ -120,6 +120,16 @@ class PageController extends Controller
|
|||
return $this->main();
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*
|
||||
* @NoCSRFRequired
|
||||
*/
|
||||
public function albums()
|
||||
{
|
||||
return $this->main();
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*
|
||||
|
|
|
@ -12,6 +12,7 @@ class TimelineQuery
|
|||
use TimelineQueryFaces;
|
||||
use TimelineQueryFilters;
|
||||
use TimelineQueryTags;
|
||||
use TimelineQueryAlbums;
|
||||
|
||||
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')">
|
||||
<Video slot="icon" :size="20" />
|
||||
</NcAppNavigationItem>
|
||||
<NcAppNavigationItem :to="{name: 'albums'}"
|
||||
:title="t('memories', 'Albums')" v-if="showAlbums">
|
||||
<AlbumIcon slot="icon" :size="20" />
|
||||
</NcAppNavigationItem>
|
||||
<NcAppNavigationItem :to="{name: 'people'}"
|
||||
:title="t('memories', 'People')" v-if="showPeople">
|
||||
<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 Star from 'vue-material-design-icons/Star.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 CalendarIcon from 'vue-material-design-icons/Calendar.vue';
|
||||
import PeopleIcon from 'vue-material-design-icons/AccountBoxMultiple.vue';
|
||||
|
@ -100,6 +105,7 @@ import MapIcon from 'vue-material-design-icons/Map.vue';
|
|||
FolderIcon,
|
||||
Star,
|
||||
Video,
|
||||
AlbumIcon,
|
||||
ArchiveIcon,
|
||||
CalendarIcon,
|
||||
PeopleIcon,
|
||||
|
@ -110,6 +116,11 @@ import MapIcon from 'vue-material-design-icons/Map.vue';
|
|||
export default class App extends Mixins(GlobalMixin, UserConfig) {
|
||||
// Outer element
|
||||
|
||||
get ncVersion() {
|
||||
const version = (<any>window.OC).config.version.split('.');
|
||||
return Number(version[0]);
|
||||
}
|
||||
|
||||
get showPeople() {
|
||||
return this.config_recognizeEnabled || getCurrentUser()?.isAdmin;
|
||||
}
|
||||
|
@ -118,9 +129,12 @@ export default class App extends Mixins(GlobalMixin, UserConfig) {
|
|||
return this.config_timelinePath === 'EMPTY';
|
||||
}
|
||||
|
||||
get showAlbums() {
|
||||
return this.ncVersion >= 25; // todo: and photos enabled
|
||||
}
|
||||
|
||||
get removeOuterGap() {
|
||||
const version = (<any>window.OC).config.version.split('.');
|
||||
return (Number(version[0]) >= 25);
|
||||
return this.ncVersion >= 25;
|
||||
}
|
||||
|
||||
async beforeMount() {
|
||||
|
|
|
@ -500,6 +500,7 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
|||
case 'favorites': return this.t('memories', 'Favorites');
|
||||
case 'people': return this.t('memories', 'People');
|
||||
case 'videos': return this.t('memories', 'Videos');
|
||||
case 'albums': return this.t('memories', 'Albums');
|
||||
case 'archive': return this.t('memories', 'Archive');
|
||||
case 'thisday': return this.t('memories', 'On this day');
|
||||
case 'tags': return this.t('memories', 'Tags');
|
||||
|
@ -555,6 +556,8 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
|||
data = await dav.getTagsData();
|
||||
} else if (this.$route.name === 'people' && !this.$route.params.name) {
|
||||
data = await dav.getPeopleData();
|
||||
} else if (this.$route.name === 'albums') {
|
||||
data = await dav.getAlbumsData();
|
||||
} else {
|
||||
// Try the cache
|
||||
try {
|
||||
|
|
|
@ -82,6 +82,15 @@
|
|||
}),
|
||||
},
|
||||
|
||||
{
|
||||
path: '/albums',
|
||||
component: Timeline,
|
||||
name: 'albums',
|
||||
props: route => ({
|
||||
rootTitle: t('memories', 'Albums'),
|
||||
}),
|
||||
},
|
||||
|
||||
{
|
||||
path: '/archive',
|
||||
component: Timeline,
|
||||
|
|
|
@ -552,3 +552,10 @@ export async function* removeFaceImages(user: string, name: string, fileIds: num
|
|||
|
||||
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