Ressurect marker to 'add a new item', but only on long press

This commit is contained in:
Pieter Vander Vennet 2024-06-20 03:30:14 +02:00
parent 9deae9e659
commit 800a4ae849
12 changed files with 111 additions and 49 deletions

View file

@ -56,6 +56,7 @@
},
"popupInFloatover": true,
"titleIcons": [],
"isShown": "mouse_button=right",
"pointRendering": [
{
"marker": [
@ -168,6 +169,11 @@
"render": {
"*": "{open_note()}"
}
},
{
"id": "debug",
"metacondition": "__featureSwitchIsDebugging=true",
"render": "{all_tags()}"
}
],
"filter": [

View file

@ -540,7 +540,7 @@
},
{
"if": "mapcomplete-more_privacy=no",
"icon": "./assets/svg/eye.svg",
"icon": "cross_bottom_right:red;./assets/svg/eye.svg",
"then": {
"en": "When making changes to OpenStreetMap, roughly indicate how far away you were from the changed objects. This helps other contributors to understand how you made the change",
"de": "Wenn du Änderungen an OpenStreetMap vornimmst, gib grob an, wie weit du von den geänderten Objekten entfernt warst. Das hilft anderen Mitwirkenden zu verstehen, wie du die Änderung vorgenommen hast.",
@ -619,7 +619,7 @@
"mappings": [
{
"if": "mapcomplete-translation-mode=false",
"icon": "./assets/layers/usersettings/translate_disabled.svg",
"icon": "cross_bottom_right:red;./assets/svg/translate.svg",
"then": {
"en": "Don't show a button to quickly change translations",
"de": "Keine Schaltfläche zum schnellen Wechseln von Übersetzungen anzeigen",
@ -856,6 +856,7 @@
},
{
"if": "mapcomplete-show_debug=no",
"icon": "cross_bottom_right:red;./assets/svg/bug.svg",
"then": {
"en": "Don't show debug info",
"de": "Keine Debug-Informationen anzeigen",

View file

@ -1,6 +1,6 @@
{
"name": "mapcomplete",
"version": "0.43.3",
"version": "0.44.0",
"repository": "https://github.com/pietervdvn/MapComplete",
"description": "A small website to edit OSM easily",
"bugs": "https://github.com/pietervdvn/MapComplete/issues",

View file

@ -1,24 +1,24 @@
import LayoutConfig from "../../../Models/ThemeConfig/LayoutConfig"
import { WritableFeatureSource } from "../FeatureSource"
import { ImmutableStore, Store, UIEventSource } from "../../UIEventSource"
import { ImmutableStore, Store } from "../../UIEventSource"
import { Feature, Point } from "geojson"
import { TagUtils } from "../../Tags/TagUtils"
import BaseUIElement from "../../../UI/BaseUIElement"
import { Utils } from "../../../Utils"
import { OsmTags } from "../../../Models/OsmFeature"
import { FeatureSource } from "../FeatureSource"
/**
* Highly specialized feature source.
* Based on a lon/lat UIEVentSource, will generate the corresponding feature with the correct properties
*/
export class LastClickFeatureSource {
export class LastClickFeatureSource implements FeatureSource{
public readonly renderings: string[]
private i: number = 0
private readonly hasPresets: boolean
private readonly hasNoteLayer: boolean
public static readonly newPointElementId = "new_point_dialog"
constructor(layout: LayoutConfig) {
public readonly features: Store<Feature[]>
constructor(layout: LayoutConfig, clickSource: Store<{lon:number,lat:number,mode:"left"|"right"|"middle"}> ) {
this.hasNoteLayer = layout.hasNoteLayer()
this.hasPresets = layout.hasPresets()
const allPresets: BaseUIElement[] = []
@ -33,7 +33,7 @@ export class LastClickFeatureSource {
}
const { html } = rendering.RenderIcon(tags, {
noSize: true,
includeBadges: false,
includeBadges: false
})
allPresets.push(html)
}
@ -43,16 +43,21 @@ export class LastClickFeatureSource {
Utils.runningFromConsole ? "" : uiElem.ConstructElement().innerHTML
)
)
this.features = clickSource.mapD(({lon, lat,mode}) =>
[this.createFeature(lon, lat, mode)])
}
public createFeature(lon: number, lat: number): Feature<Point, OsmTags> {
public createFeature(lon: number, lat: number, mode?: "left" | "right" | "middle"): Feature<Point, OsmTags> {
const properties: OsmTags = {
id: LastClickFeatureSource.newPointElementId,
id: LastClickFeatureSource.newPointElementId + "_" + this.i,
has_note_layer: this.hasNoteLayer ? "yes" : "no",
has_presets: this.hasPresets ? "yes" : "no",
renderings: this.renderings.join(""),
number_of_presets: "" + this.renderings.length,
first_preset: this.renderings[0],
mouse_button: mode ?? "none"
}
this.i++
@ -61,8 +66,8 @@ export class LastClickFeatureSource {
properties,
geometry: {
type: "Point",
coordinates: [lon, lat],
},
coordinates: [lon, lat]
}
}
}
}

View file

@ -236,8 +236,9 @@ export default class LinkedDataLoader {
static async fetchJsonLd(
url: string,
options?: JsonLdLoaderOptions,
mode: "fetch-lod" | "fetch-raw" | "proxy"
mode?: "fetch-lod" | "fetch-raw" | "proxy"
): Promise<object> {
mode ??= "fetch-lod"
if (mode === "proxy") {
url = Constants.linkedDataProxy.replace("{url}", encodeURIComponent(url))
}

View file

@ -205,6 +205,7 @@ export default class PointRenderingConfig extends WithContextLoader {
marker: this.marker,
rotation: this.rotation,
tags,
emojiHeight: iconH
}).SetClass("w-full h-full")
: undefined
let badges = undefined

View file

@ -5,7 +5,7 @@ import { Store, UIEventSource } from "../Logic/UIEventSource"
import {
FeatureSource,
IndexedFeatureSource,
WritableFeatureSource,
WritableFeatureSource
} from "../Logic/FeatureSource/FeatureSource"
import { OsmConnection } from "../Logic/Osm/OsmConnection"
import { ExportableMap, MapProperties } from "./MapProperties"
@ -51,7 +51,7 @@ import SaveFeatureSourceToLocalStorage from "../Logic/FeatureSource/Actors/SaveF
import BBoxFeatureSource from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource"
import ThemeViewStateHashActor from "../Logic/Web/ThemeViewStateHashActor"
import NoElementsInViewDetector, {
FeatureViewState,
FeatureViewState
} from "../Logic/Actors/NoElementsInViewDetector"
import FilteredLayer from "./FilteredLayer"
import { PreferredRasterLayerSelector } from "../Logic/Actors/PreferredRasterLayerSelector"
@ -64,12 +64,15 @@ import { GeolocationControlState } from "../UI/BigComponents/GeolocationControl"
import Zoomcontrol from "../UI/Zoomcontrol"
import {
SummaryTileSource,
SummaryTileSourceRewriter,
SummaryTileSourceRewriter
} from "../Logic/FeatureSource/TiledFeatureSource/SummaryTileSource"
import summaryLayer from "../assets/generated/layers/summary.json"
import last_click_layerconfig from "../assets/generated/layers/last_click.json"
import { LayerConfigJson } from "./ThemeConfig/Json/LayerConfigJson"
import Locale from "../UI/i18n/Locale"
import Hash from "../Logic/Web/Hash"
import { GeoOperations } from "../Logic/GeoOperations"
/**
*
@ -173,7 +176,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
"oauth_token",
undefined,
"Used to complete the login"
),
)
})
this.userRelatedState = new UserRelatedState(
this.osmConnection,
@ -248,8 +251,8 @@ export default class ThemeViewState implements SpecialVisualizationState {
bbox.asGeoJson({
zoom: this.mapProperties.zoom.data,
...this.mapProperties.location.data,
id: "current_view_" + currentViewIndex,
}),
id: "current_view_" + currentViewIndex
})
]
})
)
@ -266,7 +269,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
featurePropertiesStore: this.featureProperties,
osmConnection: this.osmConnection,
historicalUserLocations: this.geolocation.historicalUserLocations,
featureSwitches: this.featureSwitches,
featureSwitches: this.featureSwitches
},
layout?.isLeftRightSensitive() ?? false
)
@ -293,7 +296,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
"leftover features, such as",
features[0].properties
)
},
}
}
)
this.perLayer = perLayer.perLayer
@ -331,7 +334,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
return sorted
})
this.lastClickObject = new LastClickFeatureSource(this.layout)
this.lastClickObject = new LastClickFeatureSource(this.layout, this.mapProperties.lastClickLocation)
this.osmObjectDownloader = new OsmObjectDownloader(
this.osmConnection.Backend(),
@ -345,7 +348,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
{
currentZoom: this.mapProperties.zoom,
layerState: this.layerState,
bounds: this.visualFeedbackViewportBounds,
bounds: this.visualFeedbackViewportBounds
}
)
this.hasDataInView = new NoElementsInViewDetector(this).hasFeatureInView
@ -403,6 +406,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
})
return toLocalStorage
}
public showNormalDataOn(map: Store<MlMap>): ReadonlyMap<string, FilteringFeatureSource> {
const filteringFeatureSource = new Map<string, FilteringFeatureSource>()
this.perLayer.forEach((fs, layerName) => {
@ -436,7 +440,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
doShowLayer,
metaTags: this.userRelatedState.preferencesAsTags,
selectedElement: this.selectedElement,
fetchStore: (id) => this.featureProperties.getStore(id),
fetchStore: (id) => this.featureProperties.getStore(id)
})
})
return filteringFeatureSource
@ -460,7 +464,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
doShowLayer: flayerGps.isDisplayed,
layer: flayerGps.layerDef,
metaTags: this.userRelatedState.preferencesAsTags,
selectedElement: this.selectedElement,
selectedElement: this.selectedElement
})
}
@ -552,7 +556,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
Hotkeys.RegisterHotkey(
{
nomod: " ",
onUp: true,
onUp: true
},
docs.selectItem,
() => {
@ -582,7 +586,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
Hotkeys.RegisterHotkey(
{
nomod: "" + i,
onUp: true,
onUp: true
},
doc,
() => this.selectClosestAtCenter(i - 1)
@ -595,7 +599,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
}
Hotkeys.RegisterHotkey(
{
nomod: "b",
nomod: "b"
},
docs.openLayersPanel,
() => {
@ -606,7 +610,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
)
Hotkeys.RegisterHotkey(
{
nomod: "s",
nomod: "s"
},
Translations.t.hotkeyDocumentation.openFilterPanel,
() => {
@ -664,7 +668,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
Hotkeys.RegisterHotkey(
{
shift: "T",
shift: "T"
},
Translations.t.hotkeyDocumentation.translationMode,
() => {
@ -696,7 +700,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
this.mapProperties.zoom.map((z) => Math.max(Math.floor(z), 0)),
this.mapProperties,
{
isActive: this.mapProperties.zoom.map((z) => z <= maxzoom),
isActive: this.mapProperties.zoom.map((z) => z <= maxzoom)
}
)
return new SummaryTileSourceRewriter(summaryTileSource, this.layerState.filteredLayers)
@ -712,7 +716,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
* A listing which maps the layerId onto the featureSource
*/
const specialLayers: Record<
Exclude<AddedByDefaultTypes, "last_click"> | "current_view",
AddedByDefaultTypes | "current_view",
FeatureSource
> = {
home_location: this.userRelatedState.homeLocation,
@ -730,6 +734,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
current_view: this.currentView,
favourite: this.favourites,
summary: this.featureSummary,
last_click: this.lastClickObject
}
this.closestFeatures.registerSource(specialLayers.favourite, "favourite")
@ -774,7 +779,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
if (features === undefined) {
return
}
if (id === "summary") {
if (id === "summary" || id === "last_click") {
return
}
@ -784,7 +789,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
doShowLayer: flayer.isDisplayed,
layer: flayer.layerDef,
metaTags: this.userRelatedState.preferencesAsTags,
selectedElement: this.selectedElement,
selectedElement: this.selectedElement
})
})
@ -792,7 +797,33 @@ export default class ThemeViewState implements SpecialVisualizationState {
features: specialLayers.summary,
layer: new LayerConfig(<LayerConfigJson>summaryLayer, "summaryLayer"),
// doShowLayer: this.mapProperties.zoom.map((z) => z < maxzoom),
selectedElement: this.selectedElement,
selectedElement: this.selectedElement
})
const lastClickLayerConfig = new LayerConfig(<LayerConfigJson>last_click_layerconfig, "last_click")
const lastClickFiltered =
lastClickLayerConfig.isShown === undefined ? specialLayers.last_click :
specialLayers.last_click.features.mapD(fs =>
fs.filter(
f => {
const matches = lastClickLayerConfig.isShown.matchesProperties(f.properties)
console.log("LastClick ",f,"matches",matches)
return matches
}))
new ShowDataLayer(this.map, {
features: new StaticFeatureSource(lastClickFiltered),
layer: lastClickLayerConfig,
onClick: feature => {
console.log("Last click was clicked", feature)
if (this.mapProperties.zoom.data >= Constants.minZoomLevelToAddNewPoint) {
this.selectedElement.setData(feature)
return
}
this.map.data.flyTo({
zoom: Constants.minZoomLevelToAddNewPoint,
center: GeoOperations.centerpointCoordinates(feature)
})
}
})
}

View file

@ -15,7 +15,7 @@
/**
* Only used in case of emoji
*/
export let emojiHeight: number
export let emojiHeight: number = undefined
let _rotation: Store<string> = rotation
? tags.map((tags) => rotation.GetRenderValue(tags).Subs(tags).txt)
: new ImmutableStore("0deg")

View file

@ -1,7 +1,5 @@
import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource"
import { Map as MLMap } from "maplibre-gl"
import { Map as MlMap, SourceSpecification } from "maplibre-gl"
import maplibregl from "maplibre-gl"
import maplibregl, { Map as MLMap, Map as MlMap, SourceSpecification } from "maplibre-gl"
import { RasterLayerPolygon } from "../../Models/RasterLayers"
import { Utils } from "../../Utils"
import { BBox } from "../../Logic/BBox"
@ -13,7 +11,6 @@ import * as htmltoimage from "html-to-image"
import RasterLayerHandler from "./RasterLayerHandler"
import Constants from "../../Models/Constants"
import { Protocol } from "pmtiles"
import { bool } from "sharp"
/**
* The 'MapLibreAdaptor' bridges 'MapLibre' with the various properties of the `MapProperties`
@ -42,7 +39,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
readonly allowMoving: UIEventSource<true | boolean | undefined>
readonly allowRotating: UIEventSource<true | boolean | undefined>
readonly allowZooming: UIEventSource<true | boolean | undefined>
readonly lastClickLocation: Store<undefined | { lon: number; lat: number }>
readonly lastClickLocation: Store<undefined | { lon: number; lat: number, mode : "left" | "right" | "middle" }>
readonly minzoom: UIEventSource<number>
readonly maxzoom: UIEventSource<number>
readonly rotation: UIEventSource<number>
@ -95,20 +92,24 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
this.rasterLayer =
state?.rasterLayer ?? new UIEventSource<RasterLayerPolygon | undefined>(undefined)
const lastClickLocation = new UIEventSource<{ lon: number; lat: number }>(undefined)
const lastClickLocation = new UIEventSource<{lat:number,lon:number,mode: "left" | "right" | "middle"}>(undefined)
this.lastClickLocation = lastClickLocation
const self = this
new RasterLayerHandler(this._maplibreMap, this.rasterLayer)
function handleClick(e) {
const clickmodes = ["left" , "middle", "right"] as const
function handleClick(e: maplibregl.MapMouseEvent, mode?: "left" | "right" | "middle") {
if (e.originalEvent["consumed"]) {
// Workaround, 'ShowPointLayer' sets this flag
return
}
const lon = e.lngLat.lng
const lat = e.lngLat.lat
lastClickLocation.setData({ lon, lat })
const mouseEvent: MouseEvent = e.originalEvent
mode = mode ?? clickmodes[mouseEvent.button]
lastClickLocation.setData({ lon, lat, mode })
}
maplibreMap.addCallbackAndRunD((map) => {
@ -142,10 +143,19 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
handleClick(e)
})
map.on("contextmenu", (e) => {
handleClick(e)
// This one only works on desktop
handleClick(e, "right")
})
map._container.addEventListener("contextmenu", (e) => {
const lngLat = map.unproject([e.x, e.y])
lastClickLocation.setData({lon: lngLat.lng, lat: lngLat.lat, mode: "right"})
})
map.on("dblclick", (e) => {
handleClick(e)
handleClick(e, "left")
})
map.on("touchend", (e) => {
const touchEvent = e.originalEvent
})
map.on("rotateend", (_) => {
this.updateStores()
@ -665,4 +675,5 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
}
}
}
}

View file

@ -27,6 +27,7 @@
let _map: Map
onMount(() => {
const { lon, lat } = mapProperties?.location?.data ?? { lon: 0, lat: 0 }
const rasterLayer: RasterLayerProperties = mapProperties?.rasterLayer?.data?.properties
@ -72,8 +73,13 @@
})
onDestroy(async () => {
await Utils.waitFor(250)
try{
if (_map) _map.remove()
map = null
}catch (e) {
console.error("Could not destroy map")
}
})
</script>

View file

@ -122,7 +122,7 @@ class SingleBackgroundHandler {
console.warn(
"Layer",
addLayerBeforeId,
"not foundhttp://127.0.0.1:1234/theme.html?layout=cyclofix&z=14.8&lat=51.05282501324558&lon=3.720591622281745&layer-range=true"
"not found"
)
addLayerBeforeId = undefined
}

View file

@ -118,7 +118,7 @@
</div>
{/if}
{:else}
<div class="h-full w-full overflow-hidden">
<div class="h-full w-full overflow-auto">
<TagRenderingAnswer {config} {tags} {selectedElement} {state} {layer} />
</div>
{/if}