2024-11-25 23:44:26 +01:00
|
|
|
<script lang="ts">
|
|
|
|
import { UIEventSource } from "../Logic/UIEventSource"
|
|
|
|
import { QueryParameters } from "../Logic/Web/QueryParameters"
|
|
|
|
import ValidatedInput from "./InputElement/ValidatedInput.svelte"
|
|
|
|
import { Overpass } from "../Logic/Osm/Overpass"
|
|
|
|
import Constants from "../Models/Constants"
|
|
|
|
import MaplibreMap from "./Map/MaplibreMap.svelte"
|
|
|
|
import { MapLibreAdaptor } from "./Map/MapLibreAdaptor"
|
|
|
|
import { Map as MlMap } from "maplibre-gl"
|
|
|
|
import ShowDataLayer from "./Map/ShowDataLayer"
|
|
|
|
import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource"
|
|
|
|
import type { Feature } from "geojson"
|
|
|
|
import Loading from "./Base/Loading.svelte"
|
|
|
|
import { linear } from "svelte/easing"
|
|
|
|
import { Drawer } from "flowbite-svelte"
|
|
|
|
import History from "./History/History.svelte"
|
|
|
|
import TitledPanel from "./Base/TitledPanel.svelte"
|
|
|
|
import { XCircleIcon } from "@babeard/svelte-heroicons/solid"
|
|
|
|
import { Utils } from "../Utils"
|
|
|
|
import AggregateView from "./History/AggregateView.svelte"
|
2024-12-01 01:39:13 +01:00
|
|
|
import { HistoryUtils } from "./History/HistoryUtils"
|
|
|
|
import AggregateImages from "./History/AggregateImages.svelte"
|
2024-12-03 19:12:42 +01:00
|
|
|
import Page from "./Base/Page.svelte"
|
|
|
|
import PreviouslySpiedUsers from "./History/PreviouslySpiedUsers.svelte"
|
|
|
|
import { OsmConnection } from "../Logic/Osm/OsmConnection"
|
2024-12-04 18:48:05 +01:00
|
|
|
import Translations from "./i18n/Translations"
|
|
|
|
import Tr from "./Base/Tr.svelte"
|
2025-03-13 23:42:52 +01:00
|
|
|
import Searchbar from "../UI/Base/Searchbar.svelte"
|
|
|
|
import CombinedSearcher from "../Logic/Search/CombinedSearcher"
|
|
|
|
import CoordinateSearch from "../Logic/Search/CoordinateSearch"
|
|
|
|
import OpenLocationCodeSearch from "../Logic/Search/OpenLocationCodeSearch"
|
|
|
|
import PhotonSearch from "../Logic/Search/PhotonSearch"
|
|
|
|
import GeocodeResults from "./Search/GeocodeResults.svelte"
|
|
|
|
import MagnifyingGlassCircle from "@babeard/svelte-heroicons/mini/MagnifyingGlassCircle"
|
|
|
|
import type { GeocodeResult } from "../Logic/Search/GeocodingProvider"
|
2024-11-25 23:44:26 +01:00
|
|
|
|
2024-12-19 13:59:40 +01:00
|
|
|
console.log("Loading inspector GUI")
|
2024-11-25 23:44:26 +01:00
|
|
|
let username = QueryParameters.GetQueryParameter("user", undefined, "Inspect this user")
|
|
|
|
let step = new UIEventSource<"waiting" | "loading" | "done">("waiting")
|
|
|
|
let map = new UIEventSource<MlMap>(undefined)
|
|
|
|
let zoom = UIEventSource.asFloat(QueryParameters.GetQueryParameter("z", "0"))
|
|
|
|
let lat = UIEventSource.asFloat(QueryParameters.GetQueryParameter("lat", "0"))
|
|
|
|
let lon = UIEventSource.asFloat(QueryParameters.GetQueryParameter("lon", "0"))
|
|
|
|
let loadingData = false
|
|
|
|
let selectedElement = new UIEventSource<Feature>(undefined)
|
2025-03-13 23:42:52 +01:00
|
|
|
let searchvalue = new UIEventSource<string>("")
|
2024-11-25 23:44:26 +01:00
|
|
|
|
2025-03-13 23:42:52 +01:00
|
|
|
let geocoder = new CombinedSearcher(
|
|
|
|
new CoordinateSearch(),
|
|
|
|
new OpenLocationCodeSearch(),
|
|
|
|
new PhotonSearch(true, 2),
|
|
|
|
new PhotonSearch()
|
|
|
|
)
|
|
|
|
let showSearchDrawer = new UIEventSource(true)
|
|
|
|
let searchIsFocussed = new UIEventSource(false)
|
|
|
|
let searchIsRunning = new UIEventSource(false)
|
2024-11-25 23:44:26 +01:00
|
|
|
let maplibremap: MapLibreAdaptor = new MapLibreAdaptor(map, {
|
|
|
|
zoom,
|
2025-03-17 02:54:12 +01:00
|
|
|
location: new UIEventSource<{ lon: number; lat: number }>({ lat: lat.data, lon: lon.data }),
|
2024-11-25 23:44:26 +01:00
|
|
|
})
|
2024-12-11 02:45:44 +01:00
|
|
|
maplibremap.location.stabilized(500).addCallbackAndRunD((l) => {
|
2024-11-25 23:44:26 +01:00
|
|
|
lat.set(l.lat)
|
|
|
|
lon.set(l.lon)
|
|
|
|
})
|
2025-03-17 02:54:12 +01:00
|
|
|
let searchSuggestions = searchvalue.bindD((search) => {
|
2025-03-13 23:42:52 +01:00
|
|
|
searchIsRunning.set(true)
|
|
|
|
try {
|
|
|
|
return UIEventSource.FromPromise(geocoder.search(search))
|
|
|
|
} finally {
|
|
|
|
searchIsRunning.set(false)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
let state = {
|
|
|
|
mapProperties: maplibremap,
|
|
|
|
searchState: {
|
|
|
|
searchTerm: searchvalue,
|
|
|
|
suggestions: searchSuggestions,
|
|
|
|
suggestionsSearchRunning: searchIsRunning,
|
|
|
|
showSearchDrawer,
|
|
|
|
applyGeocodeResult(geocodeResult: GeocodeResult) {
|
|
|
|
maplibremap.location.set({ lon: geocodeResult.lon, lat: geocodeResult.lat })
|
2025-03-17 02:54:12 +01:00
|
|
|
},
|
|
|
|
},
|
2025-03-13 23:42:52 +01:00
|
|
|
}
|
2024-12-04 18:48:05 +01:00
|
|
|
let allLayers = HistoryUtils.personalTheme.layers
|
2024-12-11 02:45:44 +01:00
|
|
|
let layersNoFixme = allLayers.filter((l) => l.id !== "fixme")
|
|
|
|
let fixme = allLayers.find((l) => l.id === "fixme")
|
2024-11-25 23:44:26 +01:00
|
|
|
let featuresStore = new UIEventSource<Feature[]>([])
|
|
|
|
let features = new StaticFeatureSource(featuresStore)
|
2024-12-11 02:45:44 +01:00
|
|
|
ShowDataLayer.showMultipleLayers(map, features, [...layersNoFixme, fixme], {
|
2024-12-01 01:39:13 +01:00
|
|
|
zoomToFeatures: true,
|
|
|
|
onClick: (f: Feature) => {
|
|
|
|
selectedElement.set(undefined)
|
|
|
|
Utils.waitFor(200).then(() => {
|
|
|
|
selectedElement.set(f)
|
|
|
|
})
|
2025-03-17 02:54:12 +01:00
|
|
|
},
|
2024-12-01 01:39:13 +01:00
|
|
|
})
|
|
|
|
|
2024-12-03 19:12:42 +01:00
|
|
|
let osmConnection = new OsmConnection()
|
2024-12-11 02:45:44 +01:00
|
|
|
let inspectedContributors: UIEventSource<
|
|
|
|
{
|
|
|
|
name: string
|
|
|
|
visitedTime: string
|
|
|
|
label: string
|
|
|
|
}[]
|
|
|
|
> = UIEventSource.asObject(osmConnection.getPreference("spied-upon-users"), [])
|
2024-11-25 23:44:26 +01:00
|
|
|
|
|
|
|
async function load() {
|
2025-03-07 21:47:36 +01:00
|
|
|
try {
|
|
|
|
await loadUnsafe()
|
|
|
|
} catch (e) {
|
|
|
|
loadingData = false
|
|
|
|
alert("Loading failed")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function loadUnsafe() {
|
2024-12-03 19:12:42 +01:00
|
|
|
const user = username.data
|
2024-12-11 02:45:44 +01:00
|
|
|
if (user.indexOf(";") < 0) {
|
2024-12-03 19:12:42 +01:00
|
|
|
const inspectedData = inspectedContributors.data
|
2024-12-11 02:45:44 +01:00
|
|
|
const previousEntry = inspectedData.find((e) => e.name === user)
|
2024-12-03 19:12:42 +01:00
|
|
|
if (previousEntry) {
|
|
|
|
previousEntry.visitedTime = new Date().toISOString()
|
|
|
|
} else {
|
|
|
|
inspectedData.push({
|
|
|
|
label: undefined,
|
|
|
|
visitedTime: new Date().toISOString(),
|
2025-03-17 02:54:12 +01:00
|
|
|
name: user,
|
2024-12-03 19:12:42 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
inspectedContributors.ping()
|
|
|
|
}
|
2024-11-25 23:44:26 +01:00
|
|
|
|
|
|
|
step.setData("loading")
|
2024-12-03 19:12:42 +01:00
|
|
|
featuresStore.set([])
|
2024-12-11 02:45:44 +01:00
|
|
|
const overpass = new Overpass(
|
|
|
|
undefined,
|
2025-03-17 02:54:12 +01:00
|
|
|
user.split(";").map((user) => 'nw(user_touched:"' + user + '");'),
|
2024-12-11 02:45:44 +01:00
|
|
|
Constants.defaultOverpassUrls[0]
|
|
|
|
)
|
2024-11-25 23:44:26 +01:00
|
|
|
if (!maplibremap.bounds.data) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
loadingData = true
|
|
|
|
const [data, date] = await overpass.queryGeoJson(maplibremap.bounds.data)
|
|
|
|
console.log("Overpass result:", data)
|
|
|
|
loadingData = false
|
|
|
|
console.log(data, date)
|
|
|
|
featuresStore.set(data.features)
|
|
|
|
console.log("Loaded", data.features.length)
|
|
|
|
}
|
|
|
|
|
|
|
|
map.addCallbackAndRunD(() => {
|
|
|
|
// when the map is loaded: attempt to load the user given via Queryparams
|
|
|
|
if (username.data) {
|
|
|
|
console.log("Current username is", username.data)
|
|
|
|
load()
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
|
2024-12-01 01:39:13 +01:00
|
|
|
let mode: "map" | "table" | "aggregate" | "images" = "map"
|
2024-12-03 19:12:42 +01:00
|
|
|
|
|
|
|
let showPreviouslyVisited = new UIEventSource(true)
|
2024-12-11 02:45:44 +01:00
|
|
|
const t = Translations.t.inspector
|
2024-11-25 23:44:26 +01:00
|
|
|
</script>
|
|
|
|
|
2024-12-19 13:59:40 +01:00
|
|
|
<div class="flex h-screen w-full flex-col">
|
2024-12-11 02:45:44 +01:00
|
|
|
<div class="low-interaction flex items-center gap-x-2 p-2">
|
|
|
|
<MagnifyingGlassCircle class="h-12 w-12" />
|
|
|
|
<h1 class="m-0 mx-2 flex-shrink-0">
|
|
|
|
<Tr t={t.title} />
|
2024-12-04 18:48:05 +01:00
|
|
|
</h1>
|
2025-03-17 02:54:12 +01:00
|
|
|
<ValidatedInput
|
|
|
|
placeholder={t.previouslySpied.username}
|
|
|
|
type="string"
|
|
|
|
value={username}
|
|
|
|
on:submit={() => load()}
|
|
|
|
/>
|
2024-11-25 23:44:26 +01:00
|
|
|
{#if loadingData}
|
|
|
|
<Loading />
|
|
|
|
{:else}
|
2024-12-04 18:48:05 +01:00
|
|
|
<button class="primary" on:click={() => load()}>
|
2024-12-11 02:45:44 +01:00
|
|
|
<Tr t={t.load} />
|
2024-12-04 18:48:05 +01:00
|
|
|
</button>
|
2024-11-25 23:44:26 +01:00
|
|
|
{/if}
|
2024-12-03 19:12:42 +01:00
|
|
|
<button on:click={() => showPreviouslyVisited.setData(true)}>
|
2024-12-11 02:45:44 +01:00
|
|
|
<Tr t={t.earlierInspected} />
|
2024-12-03 19:12:42 +01:00
|
|
|
</button>
|
2024-12-04 18:48:05 +01:00
|
|
|
<a href="./index.html" class="button">
|
2024-12-11 02:45:44 +01:00
|
|
|
<Tr t={t.backToIndex} />
|
2024-12-04 18:48:05 +01:00
|
|
|
</a>
|
2024-11-25 23:44:26 +01:00
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="flex">
|
2024-12-11 02:45:44 +01:00
|
|
|
<button class:primary={mode === "map"} on:click={() => (mode = "map")}>
|
|
|
|
<Tr t={t.mapView} />
|
2024-11-25 23:44:26 +01:00
|
|
|
</button>
|
2024-12-11 02:45:44 +01:00
|
|
|
<button class:primary={mode === "table"} on:click={() => (mode = "table")}>
|
|
|
|
<Tr t={t.tableView} />
|
2024-11-25 23:44:26 +01:00
|
|
|
</button>
|
2024-12-11 02:45:44 +01:00
|
|
|
<button class:primary={mode === "aggregate"} on:click={() => (mode = "aggregate")}>
|
|
|
|
<Tr t={t.aggregateView} />
|
2024-11-25 23:44:26 +01:00
|
|
|
</button>
|
2024-12-11 02:45:44 +01:00
|
|
|
<button class:primary={mode === "images"} on:click={() => (mode = "images")}>
|
|
|
|
<Tr t={t.images} />
|
2024-12-01 01:39:13 +01:00
|
|
|
</button>
|
2024-11-25 23:44:26 +01:00
|
|
|
</div>
|
|
|
|
|
|
|
|
{#if mode === "map"}
|
|
|
|
{#if $selectedElement !== undefined}
|
|
|
|
<!-- right modal with the selected element view -->
|
|
|
|
<Drawer
|
|
|
|
placement="right"
|
|
|
|
transitionType="fly"
|
|
|
|
activateClickOutside={false}
|
|
|
|
backdrop={false}
|
|
|
|
id="drawer-right"
|
|
|
|
width="w-full md:w-6/12 lg:w-5/12 xl:w-4/12"
|
|
|
|
rightOffset="inset-y-0 right-0"
|
|
|
|
transitionParams={{
|
2024-12-11 02:45:44 +01:00
|
|
|
x: 640,
|
|
|
|
duration: 0,
|
|
|
|
easing: linear,
|
|
|
|
}}
|
2024-11-25 23:44:26 +01:00
|
|
|
divClass="overflow-y-auto z-50 bg-white"
|
|
|
|
hidden={$selectedElement === undefined}
|
|
|
|
on:close={() => {
|
2024-12-11 02:45:44 +01:00
|
|
|
selectedElement.setData(undefined)
|
|
|
|
}}
|
2024-11-25 23:44:26 +01:00
|
|
|
>
|
|
|
|
<TitledPanel>
|
|
|
|
<div slot="title" class="flex justify-between">
|
2024-12-11 02:45:44 +01:00
|
|
|
<a
|
|
|
|
target="_blank"
|
|
|
|
rel="noopener"
|
|
|
|
href={"https://osm.org/" + $selectedElement.properties.id}
|
|
|
|
>
|
|
|
|
{$selectedElement.properties.id}
|
|
|
|
</a>
|
|
|
|
<XCircleIcon class="h-6 w-6" on:click={() => selectedElement.set(undefined)} />
|
2024-11-25 23:44:26 +01:00
|
|
|
</div>
|
|
|
|
|
2024-12-19 13:59:40 +01:00
|
|
|
<History onlyShowChangesBy={$username.split(";")} id={$selectedElement.properties.id} />
|
2024-11-25 23:44:26 +01:00
|
|
|
</TitledPanel>
|
|
|
|
</Drawer>
|
|
|
|
{/if}
|
|
|
|
|
2025-03-13 23:42:52 +01:00
|
|
|
<div class="relative m-1 flex-grow overflow-hidden rounded-xl">
|
2024-12-11 02:45:44 +01:00
|
|
|
<MaplibreMap {map} mapProperties={maplibremap} autorecovery={true} />
|
2025-03-13 23:42:52 +01:00
|
|
|
<div class="absolute right-0 top-0 w-1/4 p-4">
|
2025-03-17 02:54:12 +01:00
|
|
|
<Searchbar
|
|
|
|
isFocused={searchIsFocussed}
|
|
|
|
value={searchvalue}
|
|
|
|
on:focus={() => state.searchState.showSearchDrawer.set(true)}
|
|
|
|
/>
|
2025-03-13 23:42:52 +01:00
|
|
|
{#if $searchSuggestions?.length > 0 || $searchIsFocussed}
|
|
|
|
<GeocodeResults {state} />
|
|
|
|
{/if}
|
|
|
|
</div>
|
2024-11-25 23:44:26 +01:00
|
|
|
</div>
|
|
|
|
{:else if mode === "table"}
|
2024-12-01 01:39:13 +01:00
|
|
|
<div class="m-2 h-full overflow-y-auto">
|
2024-12-03 19:12:42 +01:00
|
|
|
{#each $featuresStore as f}
|
2024-12-04 18:48:05 +01:00
|
|
|
<History onlyShowChangesBy={$username?.split(";")} id={f.properties.id} />
|
2024-12-03 19:12:42 +01:00
|
|
|
{/each}
|
2024-12-01 01:39:13 +01:00
|
|
|
</div>
|
|
|
|
{:else if mode === "aggregate"}
|
|
|
|
<div class="m-2 h-full overflow-y-auto">
|
2024-12-04 18:48:05 +01:00
|
|
|
<AggregateView features={$featuresStore} onlyShowUsername={$username?.split(";")} />
|
2024-12-01 01:39:13 +01:00
|
|
|
</div>
|
|
|
|
{:else if mode === "images"}
|
|
|
|
<div class="m-2 h-full overflow-y-auto">
|
2024-12-04 18:48:05 +01:00
|
|
|
<AggregateImages features={$featuresStore} onlyShowUsername={$username?.split(";")} />
|
2024-12-01 01:39:13 +01:00
|
|
|
</div>
|
2024-11-25 23:44:26 +01:00
|
|
|
{/if}
|
|
|
|
</div>
|
2024-12-03 19:12:42 +01:00
|
|
|
|
|
|
|
<Page shown={showPreviouslyVisited}>
|
2024-12-19 13:59:40 +01:00
|
|
|
<div slot="header">
|
|
|
|
<Tr t={t.previouslySpied.title} />
|
|
|
|
</div>
|
2024-12-11 02:45:44 +01:00
|
|
|
<PreviouslySpiedUsers
|
|
|
|
{osmConnection}
|
|
|
|
{inspectedContributors}
|
|
|
|
on:selectUser={(e) => {
|
|
|
|
username.set(e.detail)
|
|
|
|
load()
|
|
|
|
showPreviouslyVisited.set(false)
|
|
|
|
}}
|
|
|
|
/>
|
2024-12-03 19:12:42 +01:00
|
|
|
</Page>
|