forked from MapComplete/MapComplete
Merge develop
This commit is contained in:
commit
f25f5f156d
86 changed files with 1960 additions and 967 deletions
|
|
@ -13,6 +13,7 @@ class SingleTileSaver {
|
|||
private readonly _registeredIds = new Set<string>()
|
||||
private readonly _featureProperties: FeaturePropertiesStore
|
||||
private readonly _isDirty = new UIEventSource(false)
|
||||
|
||||
constructor(
|
||||
storage: UIEventSource<Feature[]> & { flush: () => void },
|
||||
featureProperties: FeaturePropertiesStore
|
||||
|
|
@ -62,6 +63,7 @@ class SingleTileSaver {
|
|||
export default class SaveFeatureSourceToLocalStorage {
|
||||
public readonly storage: TileLocalStorage<Feature[]>
|
||||
private readonly zoomlevel: number
|
||||
|
||||
constructor(
|
||||
backend: string,
|
||||
layername: string,
|
||||
|
|
@ -75,8 +77,25 @@ export default class SaveFeatureSourceToLocalStorage {
|
|||
this.storage = storage
|
||||
const singleTileSavers: Map<number, SingleTileSaver> = new Map<number, SingleTileSaver>()
|
||||
features.features.addCallbackAndRunD((features) => {
|
||||
if (features.some(f => {
|
||||
let totalPoints = 0
|
||||
if (f.geometry.type === "MultiPolygon") {
|
||||
totalPoints = f.geometry.coordinates.map(rings => rings.map(ring => ring.length).reduce((a, b) => a + b)).reduce((a, b) => a + b)
|
||||
} else if (f.geometry.type === "Polygon" || f.geometry.type === "MultiLineString") {
|
||||
totalPoints = f.geometry.coordinates.map(ring => ring.length).reduce((a, b) => a + b)
|
||||
} else if (f.geometry.type === "LineString") {
|
||||
totalPoints = f.geometry.coordinates.length
|
||||
}
|
||||
if (totalPoints > 1000) {
|
||||
console.warn(`Not caching tiles, detected a big object (${totalPoints} points for ${f.properties.id})`)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})) {
|
||||
// Has big objects
|
||||
return
|
||||
}
|
||||
const sliced = GeoOperations.spreadIntoBboxes(features, zoomlevel)
|
||||
|
||||
sliced.forEach((features, tileIndex) => {
|
||||
let tileSaver = singleTileSavers.get(tileIndex)
|
||||
if (tileSaver === undefined) {
|
||||
|
|
|
|||
|
|
@ -90,6 +90,9 @@ export default class AllImageProviders {
|
|||
const allPrefixes = Utils.Dedup(prefixes ?? [].concat(...sources.map(s => s.defaultKeyPrefixes)))
|
||||
for (const prefix of allPrefixes) {
|
||||
for (const k in tags) {
|
||||
if (!tags[k]) {
|
||||
continue
|
||||
}
|
||||
if (k === prefix || k.startsWith(prefix + ":")) {
|
||||
count++
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -18,29 +18,29 @@ interface OsmUserInfo {
|
|||
"contributor_terms": {
|
||||
"agreed": boolean,
|
||||
"pd": boolean
|
||||
},
|
||||
"img": {
|
||||
}
|
||||
"img"?: {
|
||||
"href": string,
|
||||
},
|
||||
"roles": string[],
|
||||
}
|
||||
"roles": string[]
|
||||
"changesets": {
|
||||
"count": number
|
||||
},
|
||||
"traces": {
|
||||
"count": number
|
||||
},
|
||||
}
|
||||
traces: {
|
||||
count: number
|
||||
}
|
||||
"blocks": {
|
||||
"received": {
|
||||
"count": number,
|
||||
"active": number
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"lat": number,
|
||||
"lon": number,
|
||||
"zoom": number
|
||||
},
|
||||
"languages": string[],
|
||||
}
|
||||
home?: {
|
||||
lat: number,
|
||||
lon: number,
|
||||
zoom: number
|
||||
}
|
||||
"languages": string[]
|
||||
"messages": {
|
||||
"received": {
|
||||
"count": number,
|
||||
|
|
@ -49,7 +49,22 @@ interface OsmUserInfo {
|
|||
"sent": {
|
||||
"count": number
|
||||
}
|
||||
}
|
||||
|
||||
id: number
|
||||
display_name: string
|
||||
account_created: string
|
||||
description: string
|
||||
contributor_terms: { agreed: boolean }
|
||||
roles: []
|
||||
changesets: { count: number }
|
||||
traces: { count: number }
|
||||
blocks: { received: { count: number; active: number } }
|
||||
img?: { href: string }
|
||||
home: { lat: number, lon: number }
|
||||
languages?: string[]
|
||||
messages: { received: { count: number, unread: number }, sent: { count: number } }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -231,12 +246,9 @@ export class OsmConnection {
|
|||
return <UIEventSource<T>>this.preferencesHandler.getPreference(key, defaultValue, prefix)
|
||||
}
|
||||
|
||||
|
||||
public LogOut() {
|
||||
this.auth.logout()
|
||||
this.userDetails.data.csCount = 0
|
||||
this.userDetails.data.name = ""
|
||||
this.userDetails.ping()
|
||||
this.userDetails.setData(undefined)
|
||||
console.log("Logged out")
|
||||
this.loadingStatus.setData("not-attempted")
|
||||
}
|
||||
|
|
@ -250,7 +262,7 @@ export class OsmConnection {
|
|||
return this._oauth_config.url
|
||||
}
|
||||
|
||||
public AttemptLogin() {
|
||||
public async AttemptLogin() {
|
||||
this.updateCapabilities()
|
||||
if (this.loadingStatus.data !== "logged-in") {
|
||||
// Stay 'logged-in' if we are already logged in; this simply means we are checking for messages
|
||||
|
|
@ -271,7 +283,6 @@ export class OsmConnection {
|
|||
this.loadUserInfo()
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
private async loadUserInfo() {
|
||||
|
|
@ -294,17 +305,15 @@ export class OsmConnection {
|
|||
description: user.description,
|
||||
backend: this.Backend(),
|
||||
home: user.home,
|
||||
languages: user.languages,
|
||||
languages: user.languages ?? [],
|
||||
totalMessages: user.messages.received?.count ?? 0,
|
||||
img: user.img?.href,
|
||||
account_created: user.account_created,
|
||||
tracesCount: user.traces.count,
|
||||
tracesCount: user.traces?.count ?? 0,
|
||||
unreadMessages: user.messages.received?.unread ?? 0,
|
||||
}
|
||||
console.log("Login completed, userinfo is ", userdetails)
|
||||
this.userDetails.set(userdetails)
|
||||
this.loadingStatus.setData("logged-in")
|
||||
|
||||
} catch (err) {
|
||||
console.log("Could not login due to:", err)
|
||||
this.loadingStatus.setData("error")
|
||||
|
|
@ -314,7 +323,7 @@ export class OsmConnection {
|
|||
this.auth.logout()
|
||||
this.LogOut()
|
||||
} else {
|
||||
console.log("Other error. Status:", err.status)
|
||||
console.log("Other error. Status:", err["status"])
|
||||
this.apiIsOnline.setData("unreachable")
|
||||
}
|
||||
}
|
||||
|
|
@ -362,7 +371,7 @@ export class OsmConnection {
|
|||
method,
|
||||
headers: header,
|
||||
content,
|
||||
path: `/api/0.6/${path}`,
|
||||
path: `/api/0.6/${path}`
|
||||
},
|
||||
function(err, response) {
|
||||
if (err !== null) {
|
||||
|
|
@ -444,7 +453,7 @@ export class OsmConnection {
|
|||
"notes.json",
|
||||
content,
|
||||
{
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
|
||||
},
|
||||
true,
|
||||
)
|
||||
|
|
@ -489,7 +498,7 @@ export class OsmConnection {
|
|||
file: gpx,
|
||||
description: options.description,
|
||||
tags: options.labels?.join(",") ?? "",
|
||||
visibility: options.visibility,
|
||||
visibility: options.visibility
|
||||
}
|
||||
|
||||
if (!contents.description) {
|
||||
|
|
@ -499,7 +508,7 @@ export class OsmConnection {
|
|||
file:
|
||||
"; filename=\"" +
|
||||
(options.filename ?? "gpx_track_mapcomplete_" + new Date().toISOString()) +
|
||||
"\"\r\nContent-Type: application/gpx+xml",
|
||||
"\"\r\nContent-Type: application/gpx+xml"
|
||||
}
|
||||
|
||||
const boundary = "987654"
|
||||
|
|
@ -518,7 +527,7 @@ export class OsmConnection {
|
|||
|
||||
const response = await this.post("gpx/create", body, {
|
||||
"Content-Type": "multipart/form-data; boundary=" + boundary,
|
||||
"Content-Length": "" + body.length,
|
||||
"Content-Length": "" + body.length
|
||||
})
|
||||
const parsed = JSON.parse(response)
|
||||
console.log("Uploaded GPX track", parsed)
|
||||
|
|
@ -539,7 +548,7 @@ export class OsmConnection {
|
|||
{
|
||||
method: "POST",
|
||||
|
||||
path: `/api/0.6/notes/${id}/comment?text=${encodeURIComponent(text)}`,
|
||||
path: `/api/0.6/notes/${id}/comment?text=${encodeURIComponent(text)}`
|
||||
},
|
||||
function(err) {
|
||||
if (err !== null) {
|
||||
|
|
@ -556,7 +565,6 @@ export class OsmConnection {
|
|||
* To be called by land.html
|
||||
*/
|
||||
public finishLogin(callback: (previousURL: string, oauth_token: string) => void) {
|
||||
console.log(">>> authenticating")
|
||||
this.auth.authenticate(() => {
|
||||
// Fully authed at this point
|
||||
console.log("Authentication successful!")
|
||||
|
|
@ -593,7 +601,7 @@ export class OsmConnection {
|
|||
*/
|
||||
singlepage: !this._iframeMode && !AndroidPolyfill.inAndroid.data,
|
||||
auto: autoLogin,
|
||||
apiUrl: this._oauth_config.api_url ?? this._oauth_config.url,
|
||||
apiUrl: this._oauth_config.api_url ?? this._oauth_config.url
|
||||
})
|
||||
if (AndroidPolyfill.inAndroid.data) {
|
||||
this.loginAndroidPolyfill() // NO AWAIT!
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
import GeocodingProvider, {
|
||||
SearchResult,
|
||||
GeocodingOptions,
|
||||
GeocodeResult,
|
||||
} from "./GeocodingProvider"
|
||||
import GeocodingProvider, { GeocodeResult, GeocodingOptions, SearchResult } from "./GeocodingProvider"
|
||||
import { Utils } from "../../Utils"
|
||||
import { Store, Stores } from "../UIEventSource"
|
||||
|
||||
export default class CombinedSearcher implements GeocodingProvider {
|
||||
public readonly name = "CombinedSearcher"
|
||||
private _providers: ReadonlyArray<GeocodingProvider>
|
||||
private _providersWithSuggest: ReadonlyArray<GeocodingProvider>
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@ import GeocodingProvider, { GeocodeResult } from "./GeocodingProvider"
|
|||
import { Utils } from "../../Utils"
|
||||
import { ImmutableStore, Store } from "../UIEventSource"
|
||||
import CoordinateParser from "coordinate-parser"
|
||||
|
||||
/**
|
||||
* A simple search-class which interprets possible locations
|
||||
*/
|
||||
export default class CoordinateSearch implements GeocodingProvider {
|
||||
public readonly name = "CoordinateSearch"
|
||||
private static readonly latLonRegexes: ReadonlyArray<RegExp> = [
|
||||
/^ *(-?[0-9]+\.[0-9]+)[ ,;/\\]+(-?[0-9]+\.[0-9]+)/,
|
||||
/^ *(-?[0-9]+,[0-9]+)[ ;/\\]+(-?[0-9]+,[0-9]+)/,
|
||||
|
|
|
|||
|
|
@ -49,6 +49,13 @@ export interface GeocodingOptions {
|
|||
}
|
||||
|
||||
export default interface GeocodingProvider {
|
||||
readonly name: string
|
||||
|
||||
/**
|
||||
* Performs search.
|
||||
* Note: the result _must_ return an empty list in the case of no results.
|
||||
* Undefined might be interpreted by clients as "still running"
|
||||
*/
|
||||
search(query: string, options?: GeocodingOptions): Promise<GeocodeResult[]>
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import GeocodingProvider, { SearchResult, GeocodingOptions } from "./GeocodingProvider"
|
||||
import GeocodingProvider, { GeocodingOptions, SearchResult } from "./GeocodingProvider"
|
||||
import ThemeViewState from "../../Models/ThemeViewState"
|
||||
import { Utils } from "../../Utils"
|
||||
import { Feature } from "geojson"
|
||||
|
|
@ -20,7 +20,7 @@ type IntermediateResult = {
|
|||
export default class LocalElementSearch implements GeocodingProvider {
|
||||
private readonly _state: ThemeViewState
|
||||
private readonly _limit: number
|
||||
|
||||
public readonly name = "LocalElementSearch"
|
||||
constructor(state: ThemeViewState, limit: number) {
|
||||
this._state = state
|
||||
this._limit = limit
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import GeocodingProvider, { GeocodingOptions, SearchResult } from "./GeocodingPr
|
|||
export class NominatimGeocoding implements GeocodingProvider {
|
||||
private readonly _host
|
||||
private readonly limit: number
|
||||
public readonly name = "Nominatim"
|
||||
|
||||
constructor(limit: number = 3, host: string = Constants.nominatimEndpoint) {
|
||||
this.limit = limit
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export default class OpenLocationCodeSearch implements GeocodingProvider {
|
|||
*/
|
||||
public static readonly _isPlusCode =
|
||||
/^([2-9CFGHJMPQRVWX]{2}|00){2,4}\+([2-9CFGHJMPQRVWX]{2,3})?$/
|
||||
|
||||
public readonly name = "OpenLocationCodeSearch"
|
||||
/**
|
||||
*
|
||||
* OpenLocationCodeSearch.isPlusCode("9FFW84J9+XG") // => true
|
||||
|
|
@ -26,7 +26,7 @@ export default class OpenLocationCodeSearch implements GeocodingProvider {
|
|||
|
||||
async search(query: string, options?: GeocodingOptions): Promise<GeocodeResult[]> {
|
||||
if (!OpenLocationCodeSearch.isPlusCode(query)) {
|
||||
return undefined
|
||||
return [] // Must be an empty list and not "undefined", the latter is interpreted as 'still searching'
|
||||
}
|
||||
const { latitude, longitude } = pluscode_decode(query)
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import OsmObjectDownloader from "../Osm/OsmObjectDownloader"
|
|||
export default class OpenStreetMapIdSearch implements GeocodingProvider {
|
||||
private static readonly regex =
|
||||
/((https?:\/\/)?(www.)?(osm|openstreetmap).org\/)?(n|node|w|way|r|relation)[/ ]?([0-9]+)/
|
||||
|
||||
public readonly name = "OpenStreetMapId"
|
||||
private static readonly types: Readonly<Record<string, "node" | "way" | "relation">> = {
|
||||
n: "node",
|
||||
w: "way",
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import GeocodingProvider, {
|
|||
GeocodingOptions,
|
||||
GeocodingUtils,
|
||||
ReverseGeocodingProvider,
|
||||
ReverseGeocodingResult,
|
||||
ReverseGeocodingResult
|
||||
} from "./GeocodingProvider"
|
||||
import { Utils } from "../../Utils"
|
||||
import { Feature, FeatureCollection } from "geojson"
|
||||
|
|
@ -15,6 +15,7 @@ import { Store, Stores } from "../UIEventSource"
|
|||
|
||||
export default class PhotonSearch implements GeocodingProvider, ReverseGeocodingProvider {
|
||||
private readonly _endpoint: string
|
||||
public readonly name = "photon"
|
||||
private supportedLanguages = ["en", "de", "fr"]
|
||||
private static readonly types = {
|
||||
R: "relation",
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import LayerConfig from "../Models/ThemeConfig/LayerConfig"
|
|||
import { CountryCoder } from "latlon2country"
|
||||
import Constants from "../Models/Constants"
|
||||
import { TagUtils } from "./Tags/TagUtils"
|
||||
import { Feature, LineString } from "geojson"
|
||||
import { Feature, LineString, MultiPolygon, Polygon } from "geojson"
|
||||
import { OsmTags } from "../Models/OsmFeature"
|
||||
import { UIEventSource } from "./UIEventSource"
|
||||
import ThemeConfig from "../Models/ThemeConfig/ThemeConfig"
|
||||
|
|
@ -80,7 +80,7 @@ export class ReferencingWaysMetaTagger extends SimpleMetaTagger {
|
|||
super({
|
||||
keys: ["_referencing_ways"],
|
||||
isLazy: true,
|
||||
doc: "_referencing_ways contains - for a node - which ways use this node as point in their geometry. ",
|
||||
doc: "_referencing_ways contains - for a node - which ways use this node as point in their geometry. "
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -116,7 +116,7 @@ class CountryTagger extends SimpleMetaTagger {
|
|||
super({
|
||||
keys: ["_country"],
|
||||
doc: "The country codes of the of the country/countries that the feature is located in (with latlon2country). Might contain _multiple_ countries, separated by a `;`",
|
||||
includesDates: false,
|
||||
includesDates: false
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -213,9 +213,9 @@ class RewriteMetaInfoTags extends SimpleMetaTagger {
|
|||
"_last_edit:changeset",
|
||||
"_last_edit:timestamp",
|
||||
"_version_number",
|
||||
"_backend",
|
||||
"_backend"
|
||||
],
|
||||
doc: "Information about the last edit of this object. This object will actually _rewrite_ some tags for features coming from overpass",
|
||||
doc: "Information about the last edit of this object. This object will actually _rewrite_ some tags for features coming from overpass"
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -244,6 +244,69 @@ class RewriteMetaInfoTags extends SimpleMetaTagger {
|
|||
}
|
||||
}
|
||||
|
||||
class NormalizePanoramax extends SimpleMetaTagger {
|
||||
constructor() {
|
||||
super(
|
||||
{
|
||||
keys: ["panoramax"],
|
||||
doc: "Converts a `panoramax=hash1;hash2;hash3;...` into `panoramax=hash1`,`panoramax:0=hash1`...",
|
||||
isLazy: false,
|
||||
cleanupRetagger: true
|
||||
})
|
||||
}
|
||||
|
||||
private addValue(comesFromKey: string, tags: Record<string, string>, hashesToAdd: string[], postfix?: string) {
|
||||
let basekey = "panoramax"
|
||||
if (postfix) {
|
||||
basekey = "panoramax:" + postfix
|
||||
}
|
||||
let index = -1
|
||||
for (let i = 0; i < hashesToAdd.length; i++) {
|
||||
let k = basekey
|
||||
do {
|
||||
if (index >= 0) {
|
||||
k = `${basekey}:${index}`
|
||||
}
|
||||
index++
|
||||
} while (k !== comesFromKey && tags[k])
|
||||
tags[k] = hashesToAdd[i]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* const tags = new UIEventSource({panoramax: "abc;def;ghi", "panoramax:2": "xyz;uvw", "panoramax:streetsign":"a;b;c"})
|
||||
* const _ = undefined
|
||||
* new NormalizePanoramax().applyMetaTagsOnFeature(_, _, tags, _)
|
||||
* tags.data // => {"panoramax": "abc", "panoramax:0" : "def", "panoramax:1": "ghi", "panoramax:2":"xyz", "panoramax:3":"uvw", "panoramax:streetsign":"a", "panoramax:streetsign:0":"b","panoramax:streetsign:1": "c"}
|
||||
*/
|
||||
applyMetaTagsOnFeature(feature: Feature, layer: LayerConfig, tags: UIEventSource<Record<string, string>>): boolean {
|
||||
const tgs = tags.data
|
||||
let somethingChanged = false
|
||||
for (const key in tgs) {
|
||||
if (!(key === "panoramax" || key.startsWith("panoramax:"))) {
|
||||
continue
|
||||
}
|
||||
const v = tgs[key]
|
||||
if (v.indexOf(";") < 0) {
|
||||
continue
|
||||
}
|
||||
const parts = v.split(";")
|
||||
if (key === "panoramax" || key.match("panoramax:[0-9]+")) {
|
||||
this.addValue(key, tgs, parts)
|
||||
somethingChanged = true
|
||||
} else {
|
||||
|
||||
const postfix = key.match(/panoramax:([^:]+)(:[0-9]+)?/)?.[1]
|
||||
if (postfix) {
|
||||
this.addValue(key, tgs, parts, postfix)
|
||||
somethingChanged = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return somethingChanged
|
||||
}
|
||||
}
|
||||
|
||||
export default class SimpleMetaTaggers {
|
||||
/**
|
||||
* A simple metatagger which rewrites various metatags as needed
|
||||
|
|
@ -253,7 +316,7 @@ export default class SimpleMetaTaggers {
|
|||
public static geometryType = new InlineMetaTagger(
|
||||
{
|
||||
keys: ["_geometry:type"],
|
||||
doc: "Adds the geometry type as property. This is identical to the GoeJson geometry type and is one of `Point`,`LineString`, `Polygon` and exceptionally `MultiPolygon` or `MultiLineString`",
|
||||
doc: "Adds the geometry type as property. This is identical to the GoeJson geometry type and is one of `Point`,`LineString`, `Polygon` and exceptionally `MultiPolygon` or `MultiLineString`"
|
||||
},
|
||||
(feature) => {
|
||||
const changed = feature.properties["_geometry:type"] === feature.geometry.type
|
||||
|
|
@ -262,6 +325,7 @@ export default class SimpleMetaTaggers {
|
|||
}
|
||||
)
|
||||
public static referencingWays = new ReferencingWaysMetaTagger()
|
||||
private static normalizePanoramax = new NormalizePanoramax()
|
||||
private static readonly cardinalDirections = {
|
||||
N: 0,
|
||||
NNE: 22.5,
|
||||
|
|
@ -278,12 +342,12 @@ export default class SimpleMetaTaggers {
|
|||
W: 270,
|
||||
WNW: 292.5,
|
||||
NW: 315,
|
||||
NNW: 337.5,
|
||||
NNW: 337.5
|
||||
}
|
||||
private static latlon = new InlineMetaTagger(
|
||||
{
|
||||
keys: ["_lat", "_lon"],
|
||||
doc: "The latitude and longitude of the point (or centerpoint in the case of a way/area)",
|
||||
doc: "The latitude and longitude of the point (or centerpoint in the case of a way/area)"
|
||||
},
|
||||
(feature) => {
|
||||
const centerPoint = GeoOperations.centerpoint(feature)
|
||||
|
|
@ -298,7 +362,7 @@ export default class SimpleMetaTaggers {
|
|||
{
|
||||
doc: "The layer-id to which this feature belongs. Note that this might be return any applicable if `passAllFeatures` is defined.",
|
||||
keys: ["_layer"],
|
||||
includesDates: false,
|
||||
includesDates: false
|
||||
},
|
||||
(feature, layer) => {
|
||||
if (feature.properties._layer === layer.id) {
|
||||
|
|
@ -314,11 +378,11 @@ export default class SimpleMetaTaggers {
|
|||
"sidewalk:left",
|
||||
"sidewalk:right",
|
||||
"generic_key:left:property",
|
||||
"generic_key:right:property",
|
||||
"generic_key:right:property"
|
||||
],
|
||||
doc: "Rewrites tags from 'generic_key:both:property' as 'generic_key:left:property' and 'generic_key:right:property' (and similar for sidewalk tagging). Note that this rewritten tags _will be reuploaded on a change_. To prevent to much unrelated retagging, this is only enabled if the layer has at least some lineRenderings with offset defined",
|
||||
includesDates: false,
|
||||
cleanupRetagger: true,
|
||||
cleanupRetagger: true
|
||||
},
|
||||
(feature, layer) => {
|
||||
if (!layer.lineRendering.some((lr) => lr.leftRightSensitive)) {
|
||||
|
|
@ -332,11 +396,15 @@ export default class SimpleMetaTaggers {
|
|||
{
|
||||
keys: ["_surface"],
|
||||
doc: "The surface area of the feature in square meters. Not set on points and ways",
|
||||
isLazy: true,
|
||||
isLazy: true
|
||||
},
|
||||
(feature) => {
|
||||
if (feature.geometry.type !== "Polygon" && feature.geometry.type !== "MultiPolygon") {
|
||||
return
|
||||
}
|
||||
const f = <Feature<Polygon | MultiPolygon>>feature
|
||||
Utils.AddLazyProperty(feature.properties, "_surface", () => {
|
||||
return "" + GeoOperations.surfaceAreaInSqMeters(feature)
|
||||
return "" + GeoOperations.surfaceAreaInSqMeters(f)
|
||||
})
|
||||
|
||||
return true
|
||||
|
|
@ -346,11 +414,15 @@ export default class SimpleMetaTaggers {
|
|||
{
|
||||
keys: ["_surface:ha"],
|
||||
doc: "The surface area of the feature in hectare. Not set on points and ways",
|
||||
isLazy: true,
|
||||
isLazy: true
|
||||
},
|
||||
(feature) => {
|
||||
if (feature.geometry.type !== "Polygon" && feature.geometry.type !== "MultiPolygon") {
|
||||
return
|
||||
}
|
||||
const f = <Feature<Polygon | MultiPolygon>>feature
|
||||
Utils.AddLazyProperty(feature.properties, "_surface:ha", () => {
|
||||
const sqMeters = GeoOperations.surfaceAreaInSqMeters(feature)
|
||||
const sqMeters = GeoOperations.surfaceAreaInSqMeters(f)
|
||||
return "" + Math.floor(sqMeters / 1000) / 10
|
||||
})
|
||||
|
||||
|
|
@ -360,7 +432,7 @@ export default class SimpleMetaTaggers {
|
|||
private static levels = new InlineMetaTagger(
|
||||
{
|
||||
doc: "Extract the 'level'-tag into a normalized, ';'-separated value called '_level' (which also includes 'repeat_on'). The `level` tag (without underscore) will be normalized with only the value of `level`.",
|
||||
keys: ["_level"],
|
||||
keys: ["_level"]
|
||||
},
|
||||
(feature) => {
|
||||
let somethingChanged = false
|
||||
|
|
@ -395,7 +467,7 @@ export default class SimpleMetaTaggers {
|
|||
private static canonicalize = new InlineMetaTagger(
|
||||
{
|
||||
doc: "If 'units' is defined in the layoutConfig, then this metatagger will rewrite the specified keys to have the canonical form (e.g. `1meter` will be rewritten to `1m`; `1` will be rewritten to `1m` as well)",
|
||||
keys: ["Theme-defined keys"],
|
||||
keys: ["Theme-defined keys"]
|
||||
},
|
||||
(feature, _, __, state) => {
|
||||
const units = Utils.NoNull(
|
||||
|
|
@ -452,7 +524,7 @@ export default class SimpleMetaTaggers {
|
|||
private static lngth = new InlineMetaTagger(
|
||||
{
|
||||
keys: ["_length", "_length:km"],
|
||||
doc: "The total length of a feature in meters (and in kilometers, rounded to one decimal for '_length:km'). For a surface, the length of the perimeter",
|
||||
doc: "The total length of a feature in meters (and in kilometers, rounded to one decimal for '_length:km'). For a surface, the length of the perimeter"
|
||||
},
|
||||
(feature) => {
|
||||
const l = GeoOperations.lengthInMeters(feature)
|
||||
|
|
@ -468,7 +540,7 @@ export default class SimpleMetaTaggers {
|
|||
keys: ["_isOpen"],
|
||||
doc: "If 'opening_hours' is present, it will add the current state of the feature (being 'yes' or 'no')",
|
||||
includesDates: true,
|
||||
isLazy: true,
|
||||
isLazy: true
|
||||
},
|
||||
(feature) => {
|
||||
if (Utils.runningFromConsole) {
|
||||
|
|
@ -507,8 +579,8 @@ export default class SimpleMetaTaggers {
|
|||
lon: lon,
|
||||
address: {
|
||||
country_code: tags._country.toLowerCase(),
|
||||
state: undefined,
|
||||
},
|
||||
state: undefined
|
||||
}
|
||||
},
|
||||
<any>{ tag_key: "opening_hours" }
|
||||
)
|
||||
|
|
@ -520,14 +592,14 @@ export default class SimpleMetaTaggers {
|
|||
delete tags._isOpen
|
||||
tags["_isOpen"] = "parse_error"
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
private static directionSimplified = new InlineMetaTagger(
|
||||
{
|
||||
keys: ["_direction:numerical", "_direction:leftright"],
|
||||
doc: "_direction:numerical is a normalized, numerical direction based on 'camera:direction' or on 'direction'; it is only present if a valid direction is found (e.g. 38.5 or NE). _direction:leftright is either 'left' or 'right', which is left-looking on the map or 'right-looking' on the map",
|
||||
doc: "_direction:numerical is a normalized, numerical direction based on 'camera:direction' or on 'direction'; it is only present if a valid direction is found (e.g. 38.5 or NE). _direction:leftright is either 'left' or 'right', which is left-looking on the map or 'right-looking' on the map"
|
||||
},
|
||||
(feature) => {
|
||||
const tags = feature.properties
|
||||
|
|
@ -552,7 +624,7 @@ export default class SimpleMetaTaggers {
|
|||
{
|
||||
keys: ["_direction:centerpoint"],
|
||||
isLazy: true,
|
||||
doc: "_direction:centerpoint is the direction of the linestring (in degrees) if one were standing at the projected centerpoint.",
|
||||
doc: "_direction:centerpoint is the direction of the linestring (in degrees) if one were standing at the projected centerpoint."
|
||||
},
|
||||
(feature: Feature) => {
|
||||
if (feature.geometry.type !== "LineString") {
|
||||
|
|
@ -575,7 +647,7 @@ export default class SimpleMetaTaggers {
|
|||
delete feature.properties["_direction:centerpoint"]
|
||||
feature.properties["_direction:centerpoint"] = bearing
|
||||
return bearing
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
return true
|
||||
|
|
@ -585,7 +657,7 @@ export default class SimpleMetaTaggers {
|
|||
{
|
||||
keys: ["_now:date", "_now:datetime"],
|
||||
doc: "Adds the time that the data got loaded - pretty much the time of downloading from overpass. The format is YYYY-MM-DD hh:mm, aka 'sortable' aka ISO-8601-but-not-entirely",
|
||||
includesDates: true,
|
||||
includesDates: true
|
||||
},
|
||||
(feature) => {
|
||||
const now = new Date()
|
||||
|
|
@ -609,7 +681,7 @@ export default class SimpleMetaTaggers {
|
|||
keys: ["_last_edit:passed_time"],
|
||||
doc: "Gives the number of seconds since the last edit. Note that this will _not_ update, but rather be the number of seconds elapsed at the moment this tag is read first",
|
||||
isLazy: true,
|
||||
includesDates: true,
|
||||
includesDates: true
|
||||
},
|
||||
(feature) => {
|
||||
Utils.AddLazyProperty(feature.properties, "_last_edit:passed_time", () => {
|
||||
|
|
@ -628,7 +700,7 @@ export default class SimpleMetaTaggers {
|
|||
{
|
||||
keys: ["_currency"],
|
||||
doc: "Adds the currency valid for the object, based on country or explicit tagging. Can be a single currency or a semicolon-separated list of currencies. Empty if no currency is found.",
|
||||
isLazy: true,
|
||||
isLazy: true
|
||||
},
|
||||
(feature: Feature, layer: LayerConfig, tagsStore: UIEventSource<OsmTags>) => {
|
||||
if (tagsStore === undefined) {
|
||||
|
|
@ -670,6 +742,7 @@ export default class SimpleMetaTaggers {
|
|||
}
|
||||
)
|
||||
|
||||
|
||||
public static metatags: SimpleMetaTagger[] = [
|
||||
SimpleMetaTaggers.latlon,
|
||||
SimpleMetaTaggers.layerInfo,
|
||||
|
|
@ -689,6 +762,7 @@ export default class SimpleMetaTaggers {
|
|||
SimpleMetaTaggers.referencingWays,
|
||||
SimpleMetaTaggers.timeSinceLastEdit,
|
||||
SimpleMetaTaggers.currency,
|
||||
SimpleMetaTaggers.normalizePanoramax
|
||||
]
|
||||
|
||||
/**
|
||||
|
|
@ -770,8 +844,8 @@ export default class SimpleMetaTaggers {
|
|||
[
|
||||
"Metatags are extra tags available, in order to display more data or to give better questions.",
|
||||
"They are calculated automatically on every feature when the data arrives in the webbrowser. This document gives an overview of the available metatags.",
|
||||
"**Hint:** when using metatags, add the [query parameter](URL_Parameters.md) `debug=true` to the URL. This will include a box in the popup for features which shows all the properties of the object",
|
||||
].join("\n"),
|
||||
"**Hint:** when using metatags, add the [query parameter](URL_Parameters.md) `debug=true` to the URL. This will include a box in the popup for features which shows all the properties of the object"
|
||||
].join("\n")
|
||||
]
|
||||
|
||||
subElements.push("## Metatags calculated by MapComplete")
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ export default class SearchState {
|
|||
return new ImmutableStore(true)
|
||||
}
|
||||
return Stores.concat(suggestions).map((suggestions) =>
|
||||
suggestions.some((list) => list === undefined)
|
||||
suggestions.some((list, i) => list === undefined)
|
||||
)
|
||||
})
|
||||
this.suggestions = suggestionsList.bindD((suggestions) =>
|
||||
|
|
|
|||
|
|
@ -350,10 +350,10 @@ export default class UserRelatedState {
|
|||
* List of all hidden themes that have been seen before
|
||||
* @param osmConnection
|
||||
*/
|
||||
public static initDiscoveredHiddenThemes(osmConnection: OsmConnection): Store<string[]> {
|
||||
public static initDiscoveredHiddenThemes(osmConnection: OsmConnection): Store<undefined | string[]> {
|
||||
const prefix = "mapcomplete-hidden-theme-"
|
||||
const userPreferences = osmConnection.preferencesHandler.allPreferences
|
||||
return userPreferences.map((preferences) =>
|
||||
return userPreferences.mapD((preferences) =>
|
||||
Object.keys(preferences)
|
||||
.filter((key) => key.startsWith(prefix))
|
||||
.map((key) => key.substring(prefix.length, key.length - "-enabled".length))
|
||||
|
|
@ -497,7 +497,7 @@ export default class UserRelatedState {
|
|||
amendedPrefs.ping()
|
||||
})
|
||||
|
||||
osmConnection.userDetails.addCallback((userDetails) => {
|
||||
osmConnection.userDetails.addCallbackD((userDetails) => {
|
||||
for (const k in userDetails) {
|
||||
amendedPrefs.data["_" + k] = "" + userDetails[k]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,6 +132,16 @@ export class ExpandFilter extends DesugaringStep<LayerConfigJson> {
|
|||
return filters
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* import FilterConfig from "../FilterConfig"
|
||||
*
|
||||
* // A multi-answer tagRendering should match any subkey
|
||||
* const tr = {id: "test", multiAnswer: true, mappings:[{if: "x=a", then: "A"}, {if: "x=b", then:"B"}]}
|
||||
* const filter = ExpandFilter.buildFilterFromTagRendering(tr, ConversionContext.test("ExpandFilter"))
|
||||
* const f = new FilterConfig(filter, "test")
|
||||
* f.options[1].osmTags.matchesProperties({x:"a;b"}) // => true
|
||||
*/
|
||||
public static buildFilterFromTagRendering(
|
||||
tr: TagRenderingConfigJson,
|
||||
context: ConversionContext
|
||||
|
|
@ -153,7 +163,7 @@ export class ExpandFilter extends DesugaringStep<LayerConfigJson> {
|
|||
if (qtr.multiAnswer && osmTags instanceof Tag) {
|
||||
osmTags = new RegexTag(
|
||||
osmTags.key,
|
||||
new RegExp("^(.+;)?" + osmTags.value + "(;.+)$", "is")
|
||||
new RegExp("^(.+;)?" + osmTags.value + "(;.+)?$", "is")
|
||||
)
|
||||
}
|
||||
if (mapping.alsoShowIf) {
|
||||
|
|
|
|||
|
|
@ -436,13 +436,26 @@ export interface LayerConfigJson {
|
|||
)[]
|
||||
|
||||
/**
|
||||
* All the extra questions for filtering.
|
||||
* If a string is given, mapComplete will search in
|
||||
* 1. The tagrenderings for a match on ID and use the mappings as options
|
||||
* 2. search 'filters.json' for the appropriate filter or
|
||||
* 3. will try to parse it as `layername.filterid` and us that one.
|
||||
* Filters are a way to temporarily hide the data from the map (but the data is still loaded from overpass/OSM/the specified source).
|
||||
* This is used to e.g. show "shops open now", "toilets with wheelchair access", "free toilets", ...
|
||||
*
|
||||
* Note: adding "#filter":"no-auto" will disable the filters added by tagRenderings
|
||||
* Filters can be added in various ways:
|
||||
*
|
||||
* - You can specify one explicitly here
|
||||
* - You can specify the id (as a string) of a tagRendering. The tagrendering will then automatically be converted to a filter
|
||||
* If the ID is not found locally, it will be searched in `filters.json`.
|
||||
* If a dot is present, the ID will be interpreted as "<layername>.<filterId>" instead
|
||||
* - A tagRendering might specify `filter: true`. This will add the tagRendering to the filter list automatically
|
||||
* This might introduce filters from _imported_ tagRenderings
|
||||
* Note: adding "#filter":"no-auto" in the layer object will disable this
|
||||
*
|
||||
* A special case is setting `filter: {sameAs: "layerId"}`.
|
||||
* This is only done with twin layers, where one layer is mostly visible (e.g. all shops offering some kind of bicycle service)
|
||||
* and another layer (e.g. "all shops") shows up at high zoom levels (typically 17 or 18). This way, people can mark an already existing shop
|
||||
* as bicycle shop and don't create a duplicate entry.
|
||||
* Of course, if one applies a filter (e.g. "open now") the user will expect the "all shops" layer to also only show shops open now.
|
||||
* As is often the case, the secondary layer will be hidden from the filter view, so they won't be able to enable those filters.
|
||||
* In this case, we let the secondary layer follow the first layer with a `sameAs`.
|
||||
*
|
||||
* group: filters
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@
|
|||
const tu = Translations.t.general
|
||||
const tr = Translations.t.general.morescreen
|
||||
|
||||
let userLanguages = osmConnection.userDetails.mapD((ud) => ud.languages)
|
||||
let userLanguages = osmConnection.userDetails.map((ud) => ud?.languages ?? [])
|
||||
let search: UIEventSource<string | undefined> = new UIEventSource<string>("")
|
||||
let searchStable = search.stabilized(100)
|
||||
|
||||
|
|
@ -53,8 +53,8 @@
|
|||
const hiddenThemes: MinimalThemeInformation[] = ThemeSearch.officialThemes.themes.filter(
|
||||
(th) => th.hideFromOverview === true
|
||||
)
|
||||
let visitedHiddenThemes: Store<MinimalThemeInformation[]> =
|
||||
UserRelatedState.initDiscoveredHiddenThemes(state.osmConnection).map((knownIds) =>
|
||||
let visitedHiddenThemes: Store<undefined | MinimalThemeInformation[]> =
|
||||
UserRelatedState.initDiscoveredHiddenThemes(state.osmConnection).mapD((knownIds) =>
|
||||
hiddenThemes.filter(
|
||||
(theme) =>
|
||||
knownIds.indexOf(theme.id) >= 0 ||
|
||||
|
|
@ -68,6 +68,9 @@
|
|||
function filtered(themes: Store<MinimalThemeInformation[]>): Store<MinimalThemeInformation[]> {
|
||||
return searchStable.map(
|
||||
(search) => {
|
||||
if (!themes.data) {
|
||||
return []
|
||||
}
|
||||
if (!search) {
|
||||
return themes.data
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
import { DownloadIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"
|
||||
import { Tag } from "../../Logic/Tags/Tag"
|
||||
import { MenuState } from "../../Models/MenuState"
|
||||
|
||||
export let image: ProvidedImage
|
||||
export let state: SpecialVisualizationState
|
||||
|
|
@ -26,7 +27,7 @@
|
|||
onDestroy(
|
||||
showDeleteDialog.addCallbackAndRunD((shown) => {
|
||||
if (shown) {
|
||||
state.previewedImage.set(undefined)
|
||||
MenuState.previewedImage.set(undefined)
|
||||
}
|
||||
})
|
||||
)
|
||||
|
|
@ -53,18 +54,31 @@
|
|||
issue: reportReason.data,
|
||||
sequence_id: imageInfo.collection,
|
||||
reporter_comments: (reportFreeText.data ?? "") + "\n\n" + "Reported from " + url,
|
||||
reporter_email,
|
||||
reporter_email
|
||||
})
|
||||
reported.set(true)
|
||||
}
|
||||
|
||||
async function unlink() {
|
||||
await state?.changes?.applyAction(
|
||||
new ChangeTagAction(tags.data.id, new Tag(image.key, ""), tags.data, {
|
||||
changeType: "delete-image",
|
||||
theme: state.theme.id,
|
||||
})
|
||||
)
|
||||
console.log("Unlinking image", image.key, image.id)
|
||||
if (image.id.length < 10) {
|
||||
console.error("Suspicious value, not deleting ", image.id)
|
||||
return
|
||||
}
|
||||
// The "key" is the provider key, but not necessarely the actual key that should be reset
|
||||
// We iterate over all tags. *Every* tag for which the value contains the id will be deleted
|
||||
const tgs = tags.data
|
||||
for (const key in tgs) {
|
||||
if (typeof tgs[key] !== "string" || tgs[key].indexOf(image.id) < 0) {
|
||||
continue
|
||||
}
|
||||
|
||||
await state?.changes?.applyAction(
|
||||
new ChangeTagAction(tgs.id, new Tag(key, ""), tgs, {
|
||||
changeType: "delete-image",
|
||||
theme: state.theme.id
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
const t = Translations.t.image.panoramax
|
||||
|
|
@ -161,7 +175,7 @@
|
|||
</div>
|
||||
|
||||
<style>
|
||||
:global(.carousel-max-height) {
|
||||
max-height: var(--image-carousel-height);
|
||||
}
|
||||
:global(.carousel-max-height) {
|
||||
max-height: var(--image-carousel-height);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,5 @@
|
|||
import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import maplibregl, {
|
||||
Map as MLMap,
|
||||
Map as MlMap,
|
||||
ScaleControl,
|
||||
SourceSpecification,
|
||||
} from "maplibre-gl"
|
||||
import maplibregl, { Map as MLMap, Map as MlMap, ScaleControl, SourceSpecification } from "maplibre-gl"
|
||||
import { RasterLayerPolygon } from "../../Models/RasterLayers"
|
||||
import { Utils } from "../../Utils"
|
||||
import { BBox } from "../../Logic/BBox"
|
||||
|
|
@ -179,6 +174,10 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
|||
|
||||
maplibreMap.addCallbackAndRunD((map) => {
|
||||
map.on("load", () => {
|
||||
console.log("Setting projection")
|
||||
map.setProjection({
|
||||
type: "globe" // Set projection to globe
|
||||
})
|
||||
self.MoveMapToCurrentLoc(self.location.data)
|
||||
self.SetZoom(self.zoom.data)
|
||||
self.setMaxBounds(self.maxbounds.data)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
import AccordionSingle from "../Flowbite/AccordionSingle.svelte"
|
||||
import SelectedElementView from "../BigComponents/SelectedElementView.svelte"
|
||||
import TagRenderingAnswer from "./TagRendering/TagRenderingAnswer.svelte"
|
||||
import TagRenderingEditableDynamic from "./TagRendering/TagRenderingEditableDynamic.svelte"
|
||||
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
|
||||
|
||||
export let state: SpecialVisualizationState
|
||||
export let selectedElement: Feature
|
||||
|
|
@ -16,11 +18,32 @@
|
|||
export let layer: LayerConfig
|
||||
|
||||
let headerTr = layer.tagRenderings.find((tr) => tr.id === header)
|
||||
let trgs: TagRenderingConfig[] = []
|
||||
let seenIds = new Set<string>()
|
||||
for (const label of labels) {
|
||||
for (const tr of layer.tagRenderings) {
|
||||
if (seenIds.has(tr.id)) {
|
||||
continue
|
||||
}
|
||||
if (label === tr.id || tr.labels.some(l => l === label)) {
|
||||
trgs.push(tr)
|
||||
seenIds.add(tr.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<AccordionSingle>
|
||||
<div slot="header">
|
||||
<TagRenderingAnswer {tags} {layer} config={headerTr} {state} {selectedElement} />
|
||||
</div>
|
||||
<SelectedElementView mustMatchLabels={new Set(labels)} {state} {layer} {tags} {selectedElement} />
|
||||
{#each trgs as config (config.id)}
|
||||
<TagRenderingEditableDynamic
|
||||
{tags}
|
||||
{config}
|
||||
{state}
|
||||
{selectedElement}
|
||||
{layer}
|
||||
/>
|
||||
{/each}
|
||||
</AccordionSingle>
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ export class LanguageElement implements SpecialVisualization {
|
|||
{
|
||||
name: "key",
|
||||
required: true,
|
||||
doc: "What key to use, e.g. `language`, `tactile_writing:braille:language`, ... If a language is supported, the language code will be appended to this key, resulting in `language:nl=yes` if nl is picked ",
|
||||
doc: "What key to use, e.g. `language`, `tactile_writing:braille:language`, ... If a language is supported, the language code will be appended to this key, resulting in `<key>:nl=yes` if _nl_ is picked "
|
||||
},
|
||||
{
|
||||
name: "question",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import Translations from "../i18n/Translations"
|
||||
import { Translation } from "../i18n/Translation"
|
||||
import BaseUIElement from "../BaseUIElement"
|
||||
import ChangeLocationAction from "../../Logic/Osm/Actions/ChangeLocationAction"
|
||||
import MoveConfig from "../../Models/ThemeConfig/MoveConfig"
|
||||
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"
|
||||
|
|
@ -9,9 +8,6 @@ import { And } from "../../Logic/Tags/And"
|
|||
import { Tag } from "../../Logic/Tags/Tag"
|
||||
import { SpecialVisualizationState } from "../SpecialVisualization"
|
||||
import { Feature, Point } from "geojson"
|
||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||
import Relocation from "../../assets/svg/Relocation.svelte"
|
||||
import Location from "../../assets/svg/Location.svelte"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import { WayId } from "../../Models/OsmFeature"
|
||||
|
||||
|
|
@ -104,7 +100,7 @@ export class MoveWizardState {
|
|||
for (const layerId of matchingPreset) {
|
||||
const snapOntoLayer = this._state.theme.getLayer(layerId)
|
||||
const text = <Translation>(
|
||||
t.reasons.reasonSnapTo.PartialSubsTr("name", snapOntoLayer.snapName)
|
||||
t.reasons.reasonSnapTo.PartialSubsTr("name", snapOntoLayer?.snapName)
|
||||
)
|
||||
reasons.push({
|
||||
text,
|
||||
|
|
@ -117,7 +113,7 @@ export class MoveWizardState {
|
|||
startZoom: 19,
|
||||
minZoom: 16,
|
||||
eraseAddressFields: false,
|
||||
snapTo: [snapOntoLayer.id],
|
||||
snapTo: [snapOntoLayer?.id],
|
||||
maxSnapDistance: 5,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,7 @@
|
|||
import { Translation } from "../../i18n/Translation"
|
||||
import SpecialVisualizations from "../../SpecialVisualizations"
|
||||
import Locale from "../../i18n/Locale"
|
||||
import type {
|
||||
RenderingSpecification,
|
||||
SpecialVisualizationState,
|
||||
} from "../../SpecialVisualization"
|
||||
import type { RenderingSpecification, SpecialVisualizationState } from "../../SpecialVisualization"
|
||||
import { Utils } from "../../../Utils.js"
|
||||
import type { Feature } from "geojson"
|
||||
import { UIEventSource } from "../../../Logic/UIEventSource.js"
|
||||
|
|
@ -49,13 +46,9 @@
|
|||
function createVisualisation(specpart: Exclude<RenderingSpecification, string>): BaseUIElement {
|
||||
{
|
||||
try {
|
||||
const uiEl = specpart.func
|
||||
return specpart.func
|
||||
.constr(state, tags, specpart.args, feature, layer)
|
||||
?.SetClass(specpart.style)
|
||||
if (uiEl === undefined) {
|
||||
console.error("Invalid special translation")
|
||||
}
|
||||
return uiEl
|
||||
} catch (e) {
|
||||
console.error(
|
||||
"Could not construct a special visualisation with specification",
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
import Translations from "../i18n/Translations"
|
||||
import { Store } from "../../Logic/UIEventSource"
|
||||
import SidebarUnit from "../Base/SidebarUnit.svelte"
|
||||
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
||||
import Loading from "../Base/Loading.svelte"
|
||||
import { default as GeocodeResultSvelte } from "./GeocodeResult.svelte"
|
||||
import Tr from "../Base/Tr.svelte"
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import LoginButton from "../Base/LoginButton.svelte"
|
|||
import ThemeViewState from "../../Models/ThemeViewState"
|
||||
import OrientationDebugPanel from "../Debug/OrientationDebugPanel.svelte"
|
||||
import AllTagsPanel from "../Popup/AllTagsPanel.svelte"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { ImmutableStore, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import ClearCaches from "../Popup/ClearCaches.svelte"
|
||||
import Locale from "../i18n/Locale"
|
||||
import LanguageUtils from "../../Utils/LanguageUtils"
|
||||
|
|
@ -75,6 +75,22 @@ export class SettingsVisualisations {
|
|||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
funcName: "storage_all_tags",
|
||||
group: "settings",
|
||||
docs: "Shows the current state of storage",
|
||||
args: [],
|
||||
constr(
|
||||
state: SpecialVisualizationState
|
||||
): SvelteUIElement {
|
||||
const data = {}
|
||||
for (const key in localStorage) {
|
||||
data[key] = localStorage[key]
|
||||
}
|
||||
const tags = new ImmutableStore(data)
|
||||
return new SvelteUIElement(AllTagsPanel, { state, tags })
|
||||
}
|
||||
},
|
||||
{
|
||||
funcName: "clear_caches",
|
||||
docs: "A button which clears the locally downloaded data and the service worker. Login status etc will be kept",
|
||||
|
|
@ -89,7 +105,7 @@ export class SettingsVisualisations {
|
|||
constr(
|
||||
_: SpecialVisualizationState,
|
||||
__: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
argument: string[]
|
||||
): SvelteUIElement {
|
||||
return new SvelteUIElement(ClearCaches, {
|
||||
msg: argument[0] ?? "Clear local caches"
|
||||
|
|
|
|||
|
|
@ -176,6 +176,7 @@
|
|||
</script>
|
||||
|
||||
<main>
|
||||
<div class="absolute top-0 left-0 h-screen w-screen" style="background-color: #cccccc"></div>
|
||||
<!-- Main map -->
|
||||
<div class="absolute top-0 left-0 h-screen w-screen overflow-hidden">
|
||||
<MaplibreMap map={maplibremap} mapProperties={mapproperties} autorecovery={true} />
|
||||
|
|
|
|||
|
|
@ -5,13 +5,17 @@ import CustomThemeError from "./UI/CustomThemeError.svelte"
|
|||
async function main() {
|
||||
const target = document.getElementById("maindiv")
|
||||
const childs = Array.from(target.children)
|
||||
try {
|
||||
childs.forEach((ch) => target.removeChild(ch))
|
||||
} catch (e) {
|
||||
console.error("Huh? Couldn't remove child!")
|
||||
}
|
||||
try {
|
||||
const theme = await DetermineTheme.getTheme()
|
||||
new SingleThemeGui({
|
||||
target,
|
||||
props: { theme },
|
||||
})
|
||||
childs.forEach((ch) => target.removeChild(ch))
|
||||
Array.from(document.getElementsByClassName("delete-on-load")).forEach((el) => {
|
||||
el.parentElement.removeChild(el)
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue