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).
|
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
|
### Added
|
||||||
- Link from certificate page to the claim
|
- Link from certificate page to the claim
|
||||||
### Changed
|
### Changed
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
"@veramo/did-provider-peer": "^6.0.0",
|
"@veramo/did-provider-peer": "^6.0.0",
|
||||||
"@veramo/did-resolver": "^5.6.0",
|
"@veramo/did-resolver": "^5.6.0",
|
||||||
"@veramo/key-manager": "^5.6.0",
|
"@veramo/key-manager": "^5.6.0",
|
||||||
|
"@vue-leaflet/vue-leaflet": "^0.10.1",
|
||||||
"@vueuse/core": "^10.9.0",
|
"@vueuse/core": "^10.9.0",
|
||||||
"@zxing/text-encoding": "^0.9.0",
|
"@zxing/text-encoding": "^0.9.0",
|
||||||
"asn1-ber": "^1.2.2",
|
"asn1-ber": "^1.2.2",
|
||||||
@@ -52,6 +53,7 @@
|
|||||||
"jdenticon": "^3.2.0",
|
"jdenticon": "^3.2.0",
|
||||||
"js-generate-password": "^0.1.9",
|
"js-generate-password": "^0.1.9",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
|
"leaflet": "^1.9.4",
|
||||||
"localstorage-slim": "^2.7.0",
|
"localstorage-slim": "^2.7.0",
|
||||||
"lru-cache": "^10.2.0",
|
"lru-cache": "^10.2.0",
|
||||||
"luxon": "^3.4.4",
|
"luxon": "^3.4.4",
|
||||||
@@ -91,14 +93,12 @@
|
|||||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||||
"@typescript-eslint/parser": "^6.21.0",
|
"@typescript-eslint/parser": "^6.21.0",
|
||||||
"@vitejs/plugin-vue": "^5.0.4",
|
"@vitejs/plugin-vue": "^5.0.4",
|
||||||
"@vue-leaflet/vue-leaflet": "^0.10.1",
|
|
||||||
"@vue/eslint-config-typescript": "^11.0.3",
|
"@vue/eslint-config-typescript": "^11.0.3",
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.19",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-prettier": "^5.1.3",
|
"eslint-plugin-prettier": "^5.1.3",
|
||||||
"eslint-plugin-vue": "^9.23.0",
|
"eslint-plugin-vue": "^9.23.0",
|
||||||
"leaflet": "^1.9.4",
|
|
||||||
"postcss": "^8.4.38",
|
"postcss": "^8.4.38",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"tailwindcss": "^3.4.1",
|
"tailwindcss": "^3.4.1",
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
id="QuickSearch"
|
id="QuickSearch"
|
||||||
class="mt-8 mb-4 flex"
|
class="mt-8 mb-4 flex"
|
||||||
v-on:keyup.enter="searchSelected()"
|
v-on:keyup.enter="searchSelected()"
|
||||||
|
:style="{ visibility: isSearchVisible ? 'visible' : 'hidden' }"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@@ -40,38 +41,63 @@
|
|||||||
@click="
|
@click="
|
||||||
projects = [];
|
projects = [];
|
||||||
isLocalActive = true;
|
isLocalActive = true;
|
||||||
|
isMappedActive = false;
|
||||||
isRemoteActive = false;
|
isRemoteActive = false;
|
||||||
|
isSearchVisible = true;
|
||||||
searchLocal();
|
searchLocal();
|
||||||
"
|
"
|
||||||
v-bind:class="computedLocalTabStyleClassNames()"
|
v-bind:class="computedLocalTabStyleClassNames()"
|
||||||
>
|
>
|
||||||
Nearby
|
Nearby
|
||||||
|
<!-- restore when the links don't jump around for different numbers
|
||||||
<span
|
<span
|
||||||
class="font-semibold text-sm bg-slate-200 px-1.5 py-0.5 rounded-md"
|
class="font-semibold text-sm bg-slate-200 px-1.5 py-0.5 rounded-md"
|
||||||
v-if="isLocalActive"
|
v-if="isLocalActive"
|
||||||
>
|
>
|
||||||
{{ localCount > -1 ? localCount : "?" }}
|
{{ localCount > -1 ? localCount : "?" }}
|
||||||
</span>
|
</span>
|
||||||
|
-->
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<!--
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
@click="
|
@click="
|
||||||
projects = [];
|
projects = [];
|
||||||
isRemoteActive = true;
|
|
||||||
isLocalActive = false;
|
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();
|
searchAll();
|
||||||
"
|
"
|
||||||
v-bind:class="computedRemoteTabStyleClassNames()"
|
v-bind:class="computedRemoteTabStyleClassNames()"
|
||||||
>
|
>
|
||||||
Anywhere
|
Anywhere
|
||||||
|
<!-- restore when the links don't jump around for different numbers
|
||||||
<span
|
<span
|
||||||
class="font-semibold text-sm bg-slate-200 px-1.5 py-0.5 rounded-md"
|
class="font-semibold text-sm bg-slate-200 px-1.5 py-0.5 rounded-md"
|
||||||
v-if="isRemoteActive"
|
v-if="isRemoteActive"
|
||||||
>
|
>
|
||||||
{{ remoteCount > -1 ? remoteCount : "?" }}
|
{{ remoteCount > -1 ? remoteCount : "?" }}
|
||||||
</span>
|
</span>
|
||||||
|
-->
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -89,6 +115,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</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 -->
|
<!-- Loading Animation -->
|
||||||
<div
|
<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"
|
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>
|
<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. -->
|
<!-- Otherwise there's no search area selected so we'll just leave the search box for them to click. -->
|
||||||
</span>
|
</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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -144,7 +189,15 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<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 { Component, Vue } from "vue-facing-decorator";
|
||||||
|
import {
|
||||||
|
LMap,
|
||||||
|
LTileLayer,
|
||||||
|
LMarker,
|
||||||
|
} from "@vue-leaflet/vue-leaflet";
|
||||||
import { Router } from "vue-router";
|
import { Router } from "vue-router";
|
||||||
|
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
import QuickNav from "@/components/QuickNav.vue";
|
||||||
@@ -158,10 +211,13 @@ import { Contact } from "@/db/tables/contacts";
|
|||||||
import { BoundingBox } from "@/db/tables/settings";
|
import { BoundingBox } from "@/db/tables/settings";
|
||||||
import { didInfo, getHeaders, PlanData } from "@/libs/endorserServer";
|
import { didInfo, getHeaders, PlanData } from "@/libs/endorserServer";
|
||||||
import { OnboardPage, retrieveAccountDids } from "@/libs/util";
|
import { OnboardPage, retrieveAccountDids } from "@/libs/util";
|
||||||
|
import { LocationEvent } from "leaflet";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
InfiniteScroll,
|
InfiniteScroll,
|
||||||
|
LMap,
|
||||||
|
LTileLayer,
|
||||||
OnboardingDialog,
|
OnboardingDialog,
|
||||||
ProjectIcon,
|
ProjectIcon,
|
||||||
QuickNav,
|
QuickNav,
|
||||||
@@ -179,10 +235,16 @@ export default class DiscoverView extends Vue {
|
|||||||
projects: PlanData[] = [];
|
projects: PlanData[] = [];
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
isLocalActive = true;
|
isLocalActive = true;
|
||||||
|
isMappedActive = false;
|
||||||
isRemoteActive = false;
|
isRemoteActive = false;
|
||||||
|
isSearchVisible = true;
|
||||||
|
localCenterLat = 0;
|
||||||
|
localCenterLong = 0;
|
||||||
localCount = -1;
|
localCount = -1;
|
||||||
|
markers: { [key: string]: L.Marker } = {};
|
||||||
remoteCount = -1;
|
remoteCount = -1;
|
||||||
searchBox: { name: string; bbox: BoundingBox } | null = null;
|
searchBox: { name: string; bbox: BoundingBox } | null = null;
|
||||||
|
zoomedSoDoNotMove = false;
|
||||||
|
|
||||||
// make this function available to the Vue template
|
// make this function available to the Vue template
|
||||||
didInfo = didInfo;
|
didInfo = didInfo;
|
||||||
@@ -207,8 +269,13 @@ export default class DiscoverView extends Vue {
|
|||||||
|
|
||||||
if (this.searchBox) {
|
if (this.searchBox) {
|
||||||
await this.searchLocal();
|
await this.searchLocal();
|
||||||
|
|
||||||
|
const bbox = this.searchBox.bbox;
|
||||||
|
this.localCenterLat = (bbox.maxLat + bbox.minLat) / 2;
|
||||||
|
this.localCenterLong = (bbox.eastLong + bbox.westLong) / 2;
|
||||||
} else {
|
} else {
|
||||||
this.isLocalActive = false;
|
this.isLocalActive = false;
|
||||||
|
this.isMappedActive = false;
|
||||||
this.isRemoteActive = true;
|
this.isRemoteActive = true;
|
||||||
await this.searchAll();
|
await this.searchAll();
|
||||||
}
|
}
|
||||||
@@ -222,6 +289,9 @@ export default class DiscoverView extends Vue {
|
|||||||
public async searchSelected() {
|
public async searchSelected() {
|
||||||
if (this.isLocalActive) {
|
if (this.isLocalActive) {
|
||||||
await this.searchLocal();
|
await this.searchLocal();
|
||||||
|
} else if (this.isMappedActive) {
|
||||||
|
this.isRemoteActive = true;
|
||||||
|
await this.searchAll();
|
||||||
} else {
|
} else {
|
||||||
await this.searchAll();
|
await this.searchAll();
|
||||||
}
|
}
|
||||||
@@ -406,12 +476,62 @@ export default class DiscoverView extends Vue {
|
|||||||
const latestProject = this.projects[this.projects.length - 1];
|
const latestProject = this.projects[this.projects.length - 1];
|
||||||
if (this.isLocalActive) {
|
if (this.isLocalActive) {
|
||||||
this.searchLocal(latestProject["rowid"]);
|
this.searchLocal(latestProject["rowid"]);
|
||||||
|
} else if (this.isMappedActive) {
|
||||||
|
// don't do anything since mapped items only show a limited number
|
||||||
} else if (this.isRemoteActive) {
|
} else if (this.isRemoteActive) {
|
||||||
this.searchAll(latestProject["rowid"]);
|
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
|
* Handle clicking on a project entry found in the list
|
||||||
* @param id of the project
|
* @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() {
|
public computedRemoteTabStyleClassNames() {
|
||||||
return {
|
return {
|
||||||
"inline-block": true,
|
"inline-block": true,
|
||||||
@@ -460,3 +598,18 @@ export default class DiscoverView extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</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>
|
</div>
|
||||||
|
|
||||||
<div class="mb-4 aspect-video">
|
<div class="aspect-video">
|
||||||
<l-map
|
<l-map
|
||||||
ref="map"
|
ref="map"
|
||||||
:center="[localCenterLat, localCenterLong]"
|
:center="[localCenterLat, localCenterLong]"
|
||||||
@@ -129,7 +129,7 @@ const DEFAULT_ZOOM = 2;
|
|||||||
LTileLayer,
|
LTileLayer,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class DiscoverView extends Vue {
|
export default class SearchAreaView extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
isChoosingSearchBox = false;
|
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.
|
// 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();
|
const bounds = event.target.boxZoom?._map?.getBounds();
|
||||||
if (bounds) {
|
if (bounds) {
|
||||||
latDiff = Math.abs(bounds._northEast.lat - bounds._southWest.lat) / 8;
|
latDiff = Math.abs(bounds.getNorthEast().lat - bounds.getSouthWest().lat) / 8;
|
||||||
longDiff = Math.abs(bounds._northEast.lng - bounds._southWest.lng) / 8;
|
longDiff = Math.abs(bounds.getNorthEast().lng - bounds.getSouthWest().lng) / 8;
|
||||||
}
|
}
|
||||||
this.localLatDiff = latDiff;
|
this.localLatDiff = latDiff;
|
||||||
this.localLongDiff = longDiff;
|
this.localLongDiff = longDiff;
|
||||||
|
|||||||
Reference in New Issue
Block a user