Auto-formatting

This commit is contained in:
Pieter Vander Vennet 2022-12-16 13:45:07 +01:00
parent 9e000d521f
commit fed4cff878
26 changed files with 360 additions and 304 deletions

View file

@ -210,8 +210,8 @@ export default class OverpassFeatureSource implements FeatureSource {
if (overpass === undefined) { if (overpass === undefined) {
return undefined return undefined
} }
this.runningQuery.setData(true); this.runningQuery.setData(true)
[data, date] = await overpass.queryGeoJson(bounds) ;[data, date] = await overpass.queryGeoJson(bounds)
} catch (e) { } catch (e) {
self.retries.data++ self.retries.data++
self.retries.ping() self.retries.ping()

View file

@ -1,11 +1,11 @@
/** /**
* This actor will download the latest version of the selected element from OSM and update the tags if necessary. * This actor will download the latest version of the selected element from OSM and update the tags if necessary.
*/ */
import {UIEventSource} from "../UIEventSource" import { UIEventSource } from "../UIEventSource"
import {ElementStorage} from "../ElementStorage" import { ElementStorage } from "../ElementStorage"
import {Changes} from "../Osm/Changes" import { Changes } from "../Osm/Changes"
import {OsmObject} from "../Osm/OsmObject" import { OsmObject } from "../Osm/OsmObject"
import {OsmConnection} from "../Osm/OsmConnection" import { OsmConnection } from "../Osm/OsmConnection"
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
import SimpleMetaTagger from "../SimpleMetaTagger" import SimpleMetaTagger from "../SimpleMetaTagger"
@ -59,17 +59,16 @@ export default class SelectedElementTagsUpdater {
return return
} }
try { try {
const latestTags = await OsmObject.DownloadPropertiesOf(id) const latestTags = await OsmObject.DownloadPropertiesOf(id)
if (latestTags === "deleted") { if (latestTags === "deleted") {
console.warn("The current selected element has been deleted upstream!") console.warn("The current selected element has been deleted upstream!")
const currentTagsSource = state.allElements.getEventSourceById(id) const currentTagsSource = state.allElements.getEventSourceById(id)
if(currentTagsSource.data["_deleted"] === "yes"){ if (currentTagsSource.data["_deleted"] === "yes") {
return return
} }
currentTagsSource.data["_deleted"] = "yes" currentTagsSource.data["_deleted"] = "yes"
currentTagsSource.ping() currentTagsSource.ping()
return; return
} }
SelectedElementTagsUpdater.applyUpdate(state, latestTags, id) SelectedElementTagsUpdater.applyUpdate(state, latestTags, id)
console.log("Updated", id) console.log("Updated", id)

View file

@ -85,7 +85,7 @@ export default class FilteringFeatureSource implements FeatureSourceForLayer, Ti
if (isShown !== undefined && !isShown.matchesProperties(tags)) { if (isShown !== undefined && !isShown.matchesProperties(tags)) {
return false return false
} }
if(tags._deleted === "yes"){ if (tags._deleted === "yes") {
return false return false
} }

View file

@ -1,8 +1,8 @@
/** /**
* This feature source helps the ShowDataLayer class: it introduces the necessary extra features and indicates with what renderConfig it should be rendered. * This feature source helps the ShowDataLayer class: it introduces the necessary extra features and indicates with what renderConfig it should be rendered.
*/ */
import {Store} from "../../UIEventSource" import { Store } from "../../UIEventSource"
import {GeoOperations} from "../../GeoOperations" import { GeoOperations } from "../../GeoOperations"
import FeatureSource from "../FeatureSource" import FeatureSource from "../FeatureSource"
import PointRenderingConfig from "../../../Models/ThemeConfig/PointRenderingConfig" import PointRenderingConfig from "../../../Models/ThemeConfig/PointRenderingConfig"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
@ -17,7 +17,10 @@ export default class RenderingMultiPlexerFeatureSource {
> >
private readonly pointRenderings: { rendering: PointRenderingConfig; index: number }[] private readonly pointRenderings: { rendering: PointRenderingConfig; index: number }[]
private readonly centroidRenderings: { rendering: PointRenderingConfig; index: number }[] private readonly centroidRenderings: { rendering: PointRenderingConfig; index: number }[]
private readonly projectedCentroidRenderings: { rendering: PointRenderingConfig; index: number }[] private readonly projectedCentroidRenderings: {
rendering: PointRenderingConfig
index: number
}[]
private readonly startRenderings: { rendering: PointRenderingConfig; index: number }[] private readonly startRenderings: { rendering: PointRenderingConfig; index: number }[]
private readonly endRenderings: { rendering: PointRenderingConfig; index: number }[] private readonly endRenderings: { rendering: PointRenderingConfig; index: number }[]
private readonly hasCentroid: boolean private readonly hasCentroid: boolean
@ -90,10 +93,15 @@ export default class RenderingMultiPlexerFeatureSource {
} }
} else if (feat.geometry.type === "MultiPolygon") { } else if (feat.geometry.type === "MultiPolygon") {
if (this.centroidRenderings.length > 0 || this.projectedCentroidRenderings.length > 0) { if (this.centroidRenderings.length > 0 || this.projectedCentroidRenderings.length > 0) {
const centerpoints: [number, number][] = (<[number, number][][][]>(
const centerpoints: [number, number][] = (<[number, number][][][]>feat.geometry.coordinates).map(rings => GeoOperations.centerpointCoordinates( feat.geometry.coordinates
{type: "Feature", properties: {}, geometry: {type: "Polygon", coordinates: rings}} )).map((rings) =>
)) GeoOperations.centerpointCoordinates({
type: "Feature",
properties: {},
geometry: { type: "Polygon", coordinates: rings },
})
)
for (const centroidRendering of this.centroidRenderings) { for (const centroidRendering of this.centroidRenderings) {
for (const centerpoint of centerpoints) { for (const centerpoint of centerpoints) {
addAsPoint(feat, centroidRendering, centerpoint) addAsPoint(feat, centroidRendering, centerpoint)
@ -105,8 +113,6 @@ export default class RenderingMultiPlexerFeatureSource {
addAsPoint(feat, centroidRendering, centerpoint) addAsPoint(feat, centroidRendering, centerpoint)
} }
} }
} }
// AT last, add it 'as is' to what we should render // AT last, add it 'as is' to what we should render
@ -116,7 +122,6 @@ export default class RenderingMultiPlexerFeatureSource {
lineRenderingIndex: i, lineRenderingIndex: i,
}) })
} }
} else { } else {
// This is a a line or polygon: add the centroids // This is a a line or polygon: add the centroids
let centerpoint: [number, number] = undefined let centerpoint: [number, number] = undefined

View file

@ -19,9 +19,9 @@ export default class UserDetails {
public totalMessages: number = 0 public totalMessages: number = 0
public home: { lon: number; lat: number } public home: { lon: number; lat: number }
public backend: string public backend: string
public account_created: string; public account_created: string
public tracesCount: number = 0; public tracesCount: number = 0
public description: string; public description: string
constructor(backend: string) { constructor(backend: string) {
this.backend = backend this.backend = backend
@ -214,8 +214,12 @@ export class OsmConnection {
data.name = userInfo.getAttribute("display_name") data.name = userInfo.getAttribute("display_name")
data.account_created = userInfo.getAttribute("account_created") data.account_created = userInfo.getAttribute("account_created")
data.uid = Number(userInfo.getAttribute("id")) data.uid = Number(userInfo.getAttribute("id"))
data.csCount = Number.parseInt( userInfo.getElementsByTagName("changesets")[0].getAttribute("count") ?? 0) data.csCount = Number.parseInt(
data.tracesCount = Number.parseInt( userInfo.getElementsByTagName("changesets")[0].getAttribute("count") ?? 0) userInfo.getElementsByTagName("changesets")[0].getAttribute("count") ?? 0
)
data.tracesCount = Number.parseInt(
userInfo.getElementsByTagName("changesets")[0].getAttribute("count") ?? 0
)
data.img = undefined data.img = undefined
const imgEl = userInfo.getElementsByTagName("img") const imgEl = userInfo.getElementsByTagName("img")

View file

@ -71,7 +71,7 @@ export abstract class OsmObject {
const url = `${OsmObject.backendURL}api/0.6/${id}` const url = `${OsmObject.backendURL}api/0.6/${id}`
const rawData = await Utils.downloadJsonCachedAdvanced(url, 1000) const rawData = await Utils.downloadJsonCachedAdvanced(url, 1000)
console.log(rawData) console.log(rawData)
if(rawData["error"] !== undefined && rawData["statuscode"] === 410){ if (rawData["error"] !== undefined && rawData["statuscode"] === 410) {
return "deleted" return "deleted"
} }
return rawData["contents"].elements[0].tags return rawData["contents"].elements[0].tags

View file

@ -1,29 +1,29 @@
import UserRelatedState from "./UserRelatedState" import UserRelatedState from "./UserRelatedState"
import {Store, Stores, UIEventSource} from "../UIEventSource" import { Store, Stores, UIEventSource } from "../UIEventSource"
import BaseLayer from "../../Models/BaseLayer" import BaseLayer from "../../Models/BaseLayer"
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
import AvailableBaseLayers from "../Actors/AvailableBaseLayers" import AvailableBaseLayers from "../Actors/AvailableBaseLayers"
import Attribution from "../../UI/BigComponents/Attribution" import Attribution from "../../UI/BigComponents/Attribution"
import Minimap, {MinimapObj} from "../../UI/Base/Minimap" import Minimap, { MinimapObj } from "../../UI/Base/Minimap"
import {Tiles} from "../../Models/TileRange" import { Tiles } from "../../Models/TileRange"
import BaseUIElement from "../../UI/BaseUIElement" import BaseUIElement from "../../UI/BaseUIElement"
import FilteredLayer, {FilterState} from "../../Models/FilteredLayer" import FilteredLayer, { FilterState } from "../../Models/FilteredLayer"
import TilesourceConfig from "../../Models/ThemeConfig/TilesourceConfig" import TilesourceConfig from "../../Models/ThemeConfig/TilesourceConfig"
import {QueryParameters} from "../Web/QueryParameters" import { QueryParameters } from "../Web/QueryParameters"
import ShowOverlayLayer from "../../UI/ShowDataLayer/ShowOverlayLayer" import ShowOverlayLayer from "../../UI/ShowDataLayer/ShowOverlayLayer"
import {FeatureSourceForLayer, Tiled} from "../FeatureSource/FeatureSource" import { FeatureSourceForLayer, Tiled } from "../FeatureSource/FeatureSource"
import SimpleFeatureSource from "../FeatureSource/Sources/SimpleFeatureSource" import SimpleFeatureSource from "../FeatureSource/Sources/SimpleFeatureSource"
import {LocalStorageSource} from "../Web/LocalStorageSource" import { LocalStorageSource } from "../Web/LocalStorageSource"
import {GeoOperations} from "../GeoOperations" import { GeoOperations } from "../GeoOperations"
import TitleHandler from "../Actors/TitleHandler" import TitleHandler from "../Actors/TitleHandler"
import {BBox} from "../BBox" import { BBox } from "../BBox"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import {TiledStaticFeatureSource} from "../FeatureSource/Sources/StaticFeatureSource" import { TiledStaticFeatureSource } from "../FeatureSource/Sources/StaticFeatureSource"
import {Translation, TypedTranslation} from "../../UI/i18n/Translation" import { Translation, TypedTranslation } from "../../UI/i18n/Translation"
import {Tag} from "../Tags/Tag" import { Tag } from "../Tags/Tag"
import {OsmConnection} from "../Osm/OsmConnection" import { OsmConnection } from "../Osm/OsmConnection"
import {Feature, LineString} from "geojson" import { Feature, LineString } from "geojson"
import {OsmTags} from "../../Models/OsmFeature" import { OsmTags } from "../../Models/OsmFeature"
export interface GlobalFilter { export interface GlobalFilter {
filter: FilterState filter: FilterState
@ -266,27 +266,30 @@ export default class MapState extends UserRelatedState {
this.currentUserLocation = new SimpleFeatureSource(gpsLayerDef, Tiles.tile_index(0, 0, 0)) this.currentUserLocation = new SimpleFeatureSource(gpsLayerDef, Tiles.tile_index(0, 0, 0))
} }
private initSelectedElement(){ private initSelectedElement() {
const layerDef: FilteredLayer = this.filteredLayers.data.filter( const layerDef: FilteredLayer = this.filteredLayers.data.filter(
(l) => l.layerDef.id === "selected_element" (l) => l.layerDef.id === "selected_element"
)[0] )[0]
const empty = [] const empty = []
const store = this.selectedElement.map(feature => { const store = this.selectedElement.map((feature) => {
if(feature === undefined || feature === null){ if (feature === undefined || feature === null) {
return empty return empty
} }
return [{ return [
feature: { {
type:"Feature", feature: {
properties: { type: "Feature",
selected: "yes", properties: {
id: "selected" + feature.properties.id selected: "yes",
id: "selected" + feature.properties.id,
},
geometry: feature.geometry,
}, },
geometry:feature.geometry freshness: new Date(),
} },
, freshness: new Date()}]; ]
}); })
this.selectedElementsLayer = new TiledStaticFeatureSource(store,layerDef); this.selectedElementsLayer = new TiledStaticFeatureSource(store, layerDef)
} }
private initUserLocationTrail() { private initUserLocationTrail() {

View file

@ -127,35 +127,38 @@ export default class MangroveReviews {
this._lastUpdate = new Date() this._lastUpdate = new Date()
const self = this const self = this
mangrove.getReviews({ sub: this.GetSubjectUri() }) mangrove
.getReviews({ sub: this.GetSubjectUri() })
.then((data) => { .then((data) => {
const reviews = [] const reviews = []
const reviewsByUser = [] const reviewsByUser = []
for (const review of data.reviews) { for (const review of data.reviews) {
const r = review.payload const r = review.payload
console.log( console.log(
"PublicKey is ", "PublicKey is ",
self._mangroveIdentity.kid.data, self._mangroveIdentity.kid.data,
"reviews.kid is", "reviews.kid is",
review.kid review.kid
) )
const byUser = self._mangroveIdentity.kid.map((data) => data === review.signature) const byUser = self._mangroveIdentity.kid.map(
const rev: Review = { (data) => data === review.signature
made_by_user: byUser, )
date: new Date(r.iat * 1000), const rev: Review = {
comment: r.opinion, made_by_user: byUser,
author: r.metadata.nickname, date: new Date(r.iat * 1000),
affiliated: r.metadata.is_affiliated, comment: r.opinion,
rating: r.rating, // percentage points author: r.metadata.nickname,
affiliated: r.metadata.is_affiliated,
rating: r.rating, // percentage points
}
;(rev.made_by_user ? reviewsByUser : reviews).push(rev)
} }
self._reviews.setData(reviewsByUser.concat(reviews))
;(rev.made_by_user ? reviewsByUser : reviews).push(rev) })
} .catch((e) => {
self._reviews.setData(reviewsByUser.concat(reviews)) console.error("Could not download review for ", e)
})
.catch(e => {
console.error("Could not download review for ", e);
}) })
return this._reviews return this._reviews
} }

View file

@ -147,8 +147,8 @@ export default class Wikipedia {
}, },
] ]
} }
if(result["error"]){ if (result["error"]) {
throw "Could not download: "+JSON.stringify(result) throw "Could not download: " + JSON.stringify(result)
} }
const el = document.createElement("html") const el = document.createElement("html")
el.innerHTML = result["content"].replace(/href="\//g, 'href="' + this.backend + "/") el.innerHTML = result["content"].replace(/href="\//g, 'href="' + this.backend + "/")

View file

@ -49,11 +49,12 @@ export default class DeleteConfig {
} }
}) })
if(!json.omitDefaultDeleteReasons ){ if (!json.omitDefaultDeleteReasons) {
for (const defaultDeleteReason of DeleteConfig.defaultDeleteReasons) { for (const defaultDeleteReason of DeleteConfig.defaultDeleteReasons) {
this.deleteReasons.push({ this.deleteReasons.push({
changesetMessage: defaultDeleteReason.changesetMessage, changesetMessage: defaultDeleteReason.changesetMessage,
explanation: defaultDeleteReason.explanation.Clone(/*Must clone, hides translation otherwise*/) explanation:
defaultDeleteReason.explanation.Clone(/*Must clone, hides translation otherwise*/),
}) })
} }
} }
@ -66,8 +67,12 @@ export default class DeleteConfig {
} }
}) })
if(this.nonDeleteMappings.length + this.deleteReasons.length == 0){ if (this.nonDeleteMappings.length + this.deleteReasons.length == 0) {
throw "At "+context+": a deleteconfig should have some reasons to delete: either the default delete reasons or a nonDeleteMapping or extraDeletereason should be given" throw (
"At " +
context +
": a deleteconfig should have some reasons to delete: either the default delete reasons or a nonDeleteMapping or extraDeletereason should be given"
)
} }
this.softDeletionTags = undefined this.softDeletionTags = undefined

View file

@ -10,9 +10,9 @@ import { FilterState } from "../FilteredLayer"
import { QueryParameters } from "../../Logic/Web/QueryParameters" import { QueryParameters } from "../../Logic/Web/QueryParameters"
import { Utils } from "../../Utils" import { Utils } from "../../Utils"
import { RegexTag } from "../../Logic/Tags/RegexTag" import { RegexTag } from "../../Logic/Tags/RegexTag"
import BaseUIElement from "../../UI/BaseUIElement"; import BaseUIElement from "../../UI/BaseUIElement"
import Table from "../../UI/Base/Table"; import Table from "../../UI/Base/Table"
import Combine from "../../UI/Base/Combine"; import Combine from "../../UI/Base/Combine"
export default class FilterConfig { export default class FilterConfig {
public readonly id: string public readonly id: string
@ -247,19 +247,22 @@ export default class FilterConfig {
} }
public GenerateDocs(): BaseUIElement { public GenerateDocs(): BaseUIElement {
const hasField = this.options.some(opt => opt.fields?.length > 0) const hasField = this.options.some((opt) => opt.fields?.length > 0)
return new Table( return new Table(
Utils.NoNull(["id","question","osmTags",hasField ? "fields" : undefined]), Utils.NoNull(["id", "question", "osmTags", hasField ? "fields" : undefined]),
this.options.map((opt, i) => { this.options.map((opt, i) => {
const isDefault = this.options.length > 1 && ((this.defaultSelection ?? 0) == i) const isDefault = this.options.length > 1 && (this.defaultSelection ?? 0) == i
return Utils.NoNull([ return Utils.NoNull([
this.id + "." + i, this.id + "." + i,
isDefault ? new Combine([opt.question.SetClass("font-bold"), "(default)"]) : opt.question , isDefault
? new Combine([opt.question.SetClass("font-bold"), "(default)"])
: opt.question,
opt.osmTags?.asHumanString(false, false, {}) ?? "", opt.osmTags?.asHumanString(false, false, {}) ?? "",
opt.fields?.length > 0 ? new Combine(opt.fields.map(f => f.name+" ("+f.type+")")) : undefined opt.fields?.length > 0
? new Combine(opt.fields.map((f) => f.name + " (" + f.type + ")"))
]); : undefined,
])
}) })
); )
} }
} }

View file

@ -70,7 +70,6 @@ export default interface PointRenderingConfigJson {
*/ */
css?: string | TagRenderingConfigJson css?: string | TagRenderingConfigJson
/** /**
* A snippet of css-classes. They can be space-separated * A snippet of css-classes. They can be space-separated
*/ */

View file

@ -301,7 +301,10 @@ export default class LayerConfig extends WithContextLoader {
const hasCenterRendering = this.mapRendering.some( const hasCenterRendering = this.mapRendering.some(
(r) => (r) =>
r.location.has("centroid") || r.location.has("projected_centerpoint") || r.location.has("start") || r.location.has("end") r.location.has("centroid") ||
r.location.has("projected_centerpoint") ||
r.location.has("start") ||
r.location.has("end")
) )
if (this.lineRendering.length === 0 && this.mapRendering.length === 0) { if (this.lineRendering.length === 0 && this.mapRendering.length === 0) {
@ -602,10 +605,10 @@ export default class LayerConfig extends WithContextLoader {
} }
} }
const filterDocs: (string | BaseUIElement)[] = [] const filterDocs: (string | BaseUIElement)[] = []
if(this.filters.length > 0){ if (this.filters.length > 0) {
filterDocs.push(new Title("Filters", 4)) filterDocs.push(new Title("Filters", 4))
filterDocs.push(...this.filters.map(filter => filter.GenerateDocs())) filterDocs.push(...this.filters.map((filter) => filter.GenerateDocs()))
} }
return new Combine([ return new Combine([
new Combine([new Title(this.id, 1), iconImg, this.description, "\n"]).SetClass( new Combine([new Title(this.id, 1), iconImg, this.description, "\n"]).SetClass(
@ -621,7 +624,7 @@ export default class LayerConfig extends WithContextLoader {
new Title("Supported attributes", 2), new Title("Supported attributes", 2),
quickOverview, quickOverview,
...this.tagRenderings.map((tr) => tr.GenerateDocumentation()), ...this.tagRenderings.map((tr) => tr.GenerateDocumentation()),
...filterDocs ...filterDocs,
]) ])
.SetClass("flex-col") .SetClass("flex-col")
.SetClass("link-underline") .SetClass("link-underline")

View file

@ -12,7 +12,7 @@ import { FixedUiElement } from "../../UI/Base/FixedUiElement"
import Img from "../../UI/Base/Img" import Img from "../../UI/Base/Img"
import Combine from "../../UI/Base/Combine" import Combine from "../../UI/Base/Combine"
import { VariableUiElement } from "../../UI/Base/VariableUIElement" import { VariableUiElement } from "../../UI/Base/VariableUIElement"
import {TagRenderingConfigJson} from "./Json/TagRenderingConfigJson"; import { TagRenderingConfigJson } from "./Json/TagRenderingConfigJson"
export default class PointRenderingConfig extends WithContextLoader { export default class PointRenderingConfig extends WithContextLoader {
private static readonly allowed_location_codes = new Set<string>([ private static readonly allowed_location_codes = new Set<string>([
@ -64,7 +64,7 @@ export default class PointRenderingConfig extends WithContextLoader {
) )
} }
this.icon = this.tr("icon", undefined) this.icon = this.tr("icon", undefined)
if(json.css !== undefined){ if (json.css !== undefined) {
this.cssDef = this.tr("css", undefined) this.cssDef = this.tr("css", undefined)
} }
this.cssClasses = this.tr("cssClasses", undefined) this.cssClasses = this.tr("cssClasses", undefined)
@ -247,8 +247,8 @@ export default class PointRenderingConfig extends WithContextLoader {
iconAndBadges.SetClass("w-full h-full") iconAndBadges.SetClass("w-full h-full")
} }
const css= this.cssDef?.GetRenderValue(tags , undefined)?.txt const css = this.cssDef?.GetRenderValue(tags, undefined)?.txt
const cssClasses = this.cssClasses?.GetRenderValue(tags , undefined)?.txt const cssClasses = this.cssClasses?.GetRenderValue(tags, undefined)?.txt
let label = this.GetLabel(tags) let label = this.GetLabel(tags)
let htmlEl: BaseUIElement let htmlEl: BaseUIElement
@ -262,11 +262,11 @@ export default class PointRenderingConfig extends WithContextLoader {
htmlEl = new Combine([iconAndBadges, label]).SetStyle("flex flex-col") htmlEl = new Combine([iconAndBadges, label]).SetStyle("flex flex-col")
} }
if(css !== undefined){ if (css !== undefined) {
htmlEl?.SetStyle(css) htmlEl?.SetStyle(css)
} }
if(cssClasses !== undefined){ if (cssClasses !== undefined) {
htmlEl?.SetClass(cssClasses) htmlEl?.SetClass(cssClasses)
} }
return { return {

View file

@ -5,7 +5,7 @@ import Loc from "../../Models/Loc"
import BaseLayer from "../../Models/BaseLayer" import BaseLayer from "../../Models/BaseLayer"
import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers" import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers"
import * as L from "leaflet" import * as L from "leaflet"
import {LeafletMouseEvent, Map} from "leaflet" import { LeafletMouseEvent, Map } from "leaflet"
import Minimap, { MinimapObj, MinimapOptions } from "./Minimap" import Minimap, { MinimapObj, MinimapOptions } from "./Minimap"
import { BBox } from "../../Logic/BBox" import { BBox } from "../../Logic/BBox"
import "leaflet-polylineoffset" import "leaflet-polylineoffset"
@ -317,7 +317,7 @@ export default class MinimapImplementation extends BaseUIElement implements Mini
if (this._options.lastClickLocation) { if (this._options.lastClickLocation) {
const lastClickLocation = this._options.lastClickLocation const lastClickLocation = this._options.lastClickLocation
map.on("click", function (e: LeafletMouseEvent) { map.on("click", function (e: LeafletMouseEvent) {
if(e.originalEvent["dismissed"] ){ if (e.originalEvent["dismissed"]) {
return return
} }
lastClickLocation?.setData({ lat: e.latlng.lat, lon: e.latlng.lng }) lastClickLocation?.setData({ lat: e.latlng.lat, lon: e.latlng.lng })

View file

@ -1,7 +1,7 @@
import Svg from "../../Svg" import Svg from "../../Svg"
import Combine from "./Combine" import Combine from "./Combine"
import {FixedUiElement} from "./FixedUiElement" import { FixedUiElement } from "./FixedUiElement"
import {UIEventSource} from "../../Logic/UIEventSource" import { UIEventSource } from "../../Logic/UIEventSource"
import Hash from "../../Logic/Web/Hash" import Hash from "../../Logic/Web/Hash"
import BaseUIElement from "../BaseUIElement" import BaseUIElement from "../BaseUIElement"
import Title from "./Title" import Title from "./Title"
@ -71,20 +71,17 @@ export default class ScrollableFullScreen {
} }
ScrollableFullScreen._currentlyOpen = self ScrollableFullScreen._currentlyOpen = self
self.Activate() self.Activate()
} else { } else {
if(self.hashToShow !== undefined){ if (self.hashToShow !== undefined) {
Hash.hash.setData(undefined) Hash.hash.setData(undefined)
} }
// Some cleanup... // Some cleanup...
ScrollableFullScreen.collapse() ScrollableFullScreen.collapse()
} }
}) })
} }
private static initEmpty(): FixedUiElement{ private static initEmpty(): FixedUiElement {
document.addEventListener("keyup", function (event) { document.addEventListener("keyup", function (event) {
if (event.code === "Escape") { if (event.code === "Escape") {
ScrollableFullScreen.collapse() ScrollableFullScreen.collapse()
@ -93,9 +90,8 @@ export default class ScrollableFullScreen {
}) })
return new FixedUiElement("") return new FixedUiElement("")
} }
public static collapse(){ public static collapse() {
const fs = document.getElementById("fullscreen") const fs = document.getElementById("fullscreen")
if (fs !== null) { if (fs !== null) {
ScrollableFullScreen.empty.AttachTo("fullscreen") ScrollableFullScreen.empty.AttachTo("fullscreen")
@ -103,8 +99,8 @@ export default class ScrollableFullScreen {
} }
const opened = ScrollableFullScreen._currentlyOpen const opened = ScrollableFullScreen._currentlyOpen
if( opened !== undefined){ if (opened !== undefined) {
opened?.isShown?.setData(false) opened?.isShown?.setData(false)
} }
} }
@ -124,7 +120,6 @@ export default class ScrollableFullScreen {
fs.classList.remove("hidden") fs.classList.remove("hidden")
} }
private BuildComponent(title: BaseUIElement, content: BaseUIElement): BaseUIElement { private BuildComponent(title: BaseUIElement, content: BaseUIElement): BaseUIElement {
const returnToTheMap = new Combine([ const returnToTheMap = new Combine([
Svg.back_svg().SetClass("block md:hidden w-12 h-12 p-2 svg-foreground"), Svg.back_svg().SetClass("block md:hidden w-12 h-12 p-2 svg-foreground"),

View file

@ -147,15 +147,11 @@ export default class CopyrightPanel extends Combine {
imgSize, imgSize,
} }
), ),
new SubtleButton( new SubtleButton(Svg.mastodon_ui(), t.followOnMastodon, {
Svg.mastodon_ui(), url: "https://en.osm.town/web/notifications",
t.followOnMastodon, newTab: true,
{ imgSize,
url: "https://en.osm.town/web/notifications", }),
newTab: true,
imgSize,
}
),
new OpenIdEditor(state, iconStyle), new OpenIdEditor(state, iconStyle),
new MapillaryLink(state, iconStyle), new MapillaryLink(state, iconStyle),
new OpenJosm(state, iconStyle), new OpenJosm(state, iconStyle),

View file

@ -13,7 +13,7 @@ import { VariableUiElement } from "../Base/VariableUIElement"
import FeatureInfoBox from "../Popup/FeatureInfoBox" import FeatureInfoBox from "../Popup/FeatureInfoBox"
import CopyrightPanel from "./CopyrightPanel" import CopyrightPanel from "./CopyrightPanel"
import FeaturePipelineState from "../../Logic/State/FeaturePipelineState" import FeaturePipelineState from "../../Logic/State/FeaturePipelineState"
import {FixedUiElement} from "../Base/FixedUiElement"; import { FixedUiElement } from "../Base/FixedUiElement"
export default class LeftControls extends Combine { export default class LeftControls extends Combine {
constructor( constructor(
@ -46,14 +46,11 @@ export default class LeftControls extends Combine {
}) })
).SetClass("inline-block w-full h-full") ).SetClass("inline-block w-full h-full")
feature.map((feature) => { feature.map((feature) => {
if (feature === undefined) { if (feature === undefined) {
return undefined return undefined
} }
const tagsSource = state.allElements.getEventSourceById( const tagsSource = state.allElements.getEventSourceById(feature.properties.id)
feature.properties.id
)
return new FeatureInfoBox(tagsSource, currentViewFL.layerDef, state, { return new FeatureInfoBox(tagsSource, currentViewFL.layerDef, state, {
hashToShow: "currentview", hashToShow: "currentview",
isShown: guiState.currentViewControlIsOpened, isShown: guiState.currentViewControlIsOpened,
@ -85,7 +82,6 @@ export default class LeftControls extends Combine {
) )
) )
new ScrollableFullScreen( new ScrollableFullScreen(
() => Translations.t.general.layerSelection.title.Clone(), () => Translations.t.general.layerSelection.title.Clone(),
() => () =>
@ -96,8 +92,8 @@ export default class LeftControls extends Combine {
guiState.filterViewIsOpened guiState.filterViewIsOpened
) )
const toggledFilter = new MapControlButton(Svg.layers_svg()).onClick(() => const toggledFilter = new MapControlButton(Svg.layers_svg()).onClick(() =>
guiState.filterViewIsOpened.setData(true) guiState.filterViewIsOpened.setData(true)
) )
const filterButton = new Toggle(toggledFilter, undefined, state.featureSwitchFilter) const filterButton = new Toggle(toggledFilter, undefined, state.featureSwitchFilter)
@ -110,22 +106,17 @@ export default class LeftControls extends Combine {
// If the welcomeMessage is disabled, the copyright is hidden (as that is where the copyright is located // If the welcomeMessage is disabled, the copyright is hidden (as that is where the copyright is located
const copyright = new Toggle( const copyright = new Toggle(
undefined, undefined,
new Lazy( new Lazy(() => {
() => new ScrollableFullScreen(
{ () => Translations.t.general.attribution.attributionTitle,
() => new CopyrightPanel(state),
new ScrollableFullScreen( "copyright",
() => Translations.t.general.attribution.attributionTitle, guiState.copyrightViewIsOpened
() => new CopyrightPanel(state), )
"copyright", return new MapControlButton(Svg.copyright_svg()).onClick(() =>
guiState.copyrightViewIsOpened guiState.copyrightViewIsOpened.setData(true)
); )
return new MapControlButton(Svg.copyright_svg()).onClick(() => }),
guiState.copyrightViewIsOpened.setData(true)
)
}
),
state.featureSwitchWelcomeMessage state.featureSwitchWelcomeMessage
) )

View file

@ -1,20 +1,20 @@
import ScrollableFullScreen from "../Base/ScrollableFullScreen"; import ScrollableFullScreen from "../Base/ScrollableFullScreen"
import Translations from "../i18n/Translations"; import Translations from "../i18n/Translations"
import {OsmConnection} from "../../Logic/Osm/OsmConnection"; import { OsmConnection } from "../../Logic/Osm/OsmConnection"
import Combine from "../Base/Combine"; import Combine from "../Base/Combine"
import {SubtleButton} from "../Base/SubtleButton"; import { SubtleButton } from "../Base/SubtleButton"
import Svg from "../../Svg"; import Svg from "../../Svg"
import {VariableUiElement} from "../Base/VariableUIElement"; import { VariableUiElement } from "../Base/VariableUIElement"
import Img from "../Base/Img"; import Img from "../Base/Img"
import {FixedUiElement} from "../Base/FixedUiElement"; import { FixedUiElement } from "../Base/FixedUiElement"
import Link from "../Base/Link"; import Link from "../Base/Link"
import {UIEventSource} from "../../Logic/UIEventSource"; import { UIEventSource } from "../../Logic/UIEventSource"
import Loc from "../../Models/Loc"; import Loc from "../../Models/Loc"
import BaseUIElement from "../BaseUIElement"; import BaseUIElement from "../BaseUIElement"
import Showdown from "showdown" import Showdown from "showdown"
import LanguagePicker from "../LanguagePicker"; import LanguagePicker from "../LanguagePicker"
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
import Constants from "../../Models/Constants"; import Constants from "../../Models/Constants"
export class ImportViewerLinks extends VariableUiElement { export class ImportViewerLinks extends VariableUiElement {
constructor(osmConnection: OsmConnection) { constructor(osmConnection: OsmConnection) {
@ -37,22 +37,25 @@ export class ImportViewerLinks extends VariableUiElement {
} }
class UserInformationMainPanel extends Combine { class UserInformationMainPanel extends Combine {
constructor(osmConnection: OsmConnection, locationControl: UIEventSource<Loc>, layout: LayoutConfig) { constructor(
const t = Translations.t.userinfo; osmConnection: OsmConnection,
locationControl: UIEventSource<Loc>,
layout: LayoutConfig
) {
const t = Translations.t.userinfo
const imgSize = "h-6 w-6" const imgSize = "h-6 w-6"
const ud = osmConnection.userDetails; const ud = osmConnection.userDetails
super([ super([
new VariableUiElement(
new VariableUiElement(ud.map(ud => { ud.map((ud) => {
if (!ud?.loggedIn) { if (!ud?.loggedIn) {
// Not logged in // Not logged in
return new SubtleButton( return new SubtleButton(Svg.login_svg(), "Login", { imgSize }).onClick(
Svg.login_svg(), "Login", {imgSize} osmConnection.AttemptLogin
).onClick(osmConnection.AttemptLogin) )
} }
let img: Img = Svg.person_svg(); let img: Img = Svg.person_svg()
if (ud.img !== undefined) { if (ud.img !== undefined) {
img = new Img(ud.img) img = new Img(ud.img)
} }
@ -64,69 +67,95 @@ class UserInformationMainPanel extends Combine {
Svg.pencil_svg().SetClass("h-4 w-4"), Svg.pencil_svg().SetClass("h-4 w-4"),
"https://www.openstreetmap.org/profile/edit", "https://www.openstreetmap.org/profile/edit",
true true
).SetClass("absolute block bg-subtle rounded-full p-2 bottom-2 right-2 w-min self-end") ).SetClass(
"absolute block bg-subtle rounded-full p-2 bottom-2 right-2 w-min self-end"
)
description = new Combine([ description = new Combine([
new FixedUiElement(new Showdown.Converter().makeHtml(ud.description)).SetClass("link-underline"), new FixedUiElement(
editButton new Showdown.Converter().makeHtml(ud.description)
).SetClass("link-underline"),
editButton,
]).SetClass("relative w-full m-2") ]).SetClass("relative w-full m-2")
} else { } else {
description = new Combine([ description = new Combine([
t.noDescription, new SubtleButton(Svg.pencil_svg(), t.noDescriptionCallToAction, {imgSize}) t.noDescription,
new SubtleButton(Svg.pencil_svg(), t.noDescriptionCallToAction, {
imgSize,
}),
]).SetClass("w-full m-2") ]).SetClass("w-full m-2")
} }
let panToHome: BaseUIElement; let panToHome: BaseUIElement
if (ud.home) { if (ud.home) {
panToHome = new SubtleButton(Svg.home_svg(), t.moveToHome, {imgSize}) panToHome = new SubtleButton(Svg.home_svg(), t.moveToHome, {
.onClick(() => { imgSize,
const home = ud?.home }).onClick(() => {
if (home === undefined) { const home = ud?.home
return if (home === undefined) {
} return
locationControl.setData({...home, zoom: 16}) }
} locationControl.setData({ ...home, zoom: 16 })
); })
} }
return new Combine([ return new Combine([
new Combine([img, description]).SetClass("flex border border-black rounded-md"), new Combine([img, description]).SetClass(
new LanguagePicker(layout.language, Translations.t.general.pickLanguage.Clone()), "flex border border-black rounded-md"
),
new LanguagePicker(
layout.language,
Translations.t.general.pickLanguage.Clone()
),
new SubtleButton(Svg.envelope_svg(), new Combine([t.gotoInbox, new SubtleButton(
ud.unreadMessages == 0 ? undefined : t.newMessages.SetClass("alert block") Svg.envelope_svg(),
new Combine([
t.gotoInbox,
ud.unreadMessages == 0
? undefined
: t.newMessages.SetClass("alert block"),
]), ]),
{imgSize, url: `${ud.backend}/messages/inbox`, newTab: true}), { imgSize, url: `${ud.backend}/messages/inbox`, newTab: true }
new SubtleButton(Svg.gear_svg(), t.gotoSettings, ),
{imgSize, url: `${ud.backend}/user/${encodeURIComponent(ud.name)}/account`, newTab: true}), new SubtleButton(Svg.gear_svg(), t.gotoSettings, {
imgSize,
url: `${ud.backend}/user/${encodeURIComponent(ud.name)}/account`,
newTab: true,
}),
panToHome, panToHome,
new ImportViewerLinks(osmConnection), new ImportViewerLinks(osmConnection),
new SubtleButton(Svg.logout_svg(), Translations.t.general.logout, {imgSize}).onClick(osmConnection.LogOut) new SubtleButton(Svg.logout_svg(), Translations.t.general.logout, {
imgSize,
}).onClick(osmConnection.LogOut),
]) ])
} })
)).SetClass("flex flex-col"), ).SetClass("flex flex-col"),
])
]);
} }
} }
export default class UserInformationPanel extends ScrollableFullScreen { export default class UserInformationPanel extends ScrollableFullScreen {
constructor(state: { constructor(state: {
layoutToUse: LayoutConfig; layoutToUse: LayoutConfig
osmConnection: OsmConnection, locationControl: UIEventSource<Loc> osmConnection: OsmConnection
locationControl: UIEventSource<Loc>
}) { }) {
const t = Translations.t.general; const t = Translations.t.general
super( super(
() => { () => {
return new VariableUiElement(state.osmConnection.userDetails.map(ud => "Welcome " + ud.name)) return new VariableUiElement(
state.osmConnection.userDetails.map((ud) => "Welcome " + ud.name)
)
}, },
() => { () => {
return new UserInformationMainPanel(state.osmConnection, state.locationControl, state.layoutToUse) return new UserInformationMainPanel(
state.osmConnection,
state.locationControl,
state.layoutToUse
)
}, },
"userinfo" "userinfo"
); )
} }
} }

View file

@ -235,7 +235,7 @@ export default class DeleteWizard extends Toggle {
return t.explanations.hardDelete return t.explanations.hardDelete
} }
// This is a soft deletion: we explain _why_ the deletion is soft // This is a soft deletion: we explain _why_ the deletion is soft
return t.explanations.softDelete.Subs({ reason: reason}) return t.explanations.softDelete.Subs({ reason: reason })
}) })
) )
} }

View file

@ -1,4 +1,4 @@
import {UIEventSource} from "../../Logic/UIEventSource" import { UIEventSource } from "../../Logic/UIEventSource"
import TagRenderingQuestion from "./TagRenderingQuestion" import TagRenderingQuestion from "./TagRenderingQuestion"
import Translations from "../i18n/Translations" import Translations from "../i18n/Translations"
import Combine from "../Base/Combine" import Combine from "../Base/Combine"
@ -6,10 +6,10 @@ import TagRenderingAnswer from "./TagRenderingAnswer"
import Toggle from "../Input/Toggle" import Toggle from "../Input/Toggle"
import BaseUIElement from "../BaseUIElement" import BaseUIElement from "../BaseUIElement"
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig" import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
import {Unit} from "../../Models/Unit" import { Unit } from "../../Models/Unit"
import Lazy from "../Base/Lazy" import Lazy from "../Base/Lazy"
import {FixedUiElement} from "../Base/FixedUiElement" import { FixedUiElement } from "../Base/FixedUiElement"
import {EditButton} from "./SaveButton" import { EditButton } from "./SaveButton"
export default class EditableTagRendering extends Toggle { export default class EditableTagRendering extends Toggle {
constructor( constructor(
@ -52,9 +52,9 @@ export default class EditableTagRendering extends Toggle {
undefined, undefined,
renderingIsShown renderingIsShown
) )
const self = this; const self = this
editMode.addCallback(editing => { editMode.addCallback((editing) => {
if(editing){ if (editing) {
console.log("Scrolling etr into view") console.log("Scrolling etr into view")
self.ScrollIntoView() self.ScrollIntoView()
} }
@ -91,18 +91,16 @@ export default class EditableTagRendering extends Toggle {
}) })
) )
const answerWithEditButton = new Combine([ const answerWithEditButton = new Combine([
answer, answer,
new EditButton(state.osmConnection, () => { new EditButton(state.osmConnection, () => {
editMode.setData(true) editMode.setData(true)
question.ScrollIntoView({ question.ScrollIntoView({
onlyIfPartiallyHidden:true onlyIfPartiallyHidden: true,
}) })
}), }),
]).SetClass("flex justify-between w-full") ]).SetClass("flex justify-between w-full")
rendering = new Toggle(question, answerWithEditButton, editMode) rendering = new Toggle(question, answerWithEditButton, editMode)
} }
return rendering return rendering
} }

View file

@ -79,11 +79,15 @@ export default class FeatureInfoBox extends ScrollableFullScreen {
public static GenerateContent( public static GenerateContent(
tags: UIEventSource<any>, tags: UIEventSource<any>,
layerConfig: LayerConfig, layerConfig: LayerConfig,
state: FeaturePipelineState): BaseUIElement{ state: FeaturePipelineState
): BaseUIElement {
return new Toggle( return new Toggle(
new Combine([Svg.delete_icon_svg().SetClass("w-8 h-8"), Translations.t.delete.isDeleted]).SetClass("flex justify-center font-bold items-center") , new Combine([
Svg.delete_icon_svg().SetClass("w-8 h-8"),
Translations.t.delete.isDeleted,
]).SetClass("flex justify-center font-bold items-center"),
FeatureInfoBox.GenerateMainContent(tags, layerConfig, state), FeatureInfoBox.GenerateMainContent(tags, layerConfig, state),
tags.map(t => t["_deleted"] == "yes") tags.map((t) => t["_deleted"] == "yes")
) )
} }
private static GenerateMainContent( private static GenerateMainContent(
@ -91,7 +95,6 @@ export default class FeatureInfoBox extends ScrollableFullScreen {
layerConfig: LayerConfig, layerConfig: LayerConfig,
state: FeaturePipelineState state: FeaturePipelineState
): BaseUIElement { ): BaseUIElement {
let questionBoxes: Map<string, QuestionBox> = new Map<string, QuestionBox>() let questionBoxes: Map<string, QuestionBox> = new Map<string, QuestionBox>()
const t = Translations.t.general const t = Translations.t.general
const allGroupNames = Utils.Dedup(layerConfig.tagRenderings.map((tr) => tr.group)) const allGroupNames = Utils.Dedup(layerConfig.tagRenderings.map((tr) => tr.group))

View file

@ -1,28 +1,28 @@
import {SubtleButton} from "../Base/SubtleButton" import { SubtleButton } from "../Base/SubtleButton"
import Combine from "../Base/Combine" import Combine from "../Base/Combine"
import Svg from "../../Svg" import Svg from "../../Svg"
import {OsmConnection} from "../../Logic/Osm/OsmConnection" import { OsmConnection } from "../../Logic/Osm/OsmConnection"
import Toggle from "../Input/Toggle" import Toggle from "../Input/Toggle"
import {UIEventSource} from "../../Logic/UIEventSource" import { UIEventSource } from "../../Logic/UIEventSource"
import Translations from "../i18n/Translations" import Translations from "../i18n/Translations"
import {VariableUiElement} from "../Base/VariableUIElement" import { VariableUiElement } from "../Base/VariableUIElement"
import {Translation} from "../i18n/Translation" import { Translation } from "../i18n/Translation"
import BaseUIElement from "../BaseUIElement" import BaseUIElement from "../BaseUIElement"
import LocationInput from "../Input/LocationInput" import LocationInput from "../Input/LocationInput"
import Loc from "../../Models/Loc" import Loc from "../../Models/Loc"
import {GeoOperations} from "../../Logic/GeoOperations" import { GeoOperations } from "../../Logic/GeoOperations"
import {OsmObject} from "../../Logic/Osm/OsmObject" import { OsmObject } from "../../Logic/Osm/OsmObject"
import {Changes} from "../../Logic/Osm/Changes" import { Changes } from "../../Logic/Osm/Changes"
import ChangeLocationAction from "../../Logic/Osm/Actions/ChangeLocationAction" import ChangeLocationAction from "../../Logic/Osm/Actions/ChangeLocationAction"
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
import MoveConfig from "../../Models/ThemeConfig/MoveConfig" import MoveConfig from "../../Models/ThemeConfig/MoveConfig"
import {ElementStorage} from "../../Logic/ElementStorage" import { ElementStorage } from "../../Logic/ElementStorage"
import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers" import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers"
import BaseLayer from "../../Models/BaseLayer" import BaseLayer from "../../Models/BaseLayer"
import SearchAndGo from "../BigComponents/SearchAndGo"; import SearchAndGo from "../BigComponents/SearchAndGo"
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"; import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"
import {And} from "../../Logic/Tags/And"; import { And } from "../../Logic/Tags/And"
import {Tag} from "../../Logic/Tags/Tag"; import { Tag } from "../../Logic/Tags/Tag"
interface MoveReason { interface MoveReason {
text: Translation | string text: Translation | string
@ -71,7 +71,7 @@ export default class MoveWizard extends Toggle {
includeSearch: true, includeSearch: true,
startZoom: 12, startZoom: 12,
minZoom: 6, minZoom: 6,
eraseAddressFields: true eraseAddressFields: true,
}) })
} }
if (options.enableImproveAccuracy) { if (options.enableImproveAccuracy) {
@ -85,7 +85,7 @@ export default class MoveWizard extends Toggle {
background: "photo", background: "photo",
startZoom: 17, startZoom: 17,
minZoom: 16, minZoom: 16,
eraseAddressFields: false eraseAddressFields: false,
}) })
} }
@ -166,7 +166,7 @@ export default class MoveWizard extends Toggle {
let searchPanel: BaseUIElement = undefined let searchPanel: BaseUIElement = undefined
if (reason.includeSearch) { if (reason.includeSearch) {
searchPanel = new SearchAndGo({ searchPanel = new SearchAndGo({
leafletMap: locationInput.leafletMap leafletMap: locationInput.leafletMap,
}) })
} }
@ -186,12 +186,14 @@ export default class MoveWizard extends Toggle {
if (reason.eraseAddressFields) { if (reason.eraseAddressFields) {
await state.changes.applyAction( await state.changes.applyAction(
new ChangeTagAction(featureToMove.properties.id, new ChangeTagAction(
new And([new Tag("addr:housenumber", ""), featureToMove.properties.id,
new And([
new Tag("addr:housenumber", ""),
new Tag("addr:street", ""), new Tag("addr:street", ""),
new Tag("addr:city", ""), new Tag("addr:city", ""),
new Tag("addr:postcode","")] new Tag("addr:postcode", ""),
), ]),
featureToMove.properties, featureToMove.properties,
{ {
changeType: "relocated", changeType: "relocated",

View file

@ -33,7 +33,7 @@ export default class QuestionBox extends VariableUiElement {
.filter((tr) => tr.question !== undefined) .filter((tr) => tr.question !== undefined)
.filter((tr) => tr.question !== null) .filter((tr) => tr.question !== null)
let focus: () => void = () => {}; let focus: () => void = () => {}
const tagRenderingQuestions = tagRenderings.map( const tagRenderingQuestions = tagRenderings.map(
(tagRendering, i) => (tagRendering, i) =>
@ -53,7 +53,6 @@ export default class QuestionBox extends VariableUiElement {
skippedQuestions.data.push(i) skippedQuestions.data.push(i)
skippedQuestions.ping() skippedQuestions.ping()
focus() focus()
}), }),
}) })
) )
@ -141,8 +140,9 @@ export default class QuestionBox extends VariableUiElement {
this.skippedQuestions = skippedQuestions this.skippedQuestions = skippedQuestions
this.restingQuestions = questionsToAsk this.restingQuestions = questionsToAsk
focus = () => this.ScrollIntoView({ focus = () =>
onlyIfPartiallyHidden: true this.ScrollIntoView({
}) onlyIfPartiallyHidden: true,
})
} }
} }

View file

@ -1,11 +1,11 @@
import {Store, UIEventSource} from "../../Logic/UIEventSource" import { Store, UIEventSource } from "../../Logic/UIEventSource"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import {ShowDataLayerOptions} from "./ShowDataLayerOptions" import { ShowDataLayerOptions } from "./ShowDataLayerOptions"
import {ElementStorage} from "../../Logic/ElementStorage" import { ElementStorage } from "../../Logic/ElementStorage"
import RenderingMultiPlexerFeatureSource from "../../Logic/FeatureSource/Sources/RenderingMultiPlexerFeatureSource" import RenderingMultiPlexerFeatureSource from "../../Logic/FeatureSource/Sources/RenderingMultiPlexerFeatureSource"
import ScrollableFullScreen from "../Base/ScrollableFullScreen" import ScrollableFullScreen from "../Base/ScrollableFullScreen"
import {LeafletMouseEvent} from "leaflet"; import { LeafletMouseEvent } from "leaflet"
import Hash from "../../Logic/Web/Hash"; import Hash from "../../Logic/Web/Hash"
/* /*
// import 'leaflet-polylineoffset'; // import 'leaflet-polylineoffset';
We don't actually import it here. It is imported in the 'MinimapImplementation'-class, which'll result in a patched 'L' object. We don't actually import it here. It is imported in the 'MinimapImplementation'-class, which'll result in a patched 'L' object.
@ -43,7 +43,10 @@ export default class ShowDataLayerImplementation {
* Note: the key of this dictionary is 'feature.properties.id+features.geometry.type' as one feature might have multiple presentations * Note: the key of this dictionary is 'feature.properties.id+features.geometry.type' as one feature might have multiple presentations
* @private * @private
*/ */
private readonly leafletLayersPerId = new Map<string, { feature: any; activateFunc: (event: LeafletMouseEvent) => void }>() private readonly leafletLayersPerId = new Map<
string,
{ feature: any; activateFunc: (event: LeafletMouseEvent) => void }
>()
private readonly showDataLayerid: number private readonly showDataLayerid: number
private readonly createPopup: ( private readonly createPopup: (
tags: UIEventSource<any>, tags: UIEventSource<any>,
@ -324,18 +327,18 @@ export default class ShowDataLayerImplementation {
return return
} }
const key = feature.properties.id const key = feature.properties.id
if(this.leafletLayersPerId.has(key)){ if (this.leafletLayersPerId.has(key)) {
const activate = this.leafletLayersPerId.get(key) const activate = this.leafletLayersPerId.get(key)
leafletLayer.addEventListener('click', activate.activateFunc) leafletLayer.addEventListener("click", activate.activateFunc)
if(Hash.hash.data === key ){ if (Hash.hash.data === key) {
activate.activateFunc(null) activate.activateFunc(null)
} }
return; return
} }
let infobox: ScrollableFullScreen = undefined let infobox: ScrollableFullScreen = undefined
const self = this const self = this
function activate (event: LeafletMouseEvent) { function activate(event: LeafletMouseEvent) {
if (infobox === undefined) { if (infobox === undefined) {
const tags = const tags =
self.allElements?.getEventSourceById(key) ?? self.allElements?.getEventSourceById(key) ??
@ -348,25 +351,26 @@ export default class ShowDataLayerImplementation {
}) })
} }
infobox.Activate() infobox.Activate()
self._selectedElement.setData( self.allElements.ContainingFeatures.get(feature.id) ?? feature ) self._selectedElement.setData(
self.allElements.ContainingFeatures.get(feature.id) ?? feature
)
event?.originalEvent?.preventDefault() event?.originalEvent?.preventDefault()
event?.originalEvent?.stopPropagation() event?.originalEvent?.stopPropagation()
event?.originalEvent?.stopImmediatePropagation() event?.originalEvent?.stopImmediatePropagation()
if(event?.originalEvent){ if (event?.originalEvent) {
// This is a total workaround, as 'preventDefault' and everything above seems to be not working // This is a total workaround, as 'preventDefault' and everything above seems to be not working
event.originalEvent["dismissed"] = true event.originalEvent["dismissed"] = true
} }
} }
leafletLayer.addEventListener('click', activate) leafletLayer.addEventListener("click", activate)
// Add the feature to the index to open the popup when needed // Add the feature to the index to open the popup when needed
this.leafletLayersPerId.set(key, { this.leafletLayersPerId.set(key, {
feature: feature, feature: feature,
activateFunc: activate, activateFunc: activate,
}) })
if(Hash.hash.data === key ){ if (Hash.hash.data === key) {
activate(null) activate(null)
} }
} }

View file

@ -1,6 +1,5 @@
import * as colors from "./assets/colors.json" import * as colors from "./assets/colors.json"
export class Utils { export class Utils {
/** /**
* In the 'deploy'-step, some code needs to be run by ts-node. * In the 'deploy'-step, some code needs to be run by ts-node.
@ -139,7 +138,13 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
"false", "false",
] ]
private static injectedDownloads = {} private static injectedDownloads = {}
private static _download_cache = new Map<string, { promise: Promise<any | {error: string, url: string, statuscode?: number}>; timestamp: number }>() private static _download_cache = new Map<
string,
{
promise: Promise<any | { error: string; url: string; statuscode?: number }>
timestamp: number
}
>()
/** /**
* Parses the arguments for special visualisations * Parses the arguments for special visualisations
@ -570,7 +575,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
object[head] = leaf.map((o) => replaceLeaf(o, travelledPath)) object[head] = leaf.map((o) => replaceLeaf(o, travelledPath))
} else { } else {
object[head] = replaceLeaf(leaf, travelledPath) object[head] = replaceLeaf(leaf, travelledPath)
if(object[head] === undefined){ if (object[head] === undefined) {
delete object[head] delete object[head]
} }
} }
@ -803,7 +808,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
public static async download(url: string, headers?: any): Promise<string | undefined> { public static async download(url: string, headers?: any): Promise<string | undefined> {
const result = await Utils.downloadAdvanced(url, headers) const result = await Utils.downloadAdvanced(url, headers)
if(result["error"] !== undefined){ if (result["error"] !== undefined) {
throw result["error"] throw result["error"]
} }
return result["content"] return result["content"]
@ -816,8 +821,12 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
*/ */
public static downloadAdvanced( public static downloadAdvanced(
url: string, url: string,
headers?: any, headers?: any
): Promise<{ content: string } | { redirect: string } | { error: string,url: string, statuscode?: number}> { ): Promise<
| { content: string }
| { redirect: string }
| { error: string; url: string; statuscode?: number }
> {
if (this.externalDownloadFunction !== undefined) { if (this.externalDownloadFunction !== undefined) {
return this.externalDownloadFunction(url, headers) return this.externalDownloadFunction(url, headers)
} }
@ -831,9 +840,13 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
} else if (xhr.status === 302) { } else if (xhr.status === 302) {
resolve({ redirect: xhr.getResponseHeader("location") }) resolve({ redirect: xhr.getResponseHeader("location") })
} else if (xhr.status === 509 || xhr.status === 429) { } else if (xhr.status === 509 || xhr.status === 429) {
resolve ({error: "rate limited", url, statuscode: xhr.status}) resolve({ error: "rate limited", url, statuscode: xhr.status })
} else { } else {
resolve ({error: "other error: "+xhr.statusText, url, statuscode: xhr.status}) resolve({
error: "other error: " + xhr.statusText,
url,
statuscode: xhr.status,
})
} }
} }
xhr.open("GET", url) xhr.open("GET", url)
@ -878,7 +891,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
headers?: any headers?: any
): Promise<any> { ): Promise<any> {
const result = await Utils.downloadJsonAdvanced(url, headers) const result = await Utils.downloadJsonAdvanced(url, headers)
if(result["content"]){ if (result["content"]) {
return result["content"] return result["content"]
} }
throw result["error"] throw result["error"]
@ -888,56 +901,58 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
url: string, url: string,
maxCacheTimeMs: number, maxCacheTimeMs: number,
headers?: any headers?: any
): Promise<any | {error: string, url: string, statuscode?: number}> { ): Promise<any | { error: string; url: string; statuscode?: number }> {
const cached = Utils._download_cache.get(url) const cached = Utils._download_cache.get(url)
if (cached !== undefined) { if (cached !== undefined) {
if (new Date().getTime() - cached.timestamp <= maxCacheTimeMs) { if (new Date().getTime() - cached.timestamp <= maxCacheTimeMs) {
return cached.promise return cached.promise
} }
} }
const promise = /*NO AWAIT as we work with the promise directly */ Utils.downloadJsonAdvanced( const promise =
url, /*NO AWAIT as we work with the promise directly */ Utils.downloadJsonAdvanced(
headers url,
) headers
)
Utils._download_cache.set(url, { promise, timestamp: new Date().getTime() }) Utils._download_cache.set(url, { promise, timestamp: new Date().getTime() })
return await promise return await promise
} }
public static async downloadJson(url: string, headers?: any): Promise<any> { public static async downloadJson(url: string, headers?: any): Promise<any> {
const result = await Utils.downloadJsonAdvanced(url, headers) const result = await Utils.downloadJsonAdvanced(url, headers)
if(result["content"]){ if (result["content"]) {
return result["content"] return result["content"]
} }
throw result["error"] throw result["error"]
} }
public static async downloadJsonAdvanced(
public static async downloadJsonAdvanced(url: string, headers?: any): Promise<{content: any} | {error: string, url: string, statuscode?: number}> { url: string,
headers?: any
): Promise<{ content: any } | { error: string; url: string; statuscode?: number }> {
const injected = Utils.injectedDownloads[url] const injected = Utils.injectedDownloads[url]
if (injected !== undefined) { if (injected !== undefined) {
console.log("Using injected resource for test for URL", url) console.log("Using injected resource for test for URL", url)
return new Promise((resolve, _) => resolve({content: injected})) return new Promise((resolve, _) => resolve({ content: injected }))
} }
const result = await Utils.downloadAdvanced( const result = await Utils.downloadAdvanced(
url, url,
Utils.Merge({ accept: "application/json" }, headers ?? {}) Utils.Merge({ accept: "application/json" }, headers ?? {})
) )
if(result["error"] !== undefined){ if (result["error"] !== undefined) {
return <{error: string, url: string, statuscode?: number}> result return <{ error: string; url: string; statuscode?: number }>result
} }
const data = result["content"] const data = result["content"]
try { try {
if (typeof data === "string") { if (typeof data === "string") {
return {content: JSON.parse(data)} return { content: JSON.parse(data) }
} }
return {"content": data} return { content: data }
} catch (e) { } catch (e) {
console.error("Could not parse ", data, "due to", e, "\n", e.stack) console.error("Could not parse ", data, "due to", e, "\n", e.stack)
return {error: "malformed", url} return { error: "malformed", url }
} }
} }
/** /**
* Triggers a 'download file' popup which will download the contents * Triggers a 'download file' popup which will download the contents
*/ */
@ -1261,16 +1276,15 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
public static findParentWithScrolling(element: HTMLElement): HTMLElement { public static findParentWithScrolling(element: HTMLElement): HTMLElement {
// Check if the element itself has scrolling // Check if the element itself has scrolling
if (element.scrollHeight > element.clientHeight) { if (element.scrollHeight > element.clientHeight) {
return element; return element
} }
// If the element does not have scrolling, check if it has a parent element // If the element does not have scrolling, check if it has a parent element
if (!element.parentElement) { if (!element.parentElement) {
return null; return null
} }
// If the element has a parent, repeat the process for the parent element // If the element has a parent, repeat the process for the parent element
return Utils.findParentWithScrolling(element.parentElement); return Utils.findParentWithScrolling(element.parentElement)
} }
} }