parent
9826e57149
commit
f431caeb4c
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue