UX: move map to note location if a hash is given in the note, fix #2311

This commit is contained in:
Pieter Vander Vennet 2024-12-13 14:43:53 +01:00
parent cc28932534
commit 3fb8f7342f
3 changed files with 47 additions and 34 deletions

View file

@ -4,10 +4,12 @@ import { LocalStorageSource } from "../Web/LocalStorageSource"
import { QueryParameters } from "../Web/QueryParameters" import { QueryParameters } from "../Web/QueryParameters"
import Hash from "../Web/Hash" import Hash from "../Web/Hash"
import OsmObjectDownloader from "../Osm/OsmObjectDownloader" import OsmObjectDownloader from "../Osm/OsmObjectDownloader"
import { OsmObject } from "../Osm/OsmObject"
import Constants from "../../Models/Constants" import Constants from "../../Models/Constants"
import { Utils } from "../../Utils" import { Utils } from "../../Utils"
import { GeoLocationState } from "../State/GeoLocationState" import { GeoLocationState } from "../State/GeoLocationState"
import { OsmConnection } from "../Osm/OsmConnection"
"use strict"
/** /**
* This actor is responsible to set the map location. * This actor is responsible to set the map location.
@ -25,7 +27,7 @@ export default class InitialMapPositioning {
public location: UIEventSource<{ lon: number; lat: number }> public location: UIEventSource<{ lon: number; lat: number }>
public useTerrain: Store<boolean> public useTerrain: Store<boolean>
constructor(layoutToUse: ThemeConfig, geolocationState: GeoLocationState) { constructor(layoutToUse: ThemeConfig, geolocationState: GeoLocationState, osmConnection: OsmConnection) {
function localStorageSynced( function localStorageSynced(
key: string, key: string,
deflt: number, deflt: number,
@ -47,7 +49,6 @@ export default class InitialMapPositioning {
return src return src
} }
const initialHash = Hash.hash.data
// -- Location control initialization // -- Location control initialization
this.zoom = localStorageSynced( this.zoom = localStorageSynced(
@ -72,6 +73,7 @@ export default class InitialMapPositioning {
}) })
this.useTerrain = new ImmutableStore<boolean>(layoutToUse.enableTerrain) this.useTerrain = new ImmutableStore<boolean>(layoutToUse.enableTerrain)
const initialHash = Hash.hash.data
if (initialHash?.match(/^(node|way|relation)\/[0-9]+$/)) { if (initialHash?.match(/^(node|way|relation)\/[0-9]+$/)) {
// We pan to the selected element // We pan to the selected element
const [type, id] = initialHash.split("/") const [type, id] = initialHash.split("/")
@ -88,6 +90,15 @@ export default class InitialMapPositioning {
const [lat, lon] = osmObject.centerpoint() const [lat, lon] = osmObject.centerpoint()
this.location.setData({ lon, lat }) this.location.setData({ lon, lat })
}) })
} else if (layoutToUse.id === "notes" && initialHash?.match(/[0-9]+/)) {
console.log("Loading note", initialHash)
const noteId = Number(initialHash)
osmConnection.getNote(noteId).then(note => {
const [lon, lat] = note.geometry.coordinates
console.log("Got note:", note)
this.location.set({ lon, lat })
}
)
} else if ( } else if (
Constants.GeoIpServer && Constants.GeoIpServer &&
lat.data === defaultLat && lat.data === defaultLat &&

View file

@ -5,6 +5,7 @@ import { Utils } from "../../Utils"
import { LocalStorageSource } from "../Web/LocalStorageSource" import { LocalStorageSource } from "../Web/LocalStorageSource"
import { AuthConfig } from "./AuthConfig" import { AuthConfig } from "./AuthConfig"
import Constants from "../../Models/Constants" import Constants from "../../Models/Constants"
import { Feature, Point } from "geojson"
interface OsmUserInfo { interface OsmUserInfo {
id: number id: number
@ -218,7 +219,7 @@ export class OsmConnection {
this.auth.xhr( this.auth.xhr(
{ {
method: "GET", method: "GET",
path: "/api/0.6/user/details", path: "/api/0.6/user/details"
}, },
(err, details: XMLDocument) => { (err, details: XMLDocument) => {
if (err != null) { if (err != null) {
@ -330,9 +331,9 @@ export class OsmConnection {
method, method,
headers: header, headers: header,
content, content,
path: `/api/0.6/${path}`, path: `/api/0.6/${path}`
}, },
function (err, response) { function(err, response) {
if (err !== null) { if (err !== null) {
error(err) error(err)
} else { } else {
@ -412,7 +413,7 @@ export class OsmConnection {
"notes.json", "notes.json",
content, content,
{ {
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
}, },
true true
) )
@ -423,6 +424,12 @@ export class OsmConnection {
return id return id
} }
public async getNote(id: number): Promise<Feature<Point>> {
return JSON.parse(await this.get(
"notes/" + id + ".json"
))
}
public static GpxTrackVisibility = ["private", "public", "trackable", "identifiable"] as const public static GpxTrackVisibility = ["private", "public", "trackable", "identifiable"] as const
public async uploadGpxTrack( public async uploadGpxTrack(
@ -453,7 +460,7 @@ export class OsmConnection {
file: gpx, file: gpx,
description: options.description, description: options.description,
tags: options.labels?.join(",") ?? "", tags: options.labels?.join(",") ?? "",
visibility: options.visibility, visibility: options.visibility
} }
if (!contents.description) { if (!contents.description) {
@ -461,9 +468,9 @@ export class OsmConnection {
} }
const extras = { const extras = {
file: file:
'; filename="' + "; filename=\"" +
(options.filename ?? "gpx_track_mapcomplete_" + new Date().toISOString()) + (options.filename ?? "gpx_track_mapcomplete_" + new Date().toISOString()) +
'"\r\nContent-Type: application/gpx+xml', "\"\r\nContent-Type: application/gpx+xml"
} }
const boundary = "987654" const boundary = "987654"
@ -471,7 +478,7 @@ export class OsmConnection {
let body = "" let body = ""
for (const key in contents) { for (const key in contents) {
body += "--" + boundary + "\r\n" body += "--" + boundary + "\r\n"
body += 'Content-Disposition: form-data; name="' + key + '"' body += "Content-Disposition: form-data; name=\"" + key + "\""
if (extras[key] !== undefined) { if (extras[key] !== undefined) {
body += extras[key] body += extras[key]
} }
@ -482,7 +489,7 @@ export class OsmConnection {
const response = await this.post("gpx/create", body, { const response = await this.post("gpx/create", body, {
"Content-Type": "multipart/form-data; boundary=" + boundary, "Content-Type": "multipart/form-data; boundary=" + boundary,
"Content-Length": "" + body.length, "Content-Length": "" + body.length
}) })
const parsed = JSON.parse(response) const parsed = JSON.parse(response)
console.log("Uploaded GPX track", parsed) console.log("Uploaded GPX track", parsed)
@ -503,9 +510,9 @@ export class OsmConnection {
{ {
method: "POST", method: "POST",
path: `/api/0.6/notes/${id}/comment?text=${encodeURIComponent(text)}`, path: `/api/0.6/notes/${id}/comment?text=${encodeURIComponent(text)}`
}, },
function (err) { function(err) {
if (err !== null) { if (err !== null) {
error(err) error(err)
} else { } else {
@ -520,7 +527,7 @@ export class OsmConnection {
* To be called by land.html * To be called by land.html
*/ */
public finishLogin(callback: (previousURL: string) => void) { public finishLogin(callback: (previousURL: string) => void) {
this.auth.authenticate(function () { this.auth.authenticate(function() {
// Fully authed at this point // Fully authed at this point
console.log("Authentication successful!") console.log("Authentication successful!")
const previousLocation = LocalStorageSource.get("location_before_login") const previousLocation = LocalStorageSource.get("location_before_login")
@ -541,7 +548,7 @@ export class OsmConnection {
*/ */
singlepage: !this._iframeMode, singlepage: !this._iframeMode,
auto: true, auto: true,
apiUrl: this._oauth_config.api_url ?? this._oauth_config.url, apiUrl: this._oauth_config.api_url ?? this._oauth_config.url
}) })
} }

View file

@ -2,11 +2,7 @@ import ThemeConfig from "./ThemeConfig/ThemeConfig"
import { SpecialVisualizationState } from "../UI/SpecialVisualization" import { SpecialVisualizationState } from "../UI/SpecialVisualization"
import { Changes } from "../Logic/Osm/Changes" import { Changes } from "../Logic/Osm/Changes"
import { Store, UIEventSource } from "../Logic/UIEventSource" import { Store, UIEventSource } from "../Logic/UIEventSource"
import { import { FeatureSource, IndexedFeatureSource, WritableFeatureSource } from "../Logic/FeatureSource/FeatureSource"
FeatureSource,
IndexedFeatureSource,
WritableFeatureSource,
} from "../Logic/FeatureSource/FeatureSource"
import { OsmConnection } from "../Logic/Osm/OsmConnection" import { OsmConnection } from "../Logic/Osm/OsmConnection"
import { ExportableMap, MapProperties } from "./MapProperties" import { ExportableMap, MapProperties } from "./MapProperties"
import LayerState from "../Logic/State/LayerState" import LayerState from "../Logic/State/LayerState"
@ -50,9 +46,7 @@ import BackgroundLayerResetter from "../Logic/Actors/BackgroundLayerResetter"
import SaveFeatureSourceToLocalStorage from "../Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage" import SaveFeatureSourceToLocalStorage from "../Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage"
import BBoxFeatureSource from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource" import BBoxFeatureSource from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource"
import ThemeViewStateHashActor from "../Logic/Web/ThemeViewStateHashActor" import ThemeViewStateHashActor from "../Logic/Web/ThemeViewStateHashActor"
import NoElementsInViewDetector, { import NoElementsInViewDetector, { FeatureViewState } from "../Logic/Actors/NoElementsInViewDetector"
FeatureViewState,
} from "../Logic/Actors/NoElementsInViewDetector"
import FilteredLayer from "./FilteredLayer" import FilteredLayer from "./FilteredLayer"
import { PreferredRasterLayerSelector } from "../Logic/Actors/PreferredRasterLayerSelector" import { PreferredRasterLayerSelector } from "../Logic/Actors/PreferredRasterLayerSelector"
import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager" import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager"
@ -63,7 +57,7 @@ import { GeolocationControlState } from "../UI/BigComponents/GeolocationControl"
import Zoomcontrol from "../UI/Zoomcontrol" import Zoomcontrol from "../UI/Zoomcontrol"
import { import {
SummaryTileSource, SummaryTileSource,
SummaryTileSourceRewriter, SummaryTileSourceRewriter
} from "../Logic/FeatureSource/TiledFeatureSource/SummaryTileSource" } from "../Logic/FeatureSource/TiledFeatureSource/SummaryTileSource"
import summaryLayer from "../assets/generated/layers/summary.json" import summaryLayer from "../assets/generated/layers/summary.json"
import last_click_layerconfig from "../assets/generated/layers/last_click.json" import last_click_layerconfig from "../assets/generated/layers/last_click.json"
@ -177,12 +171,6 @@ export default class ThemeViewState implements SpecialVisualizationState {
) )
this.map = new UIEventSource<MlMap>(undefined) this.map = new UIEventSource<MlMap>(undefined)
const geolocationState = new GeoLocationState() const geolocationState = new GeoLocationState()
const initial = new InitialMapPositioning(layout, geolocationState)
this.mapProperties = new MapLibreAdaptor(this.map, initial, { correctClick: 20 })
this.featureSwitchIsTesting = this.featureSwitches.featureSwitchIsTesting
this.featureSwitchUserbadge = this.featureSwitches.featureSwitchEnableLogin
this.osmConnection = new OsmConnection({ this.osmConnection = new OsmConnection({
dryRun: this.featureSwitches.featureSwitchIsTesting, dryRun: this.featureSwitches.featureSwitchIsTesting,
fakeUser: this.featureSwitches.featureSwitchFakeUser.data, fakeUser: this.featureSwitches.featureSwitchFakeUser.data,
@ -190,8 +178,15 @@ export default class ThemeViewState implements SpecialVisualizationState {
"oauth_token", "oauth_token",
undefined, undefined,
"Used to complete the login" "Used to complete the login"
), )
}) })
const initial = new InitialMapPositioning(layout, geolocationState, this.osmConnection)
this.mapProperties = new MapLibreAdaptor(this.map, initial, { correctClick: 20 })
this.featureSwitchIsTesting = this.featureSwitches.featureSwitchIsTesting
this.featureSwitchUserbadge = this.featureSwitches.featureSwitchEnableLogin
this.userRelatedState = new UserRelatedState( this.userRelatedState = new UserRelatedState(
this.osmConnection, this.osmConnection,
layout, layout,
@ -788,7 +783,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
const layers = this.theme.layers.filter( const layers = this.theme.layers.filter(
(l) => (l) =>
Constants.priviliged_layers.indexOf(<any>l.id) < 0 && (<string[]><unknown>Constants.priviliged_layers).indexOf(l.id) < 0 &&
l.source.geojsonSource === undefined && l.source.geojsonSource === undefined &&
l.doCount l.doCount
) )
@ -840,7 +835,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
this.closestFeatures.registerSource(specialLayers.favourite, "favourite") this.closestFeatures.registerSource(specialLayers.favourite, "favourite")
if (this.theme?.lockLocation) { if (this.theme?.lockLocation) {
const bbox = new BBox(<any>this.theme.lockLocation) const bbox = new BBox(<[[number, number], [number, number]]>this.theme.lockLocation)
this.mapProperties.maxbounds.setData(bbox) this.mapProperties.maxbounds.setData(bbox)
ShowDataLayer.showRange( ShowDataLayer.showRange(
this.map, this.map,