|
|
@ -16,6 +16,7 @@ |
|
|
|
id="QuickSearch" |
|
|
|
class="mt-8 mb-4 flex" |
|
|
|
v-on:keyup.enter="searchSelected()" |
|
|
|
:style="{ visibility: isSearchVisible ? 'visible' : 'hidden' }" |
|
|
|
> |
|
|
|
<input |
|
|
|
type="text" |
|
|
@ -40,38 +41,63 @@ |
|
|
|
@click=" |
|
|
|
projects = []; |
|
|
|
isLocalActive = true; |
|
|
|
isMappedActive = false; |
|
|
|
isRemoteActive = false; |
|
|
|
isSearchVisible = true; |
|
|
|
searchLocal(); |
|
|
|
" |
|
|
|
v-bind:class="computedLocalTabStyleClassNames()" |
|
|
|
> |
|
|
|
Nearby |
|
|
|
<!-- restore when the links don't jump around for different numbers |
|
|
|
<span |
|
|
|
class="font-semibold text-sm bg-slate-200 px-1.5 py-0.5 rounded-md" |
|
|
|
v-if="isLocalActive" |
|
|
|
> |
|
|
|
{{ localCount > -1 ? localCount : "?" }} |
|
|
|
</span> |
|
|
|
--> |
|
|
|
</a> |
|
|
|
</li> |
|
|
|
<!-- |
|
|
|
<li> |
|
|
|
<a |
|
|
|
href="#" |
|
|
|
@click=" |
|
|
|
projects = []; |
|
|
|
isLocalActive = false; |
|
|
|
isMappedActive = true; |
|
|
|
isRemoteActive = false; |
|
|
|
isSearchVisible = false; |
|
|
|
" |
|
|
|
v-bind:class="computedMappedTabStyleClassNames()" |
|
|
|
> |
|
|
|
Mapped |
|
|
|
</a> |
|
|
|
</li> |
|
|
|
--> |
|
|
|
<li> |
|
|
|
<a |
|
|
|
href="#" |
|
|
|
@click=" |
|
|
|
projects = []; |
|
|
|
isRemoteActive = true; |
|
|
|
isLocalActive = false; |
|
|
|
isMappedActive = false; |
|
|
|
isRemoteActive = true; |
|
|
|
isSearchVisible = true; |
|
|
|
searchAll(); |
|
|
|
" |
|
|
|
v-bind:class="computedRemoteTabStyleClassNames()" |
|
|
|
> |
|
|
|
Anywhere |
|
|
|
<!-- restore when the links don't jump around for different numbers |
|
|
|
<span |
|
|
|
class="font-semibold text-sm bg-slate-200 px-1.5 py-0.5 rounded-md" |
|
|
|
v-if="isRemoteActive" |
|
|
|
> |
|
|
|
{{ remoteCount > -1 ? remoteCount : "?" }} |
|
|
|
</span> |
|
|
|
--> |
|
|
|
</a> |
|
|
|
</li> |
|
|
|
</ul> |
|
|
@ -89,6 +115,25 @@ |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div v-if="isMappedActive"> |
|
|
|
<div class="mt-4 h-96 w-5/6 mx-auto"> |
|
|
|
<l-map |
|
|
|
ref="map" |
|
|
|
:center="[localCenterLat, localCenterLong]" |
|
|
|
:zoom="2" |
|
|
|
@moveend="onMoveEnd" |
|
|
|
@zoomend="onZoomEnd" |
|
|
|
@zoomstart="onZoomStart" |
|
|
|
> |
|
|
|
<l-tile-layer |
|
|
|
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" |
|
|
|
layer-type="base" |
|
|
|
name="OpenStreetMap" |
|
|
|
/> |
|
|
|
</l-map> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- Loading Animation --> |
|
|
|
<div |
|
|
|
class="fixed left-6 bottom-24 text-center text-4xl leading-none bg-slate-400 text-white w-14 py-2.5 rounded-full" |
|
|
@ -102,7 +147,7 @@ |
|
|
|
<span v-if="searchBox"> None found in the selected area. </span> |
|
|
|
<!-- Otherwise there's no search area selected so we'll just leave the search box for them to click. --> |
|
|
|
</span> |
|
|
|
<span v-else>No projects were found with that search.</span> |
|
|
|
<span v-else-if="isRemoteActive">No projects were found with that search.</span> |
|
|
|
</p> |
|
|
|
</div> |
|
|
|
|
|
|
@ -144,7 +189,15 @@ |
|
|
|
</template> |
|
|
|
|
|
|
|
<script lang="ts"> |
|
|
|
import "leaflet/dist/leaflet.css"; |
|
|
|
import { Map } from "leaflet"; |
|
|
|
import * as L from "leaflet"; |
|
|
|
import { Component, Vue } from "vue-facing-decorator"; |
|
|
|
import { |
|
|
|
LMap, |
|
|
|
LTileLayer, |
|
|
|
LMarker, |
|
|
|
} from "@vue-leaflet/vue-leaflet"; |
|
|
|
import { Router } from "vue-router"; |
|
|
|
|
|
|
|
import QuickNav from "@/components/QuickNav.vue"; |
|
|
@ -158,10 +211,13 @@ import { Contact } from "@/db/tables/contacts"; |
|
|
|
import { BoundingBox } from "@/db/tables/settings"; |
|
|
|
import { didInfo, getHeaders, PlanData } from "@/libs/endorserServer"; |
|
|
|
import { OnboardPage, retrieveAccountDids } from "@/libs/util"; |
|
|
|
import { LocationEvent } from "leaflet"; |
|
|
|
|
|
|
|
@Component({ |
|
|
|
components: { |
|
|
|
InfiniteScroll, |
|
|
|
LMap, |
|
|
|
LTileLayer, |
|
|
|
OnboardingDialog, |
|
|
|
ProjectIcon, |
|
|
|
QuickNav, |
|
|
@ -179,10 +235,16 @@ export default class DiscoverView extends Vue { |
|
|
|
projects: PlanData[] = []; |
|
|
|
isLoading = false; |
|
|
|
isLocalActive = true; |
|
|
|
isMappedActive = false; |
|
|
|
isRemoteActive = false; |
|
|
|
isSearchVisible = true; |
|
|
|
localCenterLat = 0; |
|
|
|
localCenterLong = 0; |
|
|
|
localCount = -1; |
|
|
|
markers: { [key: string]: L.Marker } = {}; |
|
|
|
remoteCount = -1; |
|
|
|
searchBox: { name: string; bbox: BoundingBox } | null = null; |
|
|
|
zoomedSoDoNotMove = false; |
|
|
|
|
|
|
|
// make this function available to the Vue template |
|
|
|
didInfo = didInfo; |
|
|
@ -207,8 +269,13 @@ export default class DiscoverView extends Vue { |
|
|
|
|
|
|
|
if (this.searchBox) { |
|
|
|
await this.searchLocal(); |
|
|
|
|
|
|
|
const bbox = this.searchBox.bbox; |
|
|
|
this.localCenterLat = (bbox.maxLat + bbox.minLat) / 2; |
|
|
|
this.localCenterLong = (bbox.eastLong + bbox.westLong) / 2; |
|
|
|
} else { |
|
|
|
this.isLocalActive = false; |
|
|
|
this.isMappedActive = false; |
|
|
|
this.isRemoteActive = true; |
|
|
|
await this.searchAll(); |
|
|
|
} |
|
|
@ -222,6 +289,9 @@ export default class DiscoverView extends Vue { |
|
|
|
public async searchSelected() { |
|
|
|
if (this.isLocalActive) { |
|
|
|
await this.searchLocal(); |
|
|
|
} else if (this.isMappedActive) { |
|
|
|
this.isRemoteActive = true; |
|
|
|
await this.searchAll(); |
|
|
|
} else { |
|
|
|
await this.searchAll(); |
|
|
|
} |
|
|
@ -406,12 +476,62 @@ export default class DiscoverView extends Vue { |
|
|
|
const latestProject = this.projects[this.projects.length - 1]; |
|
|
|
if (this.isLocalActive) { |
|
|
|
this.searchLocal(latestProject["rowid"]); |
|
|
|
} else if (this.isMappedActive) { |
|
|
|
// don't do anything since mapped items only show a limited number |
|
|
|
} else if (this.isRemoteActive) { |
|
|
|
this.searchAll(latestProject["rowid"]); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
async onMoveEnd(event: LocationEvent) { |
|
|
|
if (this.zoomedSoDoNotMove) { |
|
|
|
this.zoomedSoDoNotMove = false; |
|
|
|
} else { |
|
|
|
// not part of a zoom so request tiles |
|
|
|
await this.requestTiles(event); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
async onZoomEnd(event: LocationEvent) { |
|
|
|
await this.requestTiles(event); |
|
|
|
} |
|
|
|
|
|
|
|
onZoomStart(event: LocationEvent) { |
|
|
|
this.zoomedSoDoNotMove = true; |
|
|
|
} |
|
|
|
|
|
|
|
async requestTiles(event: LocationEvent) { |
|
|
|
const bounds = event.target.getBounds(); |
|
|
|
const queryParams = [ |
|
|
|
"minLocLat=" + bounds?.getSouthWest().lat, |
|
|
|
"maxLocLat=" + bounds?.getNorthEast().lat, |
|
|
|
"westLocLon=" + bounds?.getSouthWest().lng, |
|
|
|
"eastLocLon=" + bounds?.getNorthEast().lng, |
|
|
|
].join("&"); |
|
|
|
const response = await fetch(this.apiServer + "/api/v2/report/planCountsByBBox?" + queryParams); |
|
|
|
if (response.status === 200) { |
|
|
|
const results = await response.json(); |
|
|
|
if (results.data?.tiles?.length > 0) { |
|
|
|
Object.values(this.markers).forEach(marker => marker.remove()); |
|
|
|
this.markers = {}; |
|
|
|
for (const tile of results.data.tiles) { |
|
|
|
const pinLat = (tile.minFoundLat + tile.maxFoundLat) / 2; |
|
|
|
const pinLon = (tile.minFoundLon + tile.maxFoundLon) / 2; |
|
|
|
const numberIcon = L.divIcon({ |
|
|
|
className: "numbered-marker", |
|
|
|
html: `<strong>${tile.recordCount}</strong>`, |
|
|
|
iconSize: [24, 24], |
|
|
|
iconAnchor: [12, 12], // coordinates of the tip of the icon relative to the top-left corner of the icon |
|
|
|
}); |
|
|
|
const marker = L.marker([pinLat, pinLon], { icon: numberIcon }); |
|
|
|
marker.addTo(event.target); |
|
|
|
this.markers["" + tile.indexLat + "x" + tile.indexLon] = marker; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Handle clicking on a project entry found in the list |
|
|
|
* @param id of the project |
|
|
@ -441,6 +561,24 @@ export default class DiscoverView extends Vue { |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
public computedMappedTabStyleClassNames() { |
|
|
|
return { |
|
|
|
"inline-block": true, |
|
|
|
"py-3": true, |
|
|
|
"rounded-t-lg": true, |
|
|
|
"border-b-2": true, |
|
|
|
|
|
|
|
active: this.isMappedActive, |
|
|
|
"text-black": this.isMappedActive, |
|
|
|
"border-black": this.isMappedActive, |
|
|
|
"font-semibold": this.isMappedActive, |
|
|
|
|
|
|
|
"text-blue-600": !this.isMappedActive, |
|
|
|
"border-transparent": !this.isMappedActive, |
|
|
|
"hover:border-slate-400": !this.isMappedActive, |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
public computedRemoteTabStyleClassNames() { |
|
|
|
return { |
|
|
|
"inline-block": true, |
|
|
@ -460,3 +598,18 @@ export default class DiscoverView extends Vue { |
|
|
|
} |
|
|
|
} |
|
|
|
</script> |
|
|
|
<style> |
|
|
|
.numbered-marker { |
|
|
|
display: flex; |
|
|
|
align-items: center; |
|
|
|
justify-content: center; |
|
|
|
font-size: 14px; |
|
|
|
font-weight: bold; |
|
|
|
color: white; |
|
|
|
background: blue; |
|
|
|
width: 24px; |
|
|
|
height: 24px; |
|
|
|
border-radius: 50%; |
|
|
|
border: 2px solid white; |
|
|
|
} |
|
|
|
</style> |