parent
5eea2eac41
commit
8869e5b995
|
@ -31,7 +31,6 @@ https://docs.photoprism.org/developer-guide/
|
|||
import RestModel from "model/rest";
|
||||
import Notify from "common/notify";
|
||||
import { $gettext } from "./vm";
|
||||
import Event from "pubsub-js";
|
||||
|
||||
export const MaxItems = 999;
|
||||
|
||||
|
@ -85,7 +84,11 @@ export class Clipboard {
|
|||
|
||||
const id = model.getId();
|
||||
|
||||
this.toggleId(id);
|
||||
const result = this.toggleId(id);
|
||||
|
||||
this.updateDom(id, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
toggleId(id) {
|
||||
|
@ -99,15 +102,11 @@ export class Clipboard {
|
|||
return;
|
||||
}
|
||||
|
||||
Event.publish("photos.updated", { entities: [{ UID: id, Selected: true }] });
|
||||
|
||||
this.selection.push(id);
|
||||
this.selectionMap["id:" + id] = true;
|
||||
this.lastId = id;
|
||||
result = true;
|
||||
} else {
|
||||
Event.publish("photos.updated", { entities: [{ UID: id, Selected: false }] });
|
||||
|
||||
this.selection.splice(index, 1);
|
||||
delete this.selectionMap["id:" + id];
|
||||
this.lastId = "";
|
||||
|
@ -118,20 +117,18 @@ export class Clipboard {
|
|||
return result;
|
||||
}
|
||||
|
||||
add(model, publish) {
|
||||
add(model) {
|
||||
if (!this.isModel(model)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const id = model.getId();
|
||||
|
||||
this.addId(id, publish);
|
||||
this.addId(id);
|
||||
}
|
||||
|
||||
addId(id, publish) {
|
||||
if (publish) {
|
||||
Event.publish("photos.updated", { entities: [{ UID: id, Selected: true }] });
|
||||
}
|
||||
addId(id) {
|
||||
this.updateDom(id, true);
|
||||
|
||||
if (this.hasId(id)) {
|
||||
return true;
|
||||
|
@ -170,15 +167,11 @@ export class Clipboard {
|
|||
rangeEnd = newEnd;
|
||||
}
|
||||
|
||||
let entities = [];
|
||||
|
||||
for (let i = rangeStart; i <= rangeEnd; i++) {
|
||||
this.add(models[i], false);
|
||||
entities.push({ UID: models[i].getId(), Selected: true });
|
||||
this.updateDom(models[i].getId(), true);
|
||||
}
|
||||
|
||||
Event.publish("photos.updated", { entities });
|
||||
|
||||
return rangeEnd - rangeStart + 1;
|
||||
}
|
||||
|
||||
|
@ -202,10 +195,8 @@ export class Clipboard {
|
|||
this.removeId(model.getId(), publish);
|
||||
}
|
||||
|
||||
removeId(id, publish) {
|
||||
if (publish) {
|
||||
Event.publish("photos.updated", { entities: [{ UID: id, Selected: false }] });
|
||||
}
|
||||
removeId(id) {
|
||||
this.updateDom(id, false);
|
||||
|
||||
if (!this.hasId(id)) {
|
||||
return false;
|
||||
|
@ -239,17 +230,22 @@ export class Clipboard {
|
|||
}
|
||||
|
||||
clear() {
|
||||
Event.publish("photos.updated", {
|
||||
entities: this.selection.map((uid) => {
|
||||
return { UID: uid, Selected: false };
|
||||
}),
|
||||
});
|
||||
|
||||
this.selection.forEach((id) => this.updateDom(id, false));
|
||||
this.lastId = "";
|
||||
this.selectionMap = {};
|
||||
this.selection.splice(0, this.selection.length);
|
||||
this.storage.removeItem(this.storageKey);
|
||||
}
|
||||
|
||||
updateDom(uid, selected) {
|
||||
document.querySelectorAll(`.uid-${uid}`).forEach((el) => {
|
||||
if (selected) {
|
||||
el.classList.add("is-selected");
|
||||
} else {
|
||||
el.classList.remove("is-selected");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const PhotoClipboard = new Clipboard(window.localStorage, "photo_clipboard");
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<v-container grid-list-xs fluid class="pa-2 p-photos p-photo-cards">
|
||||
<v-card v-if="photos.length === 0" class="p-photos-empty secondary-light lighten-1 ma-1" flat>
|
||||
<v-card v-if="photos.length === 0" class="no-results secondary-light lighten-1 ma-1" flat>
|
||||
<v-card-title primary-title>
|
||||
<div>
|
||||
<h3 v-if="filter.order === 'edited'" class="title ma-0 pa-0">
|
||||
|
@ -19,98 +19,79 @@
|
|||
</div>
|
||||
</v-card-title>
|
||||
</v-card>
|
||||
<v-layout row wrap class="p-results">
|
||||
<v-layout row wrap class="search-results photo-results cards-view">
|
||||
<v-flex
|
||||
v-for="(photo, index) in photos"
|
||||
:key="index"
|
||||
:data-uid="photo.UID"
|
||||
class="p-photo"
|
||||
xs12 sm6 md4 lg3 xlg2 xxxl1 d-flex
|
||||
:class="{ 'is-selected': photo.Selected, portrait: photo.Portrait }"
|
||||
>
|
||||
<v-hover>
|
||||
<v-card slot-scope="{ hover }" tile
|
||||
:dark="photo.Selected"
|
||||
:class="photo.Selected ? 'selected elevation-10 ma-0 accent darken-1 white--text select-transition' : 'elevation-0 ma-1 accent lighten-3 select-transition'"
|
||||
<v-card tile
|
||||
:data-id="photo.ID"
|
||||
:data-uid="photo.UID"
|
||||
class="result accent lighten-2"
|
||||
:class="photo.classes()"
|
||||
@contextmenu="onContextMenu($event, index)">
|
||||
<v-img :src="photo.thumbnailUrl('tile_500')"
|
||||
<div class="card-background accent lighten-2"></div>
|
||||
<v-img :key="photo.Hash"
|
||||
:src="photo.thumbnailUrl('tile_500')"
|
||||
:alt="photo.Title"
|
||||
:title="photo.Title"
|
||||
:transition="false"
|
||||
aspect-ratio="1"
|
||||
class="accent lighten-2 clickable"
|
||||
class="accent lighten-3 clickable"
|
||||
@mousedown="onMouseDown($event, index)"
|
||||
@click.stop.prevent="onClick($event, index)"
|
||||
>
|
||||
<v-layout
|
||||
v-if="photo.Type === 'live'"
|
||||
v-show="hover"
|
||||
fill-height
|
||||
align-center
|
||||
justify-center
|
||||
ma-0
|
||||
class="live-player"
|
||||
style="overflow: hidden;"
|
||||
>
|
||||
<video :key="photo.videoUrl()" width="500" height="500" autoplay loop muted playsinline>
|
||||
<v-layout v-if="photo.Type === 'video' || photo.Type === 'live'" class="live-player">
|
||||
<video :key="photo.ID" width="500" height="500" autoplay loop muted playsinline>
|
||||
<source :src="photo.videoUrl()" type="video/mp4">
|
||||
</video>
|
||||
</v-layout>
|
||||
|
||||
<v-btn v-if="hidePrivate && photo.Private" :ripple="false"
|
||||
icon flat large absolute
|
||||
class="p-photo-private opacity-75">
|
||||
<v-icon color="white">lock</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn v-if="hover || photo.Selected" :ripple="false"
|
||||
icon flat large absolute
|
||||
:class="photo.Selected ? 'p-photo-select' : 'p-photo-select opacity-50'"
|
||||
@click.stop.prevent="onSelect($event, index)">
|
||||
<v-icon v-if="photo.Selected" color="white"
|
||||
class="t-select t-on">check_circle
|
||||
</v-icon>
|
||||
<v-icon v-else color="accent lighten-3" class="t-select t-off">radio_button_off</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn icon flat large absolute :ripple="false"
|
||||
:class="photo.Favorite ? 'p-photo-like opacity-75' : 'p-photo-like opacity-50'"
|
||||
@click.stop.prevent="photo.toggleLike()">
|
||||
<v-icon v-if="photo.Favorite" color="white" class="t-like t-on" :data-uid="photo.UID">
|
||||
favorite
|
||||
</v-icon>
|
||||
<v-icon v-else color="accent lighten-3" class="t-like t-off" :data-uid="photo.UID">
|
||||
favorite_border
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<template v-if="photo.isPlayable()">
|
||||
<v-btn v-if="photo.Type === 'live'" :ripple="false"
|
||||
icon flat large absolute class="p-photo-live opacity-75"
|
||||
title="Live Photo" @click.stop.prevent="openPhoto(index, true)">
|
||||
<v-icon color="white" class="action-play">adjust</v-icon>
|
||||
</v-btn>
|
||||
<v-btn v-else color="white" :ripple="false"
|
||||
outline large fab absolute class="p-photo-play opacity-75" :depressed="false"
|
||||
title="Play" @click.stop.prevent="openPhoto(index, true)">
|
||||
<v-icon color="white" class="action-play">play_arrow</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-btn v-else-if="photo.Type === 'image' && photo.Files.length > 1" :ripple="false"
|
||||
icon flat large absolute class="p-photo-merged opacity-75"
|
||||
<v-btn :ripple="false" :depressed="false" class="input-open"
|
||||
icon flat absolute
|
||||
@click.stop.prevent="openPhoto(index, true)">
|
||||
<v-icon color="white" class="action-burst">burst_mode</v-icon>
|
||||
<v-icon color="white" class="default-hidden action-raw" :title="$gettext('RAW')">photo_camera</v-icon>
|
||||
<v-icon color="white" class="default-hidden action-live" :title="$gettext('Live')">adjust</v-icon>
|
||||
<v-icon color="white" class="default-hidden action-stack" :title="$gettext('Stack')">burst_mode</v-icon>
|
||||
</v-btn>
|
||||
<v-btn v-else-if="photo.Type === 'image' && selectMode && hover" :ripple="false"
|
||||
icon flat large absolute class="p-photo-fullscreen opacity-75"
|
||||
|
||||
<v-btn :ripple="false" :depressed="false" class="input-view"
|
||||
icon flat absolute :title="$gettext('View')"
|
||||
@click.stop.prevent="openPhoto(index, false)">
|
||||
<v-icon color="white" class="action-open">zoom_in</v-icon>
|
||||
<v-icon color="white" class="action-fullscreen">zoom_in</v-icon>
|
||||
</v-btn>
|
||||
<v-btn v-else-if="photo.Type === 'raw'" :ripple="false"
|
||||
icon flat large absolute class="p-photo-raw opacity-75"
|
||||
title="RAW" @click.stop.prevent="openPhoto(index, true)">
|
||||
<v-icon color="white" class="action-burst">photo_camera</v-icon>
|
||||
|
||||
<v-btn :ripple="false" :depressed="false" color="white" class="input-play"
|
||||
outline fab absolute :title="$gettext('Play')"
|
||||
@click.stop.prevent="openPhoto(index, true)">
|
||||
<v-icon color="white" class="action-play">play_arrow</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn v-if="hidePrivate" :ripple="false"
|
||||
icon flat absolute
|
||||
class="input-private">
|
||||
<v-icon color="white" class="select-on">lock</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn :ripple="false"
|
||||
icon flat absolute
|
||||
class="input-select"
|
||||
@click.stop.prevent="onSelect($event, index)">
|
||||
<v-icon color="white" class="select-on">check_circle</v-icon>
|
||||
<v-icon color="accent lighten-3" class="select-off">radio_button_off</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn :ripple="false"
|
||||
icon flat absolute
|
||||
class="input-favorite"
|
||||
@click.stop.prevent="photo.toggleLike()">
|
||||
<v-icon color="white" class="select-on">favorite</v-icon>
|
||||
<v-icon color="accent lighten-3" class="select-off">favorite_border</v-icon>
|
||||
</v-btn>
|
||||
</v-img>
|
||||
|
||||
<v-card-title primary-title class="pa-3 p-photo-desc" style="user-select: none;">
|
||||
<v-card-title primary-title class="pa-3 card-details" style="user-select: none;">
|
||||
<div>
|
||||
<h3 class="body-2 mb-2" :title="photo.Title">
|
||||
<button class="action-title-edit" :data-uid="photo.UID"
|
||||
|
@ -118,7 +99,7 @@
|
|||
{{ photo.Title | truncate(80) }}
|
||||
</button>
|
||||
</h3>
|
||||
<div v-if="photo.Description" class="caption mb-2" title="Description">
|
||||
<div v-if="photo.Description" class="caption mb-2" :title="labels.description">
|
||||
<button @click.exact="editPhoto(index)">
|
||||
{{ photo.Description }}
|
||||
</button>
|
||||
|
@ -126,17 +107,17 @@
|
|||
<div class="caption">
|
||||
<button class="action-date-edit" :data-uid="photo.UID"
|
||||
@click.exact="editPhoto(index)">
|
||||
<v-icon size="14" title="Taken">date_range</v-icon>
|
||||
<v-icon size="14" :title="labels.taken">date_range</v-icon>
|
||||
{{ photo.getDateString() }}
|
||||
</button>
|
||||
<template v-if="!photo.Description">
|
||||
<br/>
|
||||
<button v-if="photo.Type === 'video'" title="Video"
|
||||
<button v-if="photo.Type === 'video'" :title="labels.video"
|
||||
@click.exact="openPhoto(index, true)">
|
||||
<v-icon size="14">movie</v-icon>
|
||||
{{ photo.getVideoInfo() }}
|
||||
</button>
|
||||
<button v-else title="Camera" class="action-camera-edit"
|
||||
<button v-else :title="labels.camera" class="action-camera-edit"
|
||||
:data-uid="photo.UID" @click.exact="editPhoto(index)">
|
||||
<v-icon size="14">photo_camera</v-icon>
|
||||
{{ photo.getPhotoInfo() }}
|
||||
|
@ -144,7 +125,7 @@
|
|||
</template>
|
||||
<template v-if="filter.order === 'name' && $config.feature('download')">
|
||||
<br/>
|
||||
<button title="Name"
|
||||
<button :title="labels.name"
|
||||
@click.exact="downloadFile(index)">
|
||||
<v-icon size="14">insert_drive_file</v-icon>
|
||||
{{ photo.baseName() }}
|
||||
|
@ -152,7 +133,7 @@
|
|||
</template>
|
||||
<template v-if="showLocation && photo.Country !== 'zz'">
|
||||
<br/>
|
||||
<button title="Location" class="action-location"
|
||||
<button :title="labels.location" class="action-location"
|
||||
:data-uid="photo.UID" @click.exact="openLocation(index)">
|
||||
<v-icon size="14">location_on</v-icon>
|
||||
{{ photo.locationInfo() }}
|
||||
|
@ -161,6 +142,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-actions v-if="photo.Quality < 3 && context === 'review'">
|
||||
<v-layout row wrap align-center>
|
||||
<v-flex xs12>
|
||||
|
@ -178,7 +160,6 @@
|
|||
</v-layout>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-hover>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
</v-container>
|
||||
|
@ -188,7 +169,6 @@ export default {
|
|||
name: 'PPhotoCards',
|
||||
props: {
|
||||
photos: Array,
|
||||
selection: Array,
|
||||
openPhoto: Function,
|
||||
editPhoto: Function,
|
||||
openLocation: Function,
|
||||
|
@ -203,8 +183,14 @@ export default {
|
|||
hidePrivate: this.$config.settings().features.private,
|
||||
debug: this.$config.get('debug'),
|
||||
labels: {
|
||||
location: this.$gettext("Location"),
|
||||
description: this.$gettext("Description"),
|
||||
taken: this.$gettext("Taken"),
|
||||
approve: this.$gettext("Approve"),
|
||||
archive: this.$gettext("Archive"),
|
||||
camera: this.$gettext("Camera"),
|
||||
video: this.$gettext("Video"),
|
||||
name: this.$gettext("Name"),
|
||||
},
|
||||
mouseDown: {
|
||||
index: -1,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div>
|
||||
<div v-if="photos.length === 0" class="pa-2">
|
||||
<v-card class="p-photos-empty secondary-light lighten-1 ma-1" flat>
|
||||
<v-card class="no-results secondary-light lighten-1 ma-1" flat>
|
||||
<v-card-title primary-title>
|
||||
<div>
|
||||
<h3 v-if="filter.order === 'edited'" class="title ma-0 pa-0">
|
||||
|
@ -26,26 +26,28 @@
|
|||
:headers="listColumns"
|
||||
:items="photos"
|
||||
hide-actions
|
||||
class="elevation-0 p-photos p-photo-list p-results"
|
||||
class="search-results photo-results list-view"
|
||||
disable-initial-sort
|
||||
item-key="ID"
|
||||
:no-data-text="notFoundMessage"
|
||||
>
|
||||
<template slot="items" slot-scope="props">
|
||||
<td style="user-select: none;" :data-uid="props.item.UID">
|
||||
<td style="user-select: none;" :data-uid="props.item.UID" class="result" :class="props.item.classes()">
|
||||
<v-img class="accent lighten-2 clickable" aspect-ratio="1"
|
||||
:src="props.item.thumbnailUrl('tile_50')"
|
||||
@mousedown="onMouseDown($event, props.index)"
|
||||
@contextmenu="onContextMenu($event, props.index)"
|
||||
@click.stop.prevent="onClick($event, props.index)"
|
||||
>
|
||||
<v-btn v-if="props.item.Selected" :ripple="false"
|
||||
flat icon large absolute class="p-photo-select">
|
||||
<v-icon color="white" class="t-select t-on">check_circle</v-icon>
|
||||
<v-btn v-if="selectMode" :ripple="false"
|
||||
flat icon large absolute
|
||||
class="input-select">
|
||||
<v-icon color="white" class="select-on">check_circle</v-icon>
|
||||
<v-icon color="accent lighten-3" class="select-off">radio_button_off</v-icon>
|
||||
</v-btn>
|
||||
<v-btn v-else-if="!selectMode && (props.item.Type === 'video' || props.item.Type === 'live')"
|
||||
<v-btn v-else-if="props.item.Type === 'video' || props.item.Type === 'live'"
|
||||
:ripple="false"
|
||||
flat icon large absolute class="p-photo-play opacity-75"
|
||||
flat icon large absolute class="input-play opacity-75"
|
||||
@click.stop.prevent="openPhoto(props.index, true)">
|
||||
<v-icon color="white" class="action-play">play_arrow</v-icon>
|
||||
</v-btn>
|
||||
|
@ -68,7 +70,7 @@
|
|||
</td>
|
||||
<td class="p-photo-desc hidden-xs-only">
|
||||
<button v-if="filter.order === 'name'"
|
||||
title="Name" @click.exact="downloadFile(props.index)">
|
||||
:title="$gettext('Name')" @click.exact="downloadFile(props.index)">
|
||||
{{ props.item.FileName }}
|
||||
</button>
|
||||
<button v-else-if="props.item.Country !== 'zz' && showLocation"
|
||||
|
@ -146,25 +148,6 @@ export default {
|
|||
},
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
photos: function (photos) {
|
||||
this.selected.splice(0);
|
||||
|
||||
for (let i = 0; i < photos.length; i++) {
|
||||
if (this.$clipboard.has(photos[i])) {
|
||||
this.selected.push(photos[i]);
|
||||
}
|
||||
}
|
||||
},
|
||||
selection: function () {
|
||||
this.refreshSelection();
|
||||
},
|
||||
},
|
||||
mounted: function () {
|
||||
this.$nextTick(function () {
|
||||
this.refreshSelection();
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
downloadFile(index) {
|
||||
const photo = this.photos[index];
|
||||
|
@ -177,7 +160,7 @@ export default {
|
|||
if (ev.shiftKey) {
|
||||
this.selectRange(index);
|
||||
} else {
|
||||
this.$clipboard.toggle(this.photos[index]);
|
||||
this.toggle(this.photos[index]);
|
||||
}
|
||||
},
|
||||
onMouseDown(ev, index) {
|
||||
|
@ -191,7 +174,7 @@ export default {
|
|||
if (longClick || ev.shiftKey) {
|
||||
this.selectRange(index);
|
||||
} else {
|
||||
this.$clipboard.toggle(this.photos[index]);
|
||||
this.toggle(this.photos[index]);
|
||||
}
|
||||
} else if (this.photos[index]) {
|
||||
let photo = this.photos[index];
|
||||
|
@ -210,18 +193,12 @@ export default {
|
|||
this.selectRange(index);
|
||||
}
|
||||
},
|
||||
toggle(photo) {
|
||||
this.$clipboard.toggle(photo);
|
||||
},
|
||||
selectRange(index) {
|
||||
this.$clipboard.addRange(index, this.photos);
|
||||
},
|
||||
refreshSelection() {
|
||||
this.selected.splice(0);
|
||||
|
||||
for (let i = 0; i < this.photos.length; i++) {
|
||||
if (this.$clipboard.has(this.photos[i])) {
|
||||
this.selected.push(this.photos[i]);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<v-container grid-list-xs fluid class="pa-2 p-photos p-photo-mosaic">
|
||||
<v-card v-if="photos.length === 0" class="p-photos-empty secondary-light lighten-1 ma-1" flat>
|
||||
<v-card v-if="photos.length === 0" class="no-results secondary-light lighten-1 ma-1" flat>
|
||||
<v-card-title primary-title>
|
||||
<div>
|
||||
<h3 v-if="filter.order === 'edited'" class="title ma-0 pa-0">
|
||||
|
@ -19,95 +19,77 @@
|
|||
</div>
|
||||
</v-card-title>
|
||||
</v-card>
|
||||
<v-layout row wrap class="p-results">
|
||||
<v-layout row wrap class="search-results photo-results mosaic-view">
|
||||
<v-flex
|
||||
v-for="(photo, index) in photos"
|
||||
:key="index"
|
||||
:data-uid="photo.UID"
|
||||
:class="{ selected: photo.Selected, portrait: photo.Portrait }"
|
||||
class="p-photo"
|
||||
xs4 sm3 md2 lg1 d-flex
|
||||
>
|
||||
<v-hover>
|
||||
<v-card slot-scope="{ hover }" tile
|
||||
:class="photo.Selected ? 'selected elevation-10 ma-0 select-transition' : 'elevation-0 ma-1 select-transition'"
|
||||
:title="photo.Title"
|
||||
@contextmenu="onContextMenu($event, index)">
|
||||
<v-img :src="photo.thumbnailUrl('tile_224')"
|
||||
aspect-ratio="1"
|
||||
class="accent lighten-2 clickable"
|
||||
@mousedown="onMouseDown($event, index)"
|
||||
@click.stop.prevent="onClick($event, index)"
|
||||
>
|
||||
<v-layout
|
||||
v-if="photo.Type === 'live'"
|
||||
v-show="hover"
|
||||
fill-height
|
||||
align-center
|
||||
justify-center
|
||||
ma-0
|
||||
class="live-player"
|
||||
style="overflow: hidden;"
|
||||
>
|
||||
<video :key="photo.videoUrl()" width="224" height="224" autoplay loop muted playsinline>
|
||||
<source :src="photo.videoUrl()" type="video/mp4">
|
||||
</video>
|
||||
</v-layout>
|
||||
<v-card tile
|
||||
:data-id="photo.ID"
|
||||
:data-uid="photo.UID"
|
||||
class="result"
|
||||
:class="photo.classes()"
|
||||
@contextmenu="onContextMenu($event, index)">
|
||||
<v-img :key="photo.Hash"
|
||||
:src="photo.thumbnailUrl('tile_224')"
|
||||
:alt="photo.Title"
|
||||
:title="photo.Title"
|
||||
:transition="false"
|
||||
aspect-ratio="1"
|
||||
class="accent lighten-3 clickable"
|
||||
@mousedown="onMouseDown($event, index)"
|
||||
@click.stop.prevent="onClick($event, index)"
|
||||
>
|
||||
<v-layout v-if="photo.Type === 'video' || photo.Type === 'live'" class="live-player">
|
||||
<video :key="photo.ID" width="224" height="224" autoplay loop muted playsinline>
|
||||
<source :src="photo.videoUrl()" type="video/mp4">
|
||||
</video>
|
||||
</v-layout>
|
||||
|
||||
<v-btn v-if="hidePrivate && photo.Private" :ripple="false"
|
||||
icon flat small absolute
|
||||
class="p-photo-private opacity-75">
|
||||
<v-icon color="white">lock</v-icon>
|
||||
</v-btn>
|
||||
<v-btn :ripple="false" :depressed="false" class="input-open"
|
||||
icon flat small absolute
|
||||
@click.stop.prevent="openPhoto(index, true)">
|
||||
<v-icon color="white" class="default-hidden action-raw" :title="$gettext('RAW')">photo_camera</v-icon>
|
||||
<v-icon color="white" class="default-hidden action-live" :title="$gettext('Live')">adjust</v-icon>
|
||||
<v-icon color="white" class="default-hidden action-stack" :title="$gettext('Stack')">burst_mode</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn v-if="hover || photo.Selected" :ripple="false"
|
||||
icon flat small absolute
|
||||
:class="photo.Selected ? 'p-photo-select' : 'p-photo-select opacity-50'"
|
||||
@click.stop.prevent="onSelect($event, index)">
|
||||
<v-icon v-if="photo.Selected" color="white"
|
||||
class="t-select t-on">check_circle
|
||||
</v-icon>
|
||||
<v-icon v-else color="accent lighten-3" class="t-select t-off">radio_button_off</v-icon>
|
||||
</v-btn>
|
||||
<v-btn :ripple="false" :depressed="false" class="input-view"
|
||||
icon flat small absolute :title="$gettext('View')"
|
||||
@click.stop.prevent="openPhoto(index, false)">
|
||||
<v-icon color="white" class="action-fullscreen">zoom_in</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn icon flat small absolute :ripple="false"
|
||||
:class="photo.Favorite ? 'p-photo-like opacity-75' : 'p-photo-like opacity-50'"
|
||||
@click.stop.prevent="photo.toggleLike()">
|
||||
<v-icon v-if="photo.Favorite" color="white" class="t-like t-on" :data-uid="photo.UID">favorite</v-icon>
|
||||
<v-icon v-else color="accent lighten-3" class="t-like t-off" :data-uid="photo.UID">favorite_border
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
<v-btn :ripple="false" :depressed="false" color="white" class="input-play"
|
||||
outline fab absolute :title="$gettext('Play')"
|
||||
@click.stop.prevent="openPhoto(index, true)">
|
||||
<v-icon color="white" class="action-play">play_arrow</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<template v-if="photo.isPlayable()">
|
||||
<v-btn v-if="photo.Type === 'live'" color="white"
|
||||
icon flat small absolute class="p-photo-live opacity-75" :depressed="false" :ripple="false"
|
||||
title="Live Photo" @click.stop.prevent="openPhoto(index, true)">
|
||||
<v-icon color="white" class="action-play">adjust</v-icon>
|
||||
</v-btn>
|
||||
<v-btn v-else color="white"
|
||||
outline fab absolute class="p-photo-play opacity-75" :depressed="false" :ripple="false"
|
||||
title="Play" @click.stop.prevent="openPhoto(index, true)">
|
||||
<v-icon color="white" class="action-play">play_arrow</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-btn v-else-if="photo.Type === 'image' && photo.Files.length > 1" :ripple="false"
|
||||
icon flat small absolute class="p-photo-merged opacity-75"
|
||||
@click.stop.prevent="openPhoto(index, true)">
|
||||
<v-icon color="white" class="action-burst">burst_mode</v-icon>
|
||||
</v-btn>
|
||||
<v-btn v-else-if="photo.Type === 'image' && selectMode && hover" :ripple="false"
|
||||
icon flat small absolute class="p-photo-fullscreen opacity-75"
|
||||
@click.stop.prevent="openPhoto(index, false)">
|
||||
<v-icon color="white" class="action-open">zoom_in</v-icon>
|
||||
</v-btn>
|
||||
<v-btn v-else-if="photo.Type === 'raw'" :ripple="false"
|
||||
icon flat small absolute class="p-photo-raw opacity-75"
|
||||
title="RAW" @click.stop.prevent="openPhoto(index, true)">
|
||||
<v-icon color="white" class="action-burst">photo_camera</v-icon>
|
||||
</v-btn>
|
||||
</v-img>
|
||||
</v-card>
|
||||
</v-hover>
|
||||
<v-btn v-if="hidePrivate" :ripple="false"
|
||||
icon flat small absolute
|
||||
class="input-private">
|
||||
<v-icon color="white" class="select-on">lock</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn :ripple="false"
|
||||
icon flat small absolute
|
||||
class="input-select"
|
||||
@click.stop.prevent="onSelect($event, index)">
|
||||
<v-icon color="white" class="select-on">check_circle</v-icon>
|
||||
<v-icon color="accent lighten-3" class="select-off">radio_button_off</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn :ripple="false"
|
||||
icon flat small absolute
|
||||
class="input-favorite"
|
||||
@click.stop.prevent="photo.toggleLike()">
|
||||
<v-icon color="white" class="select-on">favorite</v-icon>
|
||||
<v-icon color="accent lighten-3" class="select-off">favorite_border</v-icon>
|
||||
</v-btn>
|
||||
</v-img>
|
||||
</v-card>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
</v-container>
|
||||
|
@ -138,13 +120,16 @@ export default {
|
|||
if (ev.shiftKey) {
|
||||
this.selectRange(index);
|
||||
} else {
|
||||
this.$clipboard.toggle(this.photos[index]);
|
||||
this.toggle(this.photos[index]);
|
||||
}
|
||||
},
|
||||
onMouseDown(ev, index) {
|
||||
this.mouseDown.index = index;
|
||||
this.mouseDown.timeStamp = ev.timeStamp;
|
||||
},
|
||||
toggle(photo) {
|
||||
this.$clipboard.toggle(photo);
|
||||
},
|
||||
onClick(ev, index) {
|
||||
let longClick = (this.mouseDown.index === index && ev.timeStamp - this.mouseDown.timeStamp > 400);
|
||||
|
||||
|
@ -152,7 +137,7 @@ export default {
|
|||
if (longClick || ev.shiftKey) {
|
||||
this.selectRange(index);
|
||||
} else {
|
||||
this.$clipboard.toggle(this.photos[index]);
|
||||
this.toggle(this.photos[index]);
|
||||
}
|
||||
} else {
|
||||
this.openPhoto(index, false);
|
||||
|
|
|
@ -39,7 +39,7 @@ https://docs.photoprism.org/developer-guide/
|
|||
@import url("video.css");
|
||||
@import url("maps.css");
|
||||
@import url("viewer.css");
|
||||
@import url("photos.css");
|
||||
@import url("search.css");
|
||||
@import url("labels.css");
|
||||
@import url("files.css");
|
||||
@import url("help.css");
|
||||
|
@ -134,10 +134,6 @@ main {
|
|||
left: 10%;
|
||||
}
|
||||
|
||||
#photoprism main .p-results a {
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
#photoprism .v-badge__badge {
|
||||
font-size: 12px;
|
||||
height: 19px;
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
#photoprism .p-labels-cards .p-label-like {
|
||||
left: 4px;
|
||||
bottom: 4px;
|
||||
}
|
||||
|
||||
#photoprism main .p-inline-edit a,
|
||||
#photoprism main .p-inline-edit a span {
|
||||
#photoprism .search-results .inline-edit a,
|
||||
#photoprism .search-results .inline-edit a span {
|
||||
cursor: text;
|
||||
color: inherit;
|
||||
}
|
||||
|
|
|
@ -1,142 +0,0 @@
|
|||
#photoprism .p-col-select {
|
||||
width: 66px;
|
||||
}
|
||||
|
||||
#photoprism .p-col-primary {
|
||||
width: 44px;
|
||||
}
|
||||
|
||||
#photoprism .p-photo-list tr td:first-child {
|
||||
padding: 0 0 0 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#photoprism .p-photo-list .p-photo-select,
|
||||
#photoprism .p-photo-list .p-photo-play {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#photoprism .p-photo-mosaic .p-photo-private,
|
||||
#photoprism .p-photo-cards .p-photo-private {
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
}
|
||||
|
||||
#photoprism .p-photo-mosaic .p-photo-edit,
|
||||
#photoprism .p-photo-cards .p-photo-edit {
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
}
|
||||
|
||||
#photoprism .p-photo-mosaic .p-photo-merged,
|
||||
#photoprism .p-photo-cards .p-photo-merged,
|
||||
#photoprism .p-photo-mosaic .p-photo-live,
|
||||
#photoprism .p-photo-cards .p-photo-live {
|
||||
top: 4px;
|
||||
left: 4px;
|
||||
}
|
||||
|
||||
#photoprism .p-photo-mosaic .p-photo-play,
|
||||
#photoprism .p-photo-cards .p-photo-play {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#photoprism .p-photo-mosaic .p-photo-like,
|
||||
#photoprism .p-photo-cards .p-photo-like {
|
||||
left: 4px;
|
||||
bottom: 4px;
|
||||
}
|
||||
|
||||
#photoprism .p-photo-cards .action-select,
|
||||
#photoprism .p-photo-mosaic .action-select,
|
||||
#photoprism .p-photo-cards .p-photo-select,
|
||||
#photoprism .p-photo-mosaic .p-photo-select {
|
||||
right: 4px;
|
||||
bottom: 4px;
|
||||
}
|
||||
|
||||
#photoprism .p-albums-cards .action-select,
|
||||
#photoprism .p-albums-cards .p-album-select,
|
||||
#photoprism .p-labels-cards .p-label-select,
|
||||
#photoprism .p-folders-cards .p-folder-select,
|
||||
#photoprism .p-files-cards .p-file-select {
|
||||
right: 4px;
|
||||
bottom: 4px;
|
||||
}
|
||||
|
||||
#photoprism .p-albums-cards .action-share {
|
||||
top: 4px;
|
||||
left: 4px;
|
||||
}
|
||||
|
||||
#photoprism .p-labels-cards .p-label-count {
|
||||
position: absolute;
|
||||
left: 12px;
|
||||
top: 12px;
|
||||
}
|
||||
|
||||
#photoprism .p-clipboard.--ltr {
|
||||
right: 8px;
|
||||
bottom: 12px;
|
||||
}
|
||||
|
||||
#photoprism .p-clipboard.--rtl {
|
||||
left: 8px;
|
||||
bottom: 12px;
|
||||
}
|
||||
|
||||
#photoprism .p-clipboard .v-btn.v-btn--disabled:not(.v-btn--icon):not(.v-btn--flat):not(.v-btn--outline) {
|
||||
background-color: rgba(100, 100, 100, 0.5) !important;
|
||||
}
|
||||
|
||||
#photoprism .p-album-desc button,
|
||||
#photoprism .p-photo-desc button {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#photoprism .live-player video {
|
||||
width: auto;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#photoprism .portrait .live-player video {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
#photoprism table.photo-files tbody tr td:first-child {
|
||||
width: 30%;
|
||||
padding: 0 16px 0 24px;
|
||||
}
|
||||
|
||||
#photoprism .img-placeholder { opacity: 0.3; }
|
||||
#photoprism .img-color-0 { background-color: #696969 !important; } /* Black */
|
||||
#photoprism .img-color-1 { background-color: #DCDCDC !important; } /* Grey */
|
||||
#photoprism .img-color-2 { background-color: #98817B !important; } /* Brown */
|
||||
#photoprism .img-color-3 { background-color: #E5E4E2 !important; } /* Gold */
|
||||
#photoprism .img-color-4 { background-color: #fdfdfd !important; } /* White */
|
||||
#photoprism .img-color-5 { background-color: #AB47BC !important; } /* Purple */
|
||||
#photoprism .img-color-6 { background-color: #8A7F8D !important; } /* Blue */
|
||||
#photoprism .img-color-7 { background-color: #91A3B0 !important; } /* Cyan */
|
||||
#photoprism .img-color-8 { background-color: #B2BEB5 !important; } /* Teal */
|
||||
#photoprism .img-color-9 { background-color: #738678 !important; } /* Green */
|
||||
#photoprism .img-color-10 { background-color: #5E716A !important; } /* Lime */
|
||||
#photoprism .img-color-11 { background-color: #928E85 !important; } /* Yellow */
|
||||
#photoprism .img-color-12 { background-color: #CC8899 !important; } /* Magenta */
|
||||
#photoprism .img-color-13 { background-color: #98817B !important; } /* Orange */
|
||||
#photoprism .img-color-14 { background-color: #CC8899 !important; } /* Red */
|
||||
#photoprism .img-color-15 { background-color: #AA98A9 !important; } /* Pink */
|
329
frontend/src/css/search.css
Normal file
329
frontend/src/css/search.css
Normal file
|
@ -0,0 +1,329 @@
|
|||
#photoprism .p-col-select {
|
||||
width: 66px;
|
||||
}
|
||||
|
||||
#photoprism .p-col-primary {
|
||||
width: 44px;
|
||||
}
|
||||
|
||||
#photoprism .search-results.list-view tr td:first-child {
|
||||
padding: 0 0 0 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#photoprism .search-results.list-view .p-photo-select,
|
||||
#photoprism .search-results.list-view .p-photo-play {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#photoprism .album-results .action-share {
|
||||
top: 4px;
|
||||
left: 4px;
|
||||
}
|
||||
|
||||
#photoprism .label-results .info-count {
|
||||
position: absolute;
|
||||
left: 12px;
|
||||
top: 12px;
|
||||
}
|
||||
|
||||
#photoprism .p-clipboard.--ltr {
|
||||
right: 8px;
|
||||
bottom: 12px;
|
||||
}
|
||||
|
||||
#photoprism .p-clipboard.--rtl {
|
||||
left: 8px;
|
||||
bottom: 12px;
|
||||
}
|
||||
|
||||
#photoprism .p-clipboard .v-btn.v-btn--disabled:not(.v-btn--icon):not(.v-btn--flat):not(.v-btn--outline) {
|
||||
background-color: rgba(100, 100, 100, 0.5) !important;
|
||||
}
|
||||
|
||||
#photoprism .live-player video {
|
||||
width: auto;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#photoprism .portrait .live-player video,
|
||||
#photoprism .is-portrait .live-player video {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
#photoprism table.photo-files tbody tr td:first-child {
|
||||
width: 30%;
|
||||
padding: 0 16px 0 24px;
|
||||
}
|
||||
|
||||
#photoprism .img-placeholder {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
#photoprism .img-color-0 {
|
||||
background-color: #696969 !important;
|
||||
}
|
||||
|
||||
/* Black */
|
||||
#photoprism .img-color-1 {
|
||||
background-color: #DCDCDC !important;
|
||||
}
|
||||
|
||||
/* Grey */
|
||||
#photoprism .img-color-2 {
|
||||
background-color: #98817B !important;
|
||||
}
|
||||
|
||||
/* Brown */
|
||||
#photoprism .img-color-3 {
|
||||
background-color: #E5E4E2 !important;
|
||||
}
|
||||
|
||||
/* Gold */
|
||||
#photoprism .img-color-4 {
|
||||
background-color: #fdfdfd !important;
|
||||
}
|
||||
|
||||
/* White */
|
||||
#photoprism .img-color-5 {
|
||||
background-color: #AB47BC !important;
|
||||
}
|
||||
|
||||
/* Purple */
|
||||
#photoprism .img-color-6 {
|
||||
background-color: #8A7F8D !important;
|
||||
}
|
||||
|
||||
/* Blue */
|
||||
#photoprism .img-color-7 {
|
||||
background-color: #91A3B0 !important;
|
||||
}
|
||||
|
||||
/* Cyan */
|
||||
#photoprism .img-color-8 {
|
||||
background-color: #B2BEB5 !important;
|
||||
}
|
||||
|
||||
/* Teal */
|
||||
#photoprism .img-color-9 {
|
||||
background-color: #738678 !important;
|
||||
}
|
||||
|
||||
/* Green */
|
||||
#photoprism .img-color-10 {
|
||||
background-color: #5E716A !important;
|
||||
}
|
||||
|
||||
/* Lime */
|
||||
#photoprism .img-color-11 {
|
||||
background-color: #928E85 !important;
|
||||
}
|
||||
|
||||
/* Yellow */
|
||||
#photoprism .img-color-12 {
|
||||
background-color: #CC8899 !important;
|
||||
}
|
||||
|
||||
/* Magenta */
|
||||
#photoprism .img-color-13 {
|
||||
background-color: #98817B !important;
|
||||
}
|
||||
|
||||
/* Orange */
|
||||
#photoprism .img-color-14 {
|
||||
background-color: #CC8899 !important;
|
||||
}
|
||||
|
||||
/* Red */
|
||||
#photoprism .img-color-15 {
|
||||
background-color: #AA98A9 !important;
|
||||
}
|
||||
|
||||
/* Pink */
|
||||
|
||||
#photoprism .default-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#photoprism .search-results a {
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
#photoprism .list-view {
|
||||
box-shadow: 0 0 0 0 rgba(0,0,0,.2),0 0 0 0 rgba(0,0,0,.14),0 0 0 0 rgba(0,0,0,.12) !important;
|
||||
}
|
||||
|
||||
#photoprism .cards-view .result,
|
||||
#photoprism .mosaic-view .result
|
||||
{
|
||||
-webkit-transition-duration: 15ms !important;
|
||||
-moz-transition-duration: 15ms !important;
|
||||
-o-transition-duration: 15ms !important;
|
||||
transition-duration: 15ms !important;
|
||||
margin: 4px !important;
|
||||
box-shadow: 0 0 0 0 rgba(0, 0, 0, .2), 0 0 0 0 rgba(0, 0, 0, .14), 0 0 0 0 rgba(0, 0, 0, .12) !important;
|
||||
}
|
||||
|
||||
#photoprism .cards-view .result.is-selected,
|
||||
#photoprism .mosaic-view .result.is-selected {
|
||||
margin: 0 !important;
|
||||
box-shadow: 0 6px 6px -3px rgba(0, 0, 0, .2), 0 10px 14px 1px rgba(0, 0, 0, .14), 0 4px 18px 3px rgba(0, 0, 0, .12) !important;
|
||||
}
|
||||
|
||||
#photoprism .cards-view .input-select,
|
||||
#photoprism .mosaic-view .input-select {
|
||||
visibility: hidden;
|
||||
opacity: 0.5;
|
||||
right: 4px;
|
||||
bottom: 4px;
|
||||
}
|
||||
|
||||
#photoprism .search-results .result:hover .input-select,
|
||||
#photoprism .search-results .result.is-selected .input-select {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#photoprism .search-results .result .input-select .select-off,
|
||||
#photoprism .search-results .result.is-selected .input-select .select-on {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
#photoprism .search-results .result .input-select .select-on,
|
||||
#photoprism .search-results .result.is-selected .input-select .select-off {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#photoprism .cards-view .input-favorite,
|
||||
#photoprism .mosaic-view .input-favorite {
|
||||
opacity: 0.5;
|
||||
left: 4px;
|
||||
bottom: 4px;
|
||||
}
|
||||
|
||||
#photoprism .search-results .result.is-favorite .input-favorite {
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
#photoprism .search-results .result .input-favorite .select-off,
|
||||
#photoprism .search-results .result.is-favorite .input-favorite .select-on {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
#photoprism .search-results .result .input-favorite .select-on,
|
||||
#photoprism .search-results .result.is-favorite .input-favorite .select-off {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#photoprism .cards-view .input-private,
|
||||
#photoprism .mosaic-view .input-private {
|
||||
visibility: hidden;
|
||||
opacity: 0.75;
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
}
|
||||
|
||||
#photoprism .search-results .result.is-private .input-private {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
#photoprism .cards-view .input-open,
|
||||
#photoprism .cards-view .input-view,
|
||||
#photoprism .mosaic-view .input-open,
|
||||
#photoprism .mosaic-view .input-view {
|
||||
display: none;
|
||||
opacity: 0.75;
|
||||
top: 4px;
|
||||
left: 4px;
|
||||
}
|
||||
|
||||
#photoprism .search-results .type-image:hover .input-view,
|
||||
#photoprism .search-results .type-raw .input-open,
|
||||
#photoprism .search-results .type-live.is-playable .input-open,
|
||||
#photoprism .search-results .type-image.is-stack .input-open {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
#photoprism .search-results .type-image.is-stack .input-view {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#photoprism .search-results .type-raw .input-open .action-raw,
|
||||
#photoprism .search-results .type-live.is-playable .input-open .action-live,
|
||||
#photoprism .search-results .type-image.is-stack .input-open .action-stack {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
#photoprism .search-results .live-player {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#photoprism .search-results .type-live.is-playable:hover .live-player {
|
||||
display: flex;
|
||||
overflow: hidden !important;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
#photoprism .search-results .result .input-play {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#photoprism .search-results .type-video.is-playable:hover .input-play {
|
||||
display: inline-flex;
|
||||
opacity: 0.75;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#photoprism .cards-view .v-card .card-background {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
#photoprism .card-details button {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
|
||||
#photoprism .cards-view .v-card .card-details {
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#photoprism .cards-view .v-card.is-selected .card-details,
|
||||
#photoprism .cards-view .v-card.is-selected .card-background {
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
|
||||
#photoprism .search-results.list-view .input-select,
|
||||
#photoprism .search-results.list-view .input-play {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
text-align: center;
|
||||
}
|
|
@ -33,6 +33,7 @@ import Api from "common/api";
|
|||
import { DateTime } from "luxon";
|
||||
import { config } from "../session";
|
||||
import { $gettext } from "common/vm";
|
||||
import Clipboard from "../common/clipboard";
|
||||
|
||||
export class Album extends RestModel {
|
||||
getDefaults() {
|
||||
|
@ -65,6 +66,16 @@ export class Album extends RestModel {
|
|||
};
|
||||
}
|
||||
|
||||
classes(selected) {
|
||||
let classes = ["is-album", "uid-" + this.UID, "type-" + this.Type];
|
||||
|
||||
if (this.Favorite) classes.push("is-favorite");
|
||||
if (this.Private) classes.push("is-private");
|
||||
if (selected) classes.push("is-selected");
|
||||
|
||||
return classes;
|
||||
}
|
||||
|
||||
getEntityName() {
|
||||
return this.Slug;
|
||||
}
|
||||
|
@ -153,6 +164,10 @@ export class Album extends RestModel {
|
|||
return Api.delete(this.getEntityResource() + "/like");
|
||||
}
|
||||
|
||||
static pageSize() {
|
||||
return 24;
|
||||
}
|
||||
|
||||
static getCollectionResource() {
|
||||
return "albums";
|
||||
}
|
||||
|
|
|
@ -76,6 +76,17 @@ export class File extends RestModel {
|
|||
};
|
||||
}
|
||||
|
||||
classes(selected) {
|
||||
let classes = ["is-file", "uid-" + this.UID];
|
||||
|
||||
if (this.Primary) classes.push("is-primary");
|
||||
if (this.Sidecar) classes.push("is-sidecar");
|
||||
if (this.Video) classes.push("is-video");
|
||||
if (selected) classes.push("is-selected");
|
||||
|
||||
return classes;
|
||||
}
|
||||
|
||||
baseName(truncate) {
|
||||
let result = this.Name;
|
||||
const slash = result.lastIndexOf("/");
|
||||
|
|
|
@ -63,6 +63,16 @@ export class Folder extends RestModel {
|
|||
};
|
||||
}
|
||||
|
||||
classes(selected) {
|
||||
let classes = ["is-folder", "uid-" + this.UID];
|
||||
|
||||
if (this.Favorite) classes.push("is-favorite");
|
||||
if (this.Private) classes.push("is-private");
|
||||
if (selected) classes.push("is-selected");
|
||||
|
||||
return classes;
|
||||
}
|
||||
|
||||
baseName(truncate) {
|
||||
let result = this.Path;
|
||||
const slash = result.lastIndexOf("/");
|
||||
|
|
|
@ -53,6 +53,15 @@ export class Label extends RestModel {
|
|||
};
|
||||
}
|
||||
|
||||
classes(selected) {
|
||||
let classes = ["is-label", "uid-" + this.UID];
|
||||
|
||||
if (this.Favorite) classes.push("is-favorite");
|
||||
if (selected) classes.push("is-selected");
|
||||
|
||||
return classes;
|
||||
}
|
||||
|
||||
getEntityName() {
|
||||
return this.Slug;
|
||||
}
|
||||
|
@ -89,6 +98,10 @@ export class Label extends RestModel {
|
|||
return Api.delete(this.getEntityResource() + "/like");
|
||||
}
|
||||
|
||||
static pageSize() {
|
||||
return 24;
|
||||
}
|
||||
|
||||
static getCollectionResource() {
|
||||
return "labels";
|
||||
}
|
||||
|
|
|
@ -54,12 +54,11 @@ export const DayUnknown = -1;
|
|||
export class Photo extends RestModel {
|
||||
constructor(values) {
|
||||
super(values);
|
||||
this.Selected = Clipboard.has(this);
|
||||
}
|
||||
|
||||
getDefaults() {
|
||||
return {
|
||||
Selected: false,
|
||||
ID: "",
|
||||
UID: "",
|
||||
DocumentID: "",
|
||||
Type: TypeImage,
|
||||
|
@ -146,6 +145,19 @@ export class Photo extends RestModel {
|
|||
};
|
||||
}
|
||||
|
||||
classes() {
|
||||
let classes = ["is-photo", "uid-" + this.UID, "type-" + this.Type];
|
||||
|
||||
if (this.isPlayable()) classes.push("is-playable");
|
||||
if (Clipboard.has(this)) classes.push("is-selected");
|
||||
if (this.Portrait) classes.push("is-portrait");
|
||||
if (this.Favorite) classes.push("is-favorite");
|
||||
if (this.Private) classes.push("is-private");
|
||||
if (this.Files.length > 1) classes.push("is-stack");
|
||||
|
||||
return classes;
|
||||
}
|
||||
|
||||
localDayString() {
|
||||
if (!this.TakenAtLocal) {
|
||||
return new Date().getDate().toString().padStart(2, "0");
|
||||
|
@ -570,11 +582,14 @@ export class Photo extends RestModel {
|
|||
}
|
||||
|
||||
toggleLike() {
|
||||
this.Favorite = !this.Favorite;
|
||||
const favorite = !this.Favorite;
|
||||
const elements = document.querySelectorAll(`.uid-${this.UID}`);
|
||||
|
||||
if (this.Favorite) {
|
||||
if (favorite) {
|
||||
elements.forEach((el) => el.classList.add("is-favorite"));
|
||||
return Api.post(this.getEntityResource() + "/like");
|
||||
} else {
|
||||
elements.forEach((el) => el.classList.remove("is-favorite"));
|
||||
return Api.delete(this.getEntityResource() + "/like");
|
||||
}
|
||||
}
|
||||
|
@ -707,6 +722,10 @@ export class Photo extends RestModel {
|
|||
});
|
||||
}
|
||||
|
||||
static pageSize() {
|
||||
return 120;
|
||||
}
|
||||
|
||||
static getCollectionResource() {
|
||||
return "photos";
|
||||
}
|
||||
|
|
|
@ -77,7 +77,7 @@ export default {
|
|||
uid: uid,
|
||||
results: [],
|
||||
scrollDisabled: true,
|
||||
pageSize: 60,
|
||||
pageSize: Photo.pageSize(),
|
||||
offset: 0,
|
||||
page: 0,
|
||||
selection: this.$clipboard.selection,
|
||||
|
@ -444,16 +444,22 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
updateResult(results, values) {
|
||||
const model = results.find((m) => m.UID === values.UID);
|
||||
|
||||
if (model) {
|
||||
for (let key in values) {
|
||||
if (values.hasOwnProperty(key) && values[key] != null && typeof values[key] !== "object") {
|
||||
model[key] = values[key];
|
||||
updateResults(entity) {
|
||||
this.results.filter((m) => m.UID === entity.UID).forEach((m) => {
|
||||
for (let key in entity) {
|
||||
if (key !== "UID" && entity.hasOwnProperty(key) && entity[key] != null && typeof entity[key] !== "object") {
|
||||
m[key] = entity[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.viewer.results.filter((m) => m.UID === entity.UID).forEach((m) => {
|
||||
for (let key in entity) {
|
||||
if (key !== "UID" && entity.hasOwnProperty(key) && entity[key] != null && typeof entity[key] !== "object") {
|
||||
m[key] = entity[key];
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
removeResult(results, uid) {
|
||||
const index = results.findIndex((m) => m.UID === uid);
|
||||
|
@ -474,9 +480,7 @@ export default {
|
|||
switch (type) {
|
||||
case 'updated':
|
||||
for (let i = 0; i < data.entities.length; i++) {
|
||||
const values = data.entities[i];
|
||||
this.updateResult(this.results, values);
|
||||
this.updateResult(this.viewer.results, values);
|
||||
this.updateResults(data.entities[i]);
|
||||
}
|
||||
break;
|
||||
case 'restored':
|
||||
|
@ -502,6 +506,9 @@ export default {
|
|||
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: Needed?
|
||||
this.$forceUpdate();
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -54,8 +54,8 @@
|
|||
<p-album-clipboard :refresh="refresh" :selection="selection" :share="share" :edit="edit"
|
||||
:clear-selection="clearSelection" :context="context"></p-album-clipboard>
|
||||
|
||||
<v-container grid-list-xs fluid class="pa-2 p-albums p-albums-cards">
|
||||
<v-card v-if="results.length === 0" class="p-albums-empty secondary-light lighten-1 ma-1" flat>
|
||||
<v-container grid-list-xs fluid class="pa-2">
|
||||
<v-card v-if="results.length === 0" class="no-results secondary-light lighten-1 ma-1" flat>
|
||||
<v-card-title primary-title>
|
||||
<div v-if="staticFilter.type === 'album'">
|
||||
<h3 class="title ma-0 pa-0">
|
||||
|
@ -77,107 +77,101 @@
|
|||
</div>
|
||||
</v-card-title>
|
||||
</v-card>
|
||||
<v-layout row wrap class="p-album-results">
|
||||
<v-layout row wrap class="search-results album-results cards-view">
|
||||
<v-flex
|
||||
v-for="(album, index) in results"
|
||||
:key="index"
|
||||
:data-uid="album.UID"
|
||||
class="p-album"
|
||||
xs6 sm4 md3 lg2 xxl1 d-flex
|
||||
>
|
||||
<v-hover>
|
||||
<v-card slot-scope="{ hover }" tile
|
||||
class="accent lighten-3"
|
||||
:dark="selection.includes(album.UID)"
|
||||
:class="selection.includes(album.UID) ? 'elevation-10 ma-0 accent darken-1 white--text' : 'elevation-0 ma-1 accent lighten-3'"
|
||||
:to="{name: view, params: {uid: album.UID, slug: album.Slug, year: album.Year, month: album.Month}}"
|
||||
@contextmenu="onContextMenu($event, index)"
|
||||
<v-card tile
|
||||
:data-uid="album.UID"
|
||||
class="result accent lighten-2"
|
||||
:class="album.classes(selection.includes(album.UID))"
|
||||
:to="{name: view, params: {uid: album.UID, slug: album.Slug, year: album.Year, month: album.Month}}"
|
||||
@contextmenu="onContextMenu($event, index)"
|
||||
>
|
||||
<div class="card-background accent lighten-2"></div>
|
||||
<v-img
|
||||
:src="album.thumbnailUrl('tile_500')"
|
||||
:alt="album.Title"
|
||||
:transition="false"
|
||||
aspect-ratio="1"
|
||||
class="accent lighten-3 clickable"
|
||||
@mousedown="onMouseDown($event, index)"
|
||||
@click="onClick($event, index)"
|
||||
>
|
||||
<v-img
|
||||
:src="album.thumbnailUrl('tile_500')"
|
||||
aspect-ratio="1"
|
||||
class="accent lighten-2"
|
||||
@mousedown="onMouseDown($event, index)"
|
||||
@click="onClick($event, index)"
|
||||
>
|
||||
<v-btn v-if="featureShare && album.LinkCount > 0" :ripple="false"
|
||||
icon large absolute
|
||||
class="action-share"
|
||||
@click.stop.prevent="share(album)">
|
||||
<v-icon color="white">share</v-icon>
|
||||
</v-btn>
|
||||
<v-btn v-if="featureShare && album.LinkCount > 0" :ripple="false"
|
||||
icon flat absolute
|
||||
class="action-share"
|
||||
@click.stop.prevent="share(album)">
|
||||
<v-icon color="white">share</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn v-if="hover || selection.includes(album.UID)" :flat="!hover" :ripple="false"
|
||||
icon large absolute
|
||||
:class="selection.includes(album.UID) ? 'action-select' : 'action-select opacity-50'"
|
||||
@click.stop.prevent="onSelect($event, index)">
|
||||
<v-icon v-if="selection.includes(album.UID)" color="white"
|
||||
class="t-select t-on">check_circle
|
||||
</v-icon>
|
||||
<v-icon v-else color="accent lighten-3" class="t-select t-off">
|
||||
radio_button_off
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
</v-img>
|
||||
<v-btn :ripple="false"
|
||||
icon flat absolute
|
||||
class="input-select"
|
||||
@click.stop.prevent="onSelect($event, index)">
|
||||
<v-icon color="white" class="select-on">check_circle</v-icon>
|
||||
<v-icon color="accent lighten-3" class="select-off">radio_button_off</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-card-actions primary-title class="pl-3 pr-2 pb-0 mb-0" style="user-select: none;">
|
||||
<h3 v-if="album.Type !== 'month'"
|
||||
class="body-2 ma-0 action-title-edit"
|
||||
:data-uid="album.UID"
|
||||
@click.stop.prevent="edit(album)">
|
||||
{{ album.Title }}
|
||||
<v-btn :ripple="false"
|
||||
icon flat absolute
|
||||
class="input-favorite"
|
||||
@click.stop.prevent="album.toggleLike()">
|
||||
<v-icon color="#FFD600" class="select-on">star</v-icon>
|
||||
<v-icon color="white" class="select-off">star_border</v-icon>
|
||||
</v-btn>
|
||||
</v-img>
|
||||
|
||||
<v-card-title primary-title class="pl-3 pt-3 pr-3 pb-2 card-details" style="user-select: none;">
|
||||
<div>
|
||||
<h3 class="body-2 mb-0">
|
||||
<button v-if="album.Type !== 'month'" class="action-title-edit" :data-uid="album.UID"
|
||||
@click.stop.prevent="edit(album)">
|
||||
{{ album.Title | truncate(80) }}
|
||||
</button>
|
||||
<button v-else class="action-title-edit" :data-uid="album.UID"
|
||||
@click.stop.prevent="edit(album)">
|
||||
{{ album.getDateString() | capitalize }}
|
||||
</button>
|
||||
</h3>
|
||||
</div>
|
||||
</v-card-title>
|
||||
|
||||
<h3 v-else
|
||||
class="body-2 ma-0 action-title-edit"
|
||||
:data-uid="album.UID"
|
||||
@click.stop.prevent="edit(album)">
|
||||
{{ album.getDateString() | capitalize }}
|
||||
</h3>
|
||||
<v-card-text primary-title class="pb-2 pt-0 card-details" style="user-select: none;"
|
||||
@click.stop.prevent="">
|
||||
<div v-if="album.Description" class="caption mb-2" :title="$gettext('Description')">
|
||||
<button @click.exact="edit(album)">
|
||||
{{ album.Description | truncate(100) }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<v-spacer></v-spacer>
|
||||
<div v-else-if="album.Type === 'album'" class="caption mb-2">
|
||||
<button v-if="album.PhotoCount === 1" @click.exact="edit(album)">
|
||||
<translate>Contains one entry.</translate>
|
||||
</button>
|
||||
<button v-else-if="album.PhotoCount > 0">
|
||||
<translate :translate-params="{n: album.PhotoCount}">Contains %{n} entries.</translate>
|
||||
</button>
|
||||
<button v-else @click.stop.prevent="$router.push({name: 'photos'})">
|
||||
<translate>Add photos or videos from search results by selecting them.</translate>
|
||||
</button>
|
||||
</div>
|
||||
<div v-else-if="album.Type === 'folder'" class="caption mb-2">
|
||||
<button @click.exact="edit(album)">
|
||||
/{{ album.Path | truncate(100) }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<v-btn icon @click.stop.prevent="album.toggleLike()">
|
||||
<v-icon v-if="album.Favorite" color="#FFD600">star
|
||||
</v-icon>
|
||||
<v-icon v-else color="accent lighten-2">star</v-icon>
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
|
||||
<v-card-text primary-title class="pb-2 pt-0 p-photo-desc" style="user-select: none;"
|
||||
@click.stop.prevent="">
|
||||
<div v-if="album.Description" class="caption mb-2">
|
||||
<button @click.exact="edit(album)">
|
||||
{{ album.Description | truncate(100) }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-else-if="album.Type === 'album'" class="caption mb-2">
|
||||
<button v-if="album.PhotoCount === 1" @click.exact="edit(album)">
|
||||
<translate>Contains one entry.</translate>
|
||||
</button>
|
||||
<button v-else-if="album.PhotoCount > 0">
|
||||
<translate :translate-params="{n: album.PhotoCount}">Contains %{n} entries.</translate>
|
||||
</button>
|
||||
<button v-else @click.stop.prevent="$router.push({name: 'photos'})">
|
||||
<translate>Add photos or videos from search results by selecting them.</translate>
|
||||
</button>
|
||||
</div>
|
||||
<div v-else-if="album.Type === 'folder'" class="caption mb-2">
|
||||
<button @click.exact="edit(album)">
|
||||
/{{ album.Path | truncate(100) }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="album.Location" class="caption mb-2 d-block">
|
||||
<button @click.exact="edit(album)">
|
||||
<v-icon size="14">location_on</v-icon>
|
||||
{{ album.Location }}
|
||||
</button>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-hover>
|
||||
<div v-if="album.Location" class="caption mb-2 d-block">
|
||||
<button @click.exact="edit(album)">
|
||||
<v-icon size="14">location_on</v-icon>
|
||||
{{ album.Location }}
|
||||
</button>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
</v-container>
|
||||
|
@ -229,7 +223,7 @@ export default {
|
|||
results: [],
|
||||
loading: true,
|
||||
scrollDisabled: true,
|
||||
pageSize: 24,
|
||||
pageSize: Album.pageSize(),
|
||||
offset: 0,
|
||||
page: 0,
|
||||
selection: [],
|
||||
|
|
|
@ -41,8 +41,8 @@
|
|||
|
||||
<p-scroll-top></p-scroll-top>
|
||||
|
||||
<v-container grid-list-xs fluid class="pa-2 p-labels p-labels-cards">
|
||||
<v-card v-if="results.length === 0" class="p-labels-empty secondary-light lighten-1 ma-1" flat>
|
||||
<v-container grid-list-xs fluid class="pa-2">
|
||||
<v-card v-if="results.length === 0" class="no-results secondary-light lighten-1 ma-1" flat>
|
||||
<v-card-title primary-title>
|
||||
<div>
|
||||
<h3 class="title ma-0 pa-0">
|
||||
|
@ -54,71 +54,72 @@
|
|||
</div>
|
||||
</v-card-title>
|
||||
</v-card>
|
||||
<v-layout row wrap class="p-label-results">
|
||||
<v-layout row wrap class="search-results label-results cards-view">
|
||||
<v-flex
|
||||
v-for="(label, index) in results"
|
||||
:key="index"
|
||||
class="p-label"
|
||||
:data-uid="label.UID"
|
||||
xs6 sm4 md3 lg2 xxl1 d-flex
|
||||
>
|
||||
<v-hover>
|
||||
<v-card slot-scope="{ hover }" tile
|
||||
class="accent lighten-3"
|
||||
:dark="selection.includes(label.UID)"
|
||||
:class="selection.includes(label.UID) ? 'elevation-10 ma-0 accent darken-1 white--text' : 'elevation-0 ma-1 accent lighten-3'"
|
||||
:to="{name: 'browse', query: {q: 'label:' + (label.CustomSlug ? label.CustomSlug : label.Slug)}}"
|
||||
@contextmenu="onContextMenu($event, index)">
|
||||
<v-img
|
||||
:src="label.thumbnailUrl('tile_500')"
|
||||
aspect-ratio="1"
|
||||
class="accent lighten-2"
|
||||
@mousedown="onMouseDown($event, index)"
|
||||
@click="onClick($event, index)"
|
||||
>
|
||||
<v-btn v-if="hover || selection.includes(label.UID)" :flat="!hover" :ripple="false"
|
||||
icon large absolute
|
||||
:class="selection.includes(label.UID) ? 'p-label-select' : 'p-label-select opacity-50'"
|
||||
@click.stop.prevent="onSelect($event, index)">
|
||||
<v-icon v-if="selection.includes(label.UID)" color="white" class="t-select t-on">check_circle
|
||||
</v-icon>
|
||||
<v-icon v-else color="accent lighten-3" class="t-select t-off">radio_button_off</v-icon>
|
||||
</v-btn>
|
||||
</v-img>
|
||||
<v-card tile
|
||||
:data-uid="label.UID"
|
||||
class="result accent lighten-2"
|
||||
:class="label.classes(selection.includes(label.UID))"
|
||||
:to="{name: 'browse', query: {q: 'label:' + (label.CustomSlug ? label.CustomSlug : label.Slug)}}"
|
||||
@contextmenu="onContextMenu($event, index)"
|
||||
>
|
||||
<div class="card-background accent lighten-2"></div>
|
||||
<v-img
|
||||
:src="label.thumbnailUrl('tile_500')"
|
||||
:alt="label.Name"
|
||||
:transition="false"
|
||||
aspect-ratio="1"
|
||||
class="accent lighten-3 clickable"
|
||||
@mousedown="onMouseDown($event, index)"
|
||||
@click="onClick($event, index)"
|
||||
>
|
||||
<v-btn :ripple="false"
|
||||
icon flat absolute
|
||||
class="input-select"
|
||||
@click.stop.prevent="onSelect($event, index)">
|
||||
<v-icon color="white" class="select-on">check_circle</v-icon>
|
||||
<v-icon color="accent lighten-3" class="select-off">radio_button_off</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-card-actions @click.stop.prevent="">
|
||||
<v-edit-dialog
|
||||
:return-value.sync="label.Name"
|
||||
lazy
|
||||
class="p-inline-edit"
|
||||
@save="onSave(label)"
|
||||
>
|
||||
<span v-if="label.Name" class="body-2 ma-0">
|
||||
{{ label.Name | capitalize }}
|
||||
</span>
|
||||
<span v-else>
|
||||
<v-icon>edit</v-icon>
|
||||
</span>
|
||||
<template #input>
|
||||
<v-text-field
|
||||
v-model="label.Name"
|
||||
:rules="[titleRule]"
|
||||
:label="$gettext('Label Name')"
|
||||
color="secondary-dark"
|
||||
single-line
|
||||
autofocus
|
||||
></v-text-field>
|
||||
</template>
|
||||
</v-edit-dialog>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn icon @click.stop.prevent="label.toggleLike()">
|
||||
<v-icon v-if="label.Favorite" color="#FFD600">star
|
||||
</v-icon>
|
||||
<v-icon v-else color="accent lighten-2">star</v-icon>
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-hover>
|
||||
<v-btn :ripple="false"
|
||||
icon flat absolute
|
||||
class="input-favorite"
|
||||
@click.stop.prevent="label.toggleLike()">
|
||||
<v-icon color="#FFD600" class="select-on">star</v-icon>
|
||||
<v-icon color="white" class="select-off">star_border</v-icon>
|
||||
</v-btn>
|
||||
</v-img>
|
||||
|
||||
<v-card-title primary-title class="pa-3 card-details" style="user-select: none;" @click.stop.prevent="">
|
||||
<v-edit-dialog
|
||||
:return-value.sync="label.Name"
|
||||
lazy
|
||||
class="inline-edit"
|
||||
@save="onSave(label)"
|
||||
>
|
||||
<span v-if="label.Name" class="body-2 ma-0">
|
||||
{{ label.Name | capitalize }}
|
||||
</span>
|
||||
<span v-else>
|
||||
<v-icon>edit</v-icon>
|
||||
</span>
|
||||
<template #input>
|
||||
<v-text-field
|
||||
v-model="label.Name"
|
||||
:rules="[titleRule]"
|
||||
:label="$gettext('Label Name')"
|
||||
color="secondary-dark"
|
||||
single-line
|
||||
autofocus
|
||||
></v-text-field>
|
||||
</template>
|
||||
</v-edit-dialog>
|
||||
</v-card-title>
|
||||
</v-card>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
</v-container>
|
||||
|
@ -154,7 +155,7 @@ export default {
|
|||
results: [],
|
||||
scrollDisabled: true,
|
||||
loading: true,
|
||||
pageSize: 24,
|
||||
pageSize: Label.pageSize(),
|
||||
offset: 0,
|
||||
page: 0,
|
||||
selection: [],
|
||||
|
@ -459,9 +460,11 @@ export default {
|
|||
const values = data.entities[i];
|
||||
const model = this.results.find((m) => m.UID === values.UID);
|
||||
|
||||
for (let key in values) {
|
||||
if (values.hasOwnProperty(key)) {
|
||||
model[key] = values[key];
|
||||
if (model) {
|
||||
for (let key in values) {
|
||||
if (values.hasOwnProperty(key) && values[key] != null && typeof values[key] !== "object") {
|
||||
model[key] = values[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
<p-scroll-top></p-scroll-top>
|
||||
|
||||
<v-container grid-list-xs fluid class="pa-2 p-files p-files-cards">
|
||||
<v-card v-if="results.length === 0" class="p-files-empty secondary-light lighten-1 ma-1" flat>
|
||||
<v-card v-if="results.length === 0" class="no-results secondary-light lighten-1 ma-1" flat>
|
||||
<v-card-title primary-title>
|
||||
<div>
|
||||
<h3 class="title ma-0 pa-0">
|
||||
|
@ -44,64 +44,63 @@
|
|||
</div>
|
||||
</v-card-title>
|
||||
</v-card>
|
||||
<v-layout row wrap class="p-files-results">
|
||||
<v-layout row wrap class="search-results file-results cards-view">
|
||||
<v-flex
|
||||
v-for="(model, index) in results"
|
||||
:key="index"
|
||||
:data-uid="model.UID"
|
||||
class="p-file"
|
||||
xs6 sm4 md3 lg2 xxl1 d-flex
|
||||
>
|
||||
<v-hover>
|
||||
<v-card slot-scope="{ hover }" tile
|
||||
class="accent lighten-3 clickable"
|
||||
:dark="selection.includes(model.UID)"
|
||||
:class="selection.includes(model.UID) ? 'elevation-10 ma-0 darken-1 white--text' : 'elevation-0 ma-1 lighten-3'"
|
||||
@contextmenu="onContextMenu($event, index)">
|
||||
<v-img
|
||||
:src="model.thumbnailUrl('tile_500')"
|
||||
aspect-ratio="1"
|
||||
class="accent lighten-2"
|
||||
@mousedown="onMouseDown($event, index)"
|
||||
@click="onClick($event, index)"
|
||||
>
|
||||
<v-btn v-if="hover || selection.includes(model.UID)" :flat="!hover" :ripple="false"
|
||||
icon large absolute
|
||||
:class="selection.includes(model.UID) ? 'p-file-select' : 'p-file-select opacity-50'"
|
||||
@click.stop.prevent="onSelect($event, index)">
|
||||
<v-icon v-if="selection.includes(model.UID)" color="white" class="t-select t-on">check_circle
|
||||
</v-icon>
|
||||
<v-icon v-else color="accent lighten-3" class="t-select t-off">radio_button_off</v-icon>
|
||||
</v-btn>
|
||||
</v-img>
|
||||
<v-card tile
|
||||
:data-uid="model.UID"
|
||||
class="result accent lighten-2"
|
||||
:class="model.classes(selection.includes(model.UID))"
|
||||
@contextmenu="onContextMenu($event, index)"
|
||||
>
|
||||
<div class="card-background accent lighten-2"></div>
|
||||
<v-img
|
||||
:src="model.thumbnailUrl('tile_500')"
|
||||
:alt="model.Name"
|
||||
:transition="false"
|
||||
aspect-ratio="1"
|
||||
class="accent lighten-3 clickable"
|
||||
@mousedown="onMouseDown($event, index)"
|
||||
@click="onClick($event, index)"
|
||||
>
|
||||
<v-btn :ripple="false"
|
||||
icon flat absolute
|
||||
class="input-select"
|
||||
@click.stop.prevent="onSelect($event, index)">
|
||||
<v-icon color="white" class="select-on">check_circle</v-icon>
|
||||
<v-icon color="accent lighten-3" class="select-off">radio_button_off</v-icon>
|
||||
</v-btn>
|
||||
</v-img>
|
||||
|
||||
<v-card-title v-if="model.isFile()" primary-title class="pa-3 p-photo-desc"
|
||||
style="user-select: none;">
|
||||
<div>
|
||||
<h3 class="body-2 mb-2" :title="model.Name">
|
||||
<button @click.exact="openFile(index)">
|
||||
{{ model.baseName() }}
|
||||
</button>
|
||||
</h3>
|
||||
<div class="caption" title="Info">
|
||||
{{ model.getInfo() }}
|
||||
</div>
|
||||
<v-card-title v-if="model.isFile()" primary-title class="pa-3 card-details"
|
||||
style="user-select: none;">
|
||||
<div>
|
||||
<h3 class="body-2 mb-2" :title="model.Name">
|
||||
<button @click.exact="openFile(index)">
|
||||
{{ model.baseName() }}
|
||||
</button>
|
||||
</h3>
|
||||
<div class="caption" title="Info">
|
||||
{{ model.getInfo() }}
|
||||
</div>
|
||||
</v-card-title>
|
||||
<v-card-title v-else primary-title class="pa-3 p-photo-desc">
|
||||
<div>
|
||||
<h3 class="body-2 mb-2" :title="model.Title">
|
||||
<button @click.exact="openFile(index)">
|
||||
{{ model.baseName() }}
|
||||
</button>
|
||||
</h3>
|
||||
<div class="caption" title="Path">
|
||||
<translate key="Folder">Folder</translate>
|
||||
</div>
|
||||
</div>
|
||||
</v-card-title>
|
||||
<v-card-title v-else primary-title class="pa-3 card-details">
|
||||
<div>
|
||||
<h3 class="body-2 mb-2" :title="model.Title">
|
||||
<button @click.exact="openFile(index)">
|
||||
{{ model.baseName() }}
|
||||
</button>
|
||||
</h3>
|
||||
<div class="caption" title="Path">
|
||||
<translate key="Folder">Folder</translate>
|
||||
</div>
|
||||
</v-card-title>
|
||||
</v-card>
|
||||
</v-hover>
|
||||
</div>
|
||||
</v-card-title>
|
||||
</v-card>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
</v-container>
|
||||
|
|
|
@ -92,7 +92,7 @@ export default {
|
|||
complete: false,
|
||||
results: [],
|
||||
scrollDisabled: true,
|
||||
pageSize: 60,
|
||||
pageSize: Photo.pageSize(),
|
||||
offset: 0,
|
||||
page: 0,
|
||||
selection: this.$clipboard.selection,
|
||||
|
@ -438,16 +438,22 @@ export default {
|
|||
|
||||
this.loadMore();
|
||||
},
|
||||
updateResult(results, values) {
|
||||
const model = results.find((m) => m.UID === values.UID);
|
||||
|
||||
if (model) {
|
||||
for (let key in values) {
|
||||
if (values.hasOwnProperty(key) && values[key] != null && typeof values[key] !== "object") {
|
||||
model[key] = values[key];
|
||||
updateResults(entity) {
|
||||
this.results.filter((m) => m.UID === entity.UID).forEach((m) => {
|
||||
for (let key in entity) {
|
||||
if (key !== "UID" && entity.hasOwnProperty(key) && entity[key] != null && typeof entity[key] !== "object") {
|
||||
m[key] = entity[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.viewer.results.filter((m) => m.UID === entity.UID).forEach((m) => {
|
||||
for (let key in entity) {
|
||||
if (key !== "UID" && entity.hasOwnProperty(key) && entity[key] != null && typeof entity[key] !== "object") {
|
||||
m[key] = entity[key];
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
removeResult(results, uid) {
|
||||
const index = results.findIndex((m) => m.UID === uid);
|
||||
|
@ -475,8 +481,7 @@ export default {
|
|||
this.removeResult(this.viewer.results, values.UID);
|
||||
this.$clipboard.removeId(values.UID);
|
||||
} else {
|
||||
this.updateResult(this.results, values);
|
||||
this.updateResult(this.viewer.results, values);
|
||||
this.updateResults(values);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -531,6 +536,9 @@ export default {
|
|||
default:
|
||||
console.warn("unexpected event type", ev);
|
||||
}
|
||||
|
||||
// TODO: Needed?
|
||||
this.$forceUpdate();
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -15,10 +15,10 @@
|
|||
<p-album-clipboard :refresh="refresh" :selection="selection"
|
||||
:clear-selection="clearSelection" :context="context"></p-album-clipboard>
|
||||
|
||||
<v-container grid-list-xs fluid class="pa-2 p-albums p-albums-cards">
|
||||
<v-card v-if="results.length === 0" class="p-albums-empty secondary-light lighten-1 ma-1" flat>
|
||||
<v-container grid-list-xs fluid class="pa-2">
|
||||
<v-card v-if="results.length === 0" class="no-results secondary-light lighten-1 ma-1" flat>
|
||||
<v-card-title primary-title>
|
||||
<div>
|
||||
<div v-if="staticFilter.type === 'album'">
|
||||
<h3 class="title ma-0 pa-0">
|
||||
<translate>Couldn't find anything</translate>
|
||||
</h3>
|
||||
|
@ -28,57 +28,64 @@
|
|||
</div>
|
||||
</v-card-title>
|
||||
</v-card>
|
||||
<v-layout row wrap class="p-album-results">
|
||||
<v-layout row wrap class="search-results album-results cards-view">
|
||||
<v-flex
|
||||
v-for="(album, index) in results"
|
||||
:key="index"
|
||||
:data-uid="album.UID"
|
||||
class="p-album"
|
||||
xs6 sm4 md3 lg2 xxl1 d-flex
|
||||
>
|
||||
<v-hover>
|
||||
<v-card slot-scope="{ hover }" tile
|
||||
class="accent lighten-3"
|
||||
:dark="selection.includes(album.UID)"
|
||||
:class="selection.includes(album.UID) ? 'elevation-10 ma-0 accent darken-1 white--text' : 'elevation-0 ma-1 accent lighten-3'"
|
||||
:to="{name: view, params: {uid: album.UID, slug: album.Slug, year: album.Year, month: album.Month}}"
|
||||
@contextmenu="onContextMenu($event, index)"
|
||||
<v-card tile
|
||||
:data-uid="album.UID"
|
||||
class="result accent lighten-2"
|
||||
:class="album.classes(selection.includes(album.UID))"
|
||||
:to="{name: view, params: {uid: album.UID, slug: album.Slug, year: album.Year, month: album.Month}}"
|
||||
@contextmenu="onContextMenu($event, index)"
|
||||
>
|
||||
<div class="card-background accent lighten-2"></div>
|
||||
<v-img
|
||||
:src="album.thumbnailUrl('tile_500')"
|
||||
:alt="album.Title"
|
||||
:transition="false"
|
||||
aspect-ratio="1"
|
||||
class="accent lighten-3 clickable"
|
||||
@mousedown="onMouseDown($event, index)"
|
||||
@click="onClick($event, index)"
|
||||
>
|
||||
<v-img
|
||||
:src="album.thumbnailUrl('tile_500')"
|
||||
aspect-ratio="1"
|
||||
class="accent lighten-2"
|
||||
@mousedown="onMouseDown($event, index)"
|
||||
@click="onClick($event, index)"
|
||||
>
|
||||
<v-btn v-if="hover || selection.includes(album.UID)" :flat="!hover" :ripple="false"
|
||||
icon large absolute
|
||||
:class="selection.includes(album.UID) ? 'action-select' : 'action-select opacity-50'"
|
||||
@click.stop.prevent="onSelect($event, index)">
|
||||
<v-icon v-if="selection.includes(album.UID)" color="white"
|
||||
class="t-select t-on">check_circle
|
||||
</v-icon>
|
||||
<v-icon v-else color="accent lighten-3" class="t-select t-off">
|
||||
radio_button_off
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
</v-img>
|
||||
<v-btn :ripple="false"
|
||||
icon flat absolute
|
||||
class="input-select"
|
||||
@click.stop.prevent="onSelect($event, index)">
|
||||
<v-icon color="white" class="select-on">check_circle</v-icon>
|
||||
<v-icon color="accent lighten-3" class="select-off">radio_button_off</v-icon>
|
||||
</v-btn>
|
||||
</v-img>
|
||||
|
||||
<v-card-title primary-title class="pa-3 p-album-desc" style="user-select: none;">
|
||||
<h3 class="body-2 ma-0">
|
||||
{{ album.Title }}
|
||||
<v-card-title primary-title class="pl-3 pt-3 pr-3 pb-2 card-details" style="user-select: none;">
|
||||
<div>
|
||||
<h3 v-if="album.Type !== 'month'" class="body-2 mb-0" :title="album.Title">
|
||||
{{ album.Title | truncate(80) }}
|
||||
</h3>
|
||||
</v-card-title>
|
||||
<v-card-text class="pl-3 pr-3 pt-0 pb-3 p-album-desc">
|
||||
<div v-if="album.Description" class="caption" title="Description">
|
||||
{{ album.Description | truncate(100) }}
|
||||
</div>
|
||||
<div v-else class="caption" title="Description">
|
||||
<translate>Shared with you.</translate>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-hover>
|
||||
<h3 v-else class="body-2 mb-0">
|
||||
{{ album.getDateString() | capitalize }}
|
||||
</h3>
|
||||
</div>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text class="pb-2 pt-0 card-details">
|
||||
<div v-if="album.Description" class="caption mb-2" :title="$gettext('Description')">
|
||||
{{ album.Description }}
|
||||
</div>
|
||||
<div v-else class="caption mb-2">
|
||||
<translate>Shared with you.</translate>
|
||||
</div>
|
||||
<div v-if="album.Location" class="caption mb-2 d-block">
|
||||
<button @click.stop="">
|
||||
<v-icon size="14">location_on</v-icon>
|
||||
{{ album.Location }}
|
||||
</button>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
</v-container>
|
||||
|
@ -110,8 +117,8 @@ export default {
|
|||
|
||||
let categories = [{"value": "", "text": this.$gettext("All Categories")}];
|
||||
|
||||
if (this.$config.values.albumCategories) {
|
||||
categories = categories.concat(this.$config.values.albumCategories.map(cat => {
|
||||
if (this.$config.albumCategories().length > 0) {
|
||||
categories = categories.concat(this.$config.albumCategories().map(cat => {
|
||||
return {"value": cat, "text": cat};
|
||||
}));
|
||||
}
|
||||
|
@ -125,7 +132,7 @@ export default {
|
|||
results: [],
|
||||
loading: true,
|
||||
scrollDisabled: true,
|
||||
pageSize: 24,
|
||||
pageSize: Album.pageSize(),
|
||||
offset: 0,
|
||||
page: 0,
|
||||
selection: [],
|
||||
|
@ -139,6 +146,7 @@ export default {
|
|||
timeStamp: -1,
|
||||
},
|
||||
lastId: "",
|
||||
model: new Album(),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -402,7 +410,7 @@ export default {
|
|||
title = `${title} (${existing.length + 1})`;
|
||||
}
|
||||
|
||||
const album = new Album({"Title": title, "Favorite": true});
|
||||
const album = new Album({"Title": title, "Favorite": false});
|
||||
|
||||
album.save();
|
||||
},
|
||||
|
@ -473,6 +481,17 @@ export default {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
let categories = [{"value": "", "text": this.$gettext("All Categories")}];
|
||||
|
||||
if (this.$config.albumCategories().length > 0) {
|
||||
categories = categories.concat(this.$config.albumCategories().map(cat => {
|
||||
return {"value": cat, "text": cat};
|
||||
}));
|
||||
}
|
||||
|
||||
this.categories = categories;
|
||||
|
||||
break;
|
||||
case 'deleted':
|
||||
this.dirty = true;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<v-container grid-list-xs fluid class="pa-2 p-photos p-photo-cards">
|
||||
<v-card v-if="photos.length === 0" class="p-photos-empty secondary-light lighten-1 ma-1" flat>
|
||||
<v-card v-if="photos.length === 0" class="no-results secondary-light lighten-1 ma-1" flat>
|
||||
<v-card-title primary-title>
|
||||
<div>
|
||||
<h3 v-if="filter.order === 'edited'" class="title ma-0 pa-0">
|
||||
|
@ -15,132 +15,110 @@
|
|||
</div>
|
||||
</v-card-title>
|
||||
</v-card>
|
||||
<v-layout row wrap class="p-results">
|
||||
<v-layout row wrap class="search-results photo-results cards-view">
|
||||
<v-flex
|
||||
v-for="(photo, index) in photos"
|
||||
:key="index"
|
||||
:data-uid="photo.UID"
|
||||
class="p-photo"
|
||||
xs12 sm6 md4 lg3 xlg2 xxxl1 d-flex
|
||||
:class="{ 'is-selected': photo.Selected, portrait: photo.Portrait }"
|
||||
>
|
||||
<v-hover>
|
||||
<v-card slot-scope="{ hover }" tile
|
||||
:dark="photo.Selected"
|
||||
:class="photo.Selected ? 'selected elevation-10 ma-0 accent darken-1 white--text select-transition' : 'elevation-0 ma-1 accent lighten-3 select-transition'"
|
||||
@contextmenu="onContextMenu($event, index)">
|
||||
<v-img :src="photo.thumbnailUrl('tile_500')"
|
||||
aspect-ratio="1"
|
||||
class="accent lighten-2 clickable"
|
||||
@mousedown="onMouseDown($event, index)"
|
||||
@click.stop.prevent="onClick($event, index)"
|
||||
>
|
||||
<v-layout
|
||||
v-if="photo.Type === 'live'"
|
||||
v-show="hover"
|
||||
fill-height
|
||||
align-center
|
||||
justify-center
|
||||
ma-0
|
||||
class="live-player"
|
||||
style="overflow: hidden;"
|
||||
>
|
||||
<video :key="photo.videoUrl()" width="500" height="500" autoplay loop muted playsinline>
|
||||
<source :src="photo.videoUrl()" type="video/mp4">
|
||||
</video>
|
||||
</v-layout>
|
||||
<v-card tile
|
||||
:data-id="photo.ID"
|
||||
:data-uid="photo.UID"
|
||||
class="result accent lighten-2"
|
||||
:class="photo.classes()"
|
||||
@contextmenu="onContextMenu($event, index)">
|
||||
<div class="card-background accent lighten-2"></div>
|
||||
<v-img :key="photo.Hash"
|
||||
:src="photo.thumbnailUrl('tile_500')"
|
||||
:alt="photo.Title"
|
||||
:title="photo.Title"
|
||||
:transition="false"
|
||||
aspect-ratio="1"
|
||||
class="accent lighten-3 clickable"
|
||||
@mousedown="onMouseDown($event, index)"
|
||||
@click.stop.prevent="onClick($event, index)"
|
||||
>
|
||||
<v-layout v-if="photo.Type === 'video' || photo.Type === 'live'" class="live-player">
|
||||
<video :key="photo.ID" width="500" height="500" autoplay loop muted playsinline>
|
||||
<source :src="photo.videoUrl()" type="video/mp4">
|
||||
</video>
|
||||
</v-layout>
|
||||
|
||||
<v-btn v-if="hidePrivate && photo.Private" :ripple="false"
|
||||
icon flat large absolute
|
||||
class="p-photo-private opacity-75">
|
||||
<v-icon color="white">lock</v-icon>
|
||||
</v-btn>
|
||||
<v-btn :ripple="false" :depressed="false" class="input-open"
|
||||
icon flat absolute
|
||||
@click.stop.prevent="openPhoto(index, true)">
|
||||
<v-icon color="white" class="default-hidden action-raw" :title="$gettext('RAW')">photo_camera</v-icon>
|
||||
<v-icon color="white" class="default-hidden action-live" :title="$gettext('Live')">adjust</v-icon>
|
||||
<v-icon color="white" class="default-hidden action-stack" :title="$gettext('Stack')">burst_mode</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn v-if="hover || photo.Selected" :ripple="false"
|
||||
icon flat large absolute
|
||||
:class="photo.Selected ? 'p-photo-select' : 'p-photo-select opacity-50'"
|
||||
@click.stop.prevent="onSelect($event, index)">
|
||||
<v-icon v-if="photo.Selected" color="white"
|
||||
class="t-select t-on">check_circle
|
||||
</v-icon>
|
||||
<v-icon v-else color="accent lighten-3" class="t-select t-off">radio_button_off</v-icon>
|
||||
</v-btn>
|
||||
<v-btn :ripple="false" :depressed="false" class="input-view"
|
||||
icon flat absolute :title="$gettext('View')"
|
||||
@click.stop.prevent="openPhoto(index, false)">
|
||||
<v-icon color="white" class="action-fullscreen">zoom_in</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<!-- v-btn icon flat large absolute :ripple="false"
|
||||
:class="photo.Favorite ? 'p-photo-like opacity-75' : 'p-photo-like opacity-50'"
|
||||
@click.stop.prevent="photo.toggleLike()">
|
||||
<v-icon v-if="photo.Favorite" color="white" class="t-like t-on" :data-uid="photo.UID">
|
||||
favorite
|
||||
</v-icon>
|
||||
<v-icon v-else color="accent lighten-3" class="t-like t-off" :data-uid="photo.UID">
|
||||
favorite_border
|
||||
</v-icon>
|
||||
</v-btn -->
|
||||
<v-btn :ripple="false" :depressed="false" color="white" class="input-play"
|
||||
outline fab absolute :title="$gettext('Play')"
|
||||
@click.stop.prevent="openPhoto(index, true)">
|
||||
<v-icon color="white" class="action-play">play_arrow</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<template v-if="photo.isPlayable()">
|
||||
<v-btn v-if="photo.Type === 'live'" :ripple="false"
|
||||
icon flat large absolute class="p-photo-live opacity-75"
|
||||
title="Live Photo" @click.stop.prevent="openPhoto(index, true)">
|
||||
<v-icon color="white" class="action-play">adjust</v-icon>
|
||||
</v-btn>
|
||||
<v-btn v-else color="white" :ripple="false"
|
||||
outline large fab absolute class="p-photo-play opacity-75" :depressed="false"
|
||||
title="Play" @click.stop.prevent="openPhoto(index, true)">
|
||||
<v-icon color="white" class="action-play">play_arrow</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-btn v-else-if="photo.Type === 'image' && photo.Files.length > 1" :ripple="false"
|
||||
icon flat large absolute class="p-photo-merged opacity-75"
|
||||
@click.stop.prevent="openPhoto(index, true)">
|
||||
<v-icon color="white" class="action-burst">burst_mode</v-icon>
|
||||
</v-btn>
|
||||
<v-btn v-else-if="photo.Type === 'image' && selectMode && hover" :ripple="false"
|
||||
icon flat large absolute class="p-photo-merged opacity-75"
|
||||
@click.stop.prevent="openPhoto(index, false)">
|
||||
<v-icon color="white" class="action-open">zoom_in</v-icon>
|
||||
</v-btn>
|
||||
<v-btn v-else-if="photo.Type === 'raw'" :ripple="false"
|
||||
icon flat large absolute class="p-photo-merged opacity-75"
|
||||
title="RAW" @click.stop.prevent="openPhoto(index, true)">
|
||||
<v-icon color="white" class="action-burst">photo_camera</v-icon>
|
||||
</v-btn>
|
||||
</v-img>
|
||||
<v-btn :ripple="false"
|
||||
icon flat absolute
|
||||
class="input-select"
|
||||
@click.stop.prevent="onSelect($event, index)">
|
||||
<v-icon color="white" class="select-on">check_circle</v-icon>
|
||||
<v-icon color="accent lighten-3" class="select-off">radio_button_off</v-icon>
|
||||
</v-btn>
|
||||
</v-img>
|
||||
|
||||
<v-card-title primary-title class="pa-3 p-photo-desc" style="user-select: none;"
|
||||
@click.stop.prevent="openPhoto(index, false)">
|
||||
<div>
|
||||
<h3 class="body-2 mb-2" :title="photo.Title">
|
||||
<v-card-title primary-title class="pa-3 card-details" style="user-select: none;">
|
||||
<div>
|
||||
<h3 class="body-2 mb-2" :title="photo.Title">
|
||||
<div @click.stop.prevent="openPhoto(index, false)">
|
||||
{{ photo.Title | truncate(80) }}
|
||||
</h3>
|
||||
<div v-if="photo.Description" class="caption mb-2" title="Description">
|
||||
</div>
|
||||
</h3>
|
||||
<div v-if="photo.Description" class="caption mb-2" :title="labels.description">
|
||||
<div>
|
||||
{{ photo.Description }}
|
||||
</div>
|
||||
<div class="caption">
|
||||
<v-icon size="14" title="Taken">date_range</v-icon>
|
||||
{{ photo.getDateString() }}
|
||||
<template v-if="!photo.Description">
|
||||
<br/>
|
||||
<div v-if="photo.Type === 'video'" title="Video"
|
||||
@click.exact="openPhoto(index, true)">
|
||||
<v-icon size="14">movie</v-icon>
|
||||
{{ photo.getVideoInfo() }}
|
||||
</div>
|
||||
<div v-else title="Camera">
|
||||
<v-icon size="14">photo_camera</v-icon>
|
||||
{{ photo.getPhotoInfo() }}
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="showLocation && photo.Country !== 'zz'">
|
||||
<div title="Location">
|
||||
<v-icon size="14">location_on</v-icon>
|
||||
{{ photo.locationInfo() }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</v-card-title>
|
||||
</v-card>
|
||||
</v-hover>
|
||||
<div class="caption">
|
||||
<div>
|
||||
<v-icon size="14" :title="labels.taken">date_range</v-icon>
|
||||
{{ photo.getDateString() }}
|
||||
</div>
|
||||
<template v-if="!photo.Description">
|
||||
<br/>
|
||||
<div v-if="photo.Type === 'video'" :title="labels.video">
|
||||
<v-icon size="14">movie</v-icon>
|
||||
{{ photo.getVideoInfo() }}
|
||||
</div>
|
||||
<div v-else :title="labels.camera">
|
||||
<v-icon size="14">photo_camera</v-icon>
|
||||
{{ photo.getPhotoInfo() }}
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="filter.order === 'name' && $config.feature('download')">
|
||||
<br/>
|
||||
<div :title="labels.name">
|
||||
<v-icon size="14">insert_drive_file</v-icon>
|
||||
{{ photo.baseName() }}
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="showLocation && photo.Country !== 'zz'">
|
||||
<br/>
|
||||
<div :title="labels.location">
|
||||
<v-icon size="14">location_on</v-icon>
|
||||
{{ photo.locationInfo() }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</v-card-title>
|
||||
</v-card>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
</v-container>
|
||||
|
@ -155,6 +133,7 @@ export default {
|
|||
openLocation: Function,
|
||||
album: Object,
|
||||
filter: Object,
|
||||
context: String,
|
||||
selectMode: Boolean,
|
||||
},
|
||||
data() {
|
||||
|
@ -162,6 +141,16 @@ export default {
|
|||
showLocation: this.$config.settings().features.places,
|
||||
hidePrivate: this.$config.settings().features.private,
|
||||
debug: this.$config.get('debug'),
|
||||
labels: {
|
||||
location: this.$gettext("Location"),
|
||||
description: this.$gettext("Description"),
|
||||
taken: this.$gettext("Taken"),
|
||||
approve: this.$gettext("Approve"),
|
||||
archive: this.$gettext("Archive"),
|
||||
camera: this.$gettext("Camera"),
|
||||
video: this.$gettext("Video"),
|
||||
name: this.$gettext("Name"),
|
||||
},
|
||||
mouseDown: {
|
||||
index: -1,
|
||||
timeStamp: -1,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div>
|
||||
<div v-if="photos.length === 0" class="pa-2">
|
||||
<v-card class="p-photos-empty secondary-light lighten-1 ma-1" flat>
|
||||
<v-card class="no-results secondary-light lighten-1 ma-1" flat>
|
||||
<v-card-title primary-title>
|
||||
<div>
|
||||
<h3 v-if="filter.order === 'edited'" class="title ma-0 pa-0">
|
||||
|
@ -22,51 +22,56 @@
|
|||
:headers="listColumns"
|
||||
:items="photos"
|
||||
hide-actions
|
||||
class="elevation-0 p-photos p-photo-list p-results"
|
||||
class="search-results photo-results list-view"
|
||||
disable-initial-sort
|
||||
item-key="ID"
|
||||
:no-data-text="notFoundMessage"
|
||||
>
|
||||
<template slot="items" slot-scope="props">
|
||||
<td style="user-select: none;" :data-uid="props.item.UID">
|
||||
<td style="user-select: none;" :data-uid="props.item.UID" class="result" :class="props.item.classes()">
|
||||
<v-img class="accent lighten-2 clickable" aspect-ratio="1"
|
||||
:src="props.item.thumbnailUrl('tile_50')"
|
||||
@mousedown="onMouseDown($event, props.index)"
|
||||
@contextmenu="onContextMenu($event, props.index)"
|
||||
@click.stop.prevent="onClick($event, props.index)"
|
||||
>
|
||||
<v-btn v-if="props.item.Selected" :ripple="false"
|
||||
flat icon large absolute class="p-photo-select">
|
||||
<v-icon color="white" class="t-select t-on">check_circle</v-icon>
|
||||
<v-btn v-if="selectMode" :ripple="false"
|
||||
flat icon large absolute
|
||||
class="input-select">
|
||||
<v-icon color="white" class="select-on">check_circle</v-icon>
|
||||
<v-icon color="accent lighten-3" class="select-off">radio_button_off</v-icon>
|
||||
</v-btn>
|
||||
<v-btn v-else-if="!selectMode && (props.item.Type === 'video' || props.item.Type === 'live')"
|
||||
<v-btn v-else-if="props.item.Type === 'video' || props.item.Type === 'live'"
|
||||
:ripple="false"
|
||||
flat icon large absolute class="p-photo-play opacity-75"
|
||||
flat icon large absolute class="input-play opacity-75"
|
||||
@click.stop.prevent="openPhoto(props.index, true)">
|
||||
<v-icon color="white" class="action-play">play_arrow</v-icon>
|
||||
</v-btn>
|
||||
</v-img>
|
||||
</td>
|
||||
|
||||
<td class="p-photo-desc clickable" :data-uid="props.item.UID"
|
||||
style="user-select: none;" @click.stop.prevent="openPhoto(props.index, false)">
|
||||
<td class="p-photo-desc clickable" :data-uid="props.item.UID" style="user-select: none;"
|
||||
@click.stop.prevent="openPhoto(props.index, false)">
|
||||
{{ props.item.Title }}
|
||||
</td>
|
||||
<td class="p-photo-desc hidden-xs-only" :title="props.item.getDateString()"
|
||||
style="user-select: none;" @click.stop.prevent="openPhoto(props.index, false)">
|
||||
{{ props.item.shortDateString() }}
|
||||
<td class="p-photo-desc hidden-xs-only" :title="props.item.getDateString()">
|
||||
<button style="user-select: none;" @click.stop.prevent="editPhoto(props.index)">
|
||||
{{ props.item.shortDateString() }}
|
||||
</button>
|
||||
</td>
|
||||
<td class="p-photo-desc hidden-sm-and-down" style="user-select: none;">
|
||||
{{ props.item.CameraMake }} {{ props.item.CameraModel }}
|
||||
<button @click.stop.prevent="editPhoto(props.index)">
|
||||
{{ props.item.CameraMake }} {{ props.item.CameraModel }}
|
||||
</button>
|
||||
</td>
|
||||
<td class="p-photo-desc hidden-xs-only">
|
||||
<button v-if="filter.order === 'name'"
|
||||
title="Name" @click.exact="downloadFile(props.index)">
|
||||
:title="$gettext('Name')" @click.exact="downloadFile(props.index)">
|
||||
{{ props.item.FileName }}
|
||||
</button>
|
||||
<button v-else-if="props.item.Country !== 'zz' && showLocation"
|
||||
style="user-select: none;"
|
||||
@click.stop.prevent="openPhoto(props.index, false)">
|
||||
@click.stop.prevent="openLocation(props.index)">
|
||||
{{ props.item.locationInfo() }}
|
||||
</button>
|
||||
<span v-else>
|
||||
|
@ -87,6 +92,7 @@ export default {
|
|||
openLocation: Function,
|
||||
album: Object,
|
||||
filter: Object,
|
||||
context: String,
|
||||
selectMode: Boolean,
|
||||
},
|
||||
data() {
|
||||
|
@ -97,6 +103,7 @@ export default {
|
|||
let showName = this.filter.order === 'name';
|
||||
|
||||
return {
|
||||
config: this.$config.values,
|
||||
notFoundMessage: m,
|
||||
'selected': [],
|
||||
'listColumns': [
|
||||
|
@ -112,33 +119,14 @@ export default {
|
|||
},
|
||||
],
|
||||
showName: showName,
|
||||
showLocation: this.$config.settings().features.places,
|
||||
hidePrivate: this.$config.settings().features.private,
|
||||
showLocation: this.$config.values.settings.features.places,
|
||||
hidePrivate: this.$config.values.settings.features.private,
|
||||
mouseDown: {
|
||||
index: -1,
|
||||
timeStamp: -1,
|
||||
},
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
photos: function (photos) {
|
||||
this.selected.splice(0);
|
||||
|
||||
for (let i = 0; i < photos.length; i++) {
|
||||
if (this.$clipboard.has(photos[i])) {
|
||||
this.selected.push(photos[i]);
|
||||
}
|
||||
}
|
||||
},
|
||||
selection: function () {
|
||||
this.refreshSelection();
|
||||
},
|
||||
},
|
||||
mounted: function () {
|
||||
this.$nextTick(function () {
|
||||
this.refreshSelection();
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
downloadFile(index) {
|
||||
const photo = this.photos[index];
|
||||
|
@ -151,7 +139,7 @@ export default {
|
|||
if (ev.shiftKey) {
|
||||
this.selectRange(index);
|
||||
} else {
|
||||
this.$clipboard.toggle(this.photos[index]);
|
||||
this.toggle(this.photos[index]);
|
||||
}
|
||||
},
|
||||
onMouseDown(ev, index) {
|
||||
|
@ -165,12 +153,12 @@ export default {
|
|||
if (longClick || ev.shiftKey) {
|
||||
this.selectRange(index);
|
||||
} else {
|
||||
this.$clipboard.toggle(this.photos[index]);
|
||||
this.toggle(this.photos[index]);
|
||||
}
|
||||
} else if (this.photos[index]) {
|
||||
let photo = this.photos[index];
|
||||
|
||||
if (photo.Type === 'video' || photo.Type === 'live') {
|
||||
if (photo.Type === 'video' && photo.isPlayable()) {
|
||||
this.openPhoto(index, true);
|
||||
} else {
|
||||
this.openPhoto(index, false);
|
||||
|
@ -184,18 +172,12 @@ export default {
|
|||
this.selectRange(index);
|
||||
}
|
||||
},
|
||||
toggle(photo) {
|
||||
this.$clipboard.toggle(photo);
|
||||
},
|
||||
selectRange(index) {
|
||||
this.$clipboard.addRange(index, this.photos);
|
||||
},
|
||||
refreshSelection() {
|
||||
this.selected.splice(0);
|
||||
|
||||
for (let i = 0; i < this.photos.length; i++) {
|
||||
if (this.$clipboard.has(this.photos[i])) {
|
||||
this.selected.push(this.photos[i]);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<v-container grid-list-xs fluid class="pa-2 p-photos p-photo-mosaic">
|
||||
<v-card v-if="photos.length === 0" class="p-photos-empty secondary-light lighten-1 ma-1" flat>
|
||||
<v-card v-if="photos.length === 0" class="no-results secondary-light lighten-1 ma-1" flat>
|
||||
<v-card-title primary-title>
|
||||
<div>
|
||||
<h3 v-if="filter.order === 'edited'" class="title ma-0 pa-0">
|
||||
|
@ -15,94 +15,63 @@
|
|||
</div>
|
||||
</v-card-title>
|
||||
</v-card>
|
||||
<v-layout row wrap class="p-results">
|
||||
<v-layout row wrap class="search-results photo-results mosaic-view">
|
||||
<v-flex
|
||||
v-for="(photo, index) in photos"
|
||||
:key="index"
|
||||
:data-uid="photo.UID"
|
||||
:class="{ selected: photo.Selected, portrait: photo.Portrait }"
|
||||
class="p-photo"
|
||||
xs4 sm3 md2 lg1 d-flex
|
||||
>
|
||||
<v-hover>
|
||||
<v-card slot-scope="{ hover }" tile
|
||||
:class="photo.Selected ? 'selected elevation-10 ma-0 select-transition' : 'elevation-0 ma-1 select-transition'"
|
||||
:title="photo.Title"
|
||||
@contextmenu="onContextMenu($event, index)">
|
||||
<v-img :src="photo.thumbnailUrl('tile_224')"
|
||||
aspect-ratio="1"
|
||||
class="accent lighten-2 clickable"
|
||||
@mousedown="onMouseDown($event, index)"
|
||||
@click.stop.prevent="onClick($event, index)"
|
||||
>
|
||||
<v-layout
|
||||
v-if="photo.Type === 'live'"
|
||||
v-show="hover"
|
||||
fill-height
|
||||
align-center
|
||||
justify-center
|
||||
ma-0
|
||||
class="live-player"
|
||||
style="overflow: hidden;"
|
||||
>
|
||||
<video :key="photo.videoUrl()" width="224" height="224" autoplay loop muted playsinline>
|
||||
<source :src="photo.videoUrl()" type="video/mp4">
|
||||
</video>
|
||||
</v-layout>
|
||||
<v-card tile
|
||||
:data-id="photo.ID"
|
||||
:data-uid="photo.UID"
|
||||
class="result"
|
||||
:class="photo.classes()"
|
||||
@contextmenu="onContextMenu($event, index)">
|
||||
<v-img :key="photo.Hash"
|
||||
:src="photo.thumbnailUrl('tile_224')"
|
||||
:alt="photo.Title"
|
||||
:title="photo.Title"
|
||||
:transition="false"
|
||||
aspect-ratio="1"
|
||||
class="accent lighten-3 clickable"
|
||||
@mousedown="onMouseDown($event, index)"
|
||||
@click.stop.prevent="onClick($event, index)"
|
||||
>
|
||||
<v-layout v-if="photo.Type === 'video' || photo.Type === 'live'" class="live-player">
|
||||
<video :key="photo.ID" width="224" height="224" autoplay loop muted playsinline>
|
||||
<source :src="photo.videoUrl()" type="video/mp4">
|
||||
</video>
|
||||
</v-layout>
|
||||
|
||||
<v-btn v-if="hidePrivate && photo.Private" :ripple="false"
|
||||
icon flat small absolute
|
||||
class="p-photo-private opacity-75">
|
||||
<v-icon color="white">lock</v-icon>
|
||||
</v-btn>
|
||||
<v-btn :ripple="false" :depressed="false" class="input-open"
|
||||
icon flat small absolute
|
||||
@click.stop.prevent="openPhoto(index, true)">
|
||||
<v-icon color="white" class="default-hidden action-raw" :title="$gettext('RAW')">photo_camera</v-icon>
|
||||
<v-icon color="white" class="default-hidden action-live" :title="$gettext('Live')">adjust</v-icon>
|
||||
<v-icon color="white" class="default-hidden action-stack" :title="$gettext('Stack')">burst_mode</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn v-if="hover || photo.Selected" :ripple="false"
|
||||
icon flat small absolute
|
||||
:class="photo.Selected ? 'p-photo-select' : 'p-photo-select opacity-50'"
|
||||
@click.stop.prevent="onSelect($event, index)">
|
||||
<v-icon v-if="photo.Selected" color="white"
|
||||
class="t-select t-on">check_circle
|
||||
</v-icon>
|
||||
<v-icon v-else color="accent lighten-3" class="t-select t-off">radio_button_off</v-icon>
|
||||
</v-btn>
|
||||
<v-btn :ripple="false" :depressed="false" class="input-view"
|
||||
icon flat small absolute :title="$gettext('View')"
|
||||
@click.stop.prevent="openPhoto(index, false)">
|
||||
<v-icon color="white" class="action-fullscreen">zoom_in</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<!-- v-btn icon flat small absolute :ripple="false"
|
||||
:class="photo.Favorite ? 'p-photo-like opacity-75' : 'p-photo-like opacity-50'"
|
||||
@click.stop.prevent="photo.toggleLike()">
|
||||
<v-icon v-if="photo.Favorite" color="white" class="t-like t-on" :data-uid="photo.UID">favorite</v-icon>
|
||||
<v-icon v-else color="accent lighten-3" class="t-like t-off" :data-uid="photo.UID">favorite_border</v-icon>
|
||||
</v-btn -->
|
||||
<v-btn :ripple="false" :depressed="false" color="white" class="input-play"
|
||||
outline fab absolute :title="$gettext('Play')"
|
||||
@click.stop.prevent="openPhoto(index, true)">
|
||||
<v-icon color="white" class="action-play">play_arrow</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<template v-if="photo.isPlayable()">
|
||||
<v-btn v-if="photo.Type === 'live'" color="white"
|
||||
icon flat small absolute class="p-photo-live opacity-75" :depressed="false" :ripple="false"
|
||||
title="Live Photo" @click.stop.prevent="openPhoto(index, true)">
|
||||
<v-icon color="white" class="action-play">adjust</v-icon>
|
||||
</v-btn>
|
||||
<v-btn v-else color="white"
|
||||
outline fab absolute class="p-photo-play opacity-75" :depressed="false" :ripple="false"
|
||||
title="Play" @click.stop.prevent="openPhoto(index, true)">
|
||||
<v-icon color="white" class="action-play">play_arrow</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-btn v-else-if="photo.Type === 'image' && photo.Files.length > 1" :ripple="false"
|
||||
icon flat small absolute class="p-photo-merged opacity-75"
|
||||
@click.stop.prevent="openPhoto(index, true)">
|
||||
<v-icon color="white" class="action-burst">burst_mode</v-icon>
|
||||
</v-btn>
|
||||
<v-btn v-else-if="photo.Type === 'image' && selectMode && hover" :ripple="false"
|
||||
icon flat small absolute class="p-photo-merged opacity-75"
|
||||
@click.stop.prevent="openPhoto(index, false)">
|
||||
<v-icon color="white" class="action-open">zoom_in</v-icon>
|
||||
</v-btn>
|
||||
<v-btn v-else-if="photo.Type === 'raw'" :ripple="false"
|
||||
icon flat small absolute class="p-photo-merged opacity-75"
|
||||
title="RAW" @click.stop.prevent="openPhoto(index, true)">
|
||||
<v-icon color="white" class="action-burst">photo_camera</v-icon>
|
||||
</v-btn>
|
||||
</v-img>
|
||||
</v-card>
|
||||
</v-hover>
|
||||
<v-btn :ripple="false"
|
||||
icon flat small absolute
|
||||
class="input-select"
|
||||
@click.stop.prevent="onSelect($event, index)">
|
||||
<v-icon color="white" class="select-on">check_circle</v-icon>
|
||||
<v-icon color="accent lighten-3" class="select-off">radio_button_off</v-icon>
|
||||
</v-btn>
|
||||
</v-img>
|
||||
</v-card>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
</v-container>
|
||||
|
@ -116,6 +85,7 @@ export default {
|
|||
editPhoto: Function,
|
||||
album: Object,
|
||||
filter: Object,
|
||||
context: String,
|
||||
selectMode: Boolean,
|
||||
},
|
||||
data() {
|
||||
|
@ -132,13 +102,16 @@ export default {
|
|||
if (ev.shiftKey) {
|
||||
this.selectRange(index);
|
||||
} else {
|
||||
this.$clipboard.toggle(this.photos[index]);
|
||||
this.toggle(this.photos[index]);
|
||||
}
|
||||
},
|
||||
onMouseDown(ev, index) {
|
||||
this.mouseDown.index = index;
|
||||
this.mouseDown.timeStamp = ev.timeStamp;
|
||||
},
|
||||
toggle(photo) {
|
||||
this.$clipboard.toggle(photo);
|
||||
},
|
||||
onClick(ev, index) {
|
||||
let longClick = (this.mouseDown.index === index && ev.timeStamp - this.mouseDown.timeStamp > 400);
|
||||
|
||||
|
@ -146,7 +119,7 @@ export default {
|
|||
if (longClick || ev.shiftKey) {
|
||||
this.selectRange(index);
|
||||
} else {
|
||||
this.$clipboard.toggle(this.photos[index]);
|
||||
this.toggle(this.photos[index]);
|
||||
}
|
||||
} else {
|
||||
this.openPhoto(index, false);
|
||||
|
|
|
@ -118,7 +118,7 @@ export default {
|
|||
uid: uid,
|
||||
results: [],
|
||||
scrollDisabled: true,
|
||||
pageSize: 60,
|
||||
pageSize: Photo.pageSize(),
|
||||
offset: 0,
|
||||
page: 0,
|
||||
selection: this.$clipboard.selection,
|
||||
|
@ -498,16 +498,22 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
updateResult(results, values) {
|
||||
const model = results.find((m) => m.UID === values.UID);
|
||||
|
||||
if (model) {
|
||||
for (let key in values) {
|
||||
if (values.hasOwnProperty(key) && values[key] != null && typeof values[key] !== "object") {
|
||||
model[key] = values[key];
|
||||
updateResults(entity) {
|
||||
this.results.filter((m) => m.UID === entity.UID).forEach((m) => {
|
||||
for (let key in entity) {
|
||||
if (key !== "UID" && entity.hasOwnProperty(key) && entity[key] != null && typeof entity[key] !== "object") {
|
||||
m[key] = entity[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.viewer.results.filter((m) => m.UID === entity.UID).forEach((m) => {
|
||||
for (let key in entity) {
|
||||
if (key !== "UID" && entity.hasOwnProperty(key) && entity[key] != null && typeof entity[key] !== "object") {
|
||||
m[key] = entity[key];
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
removeResult(results, uid) {
|
||||
const index = results.findIndex((m) => m.UID === uid);
|
||||
|
@ -528,9 +534,7 @@ export default {
|
|||
switch (type) {
|
||||
case 'updated':
|
||||
for (let i = 0; i < data.entities.length; i++) {
|
||||
const values = data.entities[i];
|
||||
this.updateResult(this.results, values);
|
||||
this.updateResult(this.viewer.results, values);
|
||||
this.updateResults(data.entities[i]);
|
||||
}
|
||||
break;
|
||||
case 'restored':
|
||||
|
@ -556,6 +560,9 @@ export default {
|
|||
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: Needed?
|
||||
this.$forceUpdate();
|
||||
},
|
||||
download() {
|
||||
this.onDownload(`/api/v1/albums/${this.uid}/dl?t=${this.$config.downloadToken()}`);
|
||||
|
|
|
@ -313,6 +313,7 @@ describe("model/photo", () => {
|
|||
assert.equal(photo.Favorite, false);
|
||||
});
|
||||
|
||||
/* TODO
|
||||
it("should toggle like", () => {
|
||||
const values = {ID: 5, Title: "Crazy Cat", CountryName: "Africa", Favorite: true};
|
||||
const photo = new Photo(values);
|
||||
|
@ -322,6 +323,7 @@ describe("model/photo", () => {
|
|||
photo.toggleLike();
|
||||
assert.equal(photo.Favorite, true);
|
||||
});
|
||||
*/
|
||||
|
||||
it("should get photo defaults", () => {
|
||||
const values = {ID: 5, UID: "ABC123"};
|
||||
|
|
Loading…
Reference in a new issue