Merge branch 'issue-738.ux-improvements' of https://github.com/johnSamilin/memories into johnSamilin-issue-738.ux-improvements
commit
5890daba1a
|
@ -95,11 +95,12 @@ class AlbumsBackend extends Backend
|
|||
// Run actual query
|
||||
$list = [];
|
||||
$t = (int) $request->getParam('t', 0);
|
||||
$fileid = (int) $request->getParam('fid', -1);
|
||||
if ($t & 1) { // personal
|
||||
$list = array_merge($list, $this->albumsQuery->getList(Util::getUID()));
|
||||
$list = array_merge($list, $this->albumsQuery->getList(Util::getUID(), $fileid));
|
||||
}
|
||||
if ($t & 2) { // shared
|
||||
$list = array_merge($list, $this->albumsQuery->getList(Util::getUID(), true));
|
||||
$list = array_merge($list, $this->albumsQuery->getList(Util::getUID(), $fileid, true));
|
||||
}
|
||||
|
||||
// Remove elements with duplicate album_id
|
||||
|
|
|
@ -17,11 +17,16 @@ class AlbumsQuery
|
|||
}
|
||||
|
||||
/** Get list of albums */
|
||||
public function getList(string $uid, bool $shared = false)
|
||||
public function getList(string $uid, int $fileId, bool $shared = false)
|
||||
{
|
||||
$query = $this->connection->getQueryBuilder();
|
||||
$allPhotosQuery = $this->connection->getQueryBuilder();
|
||||
|
||||
// SELECT everything from albums
|
||||
$allPhotosQuery->select('album_id')->from('photos_albums_files');
|
||||
$allPhotosQuery->where(
|
||||
$allPhotosQuery->expr()->eq('file_id', $allPhotosQuery->createNamedParameter($fileId, IQueryBuilder::PARAM_INT))
|
||||
);
|
||||
$count = $query->func()->count($query->createFunction('DISTINCT m.fileid'), 'count');
|
||||
$query->select(
|
||||
'pa.album_id',
|
||||
|
@ -64,13 +69,21 @@ class AlbumsQuery
|
|||
|
||||
// FETCH all albums
|
||||
$albums = $query->executeQuery()->fetchAll();
|
||||
$allPhotos = $allPhotosQuery->executeQuery()->fetchAll();
|
||||
$albumIds = array();
|
||||
|
||||
foreach ($allPhotos as &$album) {
|
||||
$albumIds[$album['album_id']] = true;
|
||||
}
|
||||
|
||||
// Post process
|
||||
foreach ($albums as &$row) {
|
||||
$albumId = (int) $row['album_id'];
|
||||
$row['cluster_id'] = $row['user'].'/'.$row['name'];
|
||||
$row['album_id'] = (int) $row['album_id'];
|
||||
$row['album_id'] = $albumId;
|
||||
$row['created'] = (int) $row['created'];
|
||||
$row['last_added_photo'] = (int) $row['last_added_photo'];
|
||||
$row['has_file'] = !!$albumIds[$albumId];
|
||||
}
|
||||
|
||||
return $albums;
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="24" height="24" fill="none"/>
|
||||
<path d="M5 13.3636L8.03559 16.3204C8.42388 16.6986 9.04279 16.6986 9.43108 16.3204L19 7" stroke="#000000" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 350 B |
|
@ -0,0 +1,204 @@
|
|||
<template>
|
||||
<div class="fill-block wrapper">
|
||||
<div class="loading-icon fill-block" v-if="loadingAlbums">
|
||||
<XLoadingIcon />
|
||||
</div>
|
||||
<NcButton
|
||||
:aria-label="t('memories', 'Add to album.')"
|
||||
class="new-album-button"
|
||||
type="tertiary"
|
||||
@click="addToAlbum"
|
||||
>
|
||||
<template #icon>
|
||||
<Plus />
|
||||
</template>
|
||||
{{ t('memories', 'Add to album') }}
|
||||
</NcButton>
|
||||
<span class="empty-state" v-if="albums.length === 0">{{ t('memories', 'No albums') }}</span>
|
||||
<ul v-else class="albums-container">
|
||||
<NcListItem
|
||||
v-for="album in albums"
|
||||
class="album"
|
||||
:key="album.album_id"
|
||||
:title="album.name"
|
||||
@click="ignoreClick"
|
||||
>
|
||||
<template #icon>
|
||||
<XImg class="album__image" :src="toCoverUrl(album.last_added_photo)" />
|
||||
</template>
|
||||
|
||||
<template #subtitle>
|
||||
{{ getSubtitle(album) }}
|
||||
</template>
|
||||
|
||||
</NcListItem>
|
||||
</ul>
|
||||
<AddToAlbumModal ref="addToAlbumModal" @added="loadAlbums" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import axios from '@nextcloud/axios';
|
||||
import { subscribe, unsubscribe } from '@nextcloud/event-bus';
|
||||
import { IAlbum, IPhoto } from '../types';
|
||||
import { API } from '../services/API';
|
||||
import NcButton from '@nextcloud/vue/dist/Components/NcButton';
|
||||
import Plus from 'vue-material-design-icons/Plus.vue';
|
||||
import AddToAlbumModal from './modal/AddToAlbumModal.vue';
|
||||
import ImageMultiple from 'vue-material-design-icons/ImageMultiple.vue';
|
||||
import { getPreviewUrl } from '../services/utils/helpers';
|
||||
import { getCurrentUser } from '@nextcloud/auth';
|
||||
|
||||
const NcListItem = () => import('@nextcloud/vue/dist/Components/NcListItem');
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AlbumsList',
|
||||
components: {
|
||||
ImageMultiple,
|
||||
NcListItem,
|
||||
NcButton,
|
||||
Plus,
|
||||
AddToAlbumModal,
|
||||
},
|
||||
|
||||
props: {
|
||||
fileid: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
filename: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
photo: {} as { id: number, name: string },
|
||||
albums: [] as IAlbum[],
|
||||
loadingAlbums: false,
|
||||
}),
|
||||
|
||||
mounted() {
|
||||
subscribe('files:file:updated', this.handleFileUpdated);
|
||||
this.update({ id: this.$props.fileid, name: this.$props.filename });
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
unsubscribe('files:file:updated', this.handleFileUpdated);
|
||||
},
|
||||
|
||||
methods: {
|
||||
update(photo: { id: number, name: string }){
|
||||
this.photo = photo;
|
||||
this.loadAlbums();
|
||||
},
|
||||
|
||||
async loadAlbums() {
|
||||
try {
|
||||
this.loadingAlbums = true;
|
||||
const res = await axios.get<IAlbum[]>(API.ALBUM_LIST(3, this.photo.id));
|
||||
this.albums = res.data.filter(album => album.has_file);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
this.loadingAlbums = false;
|
||||
}
|
||||
},
|
||||
|
||||
handleFileUpdated({ fileid }: { fileid: number }) {
|
||||
if (fileid && this.photo.id === fileid) {
|
||||
this.update(this.photo);
|
||||
}
|
||||
},
|
||||
|
||||
toCoverUrl(fileId: string | number) {
|
||||
return getPreviewUrl({
|
||||
photo: {
|
||||
fileid: Number(fileId),
|
||||
} as IPhoto,
|
||||
sqsize: 256,
|
||||
});
|
||||
},
|
||||
|
||||
getSubtitle(album: IAlbum) {
|
||||
let text = this.n('memories', '%n item', '%n items', album.count);
|
||||
|
||||
if (album.user !== getCurrentUser()?.uid) {
|
||||
text +=
|
||||
' / ' +
|
||||
this.t('memories', 'shared by {owner}', {
|
||||
owner: album.user_display || album.user,
|
||||
});
|
||||
}
|
||||
|
||||
return text;
|
||||
},
|
||||
|
||||
ignoreClick(e) {
|
||||
e.preventDefault();
|
||||
},
|
||||
|
||||
addToAlbum() {
|
||||
(<any>this.$refs.addToAlbumModal).open([{
|
||||
fileid: this.photo.id,
|
||||
basename: this.photo.name,
|
||||
}]);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.loading-icon {
|
||||
height: 75%;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.albums-container {
|
||||
flex-grow: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.album {
|
||||
:deep .list-item {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
:deep .list-item-content__wrapper {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
:deep .line-one__title {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&__image {
|
||||
width: auto;
|
||||
height: 100%;
|
||||
aspect-ratio: 1/1;
|
||||
object-fit: cover;
|
||||
border-radius: 50%;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
width: 100%;
|
||||
flex-grow: 1;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.new-album-button {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -38,6 +38,8 @@
|
|||
<div v-if="lat && lon" class="map">
|
||||
<iframe class="fill-block" :src="mapUrl" />
|
||||
</div>
|
||||
|
||||
<AlbumsList :fileid="fileid" :filename="filename" />
|
||||
</div>
|
||||
|
||||
<div class="loading-icon fill-block" v-else>
|
||||
|
@ -65,6 +67,7 @@ import ImageIcon from 'vue-material-design-icons/Image.vue';
|
|||
import InfoIcon from 'vue-material-design-icons/InformationOutline.vue';
|
||||
import LocationIcon from 'vue-material-design-icons/MapMarker.vue';
|
||||
import TagIcon from 'vue-material-design-icons/Tag.vue';
|
||||
import AlbumsList from './AlbumsList.vue';
|
||||
import { API } from '../services/API';
|
||||
import type { IImageInfo, IPhoto } from '../types';
|
||||
|
||||
|
@ -82,10 +85,12 @@ export default defineComponent({
|
|||
NcActions,
|
||||
NcActionButton,
|
||||
EditIcon,
|
||||
AlbumsList,
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
fileid: null as number | null,
|
||||
filename: '',
|
||||
exif: {} as { [prop: string]: any },
|
||||
baseInfo: {} as IImageInfo,
|
||||
state: 0,
|
||||
|
@ -330,6 +335,7 @@ export default defineComponent({
|
|||
if (state !== this.state) return res.data;
|
||||
|
||||
this.fileid = res.data.fileid;
|
||||
this.filename = res.data.basename;
|
||||
this.exif = res.data.exif || {};
|
||||
this.baseInfo = res.data;
|
||||
return this.baseInfo;
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
</template>
|
||||
|
||||
<div class="outer">
|
||||
<AlbumPicker @select="selectAlbum" />
|
||||
<AlbumPicker @select="updateAlbums" :photos="photos" />
|
||||
|
||||
<div v-if="processing">
|
||||
<NcProgressBar :value="Math.round((photosDone * 100) / photos.length)" :error="true" />
|
||||
<NcProgressBar :value="progress" :error="true" />
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
@ -37,13 +37,16 @@ export default defineComponent({
|
|||
data: () => ({
|
||||
show: false,
|
||||
photos: [] as IPhoto[],
|
||||
photosDone: 0,
|
||||
progress: 0,
|
||||
processing: false,
|
||||
processed: new Set<IPhoto>(),
|
||||
photosDone: 0,
|
||||
totalOperations: 0,
|
||||
}),
|
||||
|
||||
methods: {
|
||||
open(photos: IPhoto[]) {
|
||||
this.photosDone = 0;
|
||||
this.progress = 0;
|
||||
this.processing = false;
|
||||
this.show = true;
|
||||
this.photos = photos;
|
||||
|
@ -60,20 +63,34 @@ export default defineComponent({
|
|||
this.$emit('close');
|
||||
},
|
||||
|
||||
async selectAlbum(album: IAlbum) {
|
||||
if (this.processing) return;
|
||||
|
||||
async processAlbum(album: IAlbum, action: 'add' | 'remove') {
|
||||
const name = album.name || album.album_id.toString();
|
||||
const gen = dav.addToAlbum(album.user, name, this.photos);
|
||||
this.processing = true;
|
||||
|
||||
const gen = action === 'add'
|
||||
? dav.addToAlbum(album.user, name, this.photos)
|
||||
: dav.removeFromAlbum(album.user, name, this.photos);
|
||||
|
||||
for await (const fids of gen) {
|
||||
this.photosDone += fids.filter((f) => f).length;
|
||||
this.added(this.photos.filter((p) => fids.includes(p.fileid)));
|
||||
this.photosDone += fids.length;
|
||||
this.photos.forEach((p) => {
|
||||
if (fids.includes(p.fileid)) {
|
||||
this.processed.add(p);
|
||||
}
|
||||
});
|
||||
}
|
||||
this.progress = Math.round((this.photosDone * 100) / this.totalOperations);
|
||||
},
|
||||
|
||||
const n = this.photosDone;
|
||||
showInfo(this.n('memories', '{n} item added to album', '{n} items added to album', n, { n }));
|
||||
async updateAlbums(albumsToAddTo: IAlbum[], albumsToRemoveFrom: IAlbum[] = []) {
|
||||
if (this.processing) return;
|
||||
this.processing = true;
|
||||
this.processed = new Set<IPhoto>();
|
||||
this.totalOperations = this.photos.length * (albumsToAddTo.length + albumsToRemoveFrom.length);
|
||||
|
||||
await Promise.all(albumsToAddTo.map((album) => this.processAlbum(album, 'add')));
|
||||
await Promise.all(albumsToRemoveFrom.map((album) => this.processAlbum(album, 'remove')));
|
||||
const n = this.processed.size;
|
||||
this.added(Array.from(this.processed));
|
||||
showInfo(this.n('memories', '{n} processed', '{n} processed', n, { n }));
|
||||
this.close();
|
||||
},
|
||||
},
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
albumName: album.name,
|
||||
})
|
||||
"
|
||||
@click="pickAlbum(album)"
|
||||
@click.prevent="() => {}"
|
||||
>
|
||||
<template #icon>
|
||||
<XImg v-if="album.last_added_photo !== -1" class="album__image" :src="toCoverUrl(album.last_added_photo)" />
|
||||
|
@ -23,22 +23,60 @@
|
|||
</template>
|
||||
|
||||
<template #subtitle>
|
||||
{{ getSubtitle(album) }}
|
||||
<div @click.prevent="pickAlbum(album)">
|
||||
{{ getSubtitle(album) }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #extra>
|
||||
<div
|
||||
v-if="!selectedAlbums.has(album)"
|
||||
class="check-circle-icon check-circle-icon--inactive"
|
||||
@click.prevent="toggleAlbumSelection(album)"
|
||||
>
|
||||
<XImg :src="checkmarkIcon" />
|
||||
</div>
|
||||
<div
|
||||
v-if="selectedAlbums.has(album)"
|
||||
class="check-circle-icon"
|
||||
@click.prevent="toggleAlbumSelection(album)"
|
||||
>
|
||||
<XImg :src="checkmarkIcon" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</NcListItem>
|
||||
</ul>
|
||||
|
||||
<NcButton
|
||||
:aria-label="t('memories', 'Create a new album.')"
|
||||
class="new-album-button"
|
||||
type="tertiary"
|
||||
@click="showAlbumCreationForm = true"
|
||||
>
|
||||
<template #icon>
|
||||
<Plus />
|
||||
</template>
|
||||
{{ t('memories', 'Create new album') }}
|
||||
</NcButton>
|
||||
<div class="actions">
|
||||
<NcButton
|
||||
:aria-label="t('memories', 'Create a new album.')"
|
||||
class="new-album-button"
|
||||
type="tertiary"
|
||||
@click="showAlbumCreationForm = true"
|
||||
>
|
||||
<template #icon>
|
||||
<Plus />
|
||||
</template>
|
||||
{{ t('memories', 'Create new album') }}
|
||||
</NcButton>
|
||||
|
||||
<div class="submit-btn-wrapper">
|
||||
<NcButton
|
||||
:aria-label="t('memories', `Add to ${selectedCount} albums.`)"
|
||||
class="new-album-button"
|
||||
type="primary"
|
||||
@click="submit"
|
||||
>
|
||||
{{ t('memories', 'Add to albums') }}
|
||||
</NcButton>
|
||||
<span class="remove-notice" v-if="unselectedCount > 0">
|
||||
{{ t('memories', 'And remove from') }} {{ n('memories', '{n} album', '{n} albums', unselectedCount , {
|
||||
n: unselectedCount,
|
||||
})}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AlbumForm
|
||||
|
@ -58,6 +96,7 @@ import { getCurrentUser } from '@nextcloud/auth';
|
|||
import AlbumForm from './AlbumForm.vue';
|
||||
import Plus from 'vue-material-design-icons/Plus.vue';
|
||||
import ImageMultiple from 'vue-material-design-icons/ImageMultiple.vue';
|
||||
import checkmarkIcon from '../../assets/checkmark.svg';
|
||||
|
||||
import axios from '@nextcloud/axios';
|
||||
|
||||
|
@ -67,9 +106,17 @@ const NcListItem = () => import('@nextcloud/vue/dist/Components/NcListItem');
|
|||
import { getPreviewUrl } from '../../services/utils/helpers';
|
||||
import { IAlbum, IPhoto } from '../../types';
|
||||
import { API } from '../../services/API';
|
||||
import { PropType } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AlbumPicker',
|
||||
props: {
|
||||
/** List of pictures that are selected */
|
||||
photos: {
|
||||
type: Array as PropType<IPhoto[]>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
AlbumForm,
|
||||
Plus,
|
||||
|
@ -82,9 +129,19 @@ export default defineComponent({
|
|||
showAlbumCreationForm: false,
|
||||
albums: [] as IAlbum[],
|
||||
loadingAlbums: true,
|
||||
photoId: -1,
|
||||
checkmarkIcon,
|
||||
selectedAlbums: new Set<IAlbum>(),
|
||||
unselectedAlbums: new Set<IAlbum>(),
|
||||
selectedCount: 0,
|
||||
unselectedCount: 0,
|
||||
}),
|
||||
|
||||
mounted() {
|
||||
if (this.photos.length === 1) {
|
||||
// this only makes sense when we try to add single photo to albums
|
||||
this.photoId = this.photos[0].fileid;
|
||||
}
|
||||
this.loadAlbums();
|
||||
},
|
||||
|
||||
|
@ -119,8 +176,11 @@ export default defineComponent({
|
|||
|
||||
async loadAlbums() {
|
||||
try {
|
||||
const res = await axios.get<IAlbum[]>(API.ALBUM_LIST());
|
||||
const res = await axios.get<IAlbum[]>(API.ALBUM_LIST(3, this.photoId));
|
||||
this.albums = res.data;
|
||||
this.selectedAlbums = new Set(this.albums.filter(album => album.has_file));
|
||||
this.unselectedAlbums = new Set();
|
||||
this.unselectedCount = 0;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
|
@ -128,9 +188,33 @@ export default defineComponent({
|
|||
}
|
||||
},
|
||||
|
||||
pickAlbum(album: IAlbum) {
|
||||
this.$emit('select', album);
|
||||
toggleAlbumSelection(album: IAlbum) {
|
||||
if (this.selectedAlbums.has(album)) {
|
||||
this.selectedAlbums.delete(album);
|
||||
this.unselectedAlbums.add(album)
|
||||
} else if (this.unselectedAlbums.has(album)) {
|
||||
this.selectedAlbums.add(album)
|
||||
this.unselectedAlbums.delete(album);
|
||||
} else {
|
||||
this.selectedAlbums.add(album)
|
||||
}
|
||||
|
||||
this.unselectedCount = this.albums.reduce((acc, album) => {
|
||||
if (album.has_file && this.unselectedAlbums.has(album)) {
|
||||
acc += 1;
|
||||
}
|
||||
return acc;
|
||||
}, 0); this.selectedAlbums.size;
|
||||
this.selectedCount = this.selectedAlbums.size;
|
||||
},
|
||||
|
||||
pickAlbum(album: IAlbum) {
|
||||
this.$emit('select', [album], []);
|
||||
},
|
||||
|
||||
submit() {
|
||||
this.$emit('select', Array.from(this.selectedAlbums), Array.from(this.unselectedAlbums));
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@ -156,6 +240,11 @@ export default defineComponent({
|
|||
.album {
|
||||
:deep .list-item {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
:deep .list-item-content__wrapper {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
:deep .line-one__title {
|
||||
|
@ -184,10 +273,47 @@ export default defineComponent({
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.check-circle-icon {
|
||||
border-radius: 50%;
|
||||
border: 1px solid rgba(0, 255, 0, 0.1882352941);
|
||||
background-color: rgba(0, 255, 0, 0.1882352941);
|
||||
height: 34px;
|
||||
width: 34px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&--inactive {
|
||||
border: 1px solid rgba($color: black, $alpha: 0.1);
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
& img {
|
||||
width: 50%;
|
||||
height: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.new-album-button {
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.submit-btn-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.remove-notice {
|
||||
font-size: small;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -136,6 +136,17 @@
|
|||
<EditFileIcon :size="24" />
|
||||
</template>
|
||||
</NcActionButton>
|
||||
<NcActionButton
|
||||
:aria-label="t('memories', 'Add to album')"
|
||||
v-if="canEdit"
|
||||
@click="addToAlbum"
|
||||
:close-after-click="true"
|
||||
>
|
||||
{{ t('memories', 'Add to album') }}
|
||||
<template #icon>
|
||||
<AlbumIcon :size="24" />
|
||||
</template>
|
||||
</NcActionButton>
|
||||
</NcActions>
|
||||
</div>
|
||||
|
||||
|
@ -151,6 +162,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<AddToAlbumModal ref="addToAlbumModal" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -179,6 +191,7 @@ import 'photoswipe/style.css';
|
|||
import PsImage from './PsImage';
|
||||
import PsVideo from './PsVideo';
|
||||
import PsLivePhoto from './PsLivePhoto';
|
||||
import AddToAlbumModal from '../modal/AddToAlbumModal.vue';
|
||||
|
||||
import ShareIcon from 'vue-material-design-icons/ShareVariant.vue';
|
||||
import DeleteIcon from 'vue-material-design-icons/TrashCanOutline.vue';
|
||||
|
@ -192,6 +205,7 @@ import SlideshowIcon from 'vue-material-design-icons/PlayBox.vue';
|
|||
import EditFileIcon from 'vue-material-design-icons/FileEdit.vue';
|
||||
import AlbumRemoveIcon from 'vue-material-design-icons/BookRemove.vue';
|
||||
import LivePhotoIcon from 'vue-material-design-icons/MotionPlayOutline.vue';
|
||||
import AlbumIcon from 'vue-material-design-icons/ImageAlbum.vue';
|
||||
|
||||
const SLIDESHOW_MS = 5000;
|
||||
const BODY_HAS_VIEWER = 'has-viewer';
|
||||
|
@ -216,6 +230,8 @@ export default defineComponent({
|
|||
EditFileIcon,
|
||||
AlbumRemoveIcon,
|
||||
LivePhotoIcon,
|
||||
AlbumIcon,
|
||||
AddToAlbumModal,
|
||||
},
|
||||
|
||||
mixins: [UserConfig],
|
||||
|
@ -1123,6 +1139,12 @@ export default defineComponent({
|
|||
editMetadata() {
|
||||
globalThis.editMetadata([globalThis.currentViewerPhoto]);
|
||||
},
|
||||
|
||||
addToAlbum() {
|
||||
if (this.currentPhoto) {
|
||||
(<any>this.$refs.addToAlbumModal).open([this.currentPhoto]);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -84,8 +84,8 @@ export class API {
|
|||
return tok(gen(`${BASE}/folders/sub`));
|
||||
}
|
||||
|
||||
static ALBUM_LIST(t: 1 | 2 | 3 = 3) {
|
||||
return gen(`${BASE}/clusters/albums?t=${t}`);
|
||||
static ALBUM_LIST(t: 1 | 2 | 3 = 3, photoId: number = -1) {
|
||||
return gen(`${BASE}/clusters/albums?t=${t}&fid=${photoId}`);
|
||||
}
|
||||
|
||||
static ALBUM_DOWNLOAD(user: string, name: string) {
|
||||
|
|
|
@ -140,6 +140,8 @@ export interface IAlbum extends ICluster {
|
|||
location: string;
|
||||
/** File ID of last added photo */
|
||||
last_added_photo: number;
|
||||
/** Whether an album contains the file */
|
||||
has_file: boolean;
|
||||
}
|
||||
|
||||
export interface IFace extends ICluster {
|
||||
|
|
Loading…
Reference in New Issue