parent
3bf525193d
commit
7c53248936
|
@ -60,6 +60,7 @@ return [
|
||||||
['name' => 'Tags#set', 'url' => '/api/tags/set/{id}', 'verb' => 'PATCH'],
|
['name' => 'Tags#set', 'url' => '/api/tags/set/{id}', 'verb' => 'PATCH'],
|
||||||
|
|
||||||
['name' => 'Map#clusters', 'url' => '/api/map/clusters', 'verb' => 'GET'],
|
['name' => 'Map#clusters', 'url' => '/api/map/clusters', 'verb' => 'GET'],
|
||||||
|
['name' => 'Map#init', 'url' => '/api/map/init', 'verb' => 'GET'],
|
||||||
|
|
||||||
['name' => 'Archive#archive', 'url' => '/api/archive/{id}', 'verb' => 'PATCH'],
|
['name' => 'Archive#archive', 'url' => '/api/archive/{id}', 'verb' => 'PATCH'],
|
||||||
|
|
||||||
|
|
|
@ -65,4 +65,16 @@ class MapController extends GenericApiController
|
||||||
return new JSONResponse($clusters);
|
return new JSONResponse($clusters);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*/
|
||||||
|
public function init(): Http\Response
|
||||||
|
{
|
||||||
|
return Util::guardEx(function () {
|
||||||
|
return new JSONResponse([
|
||||||
|
'pos' => $this->timelineQuery->getMapInitialPosition(),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -211,7 +211,7 @@ trait TimelineQueryDays
|
||||||
?TimelineRoot $root = null,
|
?TimelineRoot $root = null,
|
||||||
bool $recursive = true,
|
bool $recursive = true,
|
||||||
bool $archive = false
|
bool $archive = false
|
||||||
) {
|
): IQueryBuilder {
|
||||||
if (null === $root) {
|
if (null === $root) {
|
||||||
$root = $this->root();
|
$root = $this->root();
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,8 @@ use OCP\IDBConnection;
|
||||||
|
|
||||||
trait TimelineQueryMap
|
trait TimelineQueryMap
|
||||||
{
|
{
|
||||||
|
use TimelineQueryDays;
|
||||||
|
|
||||||
protected IDBConnection $connection;
|
protected IDBConnection $connection;
|
||||||
|
|
||||||
public function transformMapBoundsFilter(IQueryBuilder &$query, bool $aggregate, string $bounds, string $table = 'm')
|
public function transformMapBoundsFilter(IQueryBuilder &$query, bool $aggregate, string $bounds, string $table = 'm')
|
||||||
|
@ -121,4 +123,36 @@ trait TimelineQueryMap
|
||||||
|
|
||||||
return $files;
|
return $files;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the suggested initial coordinates for the map.
|
||||||
|
* Uses the coordinates of the newest photo (by date).
|
||||||
|
*/
|
||||||
|
public function getMapInitialPosition(): ?array
|
||||||
|
{
|
||||||
|
$query = $this->connection->getQueryBuilder();
|
||||||
|
|
||||||
|
// SELECT coordinates
|
||||||
|
$query->select('m.lat', 'm.lon')->from('memories', 'm');
|
||||||
|
|
||||||
|
// WHERE this photo is in the user's requested folder recursively
|
||||||
|
$query = $this->joinFilecache($query);
|
||||||
|
|
||||||
|
// WHERE this photo has coordinates
|
||||||
|
$query->where($query->expr()->andX(
|
||||||
|
$query->expr()->isNotNull('m.lat'),
|
||||||
|
$query->expr()->isNotNull('m.lon')
|
||||||
|
));
|
||||||
|
|
||||||
|
// ORDER BY datetaken DESC
|
||||||
|
$query->orderBy('m.datetaken', 'DESC');
|
||||||
|
|
||||||
|
// LIMIT 1
|
||||||
|
$query->setMaxResults(1);
|
||||||
|
|
||||||
|
// FETCH coordinates
|
||||||
|
$coords = $this->executeQueryWithCTEs($query)->fetch();
|
||||||
|
|
||||||
|
return $coords ?: null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -627,12 +627,13 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map Bounds
|
// Map Bounds
|
||||||
if (this.$route.name === "map" && this.$route.query.b) {
|
if (this.$route.name === "map") {
|
||||||
API.DAYS_FILTER(
|
const bounds = <string>this.$route.query.b;
|
||||||
query,
|
if (!bounds) {
|
||||||
DaysFilterType.MAP_BOUNDS,
|
throw new Error("Missing map bounds");
|
||||||
<string>this.$route.query.b
|
}
|
||||||
);
|
|
||||||
|
API.DAYS_FILTER(query, DaysFilterType.MAP_BOUNDS, bounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Month view
|
// Month view
|
||||||
|
@ -699,11 +700,20 @@ export default defineComponent({
|
||||||
|
|
||||||
/** Fetch timeline main call */
|
/** Fetch timeline main call */
|
||||||
async fetchDays(noCache = false) {
|
async fetchDays(noCache = false) {
|
||||||
// Get top folders if route
|
// Awaiting this is important because the folders must render
|
||||||
|
// before the timeline to prevent glitches
|
||||||
await this.fetchFolders();
|
await this.fetchFolders();
|
||||||
|
|
||||||
// Get URL an cache identifier
|
// Get URL an cache identifier
|
||||||
const url = API.Q(API.DAYS(), this.getQuery());
|
let url: string;
|
||||||
|
try {
|
||||||
|
url = API.Q(API.DAYS(), this.getQuery());
|
||||||
|
} catch (err) {
|
||||||
|
// Likely invalid route; just quit doing anything
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// URL for cached data
|
||||||
const cacheUrl = <string>this.$route.name + url;
|
const cacheUrl = <string>this.$route.name + url;
|
||||||
|
|
||||||
// Try cache first
|
// Try cache first
|
||||||
|
|
|
@ -110,12 +110,7 @@ export default defineComponent({
|
||||||
map.mapObject.zoomControl.setPosition("topright");
|
map.mapObject.zoomControl.setPosition("topright");
|
||||||
|
|
||||||
// Initialize
|
// Initialize
|
||||||
if (this.$route.query.b && this.$route.query.z) {
|
this.initialize();
|
||||||
this.setBoundsFromQuery();
|
|
||||||
this.fetchClusters();
|
|
||||||
} else {
|
|
||||||
this.refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
// If currently dark mode, set isDark
|
// If currently dark mode, set isDark
|
||||||
const pane = document.querySelector(".leaflet-tile-pane");
|
const pane = document.querySelector(".leaflet-tile-pane");
|
||||||
|
@ -143,11 +138,43 @@ export default defineComponent({
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
$route() {
|
$route() {
|
||||||
this.fetchClusters();
|
this.initialize(true);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
/**
|
||||||
|
* Get initial coordinates for display and set them.
|
||||||
|
* Then fetch clusters.
|
||||||
|
*/
|
||||||
|
async initialize(reinit: boolean = false) {
|
||||||
|
// Check if we have bounds and zoom in query
|
||||||
|
if (this.$route.query.b && this.$route.query.z) {
|
||||||
|
if (!reinit) {
|
||||||
|
this.setBoundsFromQuery();
|
||||||
|
}
|
||||||
|
return await this.fetchClusters();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, get location from server
|
||||||
|
try {
|
||||||
|
const init = await axios.get<any>(API.MAP_INIT());
|
||||||
|
|
||||||
|
// Init data contains position information
|
||||||
|
const map = this.$refs.map as LMap;
|
||||||
|
const pos = init?.data?.pos;
|
||||||
|
if (!pos?.lat || !pos?.lon) {
|
||||||
|
throw new Error("No position data");
|
||||||
|
}
|
||||||
|
|
||||||
|
// This will trigger route change -> fetchClusters
|
||||||
|
map.mapObject.setView([pos.lat, pos.lon], 11);
|
||||||
|
} catch (e) {
|
||||||
|
// Make sure we initialize clusters anyway
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
async refreshDebounced() {
|
async refreshDebounced() {
|
||||||
utils.setRenewingTimeout(this, "refreshTimer", this.refresh, 250);
|
utils.setRenewingTimeout(this, "refreshTimer", this.refresh, 250);
|
||||||
},
|
},
|
||||||
|
|
|
@ -188,4 +188,8 @@ export class API {
|
||||||
static MAP_CLUSTER_PREVIEW(id: number) {
|
static MAP_CLUSTER_PREVIEW(id: number) {
|
||||||
return tok(gen(`${BASE}/map/clusters/preview/{id}`, { id }));
|
return tok(gen(`${BASE}/map/clusters/preview/{id}`, { id }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static MAP_INIT() {
|
||||||
|
return tok(gen(`${BASE}/map/init`));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue