Trent Larson
12 months ago
6 changed files with 304 additions and 212 deletions
@ -0,0 +1,281 @@ |
|||
<template> |
|||
<!-- CONTENT --> |
|||
<section id="Content" class="p-6 pb-24"> |
|||
<!-- Breadcrumb --> |
|||
<div class="mb-8"> |
|||
<!-- Back --> |
|||
<div class="text-lg text-center font-light relative px-7"> |
|||
<h1 |
|||
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1" |
|||
@click="$router.back()" |
|||
> |
|||
<fa icon="chevron-left" class="fa-fw"></fa> |
|||
</h1> |
|||
</div> |
|||
|
|||
<!-- Heading --> |
|||
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8"> |
|||
Area for Nearby Search |
|||
</h1> |
|||
</div> |
|||
|
|||
<div class="px-2 py-4"> |
|||
This location is only stored on your device. It is used to show you more |
|||
appropriate projects but is not stored on any servers. |
|||
</div> |
|||
|
|||
<div> |
|||
<button v-if="!searchBox && !isNewMarkerSet" class="m-4 px-4 py-2"> |
|||
Click to Choose a Location for Nearby Search |
|||
</button> |
|||
<button |
|||
v-if="isNewMarkerSet" |
|||
class="m-4 px-4 py-2 rounded-md bg-blue-200 text-blue-500" |
|||
@click="storeSearchBox" |
|||
> |
|||
Store This Location for Nearby Search |
|||
</button> |
|||
<button |
|||
v-if="searchBox" |
|||
class="m-4 px-4 py-2 rounded-md bg-blue-200 text-blue-500" |
|||
@click="forgetSearchBox" |
|||
> |
|||
Delete Stored Location |
|||
</button> |
|||
<button |
|||
v-if="searchBox" |
|||
class="m-4 px-4 py-2 rounded-md bg-blue-200 text-blue-500" |
|||
@click="resetLatLong" |
|||
> |
|||
Reset Marker |
|||
</button> |
|||
<button |
|||
v-if="isNewMarkerSet" |
|||
class="m-4 px-4 py-2 rounded-md bg-blue-200 text-blue-500" |
|||
@click="isNewMarkerSet = false" |
|||
> |
|||
Erase Marker |
|||
</button> |
|||
<div v-if="isNewMarkerSet"> |
|||
Click on the pin to erase it. Click anywhere else to set a different |
|||
different corner. |
|||
</div> |
|||
</div> |
|||
|
|||
<div style="height: 600px; width: 800px"> |
|||
<l-map |
|||
ref="map" |
|||
:center="[localCenterLat, localCenterLong]" |
|||
v-model:zoom="localZoom" |
|||
@click="setMapPoint" |
|||
> |
|||
<l-tile-layer |
|||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" |
|||
layer-type="base" |
|||
name="OpenStreetMap" |
|||
/> |
|||
<l-marker |
|||
v-if="isNewMarkerSet" |
|||
:lat-lng="[localCenterLat, localCenterLong]" |
|||
@click="isNewMarkerSet = false" |
|||
/> |
|||
<l-rectangle |
|||
v-if="isNewMarkerSet" |
|||
:bounds="[ |
|||
[localCenterLat - localLatDiff, localCenterLong - localLongDiff], |
|||
[localCenterLat + localLatDiff, localCenterLong + localLongDiff], |
|||
]" |
|||
:weight="1" |
|||
/> |
|||
</l-map> |
|||
</div> |
|||
</section> |
|||
</template> |
|||
|
|||
<script lang="ts"> |
|||
import { LeafletMouseEvent } from "leaflet"; |
|||
import "leaflet/dist/leaflet.css"; |
|||
import { Component, Vue } from "vue-facing-decorator"; |
|||
import { |
|||
LMap, |
|||
LMarker, |
|||
LRectangle, |
|||
LTileLayer, |
|||
} from "@vue-leaflet/vue-leaflet"; |
|||
|
|||
import { db } from "@/db/index"; |
|||
import { BoundingBox, MASTER_SETTINGS_KEY } from "@/db/tables/settings"; |
|||
|
|||
const DEFAULT_LAT_LONG_DIFF = 0.01; |
|||
const WORLD_ZOOM = 2; |
|||
const DEFAULT_ZOOM = 2; |
|||
|
|||
interface Notification { |
|||
group: string; |
|||
type: string; |
|||
title: string; |
|||
text: string; |
|||
} |
|||
|
|||
@Component({ |
|||
components: { |
|||
LRectangle, |
|||
LMap, |
|||
LMarker, |
|||
LTileLayer, |
|||
}, |
|||
}) |
|||
export default class DiscoverView extends Vue { |
|||
$notify!: (notification: Notification, timeout?: number) => void; |
|||
|
|||
isChoosingSearchBox = false; |
|||
isNewMarkerSet = false; |
|||
|
|||
// "local" vars are for the currently selected map box |
|||
localCenterLat = 0; |
|||
localCenterLong = 0; |
|||
localLatDiff = DEFAULT_LAT_LONG_DIFF; |
|||
localLongDiff = DEFAULT_LAT_LONG_DIFF; |
|||
localZoom = DEFAULT_ZOOM; |
|||
|
|||
// searchBox reflects what is stored in the database |
|||
searchBox: { name: string; bbox: BoundingBox } | null = null; |
|||
|
|||
async mounted() { |
|||
await db.open(); |
|||
const settings = await db.settings.get(MASTER_SETTINGS_KEY); |
|||
this.searchBox = settings?.searchBoxes?.[0] || null; |
|||
this.resetLatLong(); |
|||
} |
|||
|
|||
setMapPoint(event: LeafletMouseEvent) { |
|||
if (this.isNewMarkerSet) { |
|||
this.localLatDiff = Math.abs(event.latlng.lat - this.localCenterLat); |
|||
this.localLongDiff = Math.abs(event.latlng.lng - this.localCenterLong); |
|||
} else { |
|||
// marker is not set |
|||
this.localCenterLat = event.latlng.lat; |
|||
this.localCenterLong = event.latlng.lng; |
|||
|
|||
let latDiff = DEFAULT_LAT_LONG_DIFF; |
|||
let longDiff = DEFAULT_LAT_LONG_DIFF; |
|||
// Guess at a size for the bounding box. |
|||
// 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; |
|||
} |
|||
this.localLatDiff = latDiff; |
|||
this.localLongDiff = longDiff; |
|||
this.isNewMarkerSet = true; |
|||
} |
|||
} |
|||
|
|||
public resetLatLong() { |
|||
if (this.searchBox?.bbox) { |
|||
const bbox = this.searchBox.bbox; |
|||
this.localCenterLat = (bbox.maxLat + bbox.minLat) / 2; |
|||
this.localCenterLong = (bbox.eastLong + bbox.westLong) / 2; |
|||
this.localLatDiff = (bbox.maxLat - bbox.minLat) / 2; |
|||
this.localLongDiff = (bbox.eastLong - bbox.westLong) / 2; |
|||
this.localZoom = WORLD_ZOOM; |
|||
this.isNewMarkerSet = true; |
|||
} else { |
|||
this.isNewMarkerSet = false; |
|||
} |
|||
} |
|||
|
|||
public async storeSearchBox() { |
|||
if (this.localCenterLong || this.localCenterLat) { |
|||
try { |
|||
const newSearchBox = { |
|||
name: "Local", |
|||
bbox: { |
|||
eastLong: this.localCenterLong + this.localLongDiff, |
|||
maxLat: this.localCenterLat + this.localLatDiff, |
|||
minLat: this.localCenterLat - this.localLatDiff, |
|||
westLong: this.localCenterLong - this.localLongDiff, |
|||
}, |
|||
}; |
|||
await db.open(); |
|||
db.settings.update(MASTER_SETTINGS_KEY, { |
|||
searchBoxes: [newSearchBox], |
|||
}); |
|||
this.searchBox = newSearchBox; |
|||
this.isChoosingSearchBox = false; |
|||
|
|||
this.$notify( |
|||
{ |
|||
group: "alert", |
|||
type: "success", |
|||
title: "Saved", |
|||
text: "That has been saved in your preferences.", |
|||
}, |
|||
-1, |
|||
); |
|||
} catch (err) { |
|||
this.$notify( |
|||
{ |
|||
group: "alert", |
|||
type: "danger", |
|||
title: "Error Updating Search Settings", |
|||
text: "Try going to a different page and then coming back.", |
|||
}, |
|||
-1, |
|||
); |
|||
console.error( |
|||
"Telling user to retry the location search setting because:", |
|||
err, |
|||
); |
|||
} |
|||
} else { |
|||
this.$notify( |
|||
{ |
|||
group: "alert", |
|||
type: "warning", |
|||
title: "No Location Selected", |
|||
text: "Select a location on the map.", |
|||
}, |
|||
-1, |
|||
); |
|||
} |
|||
} |
|||
|
|||
public async forgetSearchBox() { |
|||
try { |
|||
await db.open(); |
|||
db.settings.update(MASTER_SETTINGS_KEY, { |
|||
searchBoxes: [], |
|||
}); |
|||
this.searchBox = null; |
|||
this.localCenterLat = 0; |
|||
this.localCenterLong = 0; |
|||
this.localLatDiff = DEFAULT_LAT_LONG_DIFF; |
|||
this.localLongDiff = DEFAULT_LAT_LONG_DIFF; |
|||
this.localZoom = DEFAULT_ZOOM; |
|||
this.isChoosingSearchBox = false; |
|||
this.isNewMarkerSet = false; |
|||
} catch (err) { |
|||
this.$notify( |
|||
{ |
|||
group: "alert", |
|||
type: "danger", |
|||
title: "Error Updating Search Settings", |
|||
text: "Try going to a different page and then coming back.", |
|||
}, |
|||
-1, |
|||
); |
|||
console.error( |
|||
"Telling user to retry the location search setting because:", |
|||
err, |
|||
); |
|||
} |
|||
} |
|||
|
|||
public cancelSearchBoxSelect() { |
|||
this.isChoosingSearchBox = false; |
|||
this.localZoom = WORLD_ZOOM; |
|||
} |
|||
} |
|||
</script> |
Loading…
Reference in new issue