map: restore functionality
parent
bcc35d6132
commit
7d90aeacb1
|
@ -458,12 +458,13 @@ class ApiBase extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter geological bounds
|
// Filter geological bounds
|
||||||
$minLat = $this->request->getParam('minLat');
|
$bounds = $this->request->getParam('mapbounds');
|
||||||
$maxLat = $this->request->getParam('maxLat');
|
if ($bounds) {
|
||||||
$minLng = $this->request->getParam('minLng');
|
$bounds = explode(',', $bounds);
|
||||||
$maxLng = $this->request->getParam('maxLng');
|
$bounds = array_map('floatval', $bounds);
|
||||||
if ($minLat && $maxLat && $minLng && $maxLng) {
|
if (4 === \count($bounds)) {
|
||||||
$transforms[] = [$this->timelineQuery, 'transformBoundFilter', $minLat, $maxLat, $minLng, $maxLng];
|
$transforms[] = [$this->timelineQuery, 'transformBoundFilter', $bounds];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $transforms;
|
return $transforms;
|
||||||
|
|
|
@ -46,22 +46,24 @@ class LocationController extends ApiBase
|
||||||
|
|
||||||
// Get the folder to show
|
// Get the folder to show
|
||||||
$root = null;
|
$root = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$root = $this->getRequestRoot();
|
$root = $this->getRequestRoot();
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return new JSONResponse(['message' => $e->getMessage()], Http::STATUS_NOT_FOUND);
|
return new JSONResponse(['message' => $e->getMessage()], Http::STATUS_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Just check bound parameters instead of using them; they are used in transformation
|
// Make sure we have bounds
|
||||||
$minLat = $this->request->getParam('minLat');
|
// $bounds = $this->request->getParam('bounds');
|
||||||
$maxLat = $this->request->getParam('maxLat');
|
// if (!$bounds) {
|
||||||
$minLng = $this->request->getParam('minLng');
|
// return new JSONResponse(['message' => 'Invalid perameters'], Http::STATUS_PRECONDITION_FAILED);
|
||||||
$maxLng = $this->request->getParam('maxLng');
|
// }
|
||||||
|
|
||||||
if (!is_numeric($minLat) || !is_numeric($maxLat) || !is_numeric($minLng) || !is_numeric($maxLng)) {
|
// // Make sure we have 4 bounds
|
||||||
return new JSONResponse(['message' => 'Invalid perameters'], Http::STATUS_PRECONDITION_FAILED);
|
// $bounds = explode(',', $bounds);
|
||||||
}
|
// $bounds = array_map('floatval', $bounds);
|
||||||
|
// if (4 !== \count($bounds)) {
|
||||||
|
// return new JSONResponse(['message' => 'Invalid perameters'], Http::STATUS_PRECONDITION_FAILED);
|
||||||
|
// }
|
||||||
|
|
||||||
// Zoom level is used to determine the grid length
|
// Zoom level is used to determine the grid length
|
||||||
$zoomLevel = $this->request->getParam('zoom');
|
$zoomLevel = $this->request->getParam('zoom');
|
||||||
|
|
|
@ -40,14 +40,14 @@ trait TimelineQueryFilters
|
||||||
$query->setMaxResults($limit);
|
$query->setMaxResults($limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function transformBoundFilter(IQueryBuilder &$query, string $userId, string $minLat, string $maxLat, string $minLng, string $maxLng)
|
public function transformBoundFilter(IQueryBuilder &$query, string $userId, array $bounds)
|
||||||
{
|
{
|
||||||
$query->andWhere(
|
$query->andWhere(
|
||||||
$query->expr()->andX(
|
$query->expr()->andX(
|
||||||
$query->expr()->gte('m.lat', $query->createNamedParameter($minLat, IQueryBuilder::PARAM_STR)),
|
$query->expr()->gte('m.lat', $query->createNamedParameter($bounds[0], IQueryBuilder::PARAM_STR)),
|
||||||
$query->expr()->lte('m.lat', $query->createNamedParameter($maxLat, IQueryBuilder::PARAM_STR)),
|
$query->expr()->lte('m.lat', $query->createNamedParameter($bounds[1], IQueryBuilder::PARAM_STR)),
|
||||||
$query->expr()->gte('m.lon', $query->createNamedParameter($minLng, IQueryBuilder::PARAM_STR)),
|
$query->expr()->gte('m.lon', $query->createNamedParameter($bounds[2], IQueryBuilder::PARAM_STR)),
|
||||||
$query->expr()->lte('m.lon', $query->createNamedParameter($maxLng, IQueryBuilder::PARAM_STR))
|
$query->expr()->lte('m.lon', $query->createNamedParameter($bounds[3], IQueryBuilder::PARAM_STR))
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
15
src/App.vue
15
src/App.vue
|
@ -30,7 +30,12 @@
|
||||||
</NcAppNavigation>
|
</NcAppNavigation>
|
||||||
|
|
||||||
<NcAppContent>
|
<NcAppContent>
|
||||||
<div class="outer">
|
<div
|
||||||
|
:class="{
|
||||||
|
outer: true,
|
||||||
|
'remove-gap': removeNavGap,
|
||||||
|
}"
|
||||||
|
>
|
||||||
<router-view />
|
<router-view />
|
||||||
</div>
|
</div>
|
||||||
</NcAppContent>
|
</NcAppContent>
|
||||||
|
@ -144,6 +149,10 @@ export default defineComponent({
|
||||||
showNavigation(): boolean {
|
showNavigation(): boolean {
|
||||||
return !this.$route.name?.endsWith("-share");
|
return !this.$route.name?.endsWith("-share");
|
||||||
},
|
},
|
||||||
|
|
||||||
|
removeNavGap(): boolean {
|
||||||
|
return this.$route.name === "locations";
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
|
@ -330,6 +339,10 @@ export default defineComponent({
|
||||||
padding: 0 0 0 44px;
|
padding: 0 0 0 44px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
&.remove-gap {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<div class="primary">
|
||||||
|
<component :is="primary" />
|
||||||
|
</div>
|
||||||
|
<div class="timeline">
|
||||||
|
<Timeline />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from "vue";
|
||||||
|
import Timeline from "./Timeline.vue";
|
||||||
|
import LocationTopMatter from "./top-matter/LocationTopMatter.vue";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: "SplitTimeline",
|
||||||
|
|
||||||
|
components: {
|
||||||
|
Timeline,
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
primary() {
|
||||||
|
switch (this.$route.name) {
|
||||||
|
case "locations":
|
||||||
|
return LocationTopMatter;
|
||||||
|
default:
|
||||||
|
return "None";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.primary {
|
||||||
|
width: 60%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.timeline {
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
padding-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.container {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.primary {
|
||||||
|
width: 100%;
|
||||||
|
height: 40%;
|
||||||
|
}
|
||||||
|
.timeline {
|
||||||
|
width: 100%;
|
||||||
|
height: 60%;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -5,7 +5,7 @@
|
||||||
:class="{ 'icon-loading': loading > 0 }"
|
:class="{ 'icon-loading': loading > 0 }"
|
||||||
>
|
>
|
||||||
<!-- Static top matter -->
|
<!-- Static top matter -->
|
||||||
<TopMatter ref="topmatter" @updateBoundary="updateBoundary" />
|
<TopMatter ref="topmatter" />
|
||||||
|
|
||||||
<!-- No content found and nothing is loading -->
|
<!-- No content found and nothing is loading -->
|
||||||
<NcEmptyContent
|
<NcEmptyContent
|
||||||
|
@ -140,15 +140,7 @@ import { subscribe, unsubscribe } from "@nextcloud/event-bus";
|
||||||
import NcEmptyContent from "@nextcloud/vue/dist/Components/NcEmptyContent";
|
import NcEmptyContent from "@nextcloud/vue/dist/Components/NcEmptyContent";
|
||||||
|
|
||||||
import { getLayout } from "../services/Layout";
|
import { getLayout } from "../services/Layout";
|
||||||
import {
|
import { IDay, IFolder, IHeadRow, IPhoto, IRow, IRowType } from "../types";
|
||||||
IDay,
|
|
||||||
IFolder,
|
|
||||||
IHeadRow,
|
|
||||||
IPhoto,
|
|
||||||
IRow,
|
|
||||||
IRowType,
|
|
||||||
MapBoundary,
|
|
||||||
} from "../types";
|
|
||||||
import Folder from "./frame/Folder.vue";
|
import Folder from "./frame/Folder.vue";
|
||||||
import Photo from "./frame/Photo.vue";
|
import Photo from "./frame/Photo.vue";
|
||||||
import Tag from "./frame/Tag.vue";
|
import Tag from "./frame/Tag.vue";
|
||||||
|
@ -231,14 +223,6 @@ export default defineComponent({
|
||||||
selectionManager: null as InstanceType<typeof SelectionManager> & any,
|
selectionManager: null as InstanceType<typeof SelectionManager> & any,
|
||||||
/** Scroller manager component */
|
/** Scroller manager component */
|
||||||
scrollerManager: null as InstanceType<typeof ScrollerManager> & any,
|
scrollerManager: null as InstanceType<typeof ScrollerManager> & any,
|
||||||
|
|
||||||
/** The boundary of the map */
|
|
||||||
mapBoundary: {
|
|
||||||
minLat: -90,
|
|
||||||
maxLat: 90,
|
|
||||||
minLng: -180,
|
|
||||||
maxLng: 180,
|
|
||||||
} as MapBoundary,
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
|
@ -341,13 +325,16 @@ export default defineComponent({
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
async routeChange(to: any, from?: any) {
|
async routeChange(to: any, from?: any) {
|
||||||
if (
|
// Always do a hard refresh if the path changes
|
||||||
from?.path !== to.path ||
|
if (from?.path !== to.path) {
|
||||||
JSON.stringify(from.query) !== JSON.stringify(to.query)
|
|
||||||
) {
|
|
||||||
await this.refresh();
|
await this.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Do a soft refresh if the query changes
|
||||||
|
else if (JSON.stringify(from.query) !== JSON.stringify(to.query)) {
|
||||||
|
await this.softRefresh();
|
||||||
|
}
|
||||||
|
|
||||||
// The viewer might change the route immediately again
|
// The viewer might change the route immediately again
|
||||||
await this.$nextTick();
|
await this.$nextTick();
|
||||||
|
|
||||||
|
@ -408,7 +395,9 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
|
|
||||||
isMobileLayout() {
|
isMobileLayout() {
|
||||||
return globalThis.windowInnerWidth <= 600;
|
return (
|
||||||
|
globalThis.windowInnerWidth <= 600 || this.$route.name === "locations"
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
allowBreakout() {
|
allowBreakout() {
|
||||||
|
@ -700,20 +689,17 @@ export default defineComponent({
|
||||||
query.set("tag", <string>this.$route.params.name);
|
query.set("tag", <string>this.$route.params.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Map Bounds
|
||||||
|
if (this.$route.name === "locations" && this.$route.query.b) {
|
||||||
|
query.set("mapbounds", <string>this.$route.query.b);
|
||||||
|
}
|
||||||
|
|
||||||
// Month view
|
// Month view
|
||||||
if (this.isMonthView) {
|
if (this.isMonthView) {
|
||||||
query.set("monthView", "1");
|
query.set("monthView", "1");
|
||||||
query.set("reverse", "1");
|
query.set("reverse", "1");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Geological Bounds
|
|
||||||
if (this.$route.name === "locations") {
|
|
||||||
query.set("minLat", "" + this.mapBoundary.minLat);
|
|
||||||
query.set("maxLat", "" + this.mapBoundary.maxLat);
|
|
||||||
query.set("minLng", "" + this.mapBoundary.minLng);
|
|
||||||
query.set("maxLng", "" + this.mapBoundary.maxLng);
|
|
||||||
}
|
|
||||||
|
|
||||||
return query;
|
return query;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1314,11 +1300,6 @@ export default defineComponent({
|
||||||
this.processDay(day.dayid, newDetail);
|
this.processDay(day.dayid, newDetail);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async updateBoundary(mapBoundary: MapBoundary) {
|
|
||||||
this.mapBoundary = mapBoundary;
|
|
||||||
await this.softRefresh();
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -1347,7 +1328,7 @@ export default defineComponent({
|
||||||
will-change: scroll-position;
|
will-change: scroll-position;
|
||||||
contain: strict;
|
contain: strict;
|
||||||
height: 300px;
|
height: 300px;
|
||||||
width: calc(100% + 20px);
|
width: 100%;
|
||||||
transition: opacity 0.2s ease-in-out;
|
transition: opacity 0.2s ease-in-out;
|
||||||
|
|
||||||
:deep .vue-recycle-scroller__slot {
|
:deep .vue-recycle-scroller__slot {
|
||||||
|
@ -1365,6 +1346,7 @@ export default defineComponent({
|
||||||
&.empty {
|
&.empty {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: none;
|
transition: none;
|
||||||
|
width: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="location-top-matter">
|
<div class="location-top-matter">
|
||||||
<l-map
|
<l-map
|
||||||
style="height: 100%; width: 100%; margin-right: 3.5em; z-index: 0"
|
class="map"
|
||||||
:zoom="zoom"
|
|
||||||
ref="map"
|
ref="map"
|
||||||
@moveend="updateMapAndTimeline"
|
:zoom="zoom"
|
||||||
@zoomend="updateMapAndTimeline"
|
:minZoom="2"
|
||||||
|
@moveend="refresh"
|
||||||
|
@zoomend="refresh"
|
||||||
>
|
>
|
||||||
<l-tile-layer :url="url" :attribution="attribution" />
|
<l-tile-layer :url="url" :attribution="attribution" />
|
||||||
<l-marker
|
<l-marker
|
||||||
|
@ -20,21 +21,20 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, PropType } from "vue";
|
import { defineComponent } from "vue";
|
||||||
import { LMap, LTileLayer, LMarker, LPopup } from "vue2-leaflet";
|
import { LMap, LTileLayer, LMarker, LPopup } from "vue2-leaflet";
|
||||||
|
import { IMarkerCluster } from "../../types";
|
||||||
|
import { Icon } from "leaflet";
|
||||||
|
|
||||||
|
import { API } from "../../services/API";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
import Vue2LeafletMarkerCluster from "vue2-leaflet-markercluster";
|
import Vue2LeafletMarkerCluster from "vue2-leaflet-markercluster";
|
||||||
import "leaflet/dist/leaflet.css";
|
import "leaflet/dist/leaflet.css";
|
||||||
import { IPhoto, MarkerClusters } from "../../types";
|
|
||||||
|
|
||||||
import { Icon } from "leaflet";
|
const TILE_URL = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png";
|
||||||
import axios from "axios";
|
const ATTRIBUTION =
|
||||||
import { API } from "../../services/API";
|
'© <a target="_blank" href="http://osm.org/copyright">OpenStreetMap</a> contributors';
|
||||||
|
|
||||||
type D = Icon.Default & {
|
|
||||||
_getIconUrl?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
delete (Icon.Default.prototype as D)._getIconUrl;
|
|
||||||
|
|
||||||
Icon.Default.mergeOptions({
|
Icon.Default.mergeOptions({
|
||||||
iconRetinaUrl: require("leaflet/dist/images/marker-icon-2x.png"),
|
iconRetinaUrl: require("leaflet/dist/images/marker-icon-2x.png"),
|
||||||
|
@ -53,63 +53,55 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
name: "locations", // add for test
|
url: TILE_URL,
|
||||||
url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
|
attribution: ATTRIBUTION,
|
||||||
attribution:
|
zoom: 2,
|
||||||
'© <a target="_blank" href="http://osm.org/copyright">OpenStreetMap</a> contributors',
|
clusters: [] as IMarkerCluster[],
|
||||||
zoom: 1,
|
|
||||||
clusters: [] as MarkerClusters[],
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
watch: {
|
|
||||||
$route: function (from: any, to: any) {
|
|
||||||
this.createMatter();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.createMatter();
|
const map = this.$refs.map as LMap;
|
||||||
this.updateMapAndTimeline();
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
// Make sure the zoom control doesn't overlap with the navbar
|
||||||
getPhotos() {
|
map.mapObject.zoomControl.setPosition("topright");
|
||||||
let photos: IPhoto[] = [];
|
|
||||||
return photos;
|
// Initialize
|
||||||
},
|
this.refresh();
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
createMatter() {
|
async refresh() {
|
||||||
this.name = <string>this.$route.params.name || "";
|
const map = this.$refs.map as LMap;
|
||||||
},
|
|
||||||
|
|
||||||
async updateMapAndTimeline() {
|
// Get boundaries of the map
|
||||||
let map = this.$refs.map as LMap;
|
const boundary = map.mapObject.getBounds();
|
||||||
let boundary = map.mapObject.getBounds();
|
const minLat = boundary.getSouth();
|
||||||
let minLat = boundary.getSouth();
|
const maxLat = boundary.getNorth();
|
||||||
let maxLat = boundary.getNorth();
|
const minLon = boundary.getWest();
|
||||||
let minLng = boundary.getWest();
|
const maxLon = boundary.getEast();
|
||||||
let maxLng = boundary.getEast();
|
|
||||||
let zoomLevel = map.mapObject.getZoom().toString();
|
|
||||||
|
|
||||||
this.$parent.$emit("updateBoundary", {
|
// Set query parameters to route if required
|
||||||
minLat: minLat,
|
const s = (x: number) => x.toFixed(6);
|
||||||
maxLat: maxLat,
|
const bounds = `${s(minLat)},${s(maxLat)},${s(minLon)},${s(maxLon)}`;
|
||||||
minLng: minLng,
|
const zoom = map.mapObject.getZoom().toString();
|
||||||
maxLng: maxLng,
|
if (this.$route.query.b === bounds && this.$route.query.z === zoom) {
|
||||||
});
|
return;
|
||||||
|
}
|
||||||
|
this.$router.replace({ query: { b: bounds, z: zoom } });
|
||||||
|
|
||||||
|
// Get query parameters for cluster API
|
||||||
|
// const mapWidth = maxLat - minLat;
|
||||||
|
// const mapHeight = maxLon - minLon;
|
||||||
|
|
||||||
let mapWidth = maxLat - minLat;
|
|
||||||
let mapHeight = maxLng - minLng;
|
|
||||||
const query = new URLSearchParams();
|
|
||||||
// Show clusters correctly while draging the map
|
// Show clusters correctly while draging the map
|
||||||
query.set("minLat", (minLat - mapWidth).toString());
|
const query = new URLSearchParams();
|
||||||
query.set("maxLat", (maxLat + mapWidth).toString());
|
// query.set("minLat", (minLat - mapWidth).toString());
|
||||||
query.set("minLng", (minLng - mapHeight).toString());
|
// query.set("maxLat", (maxLat + mapWidth).toString());
|
||||||
query.set("maxLng", (maxLng + mapHeight).toString());
|
// query.set("minLon", (minLon - mapHeight).toString());
|
||||||
query.set("zoom", zoomLevel);
|
// query.set("maxLon", (maxLon + mapHeight).toString());
|
||||||
|
query.set("zoom", zoom);
|
||||||
|
|
||||||
|
// Make API call
|
||||||
const url = API.Q(API.CLUSTERS(), query);
|
const url = API.Q(API.CLUSTERS(), query);
|
||||||
const res = await axios.get(url);
|
const res = await axios.get(url);
|
||||||
this.clusters = res.data;
|
this.clusters = res.data;
|
||||||
|
@ -124,8 +116,14 @@ export default defineComponent({
|
||||||
@import "~leaflet.markercluster/dist/MarkerCluster.Default.css";
|
@import "~leaflet.markercluster/dist/MarkerCluster.Default.css";
|
||||||
|
|
||||||
.location-top-matter {
|
.location-top-matter {
|
||||||
display: flex;
|
height: 100%;
|
||||||
vertical-align: middle;
|
width: 100%;
|
||||||
height: 20em;
|
}
|
||||||
|
|
||||||
|
.map {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
z-index: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
<TagTopMatter v-else-if="type === 2" />
|
<TagTopMatter v-else-if="type === 2" />
|
||||||
<FaceTopMatter v-else-if="type === 3" />
|
<FaceTopMatter v-else-if="type === 3" />
|
||||||
<AlbumTopMatter v-else-if="type === 4" />
|
<AlbumTopMatter v-else-if="type === 4" />
|
||||||
<LocationTopMatter v-else-if="type === 5" />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -65,8 +64,6 @@ export default defineComponent({
|
||||||
return this.$route.params.name
|
return this.$route.params.name
|
||||||
? TopMatterType.TAG
|
? TopMatterType.TAG
|
||||||
: TopMatterType.NONE;
|
: TopMatterType.NONE;
|
||||||
case "locations":
|
|
||||||
return TopMatterType.LOCATION;
|
|
||||||
default:
|
default:
|
||||||
return TopMatterType.NONE;
|
return TopMatterType.NONE;
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,3 +87,18 @@ aside.app-sidebar {
|
||||||
transform: scale(1.05);
|
transform: scale(1.05);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hide scrollbar
|
||||||
|
.recycler::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
width: 0 !important;
|
||||||
|
}
|
||||||
|
.recycler {
|
||||||
|
scrollbar-width: none;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure empty content is full width
|
||||||
|
[role="note"].empty-content {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { translate as t, translatePlural as n } from "@nextcloud/l10n";
|
||||||
import Router from "vue-router";
|
import Router from "vue-router";
|
||||||
import Vue from "vue";
|
import Vue from "vue";
|
||||||
import Timeline from "./components/Timeline.vue";
|
import Timeline from "./components/Timeline.vue";
|
||||||
|
import SplitTimeline from "./components/SplitTimeline.vue";
|
||||||
|
|
||||||
Vue.use(Router);
|
Vue.use(Router);
|
||||||
|
|
||||||
|
@ -141,11 +142,11 @@ export default new Router({
|
||||||
|
|
||||||
{
|
{
|
||||||
path: "/locations",
|
path: "/locations",
|
||||||
component: Timeline,
|
component: SplitTimeline,
|
||||||
name: "locations",
|
name: "locations",
|
||||||
props: (route) => ({
|
props: (route) => ({
|
||||||
rootTitle: t("memories", "Locations"),
|
rootTitle: t("memories", "Locations"),
|
||||||
})
|
}),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
12
src/types.ts
12
src/types.ts
|
@ -240,14 +240,14 @@ export type ISelectionAction = {
|
||||||
allowPublic?: boolean;
|
allowPublic?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MapBoundary = {
|
export type IMapBoundary = {
|
||||||
minLat: number;
|
minLat: number;
|
||||||
maxLat: number;
|
maxLat: number;
|
||||||
minLng: number;
|
minLon: number;
|
||||||
maxLng: number;
|
maxLon: number;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type MarkerClusters = {
|
export type IMarkerCluster = {
|
||||||
center: [number, number];
|
center: [number, number];
|
||||||
count: number;
|
count: number;
|
||||||
}
|
};
|
||||||
|
|
Loading…
Reference in New Issue