Places: Improve UX

Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
Michael Mayer 2019-12-17 15:28:54 +01:00
parent cf0ea597c4
commit f39d34b43a
4 changed files with 157 additions and 49 deletions

View file

@ -1,8 +1,15 @@
<template>
<v-container fluid fill-height class="pa-0 p-page p-page-places">
<l-map :zoom="zoom" :center="center" :bounds="bounds" :options="options">
<l-map :zoom="zoom" :center="center" :bounds="bounds" :options="options"
@update:zoom="onZoom"
@update:center="onCenter"
@update:bounds="onBounds">
<l-control position="bottomright">
<v-toolbar dense floating color="accent lighten-4" v-on:dblclick.stop v-on:click.stop>
<!-- v-container class="pb-0 pt-0 pl-3 pr-3 mb-0 mt-0" v-if="loading">
<v-progress-linear :indeterminate="true" color="light-blue lighten-1"></v-progress-linear>
</v-container -->
<v-toolbar dense floating color="accent lighten-4 mt-0" v-on:dblclick.stop v-on:click.stop>
<v-btn icon v-on:click="currentPosition()">
<v-icon>my_location</v-icon>
</v-btn>
@ -20,9 +27,9 @@
</v-toolbar>
</l-control>
<l-tile-layer :url="url" :attribution="attribution"></l-tile-layer>
<l-marker v-for="photo in photos" v-bind:data="photo"
v-bind:key="photo.index" :lat-lng="photo.location" :icon="photo.icon"
:options="photo.options" @click="openPhoto(photo.index)"></l-marker>
<l-marker v-for="(photo, index) in photos" v-bind:data="photo"
v-bind:key="index" :lat-lng="photo.location" :icon="photo.icon"
:options="photo.options" @click="openPhoto(index)"></l-marker>
<l-marker v-if="position" :lat-lng="position" :z-index-offset="100"></l-marker>
</l-map>
</v-container>
@ -35,16 +42,17 @@
export default {
name: 'p-page-places',
data() {
const pos = this.startPos();
const query = this.$route.query;
const q = query['q'] ? query['q'] : '';
const lat = query['lat'] ? query['lat'] : '';
const long = query['long'] ? query['long'] : '';
const dist = query['dist'] ? query['dist'] : 20;
const q = query['q'] ? query['q'] : "";
const zoom = query['zoom'] ? parseInt(query['zoom']) : 15;
const dist = this.getDistance(zoom);
return {
zoom: 15,
loading: false,
zoom: zoom,
position: null,
center: L.latLng(0, 0),
center: L.latLng(parseFloat(pos.lat), parseFloat(pos.long)),
url: 'https://{s}.tile.osm.org/{z}/{x}/{y}.png',
attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors',
options: {
@ -57,9 +65,10 @@
results: [],
query: {
q: q,
lat: lat,
long: long,
dist: dist,
lat: pos.lat,
long: pos.long,
dist: dist.toString(),
zoom: zoom.toString(),
},
offset: 0,
pageSize: 101,
@ -72,25 +81,105 @@
labels: {
search: this.$gettext("Search"),
},
config: this.$config.values,
}
},
methods: {
getDistance(zoom) {
switch (zoom) {
case 18:
return 1;
case 17:
return 3;
case 16:
return 5;
case 15:
return 6;
case 14:
return 10;
case 13:
return 15;
case 12:
return 30;
case 11:
return 60;
case 10:
return 100;
case 9:
return 300;
case 8:
return 400;
case 7:
return 800;
case 6:
return 1600;
case 5:
return 2000;
}
return 2500;
},
onZoom(zoom) {
this.query.zoom = zoom.toString();
this.query.dist = this.getDistance(zoom).toString();
this.search();
},
onBounds() {
},
onCenter(pos) {
if (this.query.lat === pos.lat.toString() && this.query.long === pos.lng.toString()) {
return;
}
this.query.lat = pos.lat.toString();
this.query.long = pos.lng.toString();
this.search();
},
startPos() {
const pos = this.$config.getValue("pos");
const query = this.$route.query;
let result = {
lat: pos.lat.toString(),
long: pos.long.toString(),
};
const queryLat = query['lat'];
const queryLong = query['long'];
let storedLat = window.localStorage.getItem("lat");
let storedLong = window.localStorage.getItem("long");
if (queryLat && queryLong) {
result.lat = queryLat;
result.long = queryLong;
} else if (storedLat && storedLong) {
result.lat = storedLat;
result.long = storedLong;
}
return result;
},
openPhoto(index) {
this.$viewer.show(this.results, index)
},
currentPositionSuccess(position) {
this.query.lat = position.coords.latitude;
this.query.long = position.coords.longitude;
onPosition(position) {
this.position = L.latLng(position.coords.latitude, position.coords.longitude);
this.query.lat = position.coords.latitude.toString();
this.query.long = position.coords.longitude.toString();
this.query.q = "";
this.search();
},
currentPositionError(error) {
onPositionError(error) {
this.$notify.warning(error.message);
},
currentPosition() {
if ("geolocation" in navigator) {
this.$notify.success(this.$gettext('Finding your position...'));
navigator.geolocation.getCurrentPosition(this.currentPositionSuccess, this.currentPositionError);
navigator.geolocation.getCurrentPosition(this.onPosition.bind(this), this.onPositionError.bind(this));
} else {
this.$notify.warning(this.$gettext('Geolocation is not available'));
}
@ -101,6 +190,7 @@
this.search();
},
clearQuery() {
this.position = null;
this.query.q = "";
this.query.lat = "";
this.query.long = "";
@ -130,20 +220,23 @@
}
},
updateMap(results) {
const photos = [];
const firstResult = [];
this.resetBoundingBox();
// this.resetBoundingBox();
for (let i = 0, len = results.length; i < len; i++) {
let result = results[i];
if (!result.hasLocation()) continue;
this.fitBoundingBox(result.PhotoLat, result.PhotoLong);
// this.fitBoundingBox(result.PhotoLat, result.PhotoLong);
photos.push({
let index = this.results.findIndex((p) => p.PhotoUUID === result.PhotoUUID);
if (index !== -1) continue;
this.photos.push({
id: result.getId(),
index: i,
options: {
title: result.getTitle(),
clickable: true,
@ -158,47 +251,45 @@
});
}
if (photos.length === 0) {
if (this.photos.length === 0) {
this.$notify.warning(this.$gettext('No locations found'));
return;
}
this.results = results;
this.photos = photos;
this.$nextTick(() => {
this.center = photos[0].location;
this.bounds = [[this.maxLat, this.minLong], [this.minLat, this.maxLong]];
if(!this.query.q) return;
this.center = this.photos[this.photos.length - 1].location;
this.position = this.photos[this.photos.length - 1].location;
});
if (photos.length > 100) {
this.$notify.info(this.$gettext('More than 100 photos found'));
} else {
this.$notify.info(photos.length + this.$gettext(' photos found'));
}
},
updateQuery() {
this.$router.replace({query: this.query}).catch(err => {});
const query = Object(this.query);
if(this.query.lat && this.query.long) {
this.position = L.latLng(this.query.lat, this.query.long);
this.center = L.latLng(this.query.lat, this.query.long);
if (this.query.lat && this.query.long) {
this.center = L.latLng(parseFloat(this.query.lat), parseFloat(this.query.long));
window.localStorage.setItem("lat", this.query.lat.toString());
window.localStorage.setItem("long", this.query.long.toString());
} else {
this.position = null;
}
if (JSON.stringify(this.$route.query) !== JSON.stringify(query)) {
this.$router.replace({query: query});
}
},
search() {
if (this.loading) return;
// Don't query the same data more than once
if (JSON.stringify(this.lastQuery) === JSON.stringify(this.query)) return;
this.offset = 0;
this.loading = true;
Object.assign(this.lastQuery, this.query);
this.offset = 0;
this.updateQuery();
this.$router.replace({query: this.query}).catch(err => {});
const params = {
count: this.pageSize,
offset: this.offset,
@ -208,13 +299,14 @@
Object.assign(params, this.query);
Photo.search(params).then(response => {
this.loading = false;
if (!response.models.length) {
this.$notify.warning(this.$gettext('No photos found'));
return;
}
this.updateMap(response.models);
});
}).catch(() => this.loading = false);
},
},
created() {

View file

@ -35,7 +35,7 @@ export default [
name: "albums",
path: "/albums",
component: Albums,
meta: {title: "Photo Albums"},
meta: {title: "Albums"},
},
{
name: "album",

View file

@ -577,6 +577,21 @@ func (c *Config) ClientConfig() ClientConfig {
LocCountryCode string
}
var position struct {
PhotoUUID string `json:"uuid"`
PhotoLat float64 `json:"lat"`
PhotoLong float64 `json:"long"`
TakenAt time.Time `json:"utc"`
TakenAtLocal time.Time `json:"time"`
}
db.Table("photos").
Select("photo_uuid, photo_lat, photo_long, taken_at, taken_at_local").
Where("deleted_at IS NULL AND photo_lat != 0 AND photo_long != 0").
Order("taken_at DESC").
Limit(1).Offset(0).
Take(&position)
var countries []country
var count = struct {
Photos uint `json:"photos"`
@ -644,6 +659,7 @@ func (c *Config) ClientConfig() ClientConfig {
"cssHash": cssHash,
"settings": c.Settings(),
"count": count,
"pos": position,
}
return result

View file

@ -267,8 +267,8 @@ func (s *Repo) Photos(f form.PhotoSearch) (results []PhotoResult, err error) {
if f.Dist == 0 {
f.Dist = 20
} else if f.Dist > 1000 {
f.Dist = 1000
} else if f.Dist > 5000 {
f.Dist = 5000
}
// Inaccurate distance search, but probably 'good enough' for now