Frontend: Fix lazy loading on large screens #507 #510

This commit is contained in:
Michael Mayer 2020-10-04 12:16:09 +02:00
parent 9826e57149
commit f431caeb4c
10 changed files with 1526 additions and 1484 deletions

View file

@ -18,45 +18,45 @@
</template>
<script>
import "./css/app.css";
import Event from "pubsub-js";
import "./css/app.css";
import Event from "pubsub-js";
export default {
name: 'photoprism',
data() {
return {
touchStart: 0,
};
},
computed: {},
methods: {
onTouchStart(e) {
this.touchStart = e.touches[0].pageY;
},
onTouchMove(e) {
if(!this.touchStart) return;
if(document.querySelector('.v-dialog--active') !== null) return;
const y = e.touches[0].pageY;
const h = window.document.documentElement.scrollHeight - window.document.documentElement.clientHeight;
if(window.scrollY >= h - 200 && y < this.touchStart) {
Event.publish("touchmove.bottom");
this.touchStart = 0;
} else if (window.scrollY === 0 && y > this.touchStart + 200) {
Event.publish("touchmove.top");
this.touchStart = 0;
}
},
},
created() {
window.addEventListener('touchstart', (e) => this.onTouchStart(e), {passive: true});
window.addEventListener('touchmove', (e) => this.onTouchMove(e), {passive: true});
this.$config.setVuetify(this.$vuetify);
},
destroyed() {
window.removeEventListener('touchstart', (e) => this.onTouchStart(e), false);
window.removeEventListener('touchmove', (e) => this.onTouchMove(e), false);
},
export default {
name: 'photoprism',
data() {
return {
touchStart: 0,
};
},
computed: {},
methods: {
onTouchStart(e) {
this.touchStart = e.touches[0].pageY;
},
onTouchMove(e) {
if (!this.touchStart) return;
if (document.querySelector('.v-dialog--active') !== null) return;
const y = e.touches[0].pageY;
const h = window.document.documentElement.scrollHeight - window.document.documentElement.clientHeight;
if (window.scrollY >= h - 200 && y < this.touchStart) {
Event.publish("touchmove.bottom");
this.touchStart = 0;
} else if (window.scrollY === 0 && y > this.touchStart + 200) {
Event.publish("touchmove.top");
this.touchStart = 0;
}
},
},
created() {
window.addEventListener('touchstart', (e) => this.onTouchStart(e), {passive: true});
window.addEventListener('touchmove', (e) => this.onTouchMove(e), {passive: true});
this.$config.setVuetify(this.$vuetify);
},
destroyed() {
window.removeEventListener('touchstart', (e) => this.onTouchStart(e), false);
window.removeEventListener('touchmove', (e) => this.onTouchMove(e), false);
},
};
</script>

View file

@ -21,12 +21,12 @@
</v-card>
<v-layout row wrap class="p-results">
<v-flex
v-for="(photo, index) in photos"
:key="index"
:data-uid="photo.UID"
class="p-photo"
xs12 sm6 md4 lg3 xl2 d-flex
v-bind:class="{ 'is-selected': $clipboard.has(photo) }"
v-for="(photo, index) in photos"
:key="index"
:data-uid="photo.UID"
class="p-photo"
xs12 sm6 md4 lg3 xl2 d-flex
v-bind:class="{ 'is-selected': $clipboard.has(photo) }"
>
<v-hover>
<v-card tile slot-scope="{ hover }"
@ -41,25 +41,25 @@
@click.stop.prevent="onClick($event, index)"
>
<v-layout
slot="placeholder"
fill-height
align-center
justify-center
ma-0
slot="placeholder"
fill-height
align-center
justify-center
ma-0
>
<v-progress-circular indeterminate color="accent lighten-5"></v-progress-circular>
</v-layout>
<v-layout
fill-height
align-center
justify-center
ma-0
class="p-photo-live"
style="overflow: hidden;"
v-if="photo.Type === 'live'"
v-show="hover"
fill-height
align-center
justify-center
ma-0
class="p-photo-live"
style="overflow: hidden;"
v-if="photo.Type === 'live'"
v-show="hover"
>
<video width="500" height="500" autoplay loop muted playsinline>
<source :src="photo.videoUrl()" type="video/mp4">
@ -196,70 +196,70 @@
</v-container>
</template>
<script>
export default {
name: 'p-photo-cards',
props: {
photos: Array,
selection: Array,
openPhoto: Function,
editPhoto: Function,
openLocation: Function,
album: Object,
filter: Object,
},
data() {
return {
showLocation: this.$config.settings().features.places,
hidePrivate: this.$config.settings().features.private,
debug: this.$config.get('debug'),
mouseDown: {
index: -1,
timeStamp: -1,
},
};
},
methods: {
downloadFile(index) {
const photo = this.photos[index];
const link = document.createElement('a')
link.href = `/api/v1/dl/${photo.Hash}?t=${this.$config.downloadToken()}`;
link.download = photo.FileName;
link.click();
},
onSelect(ev, index) {
if (ev.shiftKey) {
this.selectRange(index);
} else {
this.$clipboard.toggle(this.photos[index]);
}
},
onMouseDown(ev, index) {
this.mouseDown.index = index;
this.mouseDown.timeStamp = ev.timeStamp;
},
onClick(ev, index) {
let longClick = (this.mouseDown.index === index && ev.timeStamp - this.mouseDown.timeStamp > 400);
if (longClick || this.selection.length > 0) {
if (longClick || ev.shiftKey) {
this.selectRange(index);
} else {
this.$clipboard.toggle(this.photos[index]);
}
} else {
this.openPhoto(index, false);
}
},
onContextMenu(ev, index) {
if (this.$isMobile) {
ev.preventDefault();
ev.stopPropagation();
this.selectRange(index);
}
},
selectRange(index) {
this.$clipboard.addRange(index, this.photos);
},
}
export default {
name: 'p-photo-cards',
props: {
photos: Array,
selection: Array,
openPhoto: Function,
editPhoto: Function,
openLocation: Function,
album: Object,
filter: Object,
},
data() {
return {
showLocation: this.$config.settings().features.places,
hidePrivate: this.$config.settings().features.private,
debug: this.$config.get('debug'),
mouseDown: {
index: -1,
timeStamp: -1,
},
};
},
methods: {
downloadFile(index) {
const photo = this.photos[index];
const link = document.createElement('a')
link.href = `/api/v1/dl/${photo.Hash}?t=${this.$config.downloadToken()}`;
link.download = photo.FileName;
link.click();
},
onSelect(ev, index) {
if (ev.shiftKey) {
this.selectRange(index);
} else {
this.$clipboard.toggle(this.photos[index]);
}
},
onMouseDown(ev, index) {
this.mouseDown.index = index;
this.mouseDown.timeStamp = ev.timeStamp;
},
onClick(ev, index) {
let longClick = (this.mouseDown.index === index && ev.timeStamp - this.mouseDown.timeStamp > 400);
if (longClick || this.selection.length > 0) {
if (longClick || ev.shiftKey) {
this.selectRange(index);
} else {
this.$clipboard.toggle(this.photos[index]);
}
} else {
this.openPhoto(index, false);
}
},
onContextMenu(ev, index) {
if (this.$isMobile) {
ev.preventDefault();
ev.stopPropagation();
this.selectRange(index);
}
},
selectRange(index) {
this.$clipboard.addRange(index, this.photos);
},
}
};
</script>

View file

@ -2,115 +2,115 @@
<div>
<v-container fluid class="pa-0" v-if="selection.length > 0">
<v-speed-dial
fixed bottom right
direction="top"
v-model="expanded"
transition="slide-y-reverse-transition"
class="p-clipboard p-photo-clipboard"
id="t-clipboard"
fixed bottom right
direction="top"
v-model="expanded"
transition="slide-y-reverse-transition"
class="p-clipboard p-photo-clipboard"
id="t-clipboard"
>
<v-btn
fab dark
slot="activator"
color="accent darken-2"
class="action-menu"
fab dark
slot="activator"
color="accent darken-2"
class="action-menu"
>
<v-icon v-if="selection.length === 0">menu</v-icon>
<span v-else class="count-clipboard">{{ selection.length }}</span>
</v-btn>
<v-btn
fab dark small
:title="$gettext('Share')"
color="share"
@click.stop="dialog.share = true"
:disabled="selection.length === 0"
v-if="context !== 'archive' && $config.feature('share')"
class="action-share"
fab dark small
:title="$gettext('Share')"
color="share"
@click.stop="dialog.share = true"
:disabled="selection.length === 0"
v-if="context !== 'archive' && $config.feature('share')"
class="action-share"
>
<v-icon>cloud</v-icon>
</v-btn>
<v-btn
fab dark small
:title="$gettext('Edit')"
color="edit"
:disabled="selection.length === 0"
@click.stop="edit"
v-if="context !== 'archive' && $config.feature('edit')"
class="action-edit"
fab dark small
:title="$gettext('Edit')"
color="edit"
:disabled="selection.length === 0"
@click.stop="edit"
v-if="context !== 'archive' && $config.feature('edit')"
class="action-edit"
>
<v-icon>edit</v-icon>
</v-btn>
<v-btn
fab dark small
:title="$gettext('Change private flag')"
color="private"
:disabled="selection.length === 0"
@click.stop="batchPrivate"
v-if="context !== 'archive' && config.settings.features.private"
class="action-private"
fab dark small
:title="$gettext('Change private flag')"
color="private"
:disabled="selection.length === 0"
@click.stop="batchPrivate"
v-if="context !== 'archive' && config.settings.features.private"
class="action-private"
>
<v-icon>lock</v-icon>
</v-btn>
<v-btn
fab dark small
:title="$gettext('Download')"
color="download"
@click.stop="download()"
v-if="context !== 'archive' && $config.feature('download')"
class="action-download"
fab dark small
:title="$gettext('Download')"
color="download"
@click.stop="download()"
v-if="context !== 'archive' && $config.feature('download')"
class="action-download"
>
<v-icon>get_app</v-icon>
</v-btn>
<v-btn
fab dark small
:title="$gettext('Add to album')"
color="album"
:disabled="selection.length === 0"
@click.stop="dialog.album = true"
v-if="context !== 'archive'"
class="action-album"
fab dark small
:title="$gettext('Add to album')"
color="album"
:disabled="selection.length === 0"
@click.stop="dialog.album = true"
v-if="context !== 'archive'"
class="action-album"
>
<v-icon>folder</v-icon>
</v-btn>
<v-btn
fab dark small
color="remove"
:title="$gettext('Archive')"
@click.stop="dialog.archive = true"
:disabled="selection.length === 0"
v-if="!album && context !== 'archive' && $config.feature('archive')"
class="action-archive"
fab dark small
color="remove"
:title="$gettext('Archive')"
@click.stop="dialog.archive = true"
:disabled="selection.length === 0"
v-if="!album && context !== 'archive' && $config.feature('archive')"
class="action-archive"
>
<v-icon>archive</v-icon>
</v-btn>
<v-btn
fab dark small
color="restore"
:title="$gettext('Restore')"
@click.stop="batchRestorePhotos"
:disabled="selection.length === 0"
v-if="!album && context === 'archive'"
class="action-restore"
fab dark small
color="restore"
:title="$gettext('Restore')"
@click.stop="batchRestorePhotos"
:disabled="selection.length === 0"
v-if="!album && context === 'archive'"
class="action-restore"
>
<v-icon>unarchive</v-icon>
</v-btn>
<v-btn
fab dark small
:title="$gettext('Remove')"
color="remove"
@click.stop="removeFromAlbum"
:disabled="selection.length === 0"
v-if="album"
class="action-delete"
fab dark small
:title="$gettext('Remove')"
color="remove"
@click.stop="removeFromAlbum"
:disabled="selection.length === 0"
v-if="album"
class="action-delete"
>
<v-icon>remove</v-icon>
</v-btn>
<v-btn
fab dark small
color="accent"
@click.stop="clearClipboard()"
class="action-clear"
fab dark small
color="accent"
@click.stop="clearClipboard()"
class="action-clear"
>
<v-icon>clear</v-icon>
</v-btn>
@ -125,105 +125,105 @@
</div>
</template>
<script>
import Api from "common/api";
import Notify from "common/notify";
import Event from "pubsub-js";
import Api from "common/api";
import Notify from "common/notify";
import Event from "pubsub-js";
export default {
name: 'p-photo-clipboard',
props: {
selection: Array,
refresh: Function,
album: Object,
context: String,
},
data() {
return {
config: this.$config.values,
expanded: false,
dialog: {
archive: false,
album: false,
share: false,
},
};
},
methods: {
clearClipboard() {
this.$clipboard.clear();
this.expanded = false;
},
batchArchivePhotos() {
this.dialog.archive = false;
Api.post("batch/photos/archive", {"photos": this.selection}).then(() => this.onArchived());
},
onArchived() {
Notify.success(this.$gettext("Selection archived"));
this.clearClipboard();
},
batchPrivate() {
Api.post("batch/photos/private", {"photos": this.selection}).then(() => this.onPrivateSaved());
},
onPrivateSaved() {
this.clearClipboard();
},
batchRestorePhotos() {
Api.post("batch/photos/restore", {"photos": this.selection}).then(() => this.onRestored());
},
onRestored() {
Notify.success(this.$gettext("Selection restored"));
this.clearClipboard();
},
addToAlbum(ppid) {
this.dialog.album = false;
Api.post(`albums/${ppid}/photos`, {"photos": this.selection}).then(() => this.onAdded());
},
onAdded() {
this.clearClipboard();
},
removeFromAlbum() {
if (!this.album) {
this.$notify.error(this.$gettext("remove failed: unknown album"));
return
}
const uid = this.album.UID;
this.dialog.album = false;
Api.delete(`albums/${uid}/photos`, {"data": {"photos": this.selection}}).then(() => this.onRemoved());
},
onRemoved() {
this.clearClipboard();
},
download() {
if (this.selection.length === 1) {
this.onDownload(`/api/v1/photos/${this.selection[0]}/dl?t=${this.$config.downloadToken()}`);
} else {
Api.post("zip", {"photos": this.selection}).then(r => {
this.onDownload(`/api/v1/zip/${r.data.filename}?t=${this.$config.downloadToken()}`);
});
}
this.expanded = false;
},
onDownload(path) {
Notify.success(this.$gettext("Downloading…"));
const link = document.createElement('a')
link.href = path;
link.download = "photos.zip";
link.click();
},
edit() {
// Open Edit Dialog
Event.publish("dialog.edit", {selection: this.selection, album: this.album, index: 0});
},
onShared() {
this.dialog.share = false;
this.clearClipboard();
},
}
export default {
name: 'p-photo-clipboard',
props: {
selection: Array,
refresh: Function,
album: Object,
context: String,
},
data() {
return {
config: this.$config.values,
expanded: false,
dialog: {
archive: false,
album: false,
share: false,
},
};
},
methods: {
clearClipboard() {
this.$clipboard.clear();
this.expanded = false;
},
batchArchivePhotos() {
this.dialog.archive = false;
Api.post("batch/photos/archive", {"photos": this.selection}).then(() => this.onArchived());
},
onArchived() {
Notify.success(this.$gettext("Selection archived"));
this.clearClipboard();
},
batchPrivate() {
Api.post("batch/photos/private", {"photos": this.selection}).then(() => this.onPrivateSaved());
},
onPrivateSaved() {
this.clearClipboard();
},
batchRestorePhotos() {
Api.post("batch/photos/restore", {"photos": this.selection}).then(() => this.onRestored());
},
onRestored() {
Notify.success(this.$gettext("Selection restored"));
this.clearClipboard();
},
addToAlbum(ppid) {
this.dialog.album = false;
Api.post(`albums/${ppid}/photos`, {"photos": this.selection}).then(() => this.onAdded());
},
onAdded() {
this.clearClipboard();
},
removeFromAlbum() {
if (!this.album) {
this.$notify.error(this.$gettext("remove failed: unknown album"));
return
}
const uid = this.album.UID;
this.dialog.album = false;
Api.delete(`albums/${uid}/photos`, {"data": {"photos": this.selection}}).then(() => this.onRemoved());
},
onRemoved() {
this.clearClipboard();
},
download() {
if (this.selection.length === 1) {
this.onDownload(`/api/v1/photos/${this.selection[0]}/dl?t=${this.$config.downloadToken()}`);
} else {
Api.post("zip", {"photos": this.selection}).then(r => {
this.onDownload(`/api/v1/zip/${r.data.filename}?t=${this.$config.downloadToken()}`);
});
}
this.expanded = false;
},
onDownload(path) {
Notify.success(this.$gettext("Downloading…"));
const link = document.createElement('a')
link.href = path;
link.download = "photos.zip";
link.click();
},
edit() {
// Open Edit Dialog
Event.publish("dialog.edit", {selection: this.selection, album: this.album, index: 0});
},
onShared() {
this.dialog.share = false;
this.clearClipboard();
},
}
};
</script>

View file

@ -38,11 +38,11 @@
@click.stop.prevent="onClick($event, props.index)"
>
<v-layout
slot="placeholder"
fill-height
align-center
justify-center
ma-0
slot="placeholder"
fill-height
align-center
justify-center
ma-0
>
<v-progress-circular indeterminate
color="accent lighten-5"></v-progress-circular>
@ -106,129 +106,129 @@
</div>
</template>
<script>
export default {
name: 'p-photo-list',
props: {
photos: Array,
selection: Array,
openPhoto: Function,
editPhoto: Function,
openLocation: Function,
album: Object,
filter: Object,
export default {
name: 'p-photo-list',
props: {
photos: Array,
selection: Array,
openPhoto: Function,
editPhoto: Function,
openLocation: Function,
album: Object,
filter: Object,
},
data() {
let m = this.$gettext("Couldn't find anything.");
m += " " + this.$gettext("Try again using other filters or keywords.");
if (this.$config.feature("review")) {
m += " " + this.$gettext("Non-photographic and low-quality images require a review before they appear in search results.");
}
let showName = this.filter.order === 'name'
return {
notFoundMessage: m,
'selected': [],
'listColumns': [
{text: '', value: '', align: 'center', class: 'p-col-select', sortable: false},
{text: this.$gettext('Title'), value: 'Title', sortable: false},
{text: this.$gettext('Taken'), class: 'hidden-xs-only', value: 'TakenAt', sortable: false},
{text: this.$gettext('Camera'), class: 'hidden-sm-and-down', value: 'CameraModel', sortable: false},
{
text: showName ? this.$gettext('Name') : this.$gettext('Location'),
class: 'hidden-xs-only',
value: showName ? 'FileName' : 'PlaceLabel',
sortable: false
},
data() {
let m = this.$gettext("Couldn't find anything.");
m += " " + this.$gettext("Try again using other filters or keywords.");
if (this.$config.feature("review")) {
m += " " + this.$gettext("Non-photographic and low-quality images require a review before they appear in search results.");
}
let showName = this.filter.order === 'name'
return {
notFoundMessage: m,
'selected': [],
'listColumns': [
{text: '', value: '', align: 'center', class: 'p-col-select', sortable: false},
{text: this.$gettext('Title'), value: 'Title', sortable: false},
{text: this.$gettext('Taken'), class: 'hidden-xs-only', value: 'TakenAt', sortable: false},
{text: this.$gettext('Camera'), class: 'hidden-sm-and-down', value: 'CameraModel', sortable: false},
{
text: showName ? this.$gettext('Name') : this.$gettext('Location'),
class: 'hidden-xs-only',
value: showName ? 'FileName' : 'PlaceLabel',
sortable: false
},
{text: '', value: '', align: 'center', sortable: false},
],
showName: showName,
showLocation: this.$config.settings().features.places,
hidePrivate: this.$config.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();
},
},
methods: {
downloadFile(index) {
const photo = this.photos[index];
const link = document.createElement('a')
link.href = `/api/v1/dl/${photo.Hash}?t=${this.$config.downloadToken()}`;
link.download = photo.FileName;
link.click()
},
onSelect(ev, index) {
if (ev.shiftKey) {
this.selectRange(index);
} else {
this.$clipboard.toggle(this.photos[index]);
}
},
onMouseDown(ev, index) {
this.mouseDown.index = index;
this.mouseDown.timeStamp = ev.timeStamp;
},
onClick(ev, index) {
let longClick = (this.mouseDown.index === index && ev.timeStamp - this.mouseDown.timeStamp > 400);
if (longClick || this.selection.length > 0) {
if (longClick || ev.shiftKey) {
this.selectRange(index);
} else {
this.$clipboard.toggle(this.photos[index]);
}
} else if (this.photos[index]) {
let photo = this.photos[index];
if (photo.Type === 'video' && photo.isPlayable()) {
this.openPhoto(index, true);
} else {
this.openPhoto(index, false);
}
}
},
onContextMenu(ev, index) {
if (this.$isMobile) {
ev.preventDefault();
ev.stopPropagation();
this.selectRange(index);
}
},
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]);
}
}
},
},
mounted: function () {
this.$nextTick(function () {
this.refreshSelection();
})
}
{text: '', value: '', align: 'center', sortable: false},
],
showName: showName,
showLocation: this.$config.settings().features.places,
hidePrivate: this.$config.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();
},
},
methods: {
downloadFile(index) {
const photo = this.photos[index];
const link = document.createElement('a')
link.href = `/api/v1/dl/${photo.Hash}?t=${this.$config.downloadToken()}`;
link.download = photo.FileName;
link.click()
},
onSelect(ev, index) {
if (ev.shiftKey) {
this.selectRange(index);
} else {
this.$clipboard.toggle(this.photos[index]);
}
},
onMouseDown(ev, index) {
this.mouseDown.index = index;
this.mouseDown.timeStamp = ev.timeStamp;
},
onClick(ev, index) {
let longClick = (this.mouseDown.index === index && ev.timeStamp - this.mouseDown.timeStamp > 400);
if (longClick || this.selection.length > 0) {
if (longClick || ev.shiftKey) {
this.selectRange(index);
} else {
this.$clipboard.toggle(this.photos[index]);
}
} else if (this.photos[index]) {
let photo = this.photos[index];
if (photo.Type === 'video' && photo.isPlayable()) {
this.openPhoto(index, true);
} else {
this.openPhoto(index, false);
}
}
},
onContextMenu(ev, index) {
if (this.$isMobile) {
ev.preventDefault();
ev.stopPropagation();
this.selectRange(index);
}
},
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]);
}
}
},
},
mounted: function () {
this.$nextTick(function () {
this.refreshSelection();
})
}
};
</script>

View file

@ -21,12 +21,12 @@
</v-card>
<v-layout row wrap class="p-results">
<v-flex
v-for="(photo, index) in photos"
:key="index"
:data-uid="photo.UID"
v-bind:class="{ selected: $clipboard.has(photo) }"
class="p-photo"
xs4 sm3 md2 lg1 d-flex
v-for="(photo, index) in photos"
:key="index"
:data-uid="photo.UID"
v-bind:class="{ selected: $clipboard.has(photo) }"
class="p-photo"
xs4 sm3 md2 lg1 d-flex
>
<v-hover>
<v-card tile slot-scope="{ hover }"
@ -40,25 +40,25 @@
@click.stop.prevent="onClick($event, index)"
>
<v-layout
slot="placeholder"
fill-height
align-center
justify-center
ma-0
slot="placeholder"
fill-height
align-center
justify-center
ma-0
>
<v-progress-circular indeterminate
color="accent lighten-5"></v-progress-circular>
</v-layout>
<v-layout
fill-height
align-center
justify-center
ma-0
class="p-photo-live"
style="overflow: hidden;"
v-if="photo.Type === 'live'"
v-show="hover"
fill-height
align-center
justify-center
ma-0
class="p-photo-live"
style="overflow: hidden;"
v-if="photo.Type === 'live'"
v-show="hover"
>
<video width="224" height="224" autoplay loop muted playsinline>
<source :src="photo.videoUrl()" type="video/mp4">
@ -124,60 +124,60 @@
</v-container>
</template>
<script>
export default {
name: 'p-photo-mosaic',
props: {
photos: Array,
selection: Array,
openPhoto: Function,
editPhoto: Function,
album: Object,
filter: Object,
},
data() {
return {
hidePrivate: this.$config.settings().features.private,
mouseDown: {
index: -1,
timeStamp: -1,
},
};
},
methods: {
onSelect(ev, index) {
if (ev.shiftKey) {
this.selectRange(index);
} else {
this.$clipboard.toggle(this.photos[index]);
}
},
onMouseDown(ev, index) {
this.mouseDown.index = index;
this.mouseDown.timeStamp = ev.timeStamp;
},
onClick(ev, index) {
let longClick = (this.mouseDown.index === index && ev.timeStamp - this.mouseDown.timeStamp > 400);
if (longClick || this.selection.length > 0) {
if (longClick || ev.shiftKey) {
this.selectRange(index);
} else {
this.$clipboard.toggle(this.photos[index]);
}
} else {
this.openPhoto(index, false);
}
},
onContextMenu(ev, index) {
if (this.$isMobile) {
ev.preventDefault();
ev.stopPropagation();
this.selectRange(index);
}
},
selectRange(index) {
this.$clipboard.addRange(index, this.photos);
}
},
export default {
name: 'p-photo-mosaic',
props: {
photos: Array,
selection: Array,
openPhoto: Function,
editPhoto: Function,
album: Object,
filter: Object,
},
data() {
return {
hidePrivate: this.$config.settings().features.private,
mouseDown: {
index: -1,
timeStamp: -1,
},
};
},
methods: {
onSelect(ev, index) {
if (ev.shiftKey) {
this.selectRange(index);
} else {
this.$clipboard.toggle(this.photos[index]);
}
},
onMouseDown(ev, index) {
this.mouseDown.index = index;
this.mouseDown.timeStamp = ev.timeStamp;
},
onClick(ev, index) {
let longClick = (this.mouseDown.index === index && ev.timeStamp - this.mouseDown.timeStamp > 400);
if (longClick || this.selection.length > 0) {
if (longClick || ev.shiftKey) {
this.selectRange(index);
} else {
this.$clipboard.toggle(this.photos[index]);
}
} else {
this.openPhoto(index, false);
}
},
onContextMenu(ev, index) {
if (this.$isMobile) {
ev.preventDefault();
ev.stopPropagation();
this.selectRange(index);
}
},
selectRange(index) {
this.$clipboard.addRange(index, this.photos);
}
},
};
</script>

View file

@ -36,7 +36,8 @@
<v-icon>cloud_upload</v-icon>
</v-btn>
<v-btn icon @click.stop="searchExpanded = !searchExpanded" class="p-expand-search" :title="$gettext('Expand Search')">
<v-btn icon @click.stop="searchExpanded = !searchExpanded" class="p-expand-search"
:title="$gettext('Expand Search')">
<v-icon>{{ searchExpanded ? 'keyboard_arrow_up' : 'keyboard_arrow_down' }}</v-icon>
</v-btn>
</v-toolbar>
@ -151,93 +152,93 @@
</v-form>
</template>
<script>
import Event from "pubsub-js";
import * as options from "options/options";
import Event from "pubsub-js";
import * as options from "options/options";
export default {
name: 'p-photo-toolbar',
props: {
dirty: Boolean,
filter: Object,
settings: Object,
refresh: Function,
filterChange: Function,
},
data() {
return {
experimental: this.$config.get("experimental"),
isFullScreen: !!document.fullscreenElement,
config: this.$config.values,
searchExpanded: false,
all: {
countries: [{ID: "", Name: this.$gettext("All Countries")}],
cameras: [{ID: 0, Name: this.$gettext("All Cameras")}],
lenses: [{ID: 0, Name: this.$gettext("All Lenses")}],
colors: [{Slug: "", Name: this.$gettext("All Colors")}],
categories: [{Slug: "", Name: this.$gettext("All Categories")}],
months: [{value: 0, text: this.$gettext("All Months")}],
years: [{value: 0, text: this.$gettext("All Years")}],
},
options: {
'views': [
{value: 'mosaic', text: this.$gettext('Mosaic')},
{value: 'cards', text: this.$gettext('Cards')},
{value: 'list', text: this.$gettext('List')},
],
'sorting': [
{value: 'added', text: this.$gettext('Recently added')},
{value: 'edited', text: this.$gettext('Recently edited')},
{value: 'newest', text: this.$gettext('Newest first')},
{value: 'oldest', text: this.$gettext('Oldest first')},
{value: 'name', text: this.$gettext('Sort by file name')},
{value: 'similar', text: this.$gettext('Group by similarity')},
{value: 'relevance', text: this.$gettext('Most relevant')},
],
},
};
},
computed: {
countryOptions() {
return this.all.countries.concat(this.config.countries);
},
cameraOptions() {
return this.all.cameras.concat(this.config.cameras);
},
lensOptions() {
return this.all.lenses.concat(this.config.lenses);
},
categoryOptions() {
return this.all.categories.concat(this.config.categories);
},
},
methods: {
colorOptions() {
return this.all.colors.concat(options.Colors());
},
monthOptions() {
return this.all.months.concat(options.Months());
},
yearOptions() {
return this.all.years.concat(options.IndexedYears());
},
dropdownChange() {
this.filterChange();
if (window.innerWidth < 600) {
this.searchExpanded = false;
}
},
setView(name) {
this.settings.view = name;
this.filterChange();
},
clearQuery() {
this.filter.q = '';
this.filterChange();
},
showUpload() {
Event.publish("dialog.upload");
}
},
export default {
name: 'p-photo-toolbar',
props: {
dirty: Boolean,
filter: Object,
settings: Object,
refresh: Function,
filterChange: Function,
},
data() {
return {
experimental: this.$config.get("experimental"),
isFullScreen: !!document.fullscreenElement,
config: this.$config.values,
searchExpanded: false,
all: {
countries: [{ID: "", Name: this.$gettext("All Countries")}],
cameras: [{ID: 0, Name: this.$gettext("All Cameras")}],
lenses: [{ID: 0, Name: this.$gettext("All Lenses")}],
colors: [{Slug: "", Name: this.$gettext("All Colors")}],
categories: [{Slug: "", Name: this.$gettext("All Categories")}],
months: [{value: 0, text: this.$gettext("All Months")}],
years: [{value: 0, text: this.$gettext("All Years")}],
},
options: {
'views': [
{value: 'mosaic', text: this.$gettext('Mosaic')},
{value: 'cards', text: this.$gettext('Cards')},
{value: 'list', text: this.$gettext('List')},
],
'sorting': [
{value: 'added', text: this.$gettext('Recently added')},
{value: 'edited', text: this.$gettext('Recently edited')},
{value: 'newest', text: this.$gettext('Newest first')},
{value: 'oldest', text: this.$gettext('Oldest first')},
{value: 'name', text: this.$gettext('Sort by file name')},
{value: 'similar', text: this.$gettext('Group by similarity')},
{value: 'relevance', text: this.$gettext('Most relevant')},
],
},
};
},
computed: {
countryOptions() {
return this.all.countries.concat(this.config.countries);
},
cameraOptions() {
return this.all.cameras.concat(this.config.cameras);
},
lensOptions() {
return this.all.lenses.concat(this.config.lenses);
},
categoryOptions() {
return this.all.categories.concat(this.config.categories);
},
},
methods: {
colorOptions() {
return this.all.colors.concat(options.Colors());
},
monthOptions() {
return this.all.months.concat(options.Months());
},
yearOptions() {
return this.all.years.concat(options.IndexedYears());
},
dropdownChange() {
this.filterChange();
if (window.innerWidth < 600) {
this.searchExpanded = false;
}
},
setView(name) {
this.settings.view = name;
this.filterChange();
},
clearQuery() {
this.filter.q = '';
this.filterChange();
},
showUpload() {
Event.publish("dialog.upload");
}
},
};
</script>

View file

@ -49,447 +49,457 @@ import Event from "pubsub-js";
import Thumb from "model/thumb";
export default {
name: 'p-page-album-photos',
props: {
staticFilter: Object
},
watch: {
'$route'() {
const query = this.$route.query;
this.filter.q = query['q'] ? query['q'] : '';
this.filter.camera = query['camera'] ? parseInt(query['camera']) : 0;
this.filter.country = query['country'] ? query['country'] : '';
this.settings.view = this.viewType();
this.lastFilter = {};
this.routeName = this.$route.name;
if (this.uid !== this.$route.params.uid) {
this.uid = this.$route.params.uid;
this.findAlbum().then(() => this.search());
} else {
this.search();
}
}
},
data() {
const uid = this.$route.params.uid;
const query = this.$route.query;
const routeName = this.$route.name;
const order = query['order'] ? query['order'] : 'oldest';
const camera = query['camera'] ? parseInt(query['camera']) : 0;
const q = query['q'] ? query['q'] : '';
const country = query['country'] ? query['country'] : '';
const view = this.viewType();
const filter = {country: country, camera: camera, order: order, q: q};
const settings = {view: view};
return {
subscriptions: [],
listen: false,
dirty: false,
complete: false,
model: new Album(),
uid: uid,
results: [],
scrollDisabled: true,
pageSize: 60,
offset: 0,
page: 0,
selection: this.$clipboard.selection,
settings: settings,
filter: filter,
lastFilter: {},
routeName: routeName,
loading: true,
viewer: {
results: [],
loading: false,
},
};
},
methods: {
viewType() {
let queryParam = this.$route.query['view'];
let defaultType = window.localStorage.getItem("photo_view_type");
let storedType = window.localStorage.getItem("album_view_type");
if (queryParam) {
window.localStorage.setItem("album_view_type", queryParam);
return queryParam;
} else if (storedType) {
return storedType;
} else if (defaultType) {
return defaultType;
} else if (window.innerWidth < 960) {
return 'mosaic';
}
return 'cards';
},
openLocation(index) {
const photo = this.results[index];
if (photo.CellID && photo.CellID !== "zz") {
this.$router.push({name: "place", params: {q: photo.CellID}});
} else if (photo.PlaceID && photo.PlaceID !== "zz") {
this.$router.push({name: "place", params: {q: photo.PlaceID}});
} else if (photo.Country && photo.Country !== "zz") {
this.$router.push({name: "place", params: {q: "country:" + photo.Country}});
} else {
this.$notify.warn("unknown location");
}
},
editPhoto(index) {
let selection = this.results.map((p) => {
return p.getId()
});
// Open Edit Dialog
Event.publish("dialog.edit", {selection: selection, album: this.album, index: index});
},
openPhoto(index, showMerged) {
if (this.loading || this.viewer.loading || !this.results[index]) {
return false;
}
const selected = this.results[index];
if (showMerged && (selected.Type === 'video' || selected.Type === 'live')) {
if (this.results[index].isPlayable()) {
this.$modal.show('video', {video: selected, album: this.album});
} else {
this.$viewer.show(Thumb.fromPhotos(this.results), index);
}
} else if (showMerged) {
this.$viewer.show(Thumb.fromFiles([selected]), 0)
} else {
this.viewerResults().then((results) => {
const thumbsIndex = results.findIndex(result => result.UID === selected.UID);
if (thumbsIndex < 0) {
this.$viewer.show(Thumb.fromPhotos(this.results), index);
} else {
this.$viewer.show(Thumb.fromPhotos(results), thumbsIndex);
}
});
}
return true;
},
viewerResults() {
if (this.complete || this.loading || this.viewer.loading) {
return Promise.resolve(this.results);
}
if (this.viewer.results.length >= this.results.length) {
return Promise.resolve(this.viewer.results);
}
this.viewer.loading = true;
const count = Photo.limit();
const offset = 0;
const params = {
count: count,
offset: offset,
album: this.uid,
filter: this.model.Filter ? this.model.Filter : "",
merged: true,
};
Object.assign(params, this.lastFilter);
if (this.staticFilter) {
Object.assign(params, this.staticFilter);
}
return Photo.search(params).then(resp => {
// Success.
this.viewer.loading = false;
this.viewer.results = resp.models;
return Promise.resolve(this.viewer.results);
}, () => {
// Error.
this.viewer.loading = false;
return Promise.resolve(this.results);
});
},
loadMore() {
if (this.scrollDisabled) return;
this.scrollDisabled = true;
this.listen = false;
const count = this.dirty ? (this.page + 2) * this.pageSize : this.pageSize;
const offset = this.dirty ? 0 : this.offset;
const params = {
count: count,
offset: offset,
album: this.uid,
filter: this.model.Filter ? this.model.Filter : "",
merged: true,
};
Object.assign(params, this.lastFilter);
if (this.staticFilter) {
Object.assign(params, this.staticFilter);
}
Photo.search(params).then(response => {
this.results = Photo.mergeResponse(this.results, response);
this.complete = (response.count < count);
this.scrollDisabled = this.complete;
if (this.complete) {
this.offset = offset;
if (this.results.length > 1) {
this.$notify.info(this.$gettextInterpolate(this.$gettext("All %{n} entries loaded"), {n: this.results.length}));
}
} else if (this.results.length >= Photo.limit()) {
this.offset = offset;
this.scrollDisabled = true;
this.complete = true;
this.$notify.warn(this.$gettext("Can't load more, limit reached"));
} else {
this.offset = offset + count;
this.page++;
}
}).catch(() => {
this.scrollDisabled = false;
}).finally(() => {
this.dirty = false;
this.loading = false;
this.listen = true;
if (offset === 0) {
this.viewerResults();
}
});
},
updateQuery() {
this.filter.q = this.filter.q.trim();
const len = this.filter.q.length;
if (len > 1 && len < 3) {
this.$notify.error(this.$gettext("Search term too short"));
return;
}
const query = {
view: this.settings.view
};
Object.assign(query, this.filter);
for (let key in query) {
if (query[key] === undefined || !query[key]) {
delete query[key];
}
}
if (JSON.stringify(this.$route.query) === JSON.stringify(query)) {
return
}
this.$router.replace({query: query});
},
searchParams() {
const params = {
count: this.pageSize,
offset: this.offset,
album: this.uid,
filter: this.model.Filter ? this.model.Filter : "",
merged: true,
};
Object.assign(params, this.filter);
if (this.staticFilter) {
Object.assign(params, this.staticFilter);
}
return params;
},
refresh() {
if (this.loading) {
return;
}
this.loading = true;
this.page = 0;
this.dirty = true;
this.complete = false;
this.scrollDisabled = false;
this.loadMore();
},
search() {
this.scrollDisabled = true;
// Don't query the same data more than once
if (JSON.stringify(this.lastFilter) === JSON.stringify(this.filter)) {
this.$nextTick(() => this.$emit("scrollRefresh"));
return;
}
Object.assign(this.lastFilter, this.filter);
this.offset = 0;
this.page = 0;
this.loading = true;
this.listen = false;
this.complete = false;
const params = this.searchParams();
Photo.search(params).then(response => {
this.offset = this.pageSize;
this.results = response.models;
this.complete = (response.count < this.pageSize);
this.scrollDisabled = this.complete;
if (this.complete) {
if (!this.results.length) {
this.$notify.warn(this.$gettext("No entries found"));
} else if (this.results.length === 1) {
this.$notify.info(this.$gettext("One entry found"));
} else {
this.$notify.info(this.$gettextInterpolate(this.$gettext("%{n} entries found"), {n: this.results.length}));
}
} else {
this.$notify.info(this.$gettext('More than 50 entries found'));
this.$nextTick(() => this.$emit("scrollRefresh"));
}
}).finally(() => {
this.dirty = false;
this.loading = false;
this.listen = true;
this.viewerResults();
});
},
findAlbum() {
return this.model.find(this.uid).then(m => {
this.model = m;
this.filter.order = m.Order;
window.document.title = `${this.$config.get("siteTitle")}: ${this.model.Title}`;
return Promise.resolve(this.model)
});
},
onAlbumsUpdated(ev, data) {
if (!this.listen) return;
if (!data || !data.entities) {
return
}
for (let i = 0; i < data.entities.length; i++) {
if (this.model.UID === data.entities[i].UID) {
let values = data.entities[i];
for (let key in values) {
if (values.hasOwnProperty(key)) {
this.model[key] = values[key];
}
}
window.document.title = `${this.$config.get("siteTitle")}: ${this.model.Title}`
this.dirty = true;
this.complete = false;
this.scrollDisabled = false;
if (this.filter.order !== this.model.Order) {
this.filter.order = this.model.Order;
this.updateQuery();
} else {
this.loadMore();
}
return;
}
}
},
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];
}
}
}
},
removeResult(results, uid) {
const index = results.findIndex((m) => m.UID === uid);
if (index >= 0) {
results.splice(index, 1);
}
},
onUpdate(ev, data) {
if (!this.listen) return;
if (!data || !data.entities) {
return
}
const type = ev.split('.')[1];
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);
}
break;
case 'restored':
this.dirty = true;
this.scrollDisabled = false;
this.complete = false;
this.loadMore();
break;
case 'archived':
this.dirty = true;
this.complete = false;
for (let i = 0; i < data.entities.length; i++) {
const uid = data.entities[i];
this.removeResult(this.results, uid);
this.removeResult(this.viewer.results, uid);
this.$clipboard.removeId(uid);
}
break;
}
},
},
created() {
name: 'p-page-album-photos',
props: {
staticFilter: Object
},
watch: {
'$route'() {
const query = this.$route.query;
this.filter.q = query['q'] ? query['q'] : '';
this.filter.camera = query['camera'] ? parseInt(query['camera']) : 0;
this.filter.country = query['country'] ? query['country'] : '';
this.settings.view = this.viewType();
this.lastFilter = {};
this.routeName = this.$route.name;
if (this.uid !== this.$route.params.uid) {
this.uid = this.$route.params.uid;
this.findAlbum().then(() => this.search());
} else {
this.search();
}
}
},
data() {
const uid = this.$route.params.uid;
const query = this.$route.query;
const routeName = this.$route.name;
const order = query['order'] ? query['order'] : 'oldest';
const camera = query['camera'] ? parseInt(query['camera']) : 0;
const q = query['q'] ? query['q'] : '';
const country = query['country'] ? query['country'] : '';
const view = this.viewType();
const filter = {country: country, camera: camera, order: order, q: q};
const settings = {view: view};
this.subscriptions.push(Event.subscribe("albums.updated", (ev, data) => this.onAlbumsUpdated(ev, data)));
this.subscriptions.push(Event.subscribe("photos", (ev, data) => this.onUpdate(ev, data)));
return {
subscriptions: [],
listen: false,
dirty: false,
complete: false,
model: new Album(),
uid: uid,
results: [],
scrollDisabled: true,
pageSize: 60,
offset: 0,
page: 0,
selection: this.$clipboard.selection,
settings: settings,
filter: filter,
lastFilter: {},
routeName: routeName,
loading: true,
viewer: {
results: [],
loading: false,
},
};
},
methods: {
viewType() {
let queryParam = this.$route.query['view'];
let defaultType = window.localStorage.getItem("photo_view_type");
let storedType = window.localStorage.getItem("album_view_type");
this.subscriptions.push(Event.subscribe("touchmove.top", () => this.refresh()));
this.subscriptions.push(Event.subscribe("touchmove.bottom", () => this.loadMore()));
if (queryParam) {
window.localStorage.setItem("album_view_type", queryParam);
return queryParam;
} else if (storedType) {
return storedType;
} else if (defaultType) {
return defaultType;
} else if (window.innerWidth < 960) {
return 'mosaic';
}
return 'cards';
},
destroyed() {
for (let i = 0; i < this.subscriptions.length; i++) {
Event.unsubscribe(this.subscriptions[i]);
openLocation(index) {
const photo = this.results[index];
if (photo.CellID && photo.CellID !== "zz") {
this.$router.push({name: "place", params: {q: photo.CellID}});
} else if (photo.PlaceID && photo.PlaceID !== "zz") {
this.$router.push({name: "place", params: {q: photo.PlaceID}});
} else if (photo.Country && photo.Country !== "zz") {
this.$router.push({name: "place", params: {q: "country:" + photo.Country}});
} else {
this.$notify.warn("unknown location");
}
},
editPhoto(index) {
let selection = this.results.map((p) => {
return p.getId()
});
// Open Edit Dialog
Event.publish("dialog.edit", {selection: selection, album: this.album, index: index});
},
openPhoto(index, showMerged) {
if (this.loading || this.viewer.loading || !this.results[index]) {
return false;
}
const selected = this.results[index];
if (showMerged && (selected.Type === 'video' || selected.Type === 'live')) {
if (this.results[index].isPlayable()) {
this.$modal.show('video', {video: selected, album: this.album});
} else {
this.$viewer.show(Thumb.fromPhotos(this.results), index);
}
} else if (showMerged) {
this.$viewer.show(Thumb.fromFiles([selected]), 0)
} else {
this.viewerResults().then((results) => {
const thumbsIndex = results.findIndex(result => result.UID === selected.UID);
if (thumbsIndex < 0) {
this.$viewer.show(Thumb.fromPhotos(this.results), index);
} else {
this.$viewer.show(Thumb.fromPhotos(results), thumbsIndex);
}
});
}
return true;
},
viewerResults() {
if (this.complete || this.loading || this.viewer.loading) {
return Promise.resolve(this.results);
}
if (this.viewer.results.length >= this.results.length) {
return Promise.resolve(this.viewer.results);
}
this.viewer.loading = true;
const count = Photo.limit();
const offset = 0;
const params = {
count: count,
offset: offset,
album: this.uid,
filter: this.model.Filter ? this.model.Filter : "",
merged: true,
};
Object.assign(params, this.lastFilter);
if (this.staticFilter) {
Object.assign(params, this.staticFilter);
}
return Photo.search(params).then(resp => {
// Success.
this.viewer.loading = false;
this.viewer.results = resp.models;
return Promise.resolve(this.viewer.results);
}, () => {
// Error.
this.viewer.loading = false;
return Promise.resolve(this.results);
});
},
loadMore() {
if (this.scrollDisabled) return;
this.scrollDisabled = true;
this.listen = false;
const count = this.dirty ? (this.page + 2) * this.pageSize : this.pageSize;
const offset = this.dirty ? 0 : this.offset;
const params = {
count: count,
offset: offset,
album: this.uid,
filter: this.model.Filter ? this.model.Filter : "",
merged: true,
};
Object.assign(params, this.lastFilter);
if (this.staticFilter) {
Object.assign(params, this.staticFilter);
}
Photo.search(params).then(response => {
this.results = Photo.mergeResponse(this.results, response);
this.complete = (response.count < count);
this.scrollDisabled = this.complete;
if (this.complete) {
this.offset = offset;
if (this.results.length > 1) {
this.$notify.info(this.$gettextInterpolate(this.$gettext("All %{n} entries loaded"), {n: this.results.length}));
}
} else if (this.results.length >= Photo.limit()) {
this.offset = offset;
this.scrollDisabled = true;
this.complete = true;
this.$notify.warn(this.$gettext("Can't load more, limit reached"));
} else {
this.offset = offset + count;
this.page++;
this.$nextTick(() => {
if (this.$root.$el.clientHeight <= window.document.documentElement.clientHeight) {
this.$emit("scrollRefresh");
}
});
}
}).catch(() => {
this.scrollDisabled = false;
}).finally(() => {
this.dirty = false;
this.loading = false;
this.listen = true;
if (offset === 0) {
this.viewerResults();
}
});
},
updateQuery() {
this.filter.q = this.filter.q.trim();
const len = this.filter.q.length;
if (len > 1 && len < 3) {
this.$notify.error(this.$gettext("Search term too short"));
return;
}
const query = {
view: this.settings.view
};
Object.assign(query, this.filter);
for (let key in query) {
if (query[key] === undefined || !query[key]) {
delete query[key];
}
}
if (JSON.stringify(this.$route.query) === JSON.stringify(query)) {
return
}
this.$router.replace({query: query});
},
searchParams() {
const params = {
count: this.pageSize,
offset: this.offset,
album: this.uid,
filter: this.model.Filter ? this.model.Filter : "",
merged: true,
};
Object.assign(params, this.filter);
if (this.staticFilter) {
Object.assign(params, this.staticFilter);
}
return params;
},
refresh() {
if (this.loading) {
return;
}
this.loading = true;
this.page = 0;
this.dirty = true;
this.complete = false;
this.scrollDisabled = false;
this.loadMore();
},
search() {
this.scrollDisabled = true;
// Don't query the same data more than once
if (JSON.stringify(this.lastFilter) === JSON.stringify(this.filter)) {
this.$nextTick(() => this.$emit("scrollRefresh"));
return;
}
Object.assign(this.lastFilter, this.filter);
this.offset = 0;
this.page = 0;
this.loading = true;
this.listen = false;
this.complete = false;
const params = this.searchParams();
Photo.search(params).then(response => {
this.offset = this.pageSize;
this.results = response.models;
this.complete = (response.count < this.pageSize);
this.scrollDisabled = this.complete;
if (this.complete) {
if (!this.results.length) {
this.$notify.warn(this.$gettext("No entries found"));
} else if (this.results.length === 1) {
this.$notify.info(this.$gettext("One entry found"));
} else {
this.$notify.info(this.$gettextInterpolate(this.$gettext("%{n} entries found"), {n: this.results.length}));
}
} else {
this.$notify.info(this.$gettext('More than 50 entries found'));
this.$nextTick(() => {
if (this.$root.$el.clientHeight <= window.document.documentElement.clientHeight) {
this.$emit("scrollRefresh");
}
});
}
}).finally(() => {
this.dirty = false;
this.loading = false;
this.listen = true;
this.viewerResults();
});
},
findAlbum() {
return this.model.find(this.uid).then(m => {
this.model = m;
this.filter.order = m.Order;
window.document.title = `${this.$config.get("siteTitle")}: ${this.model.Title}`;
return Promise.resolve(this.model)
});
},
onAlbumsUpdated(ev, data) {
if (!this.listen) return;
if (!data || !data.entities) {
return
}
for (let i = 0; i < data.entities.length; i++) {
if (this.model.UID === data.entities[i].UID) {
let values = data.entities[i];
for (let key in values) {
if (values.hasOwnProperty(key)) {
this.model[key] = values[key];
}
}
window.document.title = `${this.$config.get("siteTitle")}: ${this.model.Title}`
this.dirty = true;
this.complete = false;
this.scrollDisabled = false;
if (this.filter.order !== this.model.Order) {
this.filter.order = this.model.Order;
this.updateQuery();
} else {
this.loadMore();
}
return;
}
}
},
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];
}
}
}
},
removeResult(results, uid) {
const index = results.findIndex((m) => m.UID === uid);
if (index >= 0) {
results.splice(index, 1);
}
},
onUpdate(ev, data) {
if (!this.listen) return;
if (!data || !data.entities) {
return
}
const type = ev.split('.')[1];
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);
}
break;
case 'restored':
this.dirty = true;
this.scrollDisabled = false;
this.complete = false;
this.loadMore();
break;
case 'archived':
this.dirty = true;
this.complete = false;
for (let i = 0; i < data.entities.length; i++) {
const uid = data.entities[i];
this.removeResult(this.results, uid);
this.removeResult(this.viewer.results, uid);
this.$clipboard.removeId(uid);
}
break;
}
},
},
created() {
this.findAlbum().then(() => this.search());
this.subscriptions.push(Event.subscribe("albums.updated", (ev, data) => this.onAlbumsUpdated(ev, data)));
this.subscriptions.push(Event.subscribe("photos", (ev, data) => this.onUpdate(ev, data)));
this.subscriptions.push(Event.subscribe("touchmove.top", () => this.refresh()));
this.subscriptions.push(Event.subscribe("touchmove.bottom", () => this.loadMore()));
},
destroyed() {
for (let i = 0; i < this.subscriptions.length; i++) {
Event.unsubscribe(this.subscriptions[i]);
}
},
};
</script>

View file

@ -38,7 +38,8 @@
<v-icon>cloud_upload</v-icon>
</v-btn>
<v-btn icon @click.prevent="create" class="action-add" v-if="staticFilter.type === 'album'" :title="$gettext('Add Album')">
<v-btn icon @click.prevent="create" class="action-add" v-if="staticFilter.type === 'album'"
:title="$gettext('Add Album')">
<v-icon>add</v-icon>
</v-btn>
</v-toolbar>
@ -381,6 +382,12 @@ export default {
} else {
this.offset = offset + count;
this.page++;
this.$nextTick(() => {
if (this.$root.$el.clientHeight <= window.document.documentElement.clientHeight) {
this.$emit("scrollRefresh");
}
});
}
}).catch(() => {
this.scrollDisabled = false;
@ -467,7 +474,11 @@ export default {
} else {
this.$notify.info(this.$gettext('More than 20 albums found'));
this.$nextTick(() => this.$emit("scrollRefresh"));
this.$nextTick(() => {
if (this.$root.$el.clientHeight <= window.document.documentElement.clientHeight) {
this.$emit("scrollRefresh");
}
});
}
}).finally(() => {
this.dirty = false;

View file

@ -352,6 +352,12 @@ export default {
} else {
this.offset = offset + count;
this.page++;
this.$nextTick(() => {
if (this.$root.$el.clientHeight <= window.document.documentElement.clientHeight) {
this.$emit("scrollRefresh");
}
});
}
}).catch(() => {
this.scrollDisabled = false;
@ -440,7 +446,11 @@ export default {
} else {
this.$notify.info(this.$gettext('More than 20 labels found'));
this.$nextTick(() => this.$emit("scrollRefresh"));
this.$nextTick(() => {
if (this.$root.$el.clientHeight <= window.document.documentElement.clientHeight) {
this.$emit("scrollRefresh");
}
});
}
}).finally(() => {
this.dirty = false;

View file

@ -38,469 +38,479 @@
</template>
<script>
import Photo from "model/photo";
import Thumb from "model/thumb";
import Event from "pubsub-js";
export default {
name: 'p-page-photos',
props: {
staticFilter: Object
},
watch: {
'$route'() {
const query = this.$route.query;
this.filter.q = query['q'] ? query['q'] : '';
this.filter.camera = query['camera'] ? parseInt(query['camera']) : 0;
this.filter.country = query['country'] ? query['country'] : '';
this.filter.lens = query['lens'] ? parseInt(query['lens']) : 0;
this.filter.year = query['year'] ? parseInt(query['year']) : 0;
this.filter.month = query['month'] ? parseInt(query['month']) : 0;
this.filter.color = query['color'] ? query['color'] : '';
this.filter.label = query['label'] ? query['label'] : '';
this.filter.order = this.sortOrder();
this.settings.view = this.viewType();
this.lastFilter = {};
this.routeName = this.$route.name;
this.search();
}
},
data() {
const query = this.$route.query;
const routeName = this.$route.name;
const order = this.sortOrder();
const camera = query['camera'] ? parseInt(query['camera']) : 0;
const q = query['q'] ? query['q'] : '';
const country = query['country'] ? query['country'] : '';
const lens = query['lens'] ? parseInt(query['lens']) : 0;
const year = query['year'] ? parseInt(query['year']) : 0;
const month = query['month'] ? parseInt(query['month']) : 0;
const color = query['color'] ? query['color'] : '';
const label = query['label'] ? query['label'] : '';
const view = this.viewType();
const filter = {
country: country,
camera: camera,
lens: lens,
label: label,
year: year,
month: month,
color: color,
order: order,
q: q,
};
const settings = this.$config.settings();
if (settings && settings.features.private) {
filter.public = true;
}
if (settings && settings.features.review && (!this.staticFilter || !("quality" in this.staticFilter))) {
filter.quality = 3;
}
return {
subscriptions: [],
listen: false,
dirty: false,
complete: false,
results: [],
scrollDisabled: true,
pageSize: 60,
offset: 0,
page: 0,
selection: this.$clipboard.selection,
settings: {view: view},
filter: filter,
lastFilter: {},
routeName: routeName,
loading: true,
viewer: {
results: [],
loading: false,
},
};
},
computed: {
context: function () {
if (!this.staticFilter) {
return "photos";
}
if (this.staticFilter.archived) {
return "archive";
} else if (this.staticFilter.favorite) {
return "favorites";
}
return "";
}
},
methods: {
viewType() {
let queryParam = this.$route.query['view'];
let storedType = window.localStorage.getItem("photo_view");
if (queryParam) {
window.localStorage.setItem("photo_view", queryParam);
return queryParam;
} else if (storedType) {
return storedType;
} else if (window.innerWidth < 960) {
return 'mosaic';
}
return 'cards';
},
sortOrder() {
let queryParam = this.$route.query['order'];
let storedType = window.localStorage.getItem("photo_order");
if (queryParam) {
window.localStorage.setItem("photo_order", queryParam);
return queryParam;
} else if (storedType) {
return storedType;
}
return 'newest';
},
openLocation(index) {
const photo = this.results[index];
if (photo.CellID && photo.CellID !== "zz") {
this.$router.push({name: "place", params: {q: photo.CellID}});
} else if (photo.PlaceID && photo.PlaceID !== "zz") {
this.$router.push({name: "place", params: {q: photo.PlaceID}});
} else if (photo.Country && photo.Country !== "zz") {
this.$router.push({name: "place", params: {q: "country:" + photo.Country}});
} else {
this.$notify.warn("unknown location");
}
},
editPhoto(index) {
let selection = this.results.map((p) => {
return p.getId()
});
// Open Edit Dialog
Event.publish("dialog.edit", {selection: selection, album: null, index: index});
},
openPhoto(index, showMerged) {
if (this.loading || this.viewer.loading || !this.results[index]) {
return false;
}
const selected = this.results[index];
if (showMerged && (selected.Type === 'video' || selected.Type === 'live')) {
if (selected.isPlayable()) {
this.$modal.show('video', {video: selected, album: null});
} else {
this.$viewer.show(Thumb.fromPhotos(this.results), index);
}
} else if (showMerged) {
this.$viewer.show(Thumb.fromFiles([selected]), 0)
} else {
this.viewerResults().then((results) => {
const thumbsIndex = results.findIndex(result => result.UID === selected.UID);
if(thumbsIndex < 0) {
this.$viewer.show(Thumb.fromPhotos(this.results), index);
} else {
this.$viewer.show(Thumb.fromPhotos(results), thumbsIndex);
}
});
}
},
viewerResults() {
if (this.complete || this.loading || this.viewer.loading) {
return Promise.resolve(this.results);
}
if (this.viewer.results.length > (this.results.length + this.pageSize)) {
return Promise.resolve(this.viewer.results);
}
this.viewer.loading = true;
const count = this.pageSize*(this.page + 6);
const offset = 0;
const params = {
count: count,
offset: offset,
merged: true,
};
Object.assign(params, this.lastFilter);
if (this.staticFilter) {
Object.assign(params, this.staticFilter);
}
return Photo.search(params).then((resp) => {
// Success.
this.viewer.loading = false;
this.viewer.results = resp.models;
return Promise.resolve(this.viewer.results);
}, () => {
// Error.
this.viewer.loading = false;
return Promise.resolve(this.results);
}
);
},
loadMore() {
if (this.scrollDisabled) return;
this.scrollDisabled = true;
this.listen = false;
const count = this.dirty ? (this.page + 2) * this.pageSize : this.pageSize;
const offset = this.dirty ? 0 : this.offset;
const params = {
count: count,
offset: offset,
merged: true,
};
Object.assign(params, this.lastFilter);
if (this.staticFilter) {
Object.assign(params, this.staticFilter);
}
Photo.search(params).then(response => {
this.results = Photo.mergeResponse(this.results, response);
this.complete = (response.count < count);
this.scrollDisabled = this.complete;
if (this.complete) {
this.offset = offset;
if (this.results.length > 1) {
this.$notify.info(this.$gettextInterpolate(this.$gettext("Showing all %{n} results"), {n: this.results.length}));
}
} else if (this.results.length >= Photo.limit()) {
this.offset = offset;
this.complete = true;
this.scrollDisabled = true;
this.$notify.warn(this.$gettext("Can't load more, limit reached"));
} else {
this.offset = offset + count;
this.page++;
}
}).catch(() => {
this.scrollDisabled = false;
}).finally(() => {
this.dirty = false;
this.loading = false;
this.listen = true;
if(offset === 0) {
this.viewerResults();
}
});
},
updateQuery() {
this.filter.q = this.filter.q.trim();
const len = this.filter.q.length;
if (len > 1 && len < 3) {
this.$notify.error(this.$gettext("Search term too short"));
return;
}
const query = {
view: this.settings.view
};
Object.assign(query, this.filter);
for (let key in query) {
if (query[key] === undefined || !query[key]) {
delete query[key];
}
}
if (JSON.stringify(this.$route.query) === JSON.stringify(query)) {
return
}
this.$router.replace({query});
},
searchParams() {
const params = {
count: this.pageSize,
offset: this.offset,
merged: true,
};
Object.assign(params, this.filter);
if (this.staticFilter) {
Object.assign(params, this.staticFilter);
}
return params;
},
refresh() {
if (this.loading) {
return;
}
this.loading = true;
this.page = 0;
this.dirty = true;
this.complete = false;
this.scrollDisabled = false;
this.loadMore();
},
search() {
this.scrollDisabled = true;
// Don't query the same data more than once
if (JSON.stringify(this.lastFilter) === JSON.stringify(this.filter)) {
this.$nextTick(() => this.$emit("scrollRefresh"));
return;
}
Object.assign(this.lastFilter, this.filter);
this.offset = 0;
this.page = 0;
this.loading = true;
this.listen = false;
this.complete = false;
const params = this.searchParams();
Photo.search(params).then(response => {
this.offset = this.pageSize;
this.results = response.models;
this.complete = (response.count < this.pageSize);
this.scrollDisabled = this.complete;
if (this.complete) {
if (!this.results.length) {
this.$notify.warn(this.$gettext("No results"));
} else if (this.results.length === 1) {
this.$notify.info(this.$gettext("One result"));
} else {
this.$notify.info(this.$gettextInterpolate(this.$gettext("%{n} results"), {n: this.results.length}));
}
} else {
this.$notify.info(this.$gettext('More than 50 results'));
this.$nextTick(() => this.$emit("scrollRefresh"));
}
}).finally(() => {
this.dirty = false;
this.loading = false;
this.listen = true;
this.viewerResults();
});
},
onImportCompleted() {
if (!this.listen) return;
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];
}
}
}
},
removeResult(results, uid) {
const index = results.findIndex((m) => m.UID === uid);
if (index >= 0) {
results.splice(index, 1);
}
},
onUpdate(ev, data) {
if (!this.listen) return;
if (!data || !data.entities) {
return
}
const type = ev.split('.')[1];
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);
}
break;
case 'restored':
this.dirty = true;
this.complete = false;
if (this.context !== "archive") break;
for (let i = 0; i < data.entities.length; i++) {
const uid = data.entities[i];
this.removeResult(this.results, uid);
this.removeResult(this.viewer.results, uid);
}
break;
case 'archived':
this.dirty = true;
this.complete = false;
if (this.context === "archive") break;
for (let i = 0; i < data.entities.length; i++) {
const uid = data.entities[i];
this.removeResult(this.results, uid);
this.removeResult(this.viewer.results, uid);
this.$clipboard.removeId(uid);
}
break;
case 'created':
this.dirty = true;
this.scrollDisabled = false;
this.complete = false;
break;
default:
console.warn("unexpected event type", ev);
}
},
},
created() {
this.search();
this.subscriptions.push(Event.subscribe("import.completed", (ev, data) => this.onImportCompleted(ev, data)));
this.subscriptions.push(Event.subscribe("photos", (ev, data) => this.onUpdate(ev, data)));
this.subscriptions.push(Event.subscribe("touchmove.top", () => this.refresh()));
this.subscriptions.push(Event.subscribe("touchmove.bottom", () => this.loadMore()));
},
destroyed() {
for (let i = 0; i < this.subscriptions.length; i++) {
Event.unsubscribe(this.subscriptions[i]);
}
},
import Photo from "model/photo";
import Thumb from "model/thumb";
import Event from "pubsub-js";
export default {
name: 'p-page-photos',
props: {
staticFilter: Object
},
watch: {
'$route'() {
const query = this.$route.query;
this.filter.q = query['q'] ? query['q'] : '';
this.filter.camera = query['camera'] ? parseInt(query['camera']) : 0;
this.filter.country = query['country'] ? query['country'] : '';
this.filter.lens = query['lens'] ? parseInt(query['lens']) : 0;
this.filter.year = query['year'] ? parseInt(query['year']) : 0;
this.filter.month = query['month'] ? parseInt(query['month']) : 0;
this.filter.color = query['color'] ? query['color'] : '';
this.filter.label = query['label'] ? query['label'] : '';
this.filter.order = this.sortOrder();
this.settings.view = this.viewType();
this.lastFilter = {};
this.routeName = this.$route.name;
this.search();
}
},
data() {
const query = this.$route.query;
const routeName = this.$route.name;
const order = this.sortOrder();
const camera = query['camera'] ? parseInt(query['camera']) : 0;
const q = query['q'] ? query['q'] : '';
const country = query['country'] ? query['country'] : '';
const lens = query['lens'] ? parseInt(query['lens']) : 0;
const year = query['year'] ? parseInt(query['year']) : 0;
const month = query['month'] ? parseInt(query['month']) : 0;
const color = query['color'] ? query['color'] : '';
const label = query['label'] ? query['label'] : '';
const view = this.viewType();
const filter = {
country: country,
camera: camera,
lens: lens,
label: label,
year: year,
month: month,
color: color,
order: order,
q: q,
};
const settings = this.$config.settings();
if (settings && settings.features.private) {
filter.public = true;
}
if (settings && settings.features.review && (!this.staticFilter || !("quality" in this.staticFilter))) {
filter.quality = 3;
}
return {
subscriptions: [],
listen: false,
dirty: false,
complete: false,
results: [],
scrollDisabled: true,
pageSize: 60,
offset: 0,
page: 0,
selection: this.$clipboard.selection,
settings: {view: view},
filter: filter,
lastFilter: {},
routeName: routeName,
loading: true,
viewer: {
results: [],
loading: false,
},
};
},
computed: {
context: function () {
if (!this.staticFilter) {
return "photos";
}
if (this.staticFilter.archived) {
return "archive";
} else if (this.staticFilter.favorite) {
return "favorites";
}
return "";
}
},
methods: {
viewType() {
let queryParam = this.$route.query['view'];
let storedType = window.localStorage.getItem("photo_view");
if (queryParam) {
window.localStorage.setItem("photo_view", queryParam);
return queryParam;
} else if (storedType) {
return storedType;
} else if (window.innerWidth < 960) {
return 'mosaic';
}
return 'cards';
},
sortOrder() {
let queryParam = this.$route.query['order'];
let storedType = window.localStorage.getItem("photo_order");
if (queryParam) {
window.localStorage.setItem("photo_order", queryParam);
return queryParam;
} else if (storedType) {
return storedType;
}
return 'newest';
},
openLocation(index) {
const photo = this.results[index];
if (photo.CellID && photo.CellID !== "zz") {
this.$router.push({name: "place", params: {q: photo.CellID}});
} else if (photo.PlaceID && photo.PlaceID !== "zz") {
this.$router.push({name: "place", params: {q: photo.PlaceID}});
} else if (photo.Country && photo.Country !== "zz") {
this.$router.push({name: "place", params: {q: "country:" + photo.Country}});
} else {
this.$notify.warn("unknown location");
}
},
editPhoto(index) {
let selection = this.results.map((p) => {
return p.getId()
});
// Open Edit Dialog
Event.publish("dialog.edit", {selection: selection, album: null, index: index});
},
openPhoto(index, showMerged) {
if (this.loading || this.viewer.loading || !this.results[index]) {
return false;
}
const selected = this.results[index];
if (showMerged && (selected.Type === 'video' || selected.Type === 'live')) {
if (selected.isPlayable()) {
this.$modal.show('video', {video: selected, album: null});
} else {
this.$viewer.show(Thumb.fromPhotos(this.results), index);
}
} else if (showMerged) {
this.$viewer.show(Thumb.fromFiles([selected]), 0)
} else {
this.viewerResults().then((results) => {
const thumbsIndex = results.findIndex(result => result.UID === selected.UID);
if (thumbsIndex < 0) {
this.$viewer.show(Thumb.fromPhotos(this.results), index);
} else {
this.$viewer.show(Thumb.fromPhotos(results), thumbsIndex);
}
});
}
},
viewerResults() {
if (this.complete || this.loading || this.viewer.loading) {
return Promise.resolve(this.results);
}
if (this.viewer.results.length > (this.results.length + this.pageSize)) {
return Promise.resolve(this.viewer.results);
}
this.viewer.loading = true;
const count = this.pageSize * (this.page + 6);
const offset = 0;
const params = {
count: count,
offset: offset,
merged: true,
};
Object.assign(params, this.lastFilter);
if (this.staticFilter) {
Object.assign(params, this.staticFilter);
}
return Photo.search(params).then((resp) => {
// Success.
this.viewer.loading = false;
this.viewer.results = resp.models;
return Promise.resolve(this.viewer.results);
}, () => {
// Error.
this.viewer.loading = false;
return Promise.resolve(this.results);
}
);
},
loadMore() {
if (this.scrollDisabled) return;
this.scrollDisabled = true;
this.listen = false;
const count = this.dirty ? (this.page + 2) * this.pageSize : this.pageSize;
const offset = this.dirty ? 0 : this.offset;
const params = {
count: count,
offset: offset,
merged: true,
};
Object.assign(params, this.lastFilter);
if (this.staticFilter) {
Object.assign(params, this.staticFilter);
}
Photo.search(params).then(response => {
this.results = Photo.mergeResponse(this.results, response);
this.complete = (response.count < count);
this.scrollDisabled = this.complete;
if (this.complete) {
this.offset = offset;
if (this.results.length > 1) {
this.$notify.info(this.$gettextInterpolate(this.$gettext("Showing all %{n} results"), {n: this.results.length}));
}
} else if (this.results.length >= Photo.limit()) {
this.offset = offset;
this.complete = true;
this.scrollDisabled = true;
this.$notify.warn(this.$gettext("Can't load more, limit reached"));
} else {
this.offset = offset + count;
this.page++;
this.$nextTick(() => {
if (this.$root.$el.clientHeight <= window.document.documentElement.clientHeight) {
this.$emit("scrollRefresh");
}
});
}
}).catch(() => {
this.scrollDisabled = false;
}).finally(() => {
this.dirty = false;
this.loading = false;
this.listen = true;
if (offset === 0) {
this.viewerResults();
}
});
},
updateQuery() {
this.filter.q = this.filter.q.trim();
const len = this.filter.q.length;
if (len > 1 && len < 3) {
this.$notify.error(this.$gettext("Search term too short"));
return;
}
const query = {
view: this.settings.view
};
Object.assign(query, this.filter);
for (let key in query) {
if (query[key] === undefined || !query[key]) {
delete query[key];
}
}
if (JSON.stringify(this.$route.query) === JSON.stringify(query)) {
return
}
this.$router.replace({query});
},
searchParams() {
const params = {
count: this.pageSize,
offset: this.offset,
merged: true,
};
Object.assign(params, this.filter);
if (this.staticFilter) {
Object.assign(params, this.staticFilter);
}
return params;
},
refresh() {
if (this.loading) {
return;
}
this.loading = true;
this.page = 0;
this.dirty = true;
this.complete = false;
this.scrollDisabled = false;
this.loadMore();
},
search() {
this.scrollDisabled = true;
// Don't query the same data more than once
if (JSON.stringify(this.lastFilter) === JSON.stringify(this.filter)) {
this.$nextTick(() => this.$emit("scrollRefresh"));
return;
}
Object.assign(this.lastFilter, this.filter);
this.offset = 0;
this.page = 0;
this.loading = true;
this.listen = false;
this.complete = false;
const params = this.searchParams();
Photo.search(params).then(response => {
this.offset = this.pageSize;
this.results = response.models;
this.complete = (response.count < this.pageSize);
this.scrollDisabled = this.complete;
if (this.complete) {
if (!this.results.length) {
this.$notify.warn(this.$gettext("No results"));
} else if (this.results.length === 1) {
this.$notify.info(this.$gettext("One result"));
} else {
this.$notify.info(this.$gettextInterpolate(this.$gettext("%{n} results"), {n: this.results.length}));
}
} else {
this.$notify.info(this.$gettext('More than 50 results'));
this.$nextTick(() => {
if (this.$root.$el.clientHeight <= window.document.documentElement.clientHeight) {
this.$emit("scrollRefresh");
}
});
}
}).finally(() => {
this.dirty = false;
this.loading = false;
this.listen = true;
this.viewerResults();
});
},
onImportCompleted() {
if (!this.listen) return;
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];
}
}
}
},
removeResult(results, uid) {
const index = results.findIndex((m) => m.UID === uid);
if (index >= 0) {
results.splice(index, 1);
}
},
onUpdate(ev, data) {
if (!this.listen) return;
if (!data || !data.entities) {
return
}
const type = ev.split('.')[1];
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);
}
break;
case 'restored':
this.dirty = true;
this.complete = false;
if (this.context !== "archive") break;
for (let i = 0; i < data.entities.length; i++) {
const uid = data.entities[i];
this.removeResult(this.results, uid);
this.removeResult(this.viewer.results, uid);
}
break;
case 'archived':
this.dirty = true;
this.complete = false;
if (this.context === "archive") break;
for (let i = 0; i < data.entities.length; i++) {
const uid = data.entities[i];
this.removeResult(this.results, uid);
this.removeResult(this.viewer.results, uid);
this.$clipboard.removeId(uid);
}
break;
case 'created':
this.dirty = true;
this.scrollDisabled = false;
this.complete = false;
break;
default:
console.warn("unexpected event type", ev);
}
},
},
created() {
this.search();
this.subscriptions.push(Event.subscribe("import.completed", (ev, data) => this.onImportCompleted(ev, data)));
this.subscriptions.push(Event.subscribe("photos", (ev, data) => this.onUpdate(ev, data)));
this.subscriptions.push(Event.subscribe("touchmove.top", () => this.refresh()));
this.subscriptions.push(Event.subscribe("touchmove.bottom", () => this.loadMore()));
},
destroyed() {
for (let i = 0; i < this.subscriptions.length; i++) {
Event.unsubscribe(this.subscriptions[i]);
}
},
};
</script>