Places: Improve UX
Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
parent
cf0ea597c4
commit
f39d34b43a
4 changed files with 157 additions and 49 deletions
|
@ -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: '© <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() {
|
||||
|
|
|
@ -35,7 +35,7 @@ export default [
|
|||
name: "albums",
|
||||
path: "/albums",
|
||||
component: Albums,
|
||||
meta: {title: "Photo Albums"},
|
||||
meta: {title: "Albums"},
|
||||
},
|
||||
{
|
||||
name: "album",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue