forked from trent_larson/crowd-funder-for-time-pwa
add requests for map tiles with counts of plans (commented out)
This commit is contained in:
@@ -6,7 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
|
||||
## [0.3.42] - 2024.12.27
|
||||
## [0.3...]
|
||||
### Added
|
||||
- Projects on a map
|
||||
|
||||
|
||||
## [0.3.42] - 2024.12.27 - 9751934bc24a1040415a8cfeacbae59ed91f92a5
|
||||
### Added
|
||||
- Link from certificate page to the claim
|
||||
### Changed
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
"@veramo/did-provider-peer": "^6.0.0",
|
||||
"@veramo/did-resolver": "^5.6.0",
|
||||
"@veramo/key-manager": "^5.6.0",
|
||||
"@vue-leaflet/vue-leaflet": "^0.10.1",
|
||||
"@vueuse/core": "^10.9.0",
|
||||
"@zxing/text-encoding": "^0.9.0",
|
||||
"asn1-ber": "^1.2.2",
|
||||
@@ -52,6 +53,7 @@
|
||||
"jdenticon": "^3.2.0",
|
||||
"js-generate-password": "^0.1.9",
|
||||
"js-yaml": "^4.1.0",
|
||||
"leaflet": "^1.9.4",
|
||||
"localstorage-slim": "^2.7.0",
|
||||
"lru-cache": "^10.2.0",
|
||||
"luxon": "^3.4.4",
|
||||
@@ -91,14 +93,12 @@
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"@vue-leaflet/vue-leaflet": "^0.10.1",
|
||||
"@vue/eslint-config-typescript": "^11.0.3",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-vue": "^9.23.0",
|
||||
"leaflet": "^1.9.4",
|
||||
"postcss": "^8.4.38",
|
||||
"prettier": "^3.2.5",
|
||||
"tailwindcss": "^3.4.1",
|
||||
|
||||
@@ -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 = [];
|
||||
isRemoteActive = true;
|
||||
isLocalActive = false;
|
||||
isMappedActive = true;
|
||||
isRemoteActive = false;
|
||||
isSearchVisible = false;
|
||||
"
|
||||
v-bind:class="computedMappedTabStyleClassNames()"
|
||||
>
|
||||
Mapped
|
||||
</a>
|
||||
</li>
|
||||
-->
|
||||
<li>
|
||||
<a
|
||||
href="#"
|
||||
@click="
|
||||
projects = [];
|
||||
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>
|
||||
@@ -68,7 +68,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4 aspect-video">
|
||||
<div class="aspect-video">
|
||||
<l-map
|
||||
ref="map"
|
||||
:center="[localCenterLat, localCenterLong]"
|
||||
@@ -129,7 +129,7 @@ const DEFAULT_ZOOM = 2;
|
||||
LTileLayer,
|
||||
},
|
||||
})
|
||||
export default class DiscoverView extends Vue {
|
||||
export default class SearchAreaView extends Vue {
|
||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||
|
||||
isChoosingSearchBox = false;
|
||||
@@ -166,8 +166,8 @@ export default class DiscoverView extends Vue {
|
||||
// This doesn't seem like the right approach but it's the only way I can find to get the screen bounds.
|
||||
const bounds = event.target.boxZoom?._map?.getBounds();
|
||||
if (bounds) {
|
||||
latDiff = Math.abs(bounds._northEast.lat - bounds._southWest.lat) / 8;
|
||||
longDiff = Math.abs(bounds._northEast.lng - bounds._southWest.lng) / 8;
|
||||
latDiff = Math.abs(bounds.getNorthEast().lat - bounds.getSouthWest().lat) / 8;
|
||||
longDiff = Math.abs(bounds.getNorthEast().lng - bounds.getSouthWest().lng) / 8;
|
||||
}
|
||||
this.localLatDiff = latDiff;
|
||||
this.localLongDiff = longDiff;
|
||||
|
||||
Reference in New Issue
Block a user