parent
3bf525193d
commit
7c53248936
|
@ -60,6 +60,7 @@ return [
|
|||
['name' => 'Tags#set', 'url' => '/api/tags/set/{id}', 'verb' => 'PATCH'],
|
||||
|
||||
['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'],
|
||||
|
||||
|
|
|
@ -65,4 +65,16 @@ class MapController extends GenericApiController
|
|||
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,
|
||||
bool $recursive = true,
|
||||
bool $archive = false
|
||||
) {
|
||||
): IQueryBuilder {
|
||||
if (null === $root) {
|
||||
$root = $this->root();
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@ use OCP\IDBConnection;
|
|||
|
||||
trait TimelineQueryMap
|
||||
{
|
||||
use TimelineQueryDays;
|
||||
|
||||
protected IDBConnection $connection;
|
||||
|
||||
public function transformMapBoundsFilter(IQueryBuilder &$query, bool $aggregate, string $bounds, string $table = 'm')
|
||||
|
@ -121,4 +123,36 @@ trait TimelineQueryMap
|
|||
|
||||
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
|
||||
if (this.$route.name === "map" && this.$route.query.b) {
|
||||
API.DAYS_FILTER(
|
||||
query,
|
||||
DaysFilterType.MAP_BOUNDS,
|
||||
<string>this.$route.query.b
|
||||
);
|
||||
if (this.$route.name === "map") {
|
||||
const bounds = <string>this.$route.query.b;
|
||||
if (!bounds) {
|
||||
throw new Error("Missing map bounds");
|
||||
}
|
||||
|
||||
API.DAYS_FILTER(query, DaysFilterType.MAP_BOUNDS, bounds);
|
||||
}
|
||||
|
||||
// Month view
|
||||
|
@ -699,11 +700,20 @@ export default defineComponent({
|
|||
|
||||
/** Fetch timeline main call */
|
||||
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();
|
||||
|
||||
// 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;
|
||||
|
||||
// Try cache first
|
||||
|
|
|
@ -110,12 +110,7 @@ export default defineComponent({
|
|||
map.mapObject.zoomControl.setPosition("topright");
|
||||
|
||||
// Initialize
|
||||
if (this.$route.query.b && this.$route.query.z) {
|
||||
this.setBoundsFromQuery();
|
||||
this.fetchClusters();
|
||||
} else {
|
||||
this.refresh();
|
||||
}
|
||||
this.initialize();
|
||||
|
||||
// If currently dark mode, set isDark
|
||||
const pane = document.querySelector(".leaflet-tile-pane");
|
||||
|
@ -143,11 +138,43 @@ export default defineComponent({
|
|||
|
||||
watch: {
|
||||
$route() {
|
||||
this.fetchClusters();
|
||||
this.initialize(true);
|
||||
},
|
||||
},
|
||||
|
||||
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() {
|
||||
utils.setRenewingTimeout(this, "refreshTimer", this.refresh, 250);
|
||||
},
|
||||
|
|
|
@ -188,4 +188,8 @@ export class API {
|
|||
static MAP_CLUSTER_PREVIEW(id: number) {
|
||||
return tok(gen(`${BASE}/map/clusters/preview/{id}`, { id }));
|
||||
}
|
||||
|
||||
static MAP_INIT() {
|
||||
return tok(gen(`${BASE}/map/init`));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue