Chore: reformat all files with prettier

This commit is contained in:
Pieter Vander Vennet 2023-06-14 20:39:36 +02:00
parent 5757ae5dea
commit d008dcb54d
214 changed files with 8926 additions and 8196 deletions

View file

@ -1,6 +1,6 @@
import {Store, UIEventSource} from "../UIEventSource" import { Store, UIEventSource } from "../UIEventSource"
import {Utils} from "../../Utils" import { Utils } from "../../Utils"
import {RasterLayerPolygon, RasterLayerUtils,} from "../../Models/RasterLayers" import { RasterLayerPolygon, RasterLayerUtils } from "../../Models/RasterLayers"
/** /**
* When a user pans around on the map, they might pan out of the range of the current background raster layer. * When a user pans around on the map, they might pan out of the range of the current background raster layer.
@ -35,7 +35,7 @@ export default class BackgroundLayerResetter {
availableLayers, availableLayers,
currentBgPolygon?.properties?.category currentBgPolygon?.properties?.category
) )
if(!availableInSameCat){ if (!availableInSameCat) {
return return
} }
console.log("Selecting a different layer:", availableInSameCat.properties.id) console.log("Selecting a different layer:", availableInSameCat.properties.id)

View file

@ -1,15 +1,15 @@
import {QueryParameters} from "../Web/QueryParameters" import { QueryParameters } from "../Web/QueryParameters"
import {BBox} from "../BBox" import { BBox } from "../BBox"
import Constants from "../../Models/Constants" import Constants from "../../Models/Constants"
import {GeoLocationState} from "../State/GeoLocationState" import { GeoLocationState } from "../State/GeoLocationState"
import {UIEventSource} from "../UIEventSource" import { UIEventSource } from "../UIEventSource"
import {Feature, LineString, Point} from "geojson" import { Feature, LineString, Point } from "geojson"
import {FeatureSource, WritableFeatureSource} from "../FeatureSource/FeatureSource" import { FeatureSource, WritableFeatureSource } from "../FeatureSource/FeatureSource"
import {LocalStorageSource} from "../Web/LocalStorageSource" import { LocalStorageSource } from "../Web/LocalStorageSource"
import {GeoOperations} from "../GeoOperations" import { GeoOperations } from "../GeoOperations"
import {OsmTags} from "../../Models/OsmFeature" import { OsmTags } from "../../Models/OsmFeature"
import StaticFeatureSource from "../FeatureSource/Sources/StaticFeatureSource" import StaticFeatureSource from "../FeatureSource/Sources/StaticFeatureSource"
import {MapProperties} from "../../Models/MapProperties" import { MapProperties } from "../../Models/MapProperties"
/** /**
* The geolocation-handler takes a map-location and a geolocation state. * The geolocation-handler takes a map-location and a geolocation state.
@ -39,7 +39,9 @@ export default class GeoLocationHandler {
/** /**
* The last moment that the map has moved * The last moment that the map has moved
*/ */
public readonly mapHasMoved: UIEventSource<Date | undefined> = new UIEventSource<Date | undefined>(undefined) public readonly mapHasMoved: UIEventSource<Date | undefined> = new UIEventSource<
Date | undefined
>(undefined)
private readonly selectedElement: UIEventSource<any> private readonly selectedElement: UIEventSource<any>
private readonly mapProperties?: MapProperties private readonly mapProperties?: MapProperties
private readonly gpsLocationHistoryRetentionTime?: UIEventSource<number> private readonly gpsLocationHistoryRetentionTime?: UIEventSource<number>
@ -80,8 +82,11 @@ export default class GeoLocationHandler {
// The map hasn't moved yet; we received our first coordinates, so let's move there! // The map hasn't moved yet; we received our first coordinates, so let's move there!
self.MoveMapToCurrentLocation() self.MoveMapToCurrentLocation()
} }
if (timeSinceLastRequest < Constants.zoomToLocationTimeout && if (
(this.mapHasMoved.data === undefined || this.mapHasMoved.data.getTime() < geolocationState.requestMoment.data?.getTime() ) timeSinceLastRequest < Constants.zoomToLocationTimeout &&
(this.mapHasMoved.data === undefined ||
this.mapHasMoved.data.getTime() <
geolocationState.requestMoment.data?.getTime())
) { ) {
// still within request time and the map hasn't moved since requesting to jump to the current location // still within request time and the map hasn't moved since requesting to jump to the current location
self.MoveMapToCurrentLocation() self.MoveMapToCurrentLocation()
@ -155,7 +160,7 @@ export default class GeoLocationHandler {
} }
const properties = { const properties = {
id: "gps-"+i, id: "gps-" + i,
"user:location": "yes", "user:location": "yes",
date: new Date().toISOString(), date: new Date().toISOString(),
} }
@ -164,7 +169,7 @@ export default class GeoLocationHandler {
for (const k in keysToCopy) { for (const k in keysToCopy) {
// For some weird reason, the 'Object.keys' method doesn't work for the 'location: GeolocationCoordinates'-object and will thus not copy all the properties when using {...location} // For some weird reason, the 'Object.keys' method doesn't work for the 'location: GeolocationCoordinates'-object and will thus not copy all the properties when using {...location}
// As such, they are copied here // As such, they are copied here
if(location[k]){ if (location[k]) {
properties[k] = location[k] properties[k] = location[k]
} }
} }

View file

@ -20,14 +20,15 @@ import { FixImages } from "../Models/ThemeConfig/Conversion/FixImages"
import Svg from "../Svg" import Svg from "../Svg"
import { import {
DoesImageExist, DoesImageExist,
PrevalidateTheme, ValidateTagRenderings, PrevalidateTheme,
ValidateTagRenderings,
ValidateThemeAndLayers, ValidateThemeAndLayers,
} from "../Models/ThemeConfig/Conversion/Validation" } from "../Models/ThemeConfig/Conversion/Validation"
import {DesugaringContext, Each, On} from "../Models/ThemeConfig/Conversion/Conversion"; import { DesugaringContext, Each, On } from "../Models/ThemeConfig/Conversion/Conversion"
import {PrepareLayer, RewriteSpecial} from "../Models/ThemeConfig/Conversion/PrepareLayer"; import { PrepareLayer, RewriteSpecial } from "../Models/ThemeConfig/Conversion/PrepareLayer"
import {AllSharedLayers} from "../Customizations/AllSharedLayers"; import { AllSharedLayers } from "../Customizations/AllSharedLayers"
import {TagRenderingConfigJson} from "../Models/ThemeConfig/Json/TagRenderingConfigJson"; import { TagRenderingConfigJson } from "../Models/ThemeConfig/Json/TagRenderingConfigJson"
import questions from "../assets/tagRenderings/questions.json"; import questions from "../assets/tagRenderings/questions.json"
export default class DetermineLayout { export default class DetermineLayout {
private static readonly _knownImages = new Set(Array.from(licenses).map((l) => l.path)) private static readonly _knownImages = new Set(Array.from(licenses).map((l) => l.path))

View file

@ -1,11 +1,11 @@
import {GeoOperations} from "./GeoOperations" import { GeoOperations } from "./GeoOperations"
import Combine from "../UI/Base/Combine" import Combine from "../UI/Base/Combine"
import BaseUIElement from "../UI/BaseUIElement" import BaseUIElement from "../UI/BaseUIElement"
import List from "../UI/Base/List" import List from "../UI/Base/List"
import Title from "../UI/Base/Title" import Title from "../UI/Base/Title"
import {BBox} from "./BBox" import { BBox } from "./BBox"
import {Feature, Geometry, MultiPolygon, Polygon} from "geojson" import { Feature, Geometry, MultiPolygon, Polygon } from "geojson"
import {GeoJSONFeature} from "maplibre-gl"; import { GeoJSONFeature } from "maplibre-gl"
export interface ExtraFuncParams { export interface ExtraFuncParams {
/** /**
@ -13,7 +13,10 @@ export interface ExtraFuncParams {
* Note that more features then requested can be given back. * Note that more features then requested can be given back.
* Format: [ [ geojson, geojson, geojson, ... ], [geojson, ...], ...] * Format: [ [ geojson, geojson, geojson, ... ], [geojson, ...], ...]
*/ */
getFeaturesWithin: (layerId: string, bbox: BBox) => Feature<Geometry, Record<string, string>>[][] getFeaturesWithin: (
layerId: string,
bbox: BBox
) => Feature<Geometry, Record<string, string>>[][]
getFeatureById: (id: string) => Feature<Geometry, Record<string, string>> getFeatureById: (id: string) => Feature<Geometry, Record<string, string>>
} }
@ -55,7 +58,6 @@ class EnclosingFunc implements ExtraFunction {
} }
for (const otherFeatures of otherFeaturess) { for (const otherFeatures of otherFeaturess) {
for (const otherFeature of otherFeatures) { for (const otherFeature of otherFeatures) {
if (seenIds.has(otherFeature.properties.id)) { if (seenIds.has(otherFeature.properties.id)) {
continue continue
} }
@ -72,7 +74,7 @@ class EnclosingFunc implements ExtraFunction {
<Feature<Polygon | MultiPolygon, any>>otherFeature <Feature<Polygon | MultiPolygon, any>>otherFeature
) )
) { ) {
result.push({feat: otherFeature}) result.push({ feat: otherFeature })
} }
} }
} }
@ -158,11 +160,14 @@ class IntersectionFunc implements ExtraFunction {
} }
for (const otherFeatures of otherLayers) { for (const otherFeatures of otherLayers) {
for (const otherFeature of otherFeatures) { for (const otherFeature of otherFeatures) {
const intersections = GeoOperations.LineIntersections(feat, <Feature<any, Record<string, string>>>otherFeature) const intersections = GeoOperations.LineIntersections(
feat,
<Feature<any, Record<string, string>>>otherFeature
)
if (intersections.length === 0) { if (intersections.length === 0) {
continue continue
} }
result.push({feat: otherFeature, intersections}) result.push({ feat: otherFeature, intersections })
} }
} }
} }
@ -254,7 +259,14 @@ class ClosestNObjectFunc implements ExtraFunction {
const maxDistance = options?.maxDistance ?? 500 const maxDistance = options?.maxDistance ?? 500
const uniqueTag: string | undefined = options?.uniqueTag const uniqueTag: string | undefined = options?.uniqueTag
let allFeatures: Feature[][] let allFeatures: Feature[][]
console.log("Calculating closest", options?.maxFeatures, "features around", feature, "in layer", features) console.log(
"Calculating closest",
options?.maxFeatures,
"features around",
feature,
"in layer",
features
)
if (typeof features === "string") { if (typeof features === "string") {
const name = features const name = features
const bbox = GeoOperations.bbox( const bbox = GeoOperations.bbox(
@ -272,7 +284,6 @@ class ClosestNObjectFunc implements ExtraFunction {
let closestFeatures: { feat: any; distance: number }[] = [] let closestFeatures: { feat: any; distance: number }[] = []
for (const feats of allFeatures) { for (const feats of allFeatures) {
for (const otherFeature of feats) { for (const otherFeature of feats) {
if ( if (
otherFeature === feature || otherFeature === feature ||
@ -341,7 +352,7 @@ class ClosestNObjectFunc implements ExtraFunction {
// We want to see the tag `uniquetag=some_value` only once in the entire list (e.g. to prevent road segements of identical names to fill up the list of 'names of nearby roads') // We want to see the tag `uniquetag=some_value` only once in the entire list (e.g. to prevent road segements of identical names to fill up the list of 'names of nearby roads')
// AT this point, we have found a closer segment with the same, identical tag // AT this point, we have found a closer segment with the same, identical tag
// so we replace directly // so we replace directly
closestFeatures[i] = {feat: otherFeature, distance: distance} closestFeatures[i] = { feat: otherFeature, distance: distance }
} }
break break
} }
@ -468,7 +479,15 @@ export class ExtraFunctions {
.SetClass("flex-col") .SetClass("flex-col")
.AsMarkdown() .AsMarkdown()
static readonly types = ["distanceTo", "overlapWith", "enclosingFeatures", "intersectionsWith", "closest", "closestn", "get"] as const static readonly types = [
"distanceTo",
"overlapWith",
"enclosingFeatures",
"intersectionsWith",
"closest",
"closestn",
"get",
] as const
private static readonly allFuncs = [ private static readonly allFuncs = [
new DistanceToFunc(), new DistanceToFunc(),
new OverlapFunc(), new OverlapFunc(),
@ -479,8 +498,9 @@ export class ExtraFunctions {
new GetParsed(), new GetParsed(),
] ]
public static constructHelpers(
public static constructHelpers(params: ExtraFuncParams): Record<ExtraFuncType, (feature: Feature) => Function> { params: ExtraFuncParams
): Record<ExtraFuncType, (feature: Feature) => Function> {
const record: Record<string, (feature: GeoJSONFeature) => Function> = {} const record: Record<string, (feature: GeoJSONFeature) => Function> = {}
for (const f of ExtraFunctions.allFuncs) { for (const f of ExtraFunctions.allFuncs) {
if (this.types.indexOf(<any>f._name) < 0) { if (this.types.indexOf(<any>f._name) < 0) {

View file

@ -1,5 +1,5 @@
import {FeatureSource} from "../FeatureSource" import { FeatureSource } from "../FeatureSource"
import {UIEventSource} from "../../UIEventSource" import { UIEventSource } from "../../UIEventSource"
/** /**
* Constructs a UIEventStore for the properties of every Feature, indexed by id * Constructs a UIEventStore for the properties of every Feature, indexed by id

View file

@ -1,5 +1,5 @@
import {IdbLocalStorage} from "../../Web/IdbLocalStorage" import { IdbLocalStorage } from "../../Web/IdbLocalStorage"
import {UIEventSource} from "../../UIEventSource" import { UIEventSource } from "../../UIEventSource"
/** /**
* A class which allows to read/write a tile to local storage. * A class which allows to read/write a tile to local storage.
@ -14,14 +14,18 @@ export default class TileLocalStorage<T> {
private readonly _layername: string private readonly _layername: string
private readonly inUse = new UIEventSource(false) private readonly inUse = new UIEventSource(false)
private readonly cachedSources: Record<number, UIEventSource<T> & { flush: () => void }> = {} private readonly cachedSources: Record<number, UIEventSource<T> & { flush: () => void }> = {}
private readonly _maxAgeSeconds: number; private readonly _maxAgeSeconds: number
private constructor(layername: string, maxAgeSeconds: number) { private constructor(layername: string, maxAgeSeconds: number) {
this._layername = layername this._layername = layername
this._maxAgeSeconds = maxAgeSeconds; this._maxAgeSeconds = maxAgeSeconds
} }
public static construct<T>(backend: string, layername: string, maxAgeS: number): TileLocalStorage<T> { public static construct<T>(
backend: string,
layername: string,
maxAgeS: number
): TileLocalStorage<T> {
const key = backend + "_" + layername const key = backend + "_" + layername
const cached = TileLocalStorage.perLayer[key] const cached = TileLocalStorage.perLayer[key]
if (cached) { if (cached) {
@ -59,7 +63,10 @@ export default class TileLocalStorage<T> {
await this.inUse.AsPromise((inUse) => !inUse) await this.inUse.AsPromise((inUse) => !inUse)
this.inUse.setData(true) this.inUse.setData(true)
await IdbLocalStorage.SetDirectly(this._layername + "_" + tileIndex, data) await IdbLocalStorage.SetDirectly(this._layername + "_" + tileIndex, data)
await IdbLocalStorage.SetDirectly(this._layername + "_" + tileIndex + "_date", Date.now()) await IdbLocalStorage.SetDirectly(
this._layername + "_" + tileIndex + "_date",
Date.now()
)
this.inUse.setData(false) this.inUse.setData(false)
} catch (e) { } catch (e) {
@ -80,7 +87,9 @@ export default class TileLocalStorage<T> {
if (!TileLocalStorage.useIndexedDb) { if (!TileLocalStorage.useIndexedDb) {
return undefined return undefined
} }
const date = <any>await IdbLocalStorage.GetDirectly(this._layername + "_" + tileIndex + "_date") const date = <any>(
await IdbLocalStorage.GetDirectly(this._layername + "_" + tileIndex + "_date")
)
const maxAge = this._maxAgeSeconds const maxAge = this._maxAgeSeconds
const timeDiff = Date.now() - date const timeDiff = Date.now() - date
if (timeDiff >= maxAge) { if (timeDiff >= maxAge) {

View file

@ -1,8 +1,8 @@
import {FeatureSource} from "./FeatureSource" import { FeatureSource } from "./FeatureSource"
import FilteredLayer from "../../Models/FilteredLayer" import FilteredLayer from "../../Models/FilteredLayer"
import SimpleFeatureSource from "./Sources/SimpleFeatureSource" import SimpleFeatureSource from "./Sources/SimpleFeatureSource"
import {Feature} from "geojson" import { Feature } from "geojson"
import {UIEventSource} from "../UIEventSource" import { UIEventSource } from "../UIEventSource"
/** /**
* In some rare cases, some elements are shown on multiple layers (when 'passthrough' is enabled) * In some rare cases, some elements are shown on multiple layers (when 'passthrough' is enabled)
@ -59,8 +59,11 @@ export default class PerLayerFeatureSourceSplitter<T extends FeatureSource = Fea
let foundALayer = false let foundALayer = false
for (let i = 0; i < layers.length; i++) { for (let i = 0; i < layers.length; i++) {
const layer = layers[i] const layer = layers[i]
if(!layer.layerDef?.source){ if (!layer.layerDef?.source) {
console.error("PerLayerFeatureSourceSplitter got a layer without a source:", layer.layerDef.id) console.error(
"PerLayerFeatureSourceSplitter got a layer without a source:",
layer.layerDef.id
)
continue continue
} }
if (layer.layerDef.source.osmTags.matchesProperties(f.properties)) { if (layer.layerDef.source.osmTags.matchesProperties(f.properties)) {

View file

@ -6,7 +6,7 @@ import { UIEventSource } from "../../UIEventSource"
import { FeatureSource, IndexedFeatureSource } from "../FeatureSource" import { FeatureSource, IndexedFeatureSource } from "../FeatureSource"
import { ChangeDescription, ChangeDescriptionTools } from "../../Osm/Actions/ChangeDescription" import { ChangeDescription, ChangeDescriptionTools } from "../../Osm/Actions/ChangeDescription"
import { Feature } from "geojson" import { Feature } from "geojson"
import {Utils} from "../../../Utils"; import { Utils } from "../../../Utils"
export default class ChangeGeometryApplicator implements FeatureSource { export default class ChangeGeometryApplicator implements FeatureSource {
public readonly features: UIEventSource<Feature[]> = new UIEventSource<Feature[]>([]) public readonly features: UIEventSource<Feature[]> = new UIEventSource<Feature[]>([])
@ -70,7 +70,7 @@ export default class ChangeGeometryApplicator implements FeatureSource {
// We only apply the last change as that one'll have the latest geometry // We only apply the last change as that one'll have the latest geometry
const change = changesForFeature[changesForFeature.length - 1] const change = changesForFeature[changesForFeature.length - 1]
copy.geometry = ChangeDescriptionTools.getGeojsonGeometry(change) copy.geometry = ChangeDescriptionTools.getGeojsonGeometry(change)
if(Utils.SameObject(copy.geometry, feature.geometry)){ if (Utils.SameObject(copy.geometry, feature.geometry)) {
// No actual changes: pass along the original // No actual changes: pass along the original
newFeatures.push(feature) newFeatures.push(feature)
continue continue

View file

@ -30,7 +30,7 @@ export default class FeatureSourceMerger implements IndexedFeatureSource {
} }
public addSource(source: FeatureSource) { public addSource(source: FeatureSource) {
if(!source){ if (!source) {
return return
} }
this._sources.push(source) this._sources.push(source)

View file

@ -1,14 +1,14 @@
/** /**
* Fetches a geojson file somewhere and passes it along * Fetches a geojson file somewhere and passes it along
*/ */
import {Store, UIEventSource} from "../../UIEventSource" import { Store, UIEventSource } from "../../UIEventSource"
import {Utils} from "../../../Utils" import { Utils } from "../../../Utils"
import {FeatureSource} from "../FeatureSource" import { FeatureSource } from "../FeatureSource"
import {BBox} from "../../BBox" import { BBox } from "../../BBox"
import {GeoOperations} from "../../GeoOperations" import { GeoOperations } from "../../GeoOperations"
import {Feature} from "geojson" import { Feature } from "geojson"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
import {Tiles} from "../../../Models/TileRange" import { Tiles } from "../../../Models/TileRange"
export default class GeoJsonSource implements FeatureSource { export default class GeoJsonSource implements FeatureSource {
public readonly features: Store<Feature[]> public readonly features: Store<Feature[]>
@ -65,13 +65,13 @@ export default class GeoJsonSource implements FeatureSource {
return return
} }
this.LoadJSONFrom(url, eventsource, layer) this.LoadJSONFrom(url, eventsource, layer)
.then((fs) => console.debug("Loaded",fs.length, "features from", url)) .then((fs) => console.debug("Loaded", fs.length, "features from", url))
.catch((err) => console.warn("Could not load ", url, "due to", err)) .catch((err) => console.warn("Could not load ", url, "due to", err))
return true // data is loaded, we can safely unregister return true // data is loaded, we can safely unregister
}) })
} else { } else {
this.LoadJSONFrom(url, eventsource, layer) this.LoadJSONFrom(url, eventsource, layer)
.then((fs) => console.debug("Loaded",fs.length, "features from", url)) .then((fs) => console.debug("Loaded", fs.length, "features from", url))
.catch((err) => console.warn("Could not load ", url, "due to", err)) .catch((err) => console.warn("Could not load ", url, "due to", err))
} }
this.features = eventsource this.features = eventsource
@ -105,7 +105,7 @@ export default class GeoJsonSource implements FeatureSource {
let i = 0 let i = 0
let skipped = 0 let skipped = 0
for (const feature of json.features) { for (const feature of json.features) {
if(feature.geometry.type === "Point"){ if (feature.geometry.type === "Point") {
// See https://github.com/maproulette/maproulette-backend/issues/242 // See https://github.com/maproulette/maproulette-backend/issues/242
feature.geometry.coordinates = feature.geometry.coordinates.map(Number) feature.geometry.coordinates = feature.geometry.coordinates.map(Number)
} }

View file

@ -32,7 +32,9 @@ export class LastClickFeatureSource implements WritableFeatureSource {
} }
const renderings = Utils.Dedup( const renderings = Utils.Dedup(
allPresets.map((uiElem) => Utils.runningFromConsole ? "" : uiElem.ConstructElement().innerHTML) allPresets.map((uiElem) =>
Utils.runningFromConsole ? "" : uiElem.ConstructElement().innerHTML
)
) )
const properties = { const properties = {

View file

@ -1,16 +1,16 @@
import GeoJsonSource from "./GeoJsonSource" import GeoJsonSource from "./GeoJsonSource"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
import {FeatureSource} from "../FeatureSource" import { FeatureSource } from "../FeatureSource"
import {Or} from "../../Tags/Or" import { Or } from "../../Tags/Or"
import FeatureSwitchState from "../../State/FeatureSwitchState" import FeatureSwitchState from "../../State/FeatureSwitchState"
import OverpassFeatureSource from "./OverpassFeatureSource" import OverpassFeatureSource from "./OverpassFeatureSource"
import {Store, UIEventSource} from "../../UIEventSource" import { Store, UIEventSource } from "../../UIEventSource"
import OsmFeatureSource from "./OsmFeatureSource" import OsmFeatureSource from "./OsmFeatureSource"
import FeatureSourceMerger from "./FeatureSourceMerger" import FeatureSourceMerger from "./FeatureSourceMerger"
import DynamicGeoJsonTileSource from "../TiledFeatureSource/DynamicGeoJsonTileSource" import DynamicGeoJsonTileSource from "../TiledFeatureSource/DynamicGeoJsonTileSource"
import {BBox} from "../../BBox" import { BBox } from "../../BBox"
import LocalStorageFeatureSource from "../TiledFeatureSource/LocalStorageFeatureSource" import LocalStorageFeatureSource from "../TiledFeatureSource/LocalStorageFeatureSource"
import FullNodeDatabaseSource from "../TiledFeatureSource/FullNodeDatabaseSource"; import FullNodeDatabaseSource from "../TiledFeatureSource/FullNodeDatabaseSource"
/** /**
* This source will fetch the needed data from various sources for the given layout. * This source will fetch the needed data from various sources for the given layout.
@ -41,7 +41,7 @@ export default class LayoutSource extends FeatureSourceMerger {
(l) => (l) =>
new LocalStorageFeatureSource(backend, l.id, 15, mapProperties, { new LocalStorageFeatureSource(backend, l.id, 15, mapProperties, {
isActive: isDisplayed(l.id), isActive: isDisplayed(l.id),
maxAge: l.maxAgeOfCache maxAge: l.maxAgeOfCache,
}) })
) )
@ -127,7 +127,7 @@ export default class LayoutSource extends FeatureSourceMerger {
backend, backend,
isActive, isActive,
patchRelations: true, patchRelations: true,
fullNodeDatabase fullNodeDatabase,
}) })
} }

View file

@ -7,7 +7,7 @@ import { TagsFilter } from "../../Tags/TagsFilter"
import { Feature } from "geojson" import { Feature } from "geojson"
import FeatureSourceMerger from "../Sources/FeatureSourceMerger" import FeatureSourceMerger from "../Sources/FeatureSourceMerger"
import OsmObjectDownloader from "../../Osm/OsmObjectDownloader" import OsmObjectDownloader from "../../Osm/OsmObjectDownloader"
import FullNodeDatabaseSource from "../TiledFeatureSource/FullNodeDatabaseSource"; import FullNodeDatabaseSource from "../TiledFeatureSource/FullNodeDatabaseSource"
/** /**
* If a tile is needed (requested via the UIEventSource in the constructor), will download the appropriate tile and pass it via 'handleTile' * If a tile is needed (requested via the UIEventSource in the constructor), will download the appropriate tile and pass it via 'handleTile'
@ -24,16 +24,16 @@ export default class OsmFeatureSource extends FeatureSourceMerger {
/** /**
* If given: this featureSwitch will not update if the store contains 'false' * If given: this featureSwitch will not update if the store contains 'false'
*/ */
isActive?: Store<boolean>, isActive?: Store<boolean>
patchRelations?: true | boolean, patchRelations?: true | boolean
fullNodeDatabase?: FullNodeDatabaseSource fullNodeDatabase?: FullNodeDatabaseSource
}; }
public readonly isRunning: UIEventSource<boolean> = new UIEventSource<boolean>(false) public readonly isRunning: UIEventSource<boolean> = new UIEventSource<boolean>(false)
private readonly _downloadedTiles: Set<number> = new Set<number>() private readonly _downloadedTiles: Set<number> = new Set<number>()
private readonly _downloadedData: Feature[][] = [] private readonly _downloadedData: Feature[][] = []
private readonly _patchRelations: boolean; private readonly _patchRelations: boolean
/** /**
* Downloads data directly from the OSM-api within the given bounds. * Downloads data directly from the OSM-api within the given bounds.
* All features which match the TagsFilter 'allowedFeatures' are kept and converted into geojson * All features which match the TagsFilter 'allowedFeatures' are kept and converted into geojson
@ -45,12 +45,12 @@ export default class OsmFeatureSource extends FeatureSourceMerger {
/** /**
* If given: this featureSwitch will not update if the store contains 'false' * If given: this featureSwitch will not update if the store contains 'false'
*/ */
isActive?: Store<boolean>, isActive?: Store<boolean>
patchRelations?: true | boolean, patchRelations?: true | boolean
fullNodeDatabase?: FullNodeDatabaseSource fullNodeDatabase?: FullNodeDatabaseSource
}) { }) {
super() super()
this.options = options; this.options = options
this._bounds = options.bounds this._bounds = options.bounds
this.allowedTags = options.allowedFeatures this.allowedTags = options.allowedFeatures
this.isActive = options.isActive ?? new ImmutableStore(true) this.isActive = options.isActive ?? new ImmutableStore(true)

View file

@ -1,6 +1,6 @@
import {FeatureSource} from "../FeatureSource" import { FeatureSource } from "../FeatureSource"
import {ImmutableStore, Store} from "../../UIEventSource" import { ImmutableStore, Store } from "../../UIEventSource"
import {Feature} from "geojson" import { Feature } from "geojson"
/** /**
* A simple, read only feature store. * A simple, read only feature store.
@ -8,13 +8,7 @@ import {Feature} from "geojson"
export default class StaticFeatureSource<T extends Feature = Feature> implements FeatureSource<T> { export default class StaticFeatureSource<T extends Feature = Feature> implements FeatureSource<T> {
public readonly features: Store<T[]> public readonly features: Store<T[]>
constructor( constructor(features: Store<T[]> | T[] | { features: T[] } | { features: Store<T[]> }) {
features:
| Store<T[]>
| T[]
| { features: T[] }
| { features: Store<T[]> }
) {
if (features === undefined) { if (features === undefined) {
throw "Static feature source received undefined as source" throw "Static feature source received undefined as source"
} }

View file

@ -1,9 +1,9 @@
import {FeatureSource, FeatureSourceForLayer} from "../FeatureSource" import { FeatureSource, FeatureSourceForLayer } from "../FeatureSource"
import StaticFeatureSource from "./StaticFeatureSource" import StaticFeatureSource from "./StaticFeatureSource"
import {BBox} from "../../BBox" import { BBox } from "../../BBox"
import FilteredLayer from "../../../Models/FilteredLayer" import FilteredLayer from "../../../Models/FilteredLayer"
import {Store} from "../../UIEventSource" import { Store } from "../../UIEventSource"
import {Feature} from "geojson"; import { Feature } from "geojson"
/** /**
* Results in a feature source which has all the elements that touch the given features * Results in a feature source which has all the elements that touch the given features
@ -30,7 +30,10 @@ export default class BBoxFeatureSource<T extends Feature = Feature> extends Stat
} }
} }
export class BBoxFeatureSourceForLayer<T extends Feature = Feature> extends BBoxFeatureSource<T> implements FeatureSourceForLayer { export class BBoxFeatureSourceForLayer<T extends Feature = Feature>
extends BBoxFeatureSource<T>
implements FeatureSourceForLayer
{
readonly layer: FilteredLayer readonly layer: FilteredLayer
constructor(features: FeatureSourceForLayer<T>, mustTouch: Store<BBox>) { constructor(features: FeatureSourceForLayer<T>, mustTouch: Store<BBox>) {

View file

@ -72,7 +72,9 @@ export default class DynamicGeoJsonTileSource extends DynamicTileSource {
if (!isWhiteListed) { if (!isWhiteListed) {
console.debug( console.debug(
"Not downloading tile", "Not downloading tile",
zxy,"for layer",layer.id, zxy,
"for layer",
layer.id,
"as it is not on the whitelist" "as it is not on the whitelist"
) )
return undefined return undefined

View file

@ -1,11 +1,10 @@
import {OsmNode, OsmObject, OsmWay} from "../../Osm/OsmObject" import { OsmNode, OsmObject, OsmWay } from "../../Osm/OsmObject"
import {UIEventSource} from "../../UIEventSource" import { UIEventSource } from "../../UIEventSource"
import {BBox} from "../../BBox"; import { BBox } from "../../BBox"
import StaticFeatureSource from "../Sources/StaticFeatureSource"; import StaticFeatureSource from "../Sources/StaticFeatureSource"
import {Tiles} from "../../../Models/TileRange"; import { Tiles } from "../../../Models/TileRange"
export default class FullNodeDatabaseSource { export default class FullNodeDatabaseSource {
private readonly loadedTiles = new Map<number, Map<number, OsmNode>>() private readonly loadedTiles = new Map<number, Map<number, OsmNode>>()
private readonly nodeByIds = new Map<number, OsmNode>() private readonly nodeByIds = new Map<number, OsmNode>()
private readonly parentWays = new Map<number, UIEventSource<OsmWay[]>>() private readonly parentWays = new Map<number, UIEventSource<OsmWay[]>>()
@ -13,7 +12,7 @@ export default class FullNodeDatabaseSource {
private smallestZoom = 99 private smallestZoom = 99
private largestZoom = 0 private largestZoom = 0
public handleOsmJson(osmJson: any, z: number, x: number, y: number) : void { public handleOsmJson(osmJson: any, z: number, x: number, y: number): void {
const allObjects = OsmObject.ParseObjects(osmJson.elements) const allObjects = OsmObject.ParseObjects(osmJson.elements)
const nodesById = new Map<number, OsmNode>() const nodesById = new Map<number, OsmNode>()
@ -81,14 +80,14 @@ export default class FullNodeDatabaseSource {
* Gets (at least) all nodes which are part of this BBOX; might also return some nodes that fall outside of the bbox but are closeby * Gets (at least) all nodes which are part of this BBOX; might also return some nodes that fall outside of the bbox but are closeby
* @param bbox * @param bbox
*/ */
getNodesWithin(bbox: BBox) : Map<number, OsmNode>{ getNodesWithin(bbox: BBox): Map<number, OsmNode> {
const allById = new Map<number, OsmNode>() const allById = new Map<number, OsmNode>()
for (let z = this.smallestZoom; z < this.largestZoom; z++) { for (let z = this.smallestZoom; z < this.largestZoom; z++) {
const range = Tiles.tileRangeFrom(bbox, z) const range = Tiles.tileRangeFrom(bbox, z)
Tiles.MapRange(range, (x, y ) => { Tiles.MapRange(range, (x, y) => {
const tileId = Tiles.tile_index(z, x, y) const tileId = Tiles.tile_index(z, x, y)
const nodesById = this.loadedTiles.get(tileId) const nodesById = this.loadedTiles.get(tileId)
nodesById?.forEach((v,k) => allById.set(k,v)) nodesById?.forEach((v, k) => allById.set(k, v))
}) })
} }
return allById return allById

View file

@ -1,8 +1,8 @@
import DynamicTileSource from "./DynamicTileSource" import DynamicTileSource from "./DynamicTileSource"
import {Store} from "../../UIEventSource" import { Store } from "../../UIEventSource"
import {BBox} from "../../BBox" import { BBox } from "../../BBox"
import TileLocalStorage from "../Actors/TileLocalStorage" import TileLocalStorage from "../Actors/TileLocalStorage"
import {Feature} from "geojson" import { Feature } from "geojson"
import StaticFeatureSource from "../Sources/StaticFeatureSource" import StaticFeatureSource from "../Sources/StaticFeatureSource"
export default class LocalStorageFeatureSource extends DynamicTileSource { export default class LocalStorageFeatureSource extends DynamicTileSource {
@ -15,26 +15,27 @@ export default class LocalStorageFeatureSource extends DynamicTileSource {
zoom: Store<number> zoom: Store<number>
}, },
options?: { options?: {
isActive?: Store<boolean>, isActive?: Store<boolean>
maxAge?: number // In seconds maxAge?: number // In seconds
} }
) { ) {
const storage = TileLocalStorage.construct<Feature[]>(backend, layername, options?.maxAge ?? 24 * 60 * 60) const storage = TileLocalStorage.construct<Feature[]>(
backend,
layername,
options?.maxAge ?? 24 * 60 * 60
)
super( super(
zoomlevel, zoomlevel,
(tileIndex) => (tileIndex) =>
new StaticFeatureSource( new StaticFeatureSource(
storage storage.getTileSource(tileIndex).mapD((features) => {
.getTileSource(tileIndex)
.mapD((features) => {
if (features.length === undefined) { if (features.length === undefined) {
console.trace("These are not features:", features) console.trace("These are not features:", features)
storage.invalidate(zoomlevel, tileIndex) storage.invalidate(zoomlevel, tileIndex)
return [] return []
} }
return features.filter((f) => !f.properties.id.match(/(node|way)\/-[0-9]+/)); return features.filter((f) => !f.properties.id.match(/(node|way)\/-[0-9]+/))
} })
)
), ),
mapProperties, mapProperties,
options options

View file

@ -408,7 +408,10 @@ export class GeoOperations {
/** /**
* Calculates line intersection between two features. * Calculates line intersection between two features.
*/ */
public static LineIntersections(feature: Feature<LineString | MultiLineString | Polygon | MultiPolygon>, otherFeature: Feature<LineString | MultiLineString | Polygon | MultiPolygon>): [number, number][] { public static LineIntersections(
feature: Feature<LineString | MultiLineString | Polygon | MultiPolygon>,
otherFeature: Feature<LineString | MultiLineString | Polygon | MultiPolygon>
): [number, number][] {
return turf return turf
.lineIntersect(feature, otherFeature) .lineIntersect(feature, otherFeature)
.features.map((p) => <[number, number]>p.geometry.coordinates) .features.map((p) => <[number, number]>p.geometry.coordinates)
@ -420,8 +423,7 @@ export class GeoOperations {
* @param features * @param features
* @param zoomlevel * @param zoomlevel
*/ */
public static spreadIntoBboxes(features: Feature[], zoomlevel: number) : Map<number, Feature[]> { public static spreadIntoBboxes(features: Feature[], zoomlevel: number): Map<number, Feature[]> {
const perBbox = new Map<number, Feature[]>() const perBbox = new Map<number, Feature[]>()
for (const feature of features) { for (const feature of features) {
@ -430,7 +432,7 @@ export class GeoOperations {
Tiles.MapRange(tilerange, (x, y) => { Tiles.MapRange(tilerange, (x, y) => {
const tileNumber = Tiles.tile_index(zoomlevel, x, y) const tileNumber = Tiles.tile_index(zoomlevel, x, y)
let newFeatureList = perBbox.get(tileNumber) let newFeatureList = perBbox.get(tileNumber)
if(newFeatureList === undefined){ if (newFeatureList === undefined) {
newFeatureList = [] newFeatureList = []
perBbox.set(tileNumber, newFeatureList) perBbox.set(tileNumber, newFeatureList)
} }
@ -703,18 +705,18 @@ export class GeoOperations {
public static along(a: Coord, b: Coord, distanceMeter: number): Coord { public static along(a: Coord, b: Coord, distanceMeter: number): Coord {
return turf.along( return turf.along(
<any> { <any>{
type:"Feature", type: "Feature",
geometry:{ geometry: {
type:"LineString", type: "LineString",
coordinates: [a, b] coordinates: [a, b],
} },
}, distanceMeter, {units: "meters"} },
distanceMeter,
{ units: "meters" }
).geometry.coordinates ).geometry.coordinates
} }
/** /**
* Returns 'true' if one feature contains the other feature * Returns 'true' if one feature contains the other feature
* *

View file

@ -1,10 +1,10 @@
import { Mapillary } from "./Mapillary"; import { Mapillary } from "./Mapillary"
import { WikimediaImageProvider } from "./WikimediaImageProvider"; import { WikimediaImageProvider } from "./WikimediaImageProvider"
import { Imgur } from "./Imgur"; import { Imgur } from "./Imgur"
import GenericImageProvider from "./GenericImageProvider"; import GenericImageProvider from "./GenericImageProvider"
import { Store, UIEventSource } from "../UIEventSource"; import { Store, UIEventSource } from "../UIEventSource"
import ImageProvider, { ProvidedImage } from "./ImageProvider"; import ImageProvider, { ProvidedImage } from "./ImageProvider"
import { WikidataImageProvider } from "./WikidataImageProvider"; import { WikidataImageProvider } from "./WikidataImageProvider"
/** /**
* A generic 'from the interwebz' image picker, without attribution * A generic 'from the interwebz' image picker, without attribution
@ -44,7 +44,10 @@ export default class AllImageProviders {
UIEventSource<ProvidedImage[]> UIEventSource<ProvidedImage[]>
>() >()
public static LoadImagesFor(tags: Store<Record<string, string>>, tagKey?: string[]): Store<ProvidedImage[]> { public static LoadImagesFor(
tags: Store<Record<string, string>>,
tagKey?: string[]
): Store<ProvidedImage[]> {
if (tags.data.id === undefined) { if (tags.data.id === undefined) {
return undefined return undefined
} }

View file

@ -80,12 +80,12 @@ export default class Maproulette {
* Maproulette.codeToIndex("qdsf") // => undefined * Maproulette.codeToIndex("qdsf") // => undefined
* *
*/ */
public static codeToIndex(code: string) : number | undefined{ public static codeToIndex(code: string): number | undefined {
if(code === "Created"){ if (code === "Created") {
return Maproulette.STATUS_OPEN return Maproulette.STATUS_OPEN
} }
for (let i = 0; i < 9; i++) { for (let i = 0; i < 9; i++) {
if(Maproulette.STATUS_MEANING[""+i] === code){ if (Maproulette.STATUS_MEANING["" + i] === code) {
return i return i
} }
} }

View file

@ -1,14 +1,14 @@
import SimpleMetaTaggers, {MetataggingState, SimpleMetaTagger} from "./SimpleMetaTagger" import SimpleMetaTaggers, { MetataggingState, SimpleMetaTagger } from "./SimpleMetaTagger"
import {ExtraFuncParams, ExtraFunctions, ExtraFuncType} from "./ExtraFunctions" import { ExtraFuncParams, ExtraFunctions, ExtraFuncType } from "./ExtraFunctions"
import LayerConfig from "../Models/ThemeConfig/LayerConfig" import LayerConfig from "../Models/ThemeConfig/LayerConfig"
import {Feature} from "geojson" import { Feature } from "geojson"
import FeaturePropertiesStore from "./FeatureSource/Actors/FeaturePropertiesStore" import FeaturePropertiesStore from "./FeatureSource/Actors/FeaturePropertiesStore"
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig" import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"
import {GeoIndexedStoreForLayer} from "./FeatureSource/Actors/GeoIndexedStore" import { GeoIndexedStoreForLayer } from "./FeatureSource/Actors/GeoIndexedStore"
import {IndexedFeatureSource} from "./FeatureSource/FeatureSource" import { IndexedFeatureSource } from "./FeatureSource/FeatureSource"
import OsmObjectDownloader from "./Osm/OsmObjectDownloader" import OsmObjectDownloader from "./Osm/OsmObjectDownloader"
import {Utils} from "../Utils"; import { Utils } from "../Utils"
import {UIEventSource} from "./UIEventSource"; import { UIEventSource } from "./UIEventSource"
/** /**
* Metatagging adds various tags to the elements, e.g. lat, lon, surface area, ... * Metatagging adds various tags to the elements, e.g. lat, lon, surface area, ...
@ -18,7 +18,10 @@ import {UIEventSource} from "./UIEventSource";
export default class MetaTagging { export default class MetaTagging {
private static errorPrintCount = 0 private static errorPrintCount = 0
private static readonly stopErrorOutputAt = 10 private static readonly stopErrorOutputAt = 10
private static retaggingFuncCache = new Map<string, ((feature: Feature, propertiesStore: UIEventSource<any>) => void)[]>() private static retaggingFuncCache = new Map<
string,
((feature: Feature, propertiesStore: UIEventSource<any>) => void)[]
>()
constructor(state: { constructor(state: {
layout: LayoutConfig layout: LayoutConfig
@ -96,7 +99,7 @@ export default class MetaTagging {
// The calculated functions - per layer - which add the new keys // The calculated functions - per layer - which add the new keys
// Calculated functions are defined by the layer // Calculated functions are defined by the layer
const layerFuncs = this.createRetaggingFunc(layer, ExtraFunctions.constructHelpers(params)) const layerFuncs = this.createRetaggingFunc(layer, ExtraFunctions.constructHelpers(params))
const state: MetataggingState = {layout, osmObjectDownloader} const state: MetataggingState = { layout, osmObjectDownloader }
let atLeastOneFeatureChanged = false let atLeastOneFeatureChanged = false
let strictlyEvaluated = 0 let strictlyEvaluated = 0
@ -177,20 +180,20 @@ export default class MetaTagging {
} }
public static createExtraFuncParams(state: { public static createExtraFuncParams(state: {
indexedFeatures: IndexedFeatureSource, indexedFeatures: IndexedFeatureSource
perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer> perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer>
}) { }) {
return { return {
getFeatureById: (id) => state.indexedFeatures.featuresById.data.get(id), getFeatureById: (id) => state.indexedFeatures.featuresById.data.get(id),
getFeaturesWithin: (layerId, bbox) => { getFeaturesWithin: (layerId, bbox) => {
if (layerId === '*' || layerId === null || layerId === undefined) { if (layerId === "*" || layerId === null || layerId === undefined) {
const feats: Feature[][] = [] const feats: Feature[][] = []
state.perLayer.forEach((layer) => { state.perLayer.forEach((layer) => {
feats.push(layer.GetFeaturesWithin(bbox)) feats.push(layer.GetFeaturesWithin(bbox))
}) })
return feats return feats
} }
return [state.perLayer.get(layerId).GetFeaturesWithin(bbox)]; return [state.perLayer.get(layerId).GetFeaturesWithin(bbox)]
}, },
} }
} }
@ -202,7 +205,8 @@ export default class MetaTagging {
* @param layerId * @param layerId
* @private * @private
*/ */
private static createFunctionForFeature([key, code, isStrict]: [string, string, boolean], private static createFunctionForFeature(
[key, code, isStrict]: [string, string, boolean],
helperFunctions: Record<ExtraFuncType, (feature: Feature) => Function>, helperFunctions: Record<ExtraFuncType, (feature: Feature) => Function>,
layerId: string = "unkown layer" layerId: string = "unkown layer"
): ((feature: Feature, propertiesStore?: UIEventSource<any>) => void) | undefined { ): ((feature: Feature, propertiesStore?: UIEventSource<any>) => void) | undefined {
@ -210,15 +214,21 @@ export default class MetaTagging {
return undefined return undefined
} }
const calculateAndAssign: (feat: Feature, store?: UIEventSource<any>) => string | any = (
const calculateAndAssign: ((feat: Feature, store?: UIEventSource<any>) => string | any) = (feat, store) => { feat,
store
) => {
try { try {
let result = new Function("feat", "{" + ExtraFunctions.types.join(", ") + "}", "return " + code + ";")(feat, helperFunctions) let result = new Function(
"feat",
"{" + ExtraFunctions.types.join(", ") + "}",
"return " + code + ";"
)(feat, helperFunctions)
if (result === "") { if (result === "") {
result = undefined result = undefined
} }
const oldValue= feat.properties[key] const oldValue = feat.properties[key]
if(oldValue == result){ if (oldValue == result) {
return oldValue return oldValue
} }
delete feat.properties[key] delete feat.properties[key]
@ -276,9 +286,12 @@ export default class MetaTagging {
return undefined return undefined
} }
let functions: ((feature: Feature, propertiesStore?: UIEventSource<any>) => void)[] = MetaTagging.retaggingFuncCache.get(layer.id) let functions: ((feature: Feature, propertiesStore?: UIEventSource<any>) => void)[] =
MetaTagging.retaggingFuncCache.get(layer.id)
if (functions === undefined) { if (functions === undefined) {
functions = calculatedTags.map(spec => this.createFunctionForFeature(spec, helpers, layer.id)) functions = calculatedTags.map((spec) =>
this.createFunctionForFeature(spec, helpers, layer.id)
)
MetaTagging.retaggingFuncCache.set(layer.id, functions) MetaTagging.retaggingFuncCache.set(layer.id, functions)
} }

View file

@ -1,20 +1,23 @@
import {OsmCreateAction, PreviewableAction} from "./OsmChangeAction" import { OsmCreateAction, PreviewableAction } from "./OsmChangeAction"
import {Tag} from "../../Tags/Tag" import { Tag } from "../../Tags/Tag"
import {Changes} from "../Changes" import { Changes } from "../Changes"
import {ChangeDescription} from "./ChangeDescription" import { ChangeDescription } from "./ChangeDescription"
import CreateNewWayAction from "./CreateNewWayAction" import CreateNewWayAction from "./CreateNewWayAction"
import CreateWayWithPointReuseAction, {MergePointConfig} from "./CreateWayWithPointReuseAction" import CreateWayWithPointReuseAction, { MergePointConfig } from "./CreateWayWithPointReuseAction"
import {And} from "../../Tags/And" import { And } from "../../Tags/And"
import {TagUtils} from "../../Tags/TagUtils" import { TagUtils } from "../../Tags/TagUtils"
import {FeatureSource, IndexedFeatureSource} from "../../FeatureSource/FeatureSource" import { FeatureSource, IndexedFeatureSource } from "../../FeatureSource/FeatureSource"
import LayoutConfig from "../../../Models/ThemeConfig/LayoutConfig"; import LayoutConfig from "../../../Models/ThemeConfig/LayoutConfig"
import {Position} from "geojson"; import { Position } from "geojson"
import FullNodeDatabaseSource from "../../FeatureSource/TiledFeatureSource/FullNodeDatabaseSource"; import FullNodeDatabaseSource from "../../FeatureSource/TiledFeatureSource/FullNodeDatabaseSource"
/** /**
* More or less the same as 'CreateNewWay', except that it'll try to reuse already existing points * More or less the same as 'CreateNewWay', except that it'll try to reuse already existing points
*/ */
export default class CreateMultiPolygonWithPointReuseAction extends OsmCreateAction implements PreviewableAction { export default class CreateMultiPolygonWithPointReuseAction
extends OsmCreateAction
implements PreviewableAction
{
public newElementId: string = undefined public newElementId: string = undefined
public newElementIdNumber: number = undefined public newElementIdNumber: number = undefined
private readonly _tags: Tag[] private readonly _tags: Tag[]
@ -29,9 +32,9 @@ export default class CreateMultiPolygonWithPointReuseAction extends OsmCreateAct
outerRingCoordinates: Position[], outerRingCoordinates: Position[],
innerRingsCoordinates: Position[][], innerRingsCoordinates: Position[][],
state: { state: {
layout: LayoutConfig; layout: LayoutConfig
changes: Changes; changes: Changes
indexedFeatures: IndexedFeatureSource, indexedFeatures: IndexedFeatureSource
fullNodeDatabase?: FullNodeDatabaseSource fullNodeDatabase?: FullNodeDatabaseSource
}, },
config: MergePointConfig[], config: MergePointConfig[],
@ -43,7 +46,7 @@ export default class CreateMultiPolygonWithPointReuseAction extends OsmCreateAct
this.theme = state?.layout?.id ?? "" this.theme = state?.layout?.id ?? ""
this.createOuterWay = new CreateWayWithPointReuseAction( this.createOuterWay = new CreateWayWithPointReuseAction(
[], [],
<[number,number][]> outerRingCoordinates, <[number, number][]>outerRingCoordinates,
state, state,
config config
) )

View file

@ -1,9 +1,9 @@
import {ChangeDescription} from "./ChangeDescription" import { ChangeDescription } from "./ChangeDescription"
import {OsmCreateAction} from "./OsmChangeAction" import { OsmCreateAction } from "./OsmChangeAction"
import {Changes} from "../Changes" import { Changes } from "../Changes"
import {Tag} from "../../Tags/Tag" import { Tag } from "../../Tags/Tag"
import CreateNewNodeAction from "./CreateNewNodeAction" import CreateNewNodeAction from "./CreateNewNodeAction"
import {And} from "../../Tags/And" import { And } from "../../Tags/And"
export default class CreateNewWayAction extends OsmCreateAction { export default class CreateNewWayAction extends OsmCreateAction {
public newElementId: string = undefined public newElementId: string = undefined

View file

@ -1,17 +1,17 @@
import {OsmCreateAction, PreviewableAction} from "./OsmChangeAction" import { OsmCreateAction, PreviewableAction } from "./OsmChangeAction"
import {Tag} from "../../Tags/Tag" import { Tag } from "../../Tags/Tag"
import {Changes} from "../Changes" import { Changes } from "../Changes"
import {ChangeDescription} from "./ChangeDescription" import { ChangeDescription } from "./ChangeDescription"
import {BBox} from "../../BBox" import { BBox } from "../../BBox"
import {TagsFilter} from "../../Tags/TagsFilter" import { TagsFilter } from "../../Tags/TagsFilter"
import {GeoOperations} from "../../GeoOperations" import { GeoOperations } from "../../GeoOperations"
import {FeatureSource, IndexedFeatureSource} from "../../FeatureSource/FeatureSource" import { FeatureSource, IndexedFeatureSource } from "../../FeatureSource/FeatureSource"
import StaticFeatureSource from "../../FeatureSource/Sources/StaticFeatureSource" import StaticFeatureSource from "../../FeatureSource/Sources/StaticFeatureSource"
import CreateNewNodeAction from "./CreateNewNodeAction" import CreateNewNodeAction from "./CreateNewNodeAction"
import CreateNewWayAction from "./CreateNewWayAction" import CreateNewWayAction from "./CreateNewWayAction"
import LayoutConfig from "../../../Models/ThemeConfig/LayoutConfig"; import LayoutConfig from "../../../Models/ThemeConfig/LayoutConfig"
import FullNodeDatabaseSource from "../../FeatureSource/TiledFeatureSource/FullNodeDatabaseSource"; import FullNodeDatabaseSource from "../../FeatureSource/TiledFeatureSource/FullNodeDatabaseSource"
import {Position} from "geojson"; import { Position } from "geojson"
export interface MergePointConfig { export interface MergePointConfig {
withinRangeOfM: number withinRangeOfM: number
@ -56,7 +56,10 @@ interface CoordinateInfo {
/** /**
* More or less the same as 'CreateNewWay', except that it'll try to reuse already existing points * More or less the same as 'CreateNewWay', except that it'll try to reuse already existing points
*/ */
export default class CreateWayWithPointReuseAction extends OsmCreateAction implements PreviewableAction { export default class CreateWayWithPointReuseAction
extends OsmCreateAction
implements PreviewableAction
{
public newElementId: string = undefined public newElementId: string = undefined
public newElementIdNumber: number = undefined public newElementIdNumber: number = undefined
private readonly _tags: Tag[] private readonly _tags: Tag[]
@ -66,9 +69,9 @@ export default class CreateWayWithPointReuseAction extends OsmCreateAction imple
*/ */
private readonly _coordinateInfo: CoordinateInfo[] private readonly _coordinateInfo: CoordinateInfo[]
private readonly _state: { private readonly _state: {
layout: LayoutConfig; layout: LayoutConfig
changes: Changes; changes: Changes
indexedFeatures: IndexedFeatureSource, indexedFeatures: IndexedFeatureSource
fullNodeDatabase?: FullNodeDatabaseSource fullNodeDatabase?: FullNodeDatabaseSource
} }
private readonly _config: MergePointConfig[] private readonly _config: MergePointConfig[]
@ -77,9 +80,9 @@ export default class CreateWayWithPointReuseAction extends OsmCreateAction imple
tags: Tag[], tags: Tag[],
coordinates: Position[], coordinates: Position[],
state: { state: {
layout: LayoutConfig; layout: LayoutConfig
changes: Changes; changes: Changes
indexedFeatures: IndexedFeatureSource, indexedFeatures: IndexedFeatureSource
fullNodeDatabase?: FullNodeDatabaseSource fullNodeDatabase?: FullNodeDatabaseSource
}, },
config: MergePointConfig[] config: MergePointConfig[]
@ -90,7 +93,7 @@ export default class CreateWayWithPointReuseAction extends OsmCreateAction imple
this._config = config this._config = config
// The main logic of this class: the coordinateInfo contains all the changes // The main logic of this class: the coordinateInfo contains all the changes
this._coordinateInfo = this.CalculateClosebyNodes(<[number,number][]> coordinates) this._coordinateInfo = this.CalculateClosebyNodes(<[number, number][]>coordinates)
} }
public async getPreview(): Promise<FeatureSource> { public async getPreview(): Promise<FeatureSource> {
@ -245,7 +248,7 @@ export default class CreateWayWithPointReuseAction extends OsmCreateAction imple
}, },
}) })
} }
nodeIdsToUse.push({lat, lon, nodeId: id}) nodeIdsToUse.push({ lat, lon, nodeId: id })
} }
const newWay = new CreateNewWayAction(this._tags, nodeIdsToUse, { const newWay = new CreateNewWayAction(this._tags, nodeIdsToUse, {
@ -321,7 +324,7 @@ export default class CreateWayWithPointReuseAction extends OsmCreateAction imple
if (!config.ifMatches.matchesProperties(node.properties)) { if (!config.ifMatches.matchesProperties(node.properties)) {
continue continue
} }
closebyNodes.push({node, d, config}) closebyNodes.push({ node, d, config })
} }
} }

View file

@ -8,7 +8,7 @@ import { And } from "../../Tags/And"
import { Tag } from "../../Tags/Tag" import { Tag } from "../../Tags/Tag"
import { OsmId } from "../../../Models/OsmFeature" import { OsmId } from "../../../Models/OsmFeature"
import { Utils } from "../../../Utils" import { Utils } from "../../../Utils"
import OsmObjectDownloader from "../OsmObjectDownloader"; import OsmObjectDownloader from "../OsmObjectDownloader"
export default class DeleteAction extends OsmChangeAction { export default class DeleteAction extends OsmChangeAction {
private readonly _softDeletionTags: TagsFilter private readonly _softDeletionTags: TagsFilter
@ -72,9 +72,11 @@ export default class DeleteAction extends OsmChangeAction {
changes: Changes, changes: Changes,
object?: OsmObject object?: OsmObject
): Promise<ChangeDescription[]> { ): Promise<ChangeDescription[]> {
const osmObject = object ?? (await new OsmObjectDownloader(changes.backend, changes).DownloadObjectAsync(this._id)) const osmObject =
object ??
(await new OsmObjectDownloader(changes.backend, changes).DownloadObjectAsync(this._id))
if(osmObject === "deleted"){ if (osmObject === "deleted") {
// already deleted in the meantime - no more changes necessary // already deleted in the meantime - no more changes necessary
return [] return []
} }

View file

@ -4,7 +4,7 @@
*/ */
import { Changes } from "../Changes" import { Changes } from "../Changes"
import { ChangeDescription } from "./ChangeDescription" import { ChangeDescription } from "./ChangeDescription"
import {FeatureSource} from "../../FeatureSource/FeatureSource"; import { FeatureSource } from "../../FeatureSource/FeatureSource"
export default abstract class OsmChangeAction { export default abstract class OsmChangeAction {
public readonly trackStatistics: boolean public readonly trackStatistics: boolean

View file

@ -1,21 +1,21 @@
import OsmChangeAction, {PreviewableAction} from "./OsmChangeAction" import OsmChangeAction, { PreviewableAction } from "./OsmChangeAction"
import {Changes} from "../Changes" import { Changes } from "../Changes"
import {ChangeDescription} from "./ChangeDescription" import { ChangeDescription } from "./ChangeDescription"
import {Tag} from "../../Tags/Tag" import { Tag } from "../../Tags/Tag"
import {FeatureSource} from "../../FeatureSource/FeatureSource" import { FeatureSource } from "../../FeatureSource/FeatureSource"
import {OsmNode, OsmObject, OsmWay} from "../OsmObject" import { OsmNode, OsmObject, OsmWay } from "../OsmObject"
import {GeoOperations} from "../../GeoOperations" import { GeoOperations } from "../../GeoOperations"
import StaticFeatureSource from "../../FeatureSource/Sources/StaticFeatureSource" import StaticFeatureSource from "../../FeatureSource/Sources/StaticFeatureSource"
import CreateNewNodeAction from "./CreateNewNodeAction" import CreateNewNodeAction from "./CreateNewNodeAction"
import ChangeTagAction from "./ChangeTagAction" import ChangeTagAction from "./ChangeTagAction"
import {And} from "../../Tags/And" import { And } from "../../Tags/And"
import {Utils} from "../../../Utils" import { Utils } from "../../../Utils"
import {OsmConnection} from "../OsmConnection" import { OsmConnection } from "../OsmConnection"
import {Feature} from "@turf/turf" import { Feature } from "@turf/turf"
import {Geometry, LineString, Point} from "geojson" import { Geometry, LineString, Point } from "geojson"
import FullNodeDatabaseSource from "../../FeatureSource/TiledFeatureSource/FullNodeDatabaseSource" import FullNodeDatabaseSource from "../../FeatureSource/TiledFeatureSource/FullNodeDatabaseSource"
export default class ReplaceGeometryAction extends OsmChangeAction implements PreviewableAction{ export default class ReplaceGeometryAction extends OsmChangeAction implements PreviewableAction {
/** /**
* The target feature - mostly used for the metadata * The target feature - mostly used for the metadata
*/ */
@ -45,7 +45,7 @@ export default class ReplaceGeometryAction extends OsmChangeAction implements Pr
public readonly newElementId: string public readonly newElementId: string
constructor( constructor(
state: { state: {
osmConnection: OsmConnection, osmConnection: OsmConnection
fullNodeDatabase?: FullNodeDatabaseSource fullNodeDatabase?: FullNodeDatabaseSource
}, },
feature: any, feature: any,
@ -460,7 +460,7 @@ export default class ReplaceGeometryAction extends OsmChangeAction implements Pr
} }
} }
console.log("Adding tags", this.newTags,"to conflated way nr", this.wayToReplaceId) console.log("Adding tags", this.newTags, "to conflated way nr", this.wayToReplaceId)
if (this.newTags !== undefined && this.newTags.length > 0) { if (this.newTags !== undefined && this.newTags.length > 0) {
const addExtraTags = new ChangeTagAction( const addExtraTags = new ChangeTagAction(
this.wayToReplaceId, this.wayToReplaceId,

View file

@ -1,10 +1,10 @@
import {OsmWay} from "../OsmObject" import { OsmWay } from "../OsmObject"
import {Changes} from "../Changes" import { Changes } from "../Changes"
import {GeoOperations} from "../../GeoOperations" import { GeoOperations } from "../../GeoOperations"
import OsmChangeAction from "./OsmChangeAction" import OsmChangeAction from "./OsmChangeAction"
import {ChangeDescription} from "./ChangeDescription" import { ChangeDescription } from "./ChangeDescription"
import RelationSplitHandler from "./RelationSplitHandler" import RelationSplitHandler from "./RelationSplitHandler"
import {Feature, LineString} from "geojson" import { Feature, LineString } from "geojson"
import OsmObjectDownloader from "../OsmObjectDownloader" import OsmObjectDownloader from "../OsmObjectDownloader"
interface SplitInfo { interface SplitInfo {

View file

@ -1,16 +1,16 @@
import {OsmNode, OsmObject, OsmRelation, OsmWay} from "./OsmObject" import { OsmNode, OsmObject, OsmRelation, OsmWay } from "./OsmObject"
import {Store, UIEventSource} from "../UIEventSource" import { Store, UIEventSource } from "../UIEventSource"
import Constants from "../../Models/Constants" import Constants from "../../Models/Constants"
import OsmChangeAction from "./Actions/OsmChangeAction" import OsmChangeAction from "./Actions/OsmChangeAction"
import {ChangeDescription, ChangeDescriptionTools} from "./Actions/ChangeDescription" import { ChangeDescription, ChangeDescriptionTools } from "./Actions/ChangeDescription"
import {Utils} from "../../Utils" import { Utils } from "../../Utils"
import {LocalStorageSource} from "../Web/LocalStorageSource" import { LocalStorageSource } from "../Web/LocalStorageSource"
import SimpleMetaTagger from "../SimpleMetaTagger" import SimpleMetaTagger from "../SimpleMetaTagger"
import {FeatureSource, IndexedFeatureSource} from "../FeatureSource/FeatureSource" import { FeatureSource, IndexedFeatureSource } from "../FeatureSource/FeatureSource"
import {GeoLocationPointProperties} from "../State/GeoLocationState" import { GeoLocationPointProperties } from "../State/GeoLocationState"
import {GeoOperations} from "../GeoOperations" import { GeoOperations } from "../GeoOperations"
import {ChangesetHandler, ChangesetTag} from "./ChangesetHandler" import { ChangesetHandler, ChangesetTag } from "./ChangesetHandler"
import {OsmConnection} from "./OsmConnection" import { OsmConnection } from "./OsmConnection"
import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore" import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore"
import OsmObjectDownloader from "./OsmObjectDownloader" import OsmObjectDownloader from "./OsmObjectDownloader"
@ -408,7 +408,7 @@ export class Changes {
neededIds.map(async (id) => { neededIds.map(async (id) => {
try { try {
const osmObj = await downloader.DownloadObjectAsync(id) const osmObj = await downloader.DownloadObjectAsync(id)
return {id, osmObj} return { id, osmObj }
} catch (e) { } catch (e) {
console.error( console.error(
"Could not download OSM-object", "Could not download OSM-object",
@ -422,7 +422,7 @@ export class Changes {
osmObjects = Utils.NoNull(osmObjects) osmObjects = Utils.NoNull(osmObjects)
for (const {osmObj, id} of osmObjects) { for (const { osmObj, id } of osmObjects) {
if (osmObj === "deleted") { if (osmObj === "deleted") {
pending = pending.filter((ch) => ch.type + "/" + ch.id !== id) pending = pending.filter((ch) => ch.type + "/" + ch.id !== id)
} }

View file

@ -131,7 +131,8 @@ export class ChangesetHandler {
const changeset = generateChangeXML(csId, this._remappings) const changeset = generateChangeXML(csId, this._remappings)
console.log( console.log(
"Opened a new changeset (openChangeset.data is undefined):", "Opened a new changeset (openChangeset.data is undefined):",
changeset, extraMetaTags changeset,
extraMetaTags
) )
const changes = await this.UploadChange(csId, changeset) const changes = await this.UploadChange(csId, changeset)
const hasSpecialMotivationChanges = ChangesetHandler.rewriteMetaTags( const hasSpecialMotivationChanges = ChangesetHandler.rewriteMetaTags(

View file

@ -1,8 +1,8 @@
import {Utils} from "../../Utils" import { Utils } from "../../Utils"
import polygon_features from "../../assets/polygon-features.json" import polygon_features from "../../assets/polygon-features.json"
import OsmToGeoJson from "osmtogeojson" import OsmToGeoJson from "osmtogeojson"
import {OsmFeature, OsmId, OsmTags, WayId} from "../../Models/OsmFeature" import { OsmFeature, OsmId, OsmTags, WayId } from "../../Models/OsmFeature"
import {Feature, LineString, Polygon} from "geojson" import { Feature, LineString, Polygon } from "geojson"
export abstract class OsmObject { export abstract class OsmObject {
private static defaultBackend = "https://www.openstreetmap.org/" private static defaultBackend = "https://www.openstreetmap.org/"
@ -198,7 +198,6 @@ export class OsmNode extends OsmObject {
this.LoadData(extraData) this.LoadData(extraData)
} }
/** /**
* *
* const obj = new OsmNode(1234) * const obj = new OsmNode(1234)
@ -213,11 +212,11 @@ export class OsmNode extends OsmObject {
*/ */
ChangesetXML(changesetId: string, header?: string): string { ChangesetXML(changesetId: string, header?: string): string {
let tags = this.TagsXML() let tags = this.TagsXML()
return ( return ` <node id="${this.id}" ${header ?? ""} ${
` <node id="${this.id}" ${header ?? ""} ${changesetId ? (' changeset="' + changesetId+ '" ') : ""}${this.VersionXML()} lat="${this.lat}" lon="${this.lon}"> changesetId ? ' changeset="' + changesetId + '" ' : ""
}${this.VersionXML()} lat="${this.lat}" lon="${this.lon}">
${tags} </node> ${tags} </node>
` `
)
} }
SaveExtraData(element) { SaveExtraData(element) {
@ -269,11 +268,11 @@ export class OsmWay extends OsmObject {
nds += ' <nd ref="' + this.nodes[node] + '"/>\n' nds += ' <nd ref="' + this.nodes[node] + '"/>\n'
} }
return ( return ` <way id="${this.id}" ${header ?? ""} ${
` <way id="${this.id}" ${header ?? ""} ${changesetId ? ('changeset="' + changesetId + '" ') : ""} ${this.VersionXML()}> changesetId ? 'changeset="' + changesetId + '" ' : ""
} ${this.VersionXML()}>
${nds}${tags} </way> ${nds}${tags} </way>
` `
)
} }
SaveExtraData(element, allNodes: OsmNode[]) { SaveExtraData(element, allNodes: OsmNode[]) {

View file

@ -132,7 +132,7 @@ class CountryTagger extends SimpleMetaTagger {
CountryTagger.coder CountryTagger.coder
.GetCountryCodeAsync(lon, lat) .GetCountryCodeAsync(lon, lat)
.then((countries) => { .then((countries) => {
if(!countries){ if (!countries) {
console.warn("Country coder returned ", countries) console.warn("Country coder returned ", countries)
return return
} }
@ -316,7 +316,6 @@ export default class SimpleMetaTaggers {
(feature) => { (feature) => {
Utils.AddLazyProperty(feature.properties, "_surface", () => { Utils.AddLazyProperty(feature.properties, "_surface", () => {
return "" + GeoOperations.surfaceAreaInSqMeters(feature) return "" + GeoOperations.surfaceAreaInSqMeters(feature)
}) })
return true return true

View file

@ -2,23 +2,15 @@
* The part of the global state which initializes the feature switches, based on default values and on the layoutToUse * The part of the global state which initializes the feature switches, based on default values and on the layoutToUse
*/ */
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
import {UIEventSource} from "../UIEventSource" import { UIEventSource } from "../UIEventSource"
import {QueryParameters} from "../Web/QueryParameters" import { QueryParameters } from "../Web/QueryParameters"
import Constants from "../../Models/Constants" import Constants from "../../Models/Constants"
import {Utils} from "../../Utils" import { Utils } from "../../Utils"
class FeatureSwitchUtils { class FeatureSwitchUtils {
static initSwitch( static initSwitch(key: string, deflt: boolean, documentation: string): UIEventSource<boolean> {
key: string,
deflt:boolean,
documentation: string
): UIEventSource<boolean> {
const defaultValue = deflt const defaultValue = deflt
const queryParam = QueryParameters.GetQueryParameter( const queryParam = QueryParameters.GetQueryParameter(key, "" + defaultValue, documentation)
key,
"" + defaultValue,
documentation
)
// It takes the current layout, extracts the default value for this query parameter. A query parameter event source is then retrieved and flattened // It takes the current layout, extracts the default value for this query parameter. A query parameter event source is then retrieved and flattened
return queryParam.sync( return queryParam.sync(
@ -26,14 +18,10 @@ class FeatureSwitchUtils {
[], [],
(b) => (b == defaultValue ? undefined : "" + b) (b) => (b == defaultValue ? undefined : "" + b)
) )
} }
} }
export class OsmConnectionFeatureSwitches { export class OsmConnectionFeatureSwitches {
public readonly featureSwitchFakeUser: UIEventSource<boolean> public readonly featureSwitchFakeUser: UIEventSource<boolean>
public readonly featureSwitchApiURL: UIEventSource<string> public readonly featureSwitchApiURL: UIEventSource<string>
@ -52,8 +40,7 @@ export class OsmConnectionFeatureSwitches {
} }
} }
export default class FeatureSwitchState extends OsmConnectionFeatureSwitches {
export default class FeatureSwitchState extends OsmConnectionFeatureSwitches{
/** /**
* The layout that is being used in this run * The layout that is being used in this run
*/ */
@ -154,7 +141,6 @@ export default class FeatureSwitchState extends OsmConnectionFeatureSwitches{
"Enable the export as GeoJSON and CSV button" "Enable the export as GeoJSON and CSV button"
) )
let testingDefaultValue = false let testingDefaultValue = false
if ( if (
this.featureSwitchApiURL.data !== "osm-test" && this.featureSwitchApiURL.data !== "osm-test" &&

View file

@ -21,7 +21,9 @@ export class GeoLocationState {
* 'granted' means that it is granted * 'granted' means that it is granted
* 'denied' means that we don't have access * 'denied' means that we don't have access
*/ */
public readonly permission: UIEventSource<GeolocationPermissionState> = new UIEventSource("prompt") public readonly permission: UIEventSource<GeolocationPermissionState> = new UIEventSource(
"prompt"
)
/** /**
* Important to determine e.g. if we move automatically on fix or not * Important to determine e.g. if we move automatically on fix or not

View file

@ -69,7 +69,7 @@ export default class LayerState {
new Tag("level", "" + level), new Tag("level", "" + level),
new RegexTag("level", new RegExp("(.*;)?" + level + "(;.*)?")), new RegexTag("level", new RegExp("(.*;)?" + level + "(;.*)?")),
] ]
if(level === "0") { if (level === "0") {
conditionsOrred.push(new Tag("level", "")) // No level tag is the same as level '0' conditionsOrred.push(new Tag("level", "")) // No level tag is the same as level '0'
} }
console.log("Setting levels filter to", conditionsOrred) console.log("Setting levels filter to", conditionsOrred)

View file

@ -1,20 +1,20 @@
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
import {OsmConnection} from "../Osm/OsmConnection" import { OsmConnection } from "../Osm/OsmConnection"
import {MangroveIdentity} from "../Web/MangroveReviews" import { MangroveIdentity } from "../Web/MangroveReviews"
import {Store, Stores, UIEventSource} from "../UIEventSource" import { Store, Stores, UIEventSource } from "../UIEventSource"
import StaticFeatureSource from "../FeatureSource/Sources/StaticFeatureSource" import StaticFeatureSource from "../FeatureSource/Sources/StaticFeatureSource"
import {FeatureSource} from "../FeatureSource/FeatureSource" import { FeatureSource } from "../FeatureSource/FeatureSource"
import {Feature} from "geojson" import { Feature } from "geojson"
import {Utils} from "../../Utils" import { Utils } from "../../Utils"
import translators from "../../assets/translators.json" import translators from "../../assets/translators.json"
import codeContributors from "../../assets/contributors.json" import codeContributors from "../../assets/contributors.json"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import {LayerConfigJson} from "../../Models/ThemeConfig/Json/LayerConfigJson" import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson"
import usersettings from "../../assets/generated/layers/usersettings.json" import usersettings from "../../assets/generated/layers/usersettings.json"
import Locale from "../../UI/i18n/Locale" import Locale from "../../UI/i18n/Locale"
import LinkToWeblate from "../../UI/Base/LinkToWeblate" import LinkToWeblate from "../../UI/Base/LinkToWeblate"
import FeatureSwitchState from "./FeatureSwitchState" import FeatureSwitchState from "./FeatureSwitchState"
import Constants from "../../Models/Constants"; import Constants from "../../Models/Constants"
/** /**
* The part of the state which keeps track of user-related stuff, e.g. the OSM-connection, * The part of the state which keeps track of user-related stuff, e.g. the OSM-connection,
@ -34,8 +34,8 @@ export default class UserRelatedState {
public readonly mangroveIdentity: MangroveIdentity public readonly mangroveIdentity: MangroveIdentity
public readonly installedUserThemes: Store<string[]> public readonly installedUserThemes: Store<string[]>
public readonly showAllQuestionsAtOnce: UIEventSource<boolean> public readonly showAllQuestionsAtOnce: UIEventSource<boolean>
public static readonly SHOW_TAGS_VALUES = ["always","yes","full"] as const public static readonly SHOW_TAGS_VALUES = ["always", "yes", "full"] as const
public readonly showTags: UIEventSource<"no" | undefined | "always" | "yes" | "full">; public readonly showTags: UIEventSource<"no" | undefined | "always" | "yes" | "full">
public readonly homeLocation: FeatureSource public readonly homeLocation: FeatureSource
public readonly language: UIEventSource<string> public readonly language: UIEventSource<string>
/** /**
@ -102,13 +102,9 @@ export default class UserRelatedState {
} }
private static initUserRelatedState(): LayerConfig { private static initUserRelatedState(): LayerConfig {
try{ try {
return new LayerConfig(<LayerConfigJson>usersettings, "userinformationpanel")
return new LayerConfig( } catch (e) {
<LayerConfigJson>usersettings,
"userinformationpanel"
)
}catch(e){
return undefined return undefined
} }
} }
@ -184,7 +180,7 @@ export default class UserRelatedState {
} }
private InitializeLanguage(availableLanguages?: string[]) { private InitializeLanguage(availableLanguages?: string[]) {
this.language.addCallbackAndRunD(language => Locale.language.setData(language)) this.language.addCallbackAndRunD((language) => Locale.language.setData(language))
Locale.language.addCallback((currentLanguage) => { Locale.language.addCallback((currentLanguage) => {
if (Locale.showLinkToWeblate.data) { if (Locale.showLinkToWeblate.data) {
return true // Disable auto switching as we are in translators mode return true // Disable auto switching as we are in translators mode
@ -262,7 +258,8 @@ export default class UserRelatedState {
_theme: layout?.id, _theme: layout?.id,
_backend: this.osmConnection.Backend(), _backend: this.osmConnection.Backend(),
_applicationOpened: new Date().toISOString(), _applicationOpened: new Date().toISOString(),
_supports_sharing: (typeof window === "undefined") ? "no" : (window.navigator.share ? "yes" : "no") _supports_sharing:
typeof window === "undefined" ? "no" : window.navigator.share ? "yes" : "no",
}) })
for (const key in Constants.userJourney) { for (const key in Constants.userJourney) {
@ -375,7 +372,7 @@ export default class UserRelatedState {
// Language is managed seperately // Language is managed seperately
continue continue
} }
this.osmConnection.GetPreference(key, undefined, {prefix: ""}).setData(tags[key]) this.osmConnection.GetPreference(key, undefined, { prefix: "" }).setData(tags[key])
} }
}) })

View file

@ -1,5 +1,5 @@
import {Tag} from "./Tag" import { Tag } from "./Tag"
import {TagsFilter} from "./TagsFilter" import { TagsFilter } from "./TagsFilter"
export class RegexTag extends TagsFilter { export class RegexTag extends TagsFilter {
public readonly key: RegExp | string public readonly key: RegExp | string
@ -39,7 +39,6 @@ export class RegexTag extends TagsFilter {
return fromTag === possibleRegex return fromTag === possibleRegex
} }
return possibleRegex.test(fromTag) return possibleRegex.test(fromTag)
} }
private static source(r: string | RegExp) { private static source(r: string | RegExp) {
@ -155,7 +154,7 @@ export class RegexTag extends TagsFilter {
matchesProperties(tags: Record<string, string | number | boolean>): boolean { matchesProperties(tags: Record<string, string | number | boolean>): boolean {
if (typeof this.key === "string") { if (typeof this.key === "string") {
let value = tags[this.key] let value = tags[this.key]
if(!value || value === ""){ if (!value || value === "") {
// No tag is known, so we assume the empty string // No tag is known, so we assume the empty string
// If this regexTag matches the empty string, we return true, otherwise false // If this regexTag matches the empty string, we return true, otherwise false
// (Note: if inverted, we must reverse this) // (Note: if inverted, we must reverse this)
@ -176,7 +175,7 @@ export class RegexTag extends TagsFilter {
return !this.invert return !this.invert
} }
} }
if(typeof value !== "string"){ if (typeof value !== "string") {
value = JSON.stringify(value) value = JSON.stringify(value)
} }
return RegexTag.doesMatch(value, this.value) != this.invert return RegexTag.doesMatch(value, this.value) != this.invert
@ -188,7 +187,7 @@ export class RegexTag extends TagsFilter {
} }
if (RegexTag.doesMatch(key, this.key)) { if (RegexTag.doesMatch(key, this.key)) {
let value = tags[key] ?? "" let value = tags[key] ?? ""
if(typeof value !== "string"){ if (typeof value !== "string") {
value = JSON.stringify(value) value = JSON.stringify(value)
} }
return RegexTag.doesMatch(value, this.value) != this.invert return RegexTag.doesMatch(value, this.value) != this.invert

View file

@ -47,14 +47,14 @@ export class Tag extends TagsFilter {
// and it shouldn't be found! // and it shouldn't be found!
return true return true
} }
if(typeof foundValue !== "string"){ if (typeof foundValue !== "string") {
if(foundValue === true && (this.value === "true" || this.value === "yes" )){ if (foundValue === true && (this.value === "true" || this.value === "yes")) {
return true return true
} }
if(foundValue === false && (this.value === "false" || this.value === "no" )){ if (foundValue === false && (this.value === "false" || this.value === "no")) {
return true return true
} }
foundValue = ""+foundValue foundValue = "" + foundValue
} }
return foundValue === this.value return foundValue === this.value
} }

View file

@ -22,7 +22,7 @@ export class TagUtils {
] ]
static KVtoProperties(tags: Tag[]): Record<string, string> { static KVtoProperties(tags: Tag[]): Record<string, string> {
const properties : Record<string, string> = {} const properties: Record<string, string> = {}
for (const tag of tags) { for (const tag of tags) {
properties[tag.key] = tag.value properties[tag.key] = tag.value
} }

View file

@ -416,7 +416,7 @@ class MappedStore<TIn, T> extends Store<T> {
this._upstreamPingCount = upstreamListenerHandler?.pingCount this._upstreamPingCount = upstreamListenerHandler?.pingCount
this._extraStores = extraStores this._extraStores = extraStores
this.registerCallbacksToUpstream() this.registerCallbacksToUpstream()
if(onDestroy !== undefined){ if (onDestroy !== undefined) {
onDestroy(() => this.unregisterFromUpstream()) onDestroy(() => this.unregisterFromUpstream())
} }
} }
@ -698,7 +698,11 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
* srcSeen // => 21 * srcSeen // => 21
* lastSeen // => 42 * lastSeen // => 42
*/ */
public map<J>(f: (t: T) => J, extraSources: Store<any>[] = [], onDestroy?: (f : () => void ) => void): Store<J> { public map<J>(
f: (t: T) => J,
extraSources: Store<any>[] = [],
onDestroy?: (f: () => void) => void
): Store<J> {
return new MappedStore(this, f, extraSources, this._callbacks, f(this.data), onDestroy) return new MappedStore(this, f, extraSources, this._callbacks, f(this.data), onDestroy)
} }
/** /**

View file

@ -76,7 +76,8 @@ export default class FeatureReviews {
) { ) {
const centerLonLat = GeoOperations.centerpointCoordinates(feature) const centerLonLat = GeoOperations.centerpointCoordinates(feature)
;[this._lon, this._lat] = centerLonLat ;[this._lon, this._lat] = centerLonLat
this._identity = mangroveIdentity ?? new MangroveIdentity(new UIEventSource<string>(undefined)) this._identity =
mangroveIdentity ?? new MangroveIdentity(new UIEventSource<string>(undefined))
const nameKey = options?.nameKey ?? "name" const nameKey = options?.nameKey ?? "name"
if (feature.geometry.type === "Point") { if (feature.geometry.type === "Point") {
@ -103,7 +104,7 @@ export default class FeatureReviews {
this._uncertainty = options?.uncertaintyRadius ?? maxDistance this._uncertainty = options?.uncertaintyRadius ?? maxDistance
} }
this._name = tagsSource .map((tags) => tags[nameKey] ?? options?.fallbackName) this._name = tagsSource.map((tags) => tags[nameKey] ?? options?.fallbackName)
this.subjectUri = this.ConstructSubjectUri() this.subjectUri = this.ConstructSubjectUri()

View file

@ -1,8 +1,8 @@
import ThemeViewState from "../../Models/ThemeViewState"; import ThemeViewState from "../../Models/ThemeViewState"
import Hash from "./Hash"; import Hash from "./Hash"
export default class ThemeViewStateHashActor { export default class ThemeViewStateHashActor {
private readonly _state: ThemeViewState; private readonly _state: ThemeViewState
/** /**
* Converts the hash to the appropriate themeview state and, vice versa, sets the hash. * Converts the hash to the appropriate themeview state and, vice versa, sets the hash.
@ -15,27 +15,27 @@ export default class ThemeViewStateHashActor {
* @param state * @param state
*/ */
constructor(state: ThemeViewState) { constructor(state: ThemeViewState) {
this._state = state; this._state = state
// First of all, try to recover the selected element // First of all, try to recover the selected element
if (Hash.hash.data) { if (Hash.hash.data) {
const hash = Hash.hash.data const hash = Hash.hash.data
this.loadStateFromHash(hash) this.loadStateFromHash(hash)
Hash.hash.setData(hash) // reapply the previous hash Hash.hash.setData(hash) // reapply the previous hash
state.indexedFeatures.featuresById.addCallbackAndRunD(_ => { state.indexedFeatures.featuresById.addCallbackAndRunD((_) => {
let unregister = this.loadSelectedElementFromHash(hash); let unregister = this.loadSelectedElementFromHash(hash)
// once that we have found a matching element, we can be sure the indexedFeaturesource was popuplated and that the job is done // once that we have found a matching element, we can be sure the indexedFeaturesource was popuplated and that the job is done
return unregister return unregister
}) })
} }
// Register a hash change listener to correctly handle the back button // Register a hash change listener to correctly handle the back button
Hash.hash.addCallback(hash => { Hash.hash.addCallback((hash) => {
if (!!hash) { if (!!hash) {
// There is still a hash // There is still a hash
// We _only_ have to (at most) close the overlays in this case // We _only_ have to (at most) close the overlays in this case
const parts = hash.split(";") const parts = hash.split(";")
if(parts.indexOf("background") < 0){ if (parts.indexOf("background") < 0) {
state.guistate.backgroundLayerSelectionIsOpened.setData(false) state.guistate.backgroundLayerSelectionIsOpened.setData(false)
} }
this.loadSelectedElementFromHash(hash) this.loadSelectedElementFromHash(hash)
@ -46,16 +46,14 @@ export default class ThemeViewStateHashActor {
// At last, register callbacks on the state to update the hash when they change. // At last, register callbacks on the state to update the hash when they change.
// Note: these should use 'addCallback', not 'addCallbackAndRun' // Note: these should use 'addCallback', not 'addCallbackAndRun'
state.selectedElement.addCallback(_ => this.setHash()) state.selectedElement.addCallback((_) => this.setHash())
state.guistate.allToggles.forEach(({toggle, submenu}) => { state.guistate.allToggles.forEach(({ toggle, submenu }) => {
submenu?.addCallback(_ => this.setHash()) submenu?.addCallback((_) => this.setHash())
toggle.addCallback(_ => this.setHash()); toggle.addCallback((_) => this.setHash())
}) })
// When all is done, set the hash. This must happen last to give the code above correct info // When all is done, set the hash. This must happen last to give the code above correct info
this.setHash() this.setHash()
} }
/** /**
@ -103,8 +101,7 @@ export default class ThemeViewStateHashActor {
private loadStateFromHash(hash: string) { private loadStateFromHash(hash: string) {
const state = this._state const state = this._state
const parts = hash.split(";") const parts = hash.split(";")
outer: for (const {toggle, name, showOverOthers, submenu} of state.guistate.allToggles) { outer: for (const { toggle, name, showOverOthers, submenu } of state.guistate.allToggles) {
for (const part of parts) { for (const part of parts) {
if (part === name) { if (part === name) {
toggle.setData(true) toggle.setData(true)
@ -125,17 +122,15 @@ export default class ThemeViewStateHashActor {
// If we arrive here, the loop above has not found any match // If we arrive here, the loop above has not found any match
toggle.setData(false) toggle.setData(false)
} }
} }
private setHash() { private setHash() {
const s = this._state const s = this._state
let h = "" let h = ""
for (const {toggle, showOverOthers, name, submenu} of s.guistate.allToggles) { for (const { toggle, showOverOthers, name, submenu } of s.guistate.allToggles) {
if (showOverOthers || !toggle.data) { if (showOverOthers || !toggle.data) {
continue; continue
} }
h = name h = name
if (submenu?.data) { if (submenu?.data) {
@ -147,9 +142,9 @@ export default class ThemeViewStateHashActor {
h = s.selectedElement.data.properties.id h = s.selectedElement.data.properties.id
} }
for (const {toggle, showOverOthers, name, submenu} of s.guistate.allToggles) { for (const { toggle, showOverOthers, name, submenu } of s.guistate.allToggles) {
if (!showOverOthers || !toggle.data) { if (!showOverOthers || !toggle.data) {
continue; continue
} }
if (h) { if (h) {
h += ";" + name h += ";" + name
@ -161,7 +156,6 @@ export default class ThemeViewStateHashActor {
} }
} }
Hash.hash.setData(h) Hash.hash.setData(h)
} }
private back() { private back() {
@ -176,5 +170,4 @@ export default class ThemeViewStateHashActor {
return return
} }
} }
} }

View file

@ -1,4 +1,4 @@
import {Utils} from "../Utils" import { Utils } from "../Utils"
import * as meta from "../package.json" import * as meta from "../package.json"
export type PriviligedLayerType = typeof Constants.priviliged_layers[number] export type PriviligedLayerType = typeof Constants.priviliged_layers[number]
@ -119,7 +119,19 @@ export default class Constants {
/** /**
* These are the values that are allowed to use as 'backdrop' icon for a map pin * These are the values that are allowed to use as 'backdrop' icon for a map pin
*/ */
private static readonly _defaultPinIcons = ["square", "circle", "none", "pin", "person", "plus", "ring", "star", "teardrop", "triangle", "crosshair",] as const private static readonly _defaultPinIcons = [
"square",
"circle",
"none",
"pin",
"person",
"plus",
"ring",
"star",
"teardrop",
"triangle",
"crosshair",
] as const
public static readonly defaultPinIcons: string[] = <any>Constants._defaultPinIcons public static readonly defaultPinIcons: string[] = <any>Constants._defaultPinIcons
private static isRetina(): boolean { private static isRetina(): boolean {

View file

@ -51,7 +51,6 @@ export class Denomination {
return (this._humanSingular ?? this._human).Clone() return (this._humanSingular ?? this._human).Clone()
} }
/** /**
* Create a representation of the given value * Create a representation of the given value
* @param value: the value from OSM * @param value: the value from OSM

View file

@ -1,8 +1,8 @@
import LayerConfig from "./ThemeConfig/LayerConfig" import LayerConfig from "./ThemeConfig/LayerConfig"
import {UIEventSource} from "../Logic/UIEventSource" import { UIEventSource } from "../Logic/UIEventSource"
import UserRelatedState from "../Logic/State/UserRelatedState" import UserRelatedState from "../Logic/State/UserRelatedState"
import {Utils} from "../Utils" import { Utils } from "../Utils"
import {LocalStorageSource} from "../Logic/Web/LocalStorageSource" import { LocalStorageSource } from "../Logic/Web/LocalStorageSource"
export type ThemeViewTabStates = typeof MenuState._themeviewTabs[number] export type ThemeViewTabStates = typeof MenuState._themeviewTabs[number]
export type MenuViewTabStates = typeof MenuState._menuviewTabs[number] export type MenuViewTabStates = typeof MenuState._menuviewTabs[number]
@ -14,8 +14,20 @@ export type MenuViewTabStates = typeof MenuState._menuviewTabs[number]
* Some convenience methods are provided for this as well * Some convenience methods are provided for this as well
*/ */
export class MenuState { export class MenuState {
public static readonly _themeviewTabs = ["intro", "filters", "download", "copyright","share"] as const public static readonly _themeviewTabs = [
public static readonly _menuviewTabs = ["about", "settings", "community", "privacy","advanced"] as const "intro",
"filters",
"download",
"copyright",
"share",
] as const
public static readonly _menuviewTabs = [
"about",
"settings",
"community",
"privacy",
"advanced",
] as const
public readonly themeIsOpened: UIEventSource<boolean> public readonly themeIsOpened: UIEventSource<boolean>
public readonly themeViewTabIndex: UIEventSource<number> public readonly themeViewTabIndex: UIEventSource<number>
public readonly themeViewTab: UIEventSource<ThemeViewTabStates> public readonly themeViewTab: UIEventSource<ThemeViewTabStates>
@ -23,11 +35,13 @@ export class MenuState {
public readonly menuViewTabIndex: UIEventSource<number> public readonly menuViewTabIndex: UIEventSource<number>
public readonly menuViewTab: UIEventSource<MenuViewTabStates> public readonly menuViewTab: UIEventSource<MenuViewTabStates>
public readonly backgroundLayerSelectionIsOpened: UIEventSource<boolean> = new UIEventSource<boolean>(false) public readonly backgroundLayerSelectionIsOpened: UIEventSource<boolean> =
new UIEventSource<boolean>(false)
public readonly allToggles: { public readonly allToggles: {
toggle: UIEventSource<boolean>, name: string, toggle: UIEventSource<boolean>
submenu?: UIEventSource<string>, name: string
submenu?: UIEventSource<string>
showOverOthers?: boolean showOverOthers?: boolean
}[] }[]
@ -81,16 +95,19 @@ export class MenuState {
{ {
toggle: this.menuIsOpened, toggle: this.menuIsOpened,
name: "menu", name: "menu",
submenu: this.menuViewTab submenu: this.menuViewTab,
}, { },
{
toggle: this.themeIsOpened, toggle: this.themeIsOpened,
name: "theme-menu", name: "theme-menu",
submenu: this.themeViewTab submenu: this.themeViewTab,
}, { },
{
toggle: this.backgroundLayerSelectionIsOpened, toggle: this.backgroundLayerSelectionIsOpened,
name: "background", name: "background",
showOverOthers: true showOverOthers: true,
}] },
]
} }
public openFilterView(highlightLayer?: LayerConfig | string) { public openFilterView(highlightLayer?: LayerConfig | string) {
@ -128,10 +145,13 @@ export class MenuState {
* Returns 'true' if at least one menu was opened * Returns 'true' if at least one menu was opened
*/ */
public closeAll(): boolean { public closeAll(): boolean {
const toggles = [this.menuIsOpened, this.themeIsOpened, this.backgroundLayerSelectionIsOpened] const toggles = [
const somethingIsOpen = toggles.some(t => t.data) this.menuIsOpened,
toggles.forEach(t => t.setData(false)) this.themeIsOpened,
this.backgroundLayerSelectionIsOpened,
]
const somethingIsOpen = toggles.some((t) => t.data)
toggles.forEach((t) => t.setData(false))
return somethingIsOpen return somethingIsOpen
} }
} }

View file

@ -1,10 +1,10 @@
import {Feature, Polygon} from "geojson" import { Feature, Polygon } from "geojson"
import * as editorlayerindex from "../assets/editor-layer-index.json" import * as editorlayerindex from "../assets/editor-layer-index.json"
import * as globallayers from "../assets/global-raster-layers.json" import * as globallayers from "../assets/global-raster-layers.json"
import {BBox} from "../Logic/BBox" import { BBox } from "../Logic/BBox"
import {Store, Stores} from "../Logic/UIEventSource" import { Store, Stores } from "../Logic/UIEventSource"
import {GeoOperations} from "../Logic/GeoOperations" import { GeoOperations } from "../Logic/GeoOperations"
import {RasterLayerProperties} from "./RasterLayerProperties" import { RasterLayerProperties } from "./RasterLayerProperties"
export class AvailableRasterLayers { export class AvailableRasterLayers {
public static EditorLayerIndex: (Feature<Polygon, EditorLayerIndexProperties> & public static EditorLayerIndex: (Feature<Polygon, EditorLayerIndexProperties> &
@ -47,8 +47,8 @@ export class AvailableRasterLayers {
type: "vector", type: "vector",
attribution: { attribution: {
text: "Maptiler", text: "Maptiler",
url: "https://www.maptiler.com/copyright/" url: "https://www.maptiler.com/copyright/",
} },
}, },
geometry: BBox.global.asGeometry(), geometry: BBox.global.asGeometry(),
} }
@ -63,8 +63,8 @@ export class AvailableRasterLayers {
type: "vector", type: "vector",
attribution: { attribution: {
text: "Americana", text: "Americana",
url: "https://github.com/ZeLonewolf/openstreetmap-americana/" url: "https://github.com/ZeLonewolf/openstreetmap-americana/",
} },
}, },
geometry: BBox.global.asGeometry(), geometry: BBox.global.asGeometry(),
} }
@ -151,7 +151,14 @@ export interface EditorLayerIndexProperties extends RasterLayerProperties {
* Whether the imagery name should be translated * Whether the imagery name should be translated
*/ */
readonly i18n?: boolean readonly i18n?: boolean
readonly type: "tms" | "wms" | "bing" | "scanex" | "wms_endpoint" | "wmts" | "vector" /* Vector is not actually part of the ELI-spec, we add it for vector layers */ readonly type:
| "tms"
| "wms"
| "bing"
| "scanex"
| "wms_endpoint"
| "wmts"
| "vector" /* Vector is not actually part of the ELI-spec, we add it for vector layers */
/** /**
* A rough categorisation of different types of layers. See https://github.com/osmlab/editor-layer-index/blob/gh-pages/CONTRIBUTING.md#categories for a description of the individual categories. * A rough categorisation of different types of layers. See https://github.com/osmlab/editor-layer-index/blob/gh-pages/CONTRIBUTING.md#categories for a description of the individual categories.
*/ */

View file

@ -297,8 +297,8 @@ export class Fuse<T> extends DesugaringStep<T> {
const step = this.steps[i] const step = this.steps[i]
try { try {
let r = step.convert(json, "While running step " + step.name + ": " + context) let r = step.convert(json, "While running step " + step.name + ": " + context)
if(r.result["tagRenderings"]?.some(tr => tr === undefined)){ if (r.result["tagRenderings"]?.some((tr) => tr === undefined)) {
throw step.name+" introduced an undefined tagRendering" throw step.name + " introduced an undefined tagRendering"
} }
errors.push(...(r.errors ?? [])) errors.push(...(r.errors ?? []))
warnings.push(...(r.warnings ?? [])) warnings.push(...(r.warnings ?? []))

View file

@ -1,9 +1,9 @@
import {Conversion} from "./Conversion" import { Conversion } from "./Conversion"
import LayerConfig from "../LayerConfig" import LayerConfig from "../LayerConfig"
import {LayerConfigJson} from "../Json/LayerConfigJson" import { LayerConfigJson } from "../Json/LayerConfigJson"
import Translations from "../../../UI/i18n/Translations" import Translations from "../../../UI/i18n/Translations"
import PointRenderingConfigJson from "../Json/PointRenderingConfigJson" import PointRenderingConfigJson from "../Json/PointRenderingConfigJson"
import {Translation, TypedTranslation} from "../../../UI/i18n/Translation" import { Translation, TypedTranslation } from "../../../UI/i18n/Translation"
export default class CreateNoteImportLayer extends Conversion<LayerConfigJson, LayerConfigJson> { export default class CreateNoteImportLayer extends Conversion<LayerConfigJson, LayerConfigJson> {
/** /**
@ -175,7 +175,7 @@ export default class CreateNoteImportLayer extends Conversion<LayerConfigJson, L
render: tr(t.nearbyImagesIntro), render: tr(t.nearbyImagesIntro),
}, },
{ {
id:"all_tags", id: "all_tags",
render: "{all_tags()}", render: "{all_tags()}",
metacondition: { metacondition: {
or: [ or: [
@ -184,7 +184,7 @@ export default class CreateNoteImportLayer extends Conversion<LayerConfigJson, L
"mapcomplete-show_debug=yes", "mapcomplete-show_debug=yes",
], ],
}, },
} },
], ],
mapRendering: [ mapRendering: [
{ {

View file

@ -24,8 +24,8 @@ import { TagConfigJson } from "../Json/TagConfigJson"
import PointRenderingConfigJson from "../Json/PointRenderingConfigJson" import PointRenderingConfigJson from "../Json/PointRenderingConfigJson"
import LineRenderingConfigJson from "../Json/LineRenderingConfigJson" import LineRenderingConfigJson from "../Json/LineRenderingConfigJson"
import ValidationUtils from "./ValidationUtils" import ValidationUtils from "./ValidationUtils"
import {RenderingSpecification} from "../../../UI/SpecialVisualization" import { RenderingSpecification } from "../../../UI/SpecialVisualization"
import {QuestionableTagRenderingConfigJson} from "../Json/QuestionableTagRenderingConfigJson" import { QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson"
class ExpandFilter extends DesugaringStep<LayerConfigJson> { class ExpandFilter extends DesugaringStep<LayerConfigJson> {
private static readonly predefinedFilters = ExpandFilter.load_filters() private static readonly predefinedFilters = ExpandFilter.load_filters()
@ -446,11 +446,11 @@ class DetectInline extends DesugaringStep<QuestionableTagRenderingConfigJson> {
information?: string[] information?: string[]
} { } {
if (json.freeform === undefined) { if (json.freeform === undefined) {
return {result: json} return { result: json }
} }
let spec: Record<string, string> let spec: Record<string, string>
if (typeof json.render === "string") { if (typeof json.render === "string") {
spec = {"*": json.render} spec = { "*": json.render }
} else { } else {
spec = <Record<string, string>>json.render spec = <Record<string, string>>json.render
} }
@ -459,7 +459,7 @@ class DetectInline extends DesugaringStep<QuestionableTagRenderingConfigJson> {
if (spec[key].indexOf("<a ") >= 0) { if (spec[key].indexOf("<a ") >= 0) {
// We have a link element, it probably contains something that needs to be substituted... // We have a link element, it probably contains something that needs to be substituted...
// Let's play this safe and not inline it // Let's play this safe and not inline it
return {result: json} return { result: json }
} }
const fullSpecification = SpecialVisualizations.constructSpecification(spec[key]) const fullSpecification = SpecialVisualizations.constructSpecification(spec[key])
if (fullSpecification.length > 1) { if (fullSpecification.length > 1) {
@ -474,12 +474,12 @@ class DetectInline extends DesugaringStep<QuestionableTagRenderingConfigJson> {
} }
json = JSON.parse(JSON.stringify(json)) json = JSON.parse(JSON.stringify(json))
json.freeform.inline = false json.freeform.inline = false
return {result: json, errors} return { result: json, errors }
} }
} }
json = JSON.parse(JSON.stringify(json)) json = JSON.parse(JSON.stringify(json))
json.freeform.inline ??= true json.freeform.inline ??= true
return {result: json, errors} return { result: json, errors }
} }
} }
@ -500,7 +500,7 @@ export class AddQuestionBox extends DesugaringStep<LayerConfigJson> {
json.tagRenderings === undefined || json.tagRenderings === undefined ||
json.tagRenderings.some((tr) => tr["id"] === "leftover-questions") json.tagRenderings.some((tr) => tr["id"] === "leftover-questions")
) { ) {
return {result: json} return { result: json }
} }
json = JSON.parse(JSON.stringify(json)) json = JSON.parse(JSON.stringify(json))
const allSpecials: Exclude<RenderingSpecification, string>[] = [] const allSpecials: Exclude<RenderingSpecification, string>[] = []
@ -619,7 +619,7 @@ export class AddEditingElements extends DesugaringStep<LayerConfigJson> {
if (json.allowSplit && !ValidationUtils.hasSpecialVisualisation(json, "split_button")) { if (json.allowSplit && !ValidationUtils.hasSpecialVisualisation(json, "split_button")) {
json.tagRenderings.push({ json.tagRenderings.push({
id: "split-button", id: "split-button",
render: {"*": "{split_button()}"}, render: { "*": "{split_button()}" },
}) })
delete json.allowSplit delete json.allowSplit
} }
@ -627,13 +627,13 @@ export class AddEditingElements extends DesugaringStep<LayerConfigJson> {
if (json.allowMove && !ValidationUtils.hasSpecialVisualisation(json, "move_button")) { if (json.allowMove && !ValidationUtils.hasSpecialVisualisation(json, "move_button")) {
json.tagRenderings.push({ json.tagRenderings.push({
id: "move-button", id: "move-button",
render: {"*": "{move_button()}"}, render: { "*": "{move_button()}" },
}) })
} }
if (json.deletion && !ValidationUtils.hasSpecialVisualisation(json, "delete_button")) { if (json.deletion && !ValidationUtils.hasSpecialVisualisation(json, "delete_button")) {
json.tagRenderings.push({ json.tagRenderings.push({
id: "delete-button", id: "delete-button",
render: {"*": "{delete_button()}"}, render: { "*": "{delete_button()}" },
}) })
} }
@ -650,7 +650,7 @@ export class AddEditingElements extends DesugaringStep<LayerConfigJson> {
if (!ValidationUtils.hasSpecialVisualisation(json, "all_tags")) { if (!ValidationUtils.hasSpecialVisualisation(json, "all_tags")) {
const trc: TagRenderingConfigJson = { const trc: TagRenderingConfigJson = {
id: "all-tags", id: "all-tags",
render: {"*": "{all_tags()}"}, render: { "*": "{all_tags()}" },
metacondition: { metacondition: {
or: [ or: [
@ -663,7 +663,7 @@ export class AddEditingElements extends DesugaringStep<LayerConfigJson> {
json.tagRenderings?.push(trc) json.tagRenderings?.push(trc)
} }
return {result: json} return { result: json }
} }
} }
@ -1161,31 +1161,37 @@ class PreparePointRendering extends Fuse<PointRenderingConfigJson | LineRenderin
class SetFullNodeDatabase extends DesugaringStep<LayerConfigJson> { class SetFullNodeDatabase extends DesugaringStep<LayerConfigJson> {
constructor() { constructor() {
super("sets the fullNodeDatabase-bit if needed", super(
"sets the fullNodeDatabase-bit if needed",
["fullNodeDatabase"], ["fullNodeDatabase"],
"SetFullNodeDatabase") "SetFullNodeDatabase"
)
} }
convert(json: LayerConfigJson, context: string): { convert(
result: LayerConfigJson; json: LayerConfigJson,
errors?: string[]; context: string
warnings?: string[]; ): {
result: LayerConfigJson
errors?: string[]
warnings?: string[]
information?: string[] information?: string[]
} { } {
const needsSpecial = json.tagRenderings?.some(tr => { const needsSpecial =
json.tagRenderings?.some((tr) => {
if (typeof tr === "string") { if (typeof tr === "string") {
return false return false
} }
const specs = ValidationUtils.getSpecialVisualisations(<TagRenderingConfigJson>tr) const specs = ValidationUtils.getSpecialVisualisations(<TagRenderingConfigJson>tr)
return specs?.some(sp => sp.needsNodeDatabase) return specs?.some((sp) => sp.needsNodeDatabase)
}) ?? false }) ?? false
if (!needsSpecial) { if (!needsSpecial) {
return {result: json} return { result: json }
} }
return { return {
result: {...json, fullNodeDatabase: true}, result: { ...json, fullNodeDatabase: true },
information: ["Layer " + json.id + " needs the fullNodeDatabase"] information: ["Layer " + json.id + " needs the fullNodeDatabase"],
}; }
} }
} }
@ -1203,12 +1209,12 @@ export class AddMiniMap extends DesugaringStep<LayerConfigJson> {
convert(layerConfig: LayerConfigJson, context: string): { result: LayerConfigJson } { convert(layerConfig: LayerConfigJson, context: string): { result: LayerConfigJson } {
if (!layerConfig.tagRenderings || layerConfig.source === "special") { if (!layerConfig.tagRenderings || layerConfig.source === "special") {
return {result: layerConfig} return { result: layerConfig }
} }
const state = this._state const state = this._state
const hasMinimap = ValidationUtils.hasSpecialVisualisation(layerConfig, "minimap") const hasMinimap = ValidationUtils.hasSpecialVisualisation(layerConfig, "minimap")
if (!hasMinimap) { if (!hasMinimap) {
layerConfig = {...layerConfig} layerConfig = { ...layerConfig }
layerConfig.tagRenderings = [...layerConfig.tagRenderings] layerConfig.tagRenderings = [...layerConfig.tagRenderings]
const minimap = state.tagRenderings.get("minimap") const minimap = state.tagRenderings.get("minimap")
if (minimap === undefined) { if (minimap === undefined) {

View file

@ -1,8 +1,8 @@
import {TagRenderingConfigJson} from "../Json/TagRenderingConfigJson" import { TagRenderingConfigJson } from "../Json/TagRenderingConfigJson"
import {Utils} from "../../../Utils" import { Utils } from "../../../Utils"
import SpecialVisualizations from "../../../UI/SpecialVisualizations" import SpecialVisualizations from "../../../UI/SpecialVisualizations"
import {RenderingSpecification, SpecialVisualization} from "../../../UI/SpecialVisualization" import { RenderingSpecification, SpecialVisualization } from "../../../UI/SpecialVisualization"
import {LayerConfigJson} from "../Json/LayerConfigJson" import { LayerConfigJson } from "../Json/LayerConfigJson"
export default class ValidationUtils { export default class ValidationUtils {
public static hasSpecialVisualisation( public static hasSpecialVisualisation(
@ -11,14 +11,15 @@ export default class ValidationUtils {
): boolean { ): boolean {
return ( return (
layer.tagRenderings?.some((tagRendering) => { layer.tagRenderings?.some((tagRendering) => {
if(tagRendering === undefined){ if (tagRendering === undefined) {
return false return false
} }
const spec = ValidationUtils.getSpecialVisualisations(<TagRenderingConfigJson>tagRendering) const spec = ValidationUtils.getSpecialVisualisations(
return spec.some((vis) => vis.funcName === specialVisualisation); <TagRenderingConfigJson>tagRendering
} )
) ?? false return spec.some((vis) => vis.funcName === specialVisualisation)
}) ?? false
) )
} }
@ -44,7 +45,7 @@ export default class ValidationUtils {
const all: RenderingSpecification[] = [] const all: RenderingSpecification[] = []
for (let translation of translations) { for (let translation of translations) {
if (typeof translation == "string") { if (typeof translation == "string") {
translation = {"*": translation} translation = { "*": translation }
} }
for (const key in translation) { for (const key in translation) {

View file

@ -3,9 +3,9 @@ import { TagsFilter } from "../../Logic/Tags/TagsFilter"
import { DeleteConfigJson } from "./Json/DeleteConfigJson" import { DeleteConfigJson } from "./Json/DeleteConfigJson"
import Translations from "../../UI/i18n/Translations" import Translations from "../../UI/i18n/Translations"
import { TagUtils } from "../../Logic/Tags/TagUtils" import { TagUtils } from "../../Logic/Tags/TagUtils"
import TagRenderingConfig from "./TagRenderingConfig"; import TagRenderingConfig from "./TagRenderingConfig"
import {QuestionableTagRenderingConfigJson} from "./Json/QuestionableTagRenderingConfigJson"; import { QuestionableTagRenderingConfigJson } from "./Json/QuestionableTagRenderingConfigJson"
import {TagConfigJson} from "./Json/TagConfigJson"; import { TagConfigJson } from "./Json/TagConfigJson"
export default class DeleteConfig { export default class DeleteConfig {
public static readonly deleteReasonKey = "_delete_reason" public static readonly deleteReasonKey = "_delete_reason"
@ -97,24 +97,24 @@ export default class DeleteConfig {
public constructTagRendering(): TagRenderingConfig { public constructTagRendering(): TagRenderingConfig {
const t = Translations.t.delete const t = Translations.t.delete
const mappings: {if: TagConfigJson, then: Record<string, string>} []= [] const mappings: { if: TagConfigJson; then: Record<string, string> }[] = []
for (const nonDeleteMapping of this.nonDeleteMappings) { for (const nonDeleteMapping of this.nonDeleteMappings) {
mappings.push({ mappings.push({
if: nonDeleteMapping.if, if: nonDeleteMapping.if,
then: nonDeleteMapping.then.translations then: nonDeleteMapping.then.translations,
}) })
} }
for (const deleteReason of this.deleteReasons) { for (const deleteReason of this.deleteReasons) {
mappings.push({ mappings.push({
if: DeleteConfig.deleteReasonKey+ "="+ deleteReason.changesetMessage, if: DeleteConfig.deleteReasonKey + "=" + deleteReason.changesetMessage,
then: deleteReason.explanation.translations then: deleteReason.explanation.translations,
}) })
} }
const config: QuestionableTagRenderingConfigJson = { const config: QuestionableTagRenderingConfigJson = {
question: t.whyDelete.translations, question: t.whyDelete.translations,
mappings mappings,
} }
return new TagRenderingConfig(config) return new TagRenderingConfig(config)
} }

View file

@ -103,7 +103,11 @@ export default class DependencyCalculator {
currentLine = i // Leak the state... currentLine = i // Leak the state...
currentKey = key currentKey = key
try { try {
const func = new Function("feat", "{"+ExtraFunctions.types.join(",")+"}", "return " + code + ";") const func = new Function(
"feat",
"{" + ExtraFunctions.types.join(",") + "}",
"return " + code + ";"
)
const result = func(obj, helpers) const result = func(obj, helpers)
obj.properties[key] = JSON.stringify(result) obj.properties[key] = JSON.stringify(result)
} catch (e) {} } catch (e) {}

View file

@ -31,7 +31,6 @@ export default interface LineRenderingConfigJson {
*/ */
lineCap?: "round" | "square" | "butt" | string | TagRenderingConfigJson lineCap?: "round" | "square" | "butt" | string | TagRenderingConfigJson
/** /**
* The color to fill a polygon with. * The color to fill a polygon with.
* If undefined, this will be slightly more opaque version of the stroke line. * If undefined, this will be slightly more opaque version of the stroke line.

View file

@ -417,7 +417,6 @@ export default class LayerConfig extends WithContextLoader {
) )
} }
this.popupInFloatover = json.popupInFloatover ?? false this.popupInFloatover = json.popupInFloatover ?? false
} }
public defaultIcon(): BaseUIElement | undefined { public defaultIcon(): BaseUIElement | undefined {

View file

@ -1,16 +1,16 @@
import PointRenderingConfigJson from "./Json/PointRenderingConfigJson" import PointRenderingConfigJson from "./Json/PointRenderingConfigJson"
import TagRenderingConfig from "./TagRenderingConfig" import TagRenderingConfig from "./TagRenderingConfig"
import {TagsFilter} from "../../Logic/Tags/TagsFilter" import { TagsFilter } from "../../Logic/Tags/TagsFilter"
import {TagUtils} from "../../Logic/Tags/TagUtils" import { TagUtils } from "../../Logic/Tags/TagUtils"
import {Utils} from "../../Utils" import { Utils } from "../../Utils"
import Svg from "../../Svg" import Svg from "../../Svg"
import WithContextLoader from "./WithContextLoader" import WithContextLoader from "./WithContextLoader"
import {Store} from "../../Logic/UIEventSource" import { Store } from "../../Logic/UIEventSource"
import BaseUIElement from "../../UI/BaseUIElement" import BaseUIElement from "../../UI/BaseUIElement"
import {FixedUiElement} from "../../UI/Base/FixedUiElement" 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"
export default class PointRenderingConfig extends WithContextLoader { export default class PointRenderingConfig extends WithContextLoader {
static readonly allowed_location_codes: ReadonlySet<string> = new Set<string>([ static readonly allowed_location_codes: ReadonlySet<string> = new Set<string>([
@ -83,7 +83,7 @@ export default class PointRenderingConfig extends WithContextLoader {
} }
}) })
const iconPath = this.icon?.GetRenderValue({id: "node/-1"})?.txt const iconPath = this.icon?.GetRenderValue({ id: "node/-1" })?.txt
if (iconPath !== undefined && iconPath.startsWith(Utils.assets_path)) { if (iconPath !== undefined && iconPath.startsWith(Utils.assets_path)) {
const iconKey = iconPath.substr(Utils.assets_path.length) const iconKey = iconPath.substr(Utils.assets_path.length)
if (Svg.All[iconKey] === undefined) { if (Svg.All[iconKey] === undefined) {
@ -168,7 +168,7 @@ export default class PointRenderingConfig extends WithContextLoader {
noFullWidth?: boolean noFullWidth?: boolean
} }
): BaseUIElement { ): BaseUIElement {
tags = tags ?? {id: "node/-1"} tags = tags ?? { id: "node/-1" }
let defaultPin: BaseUIElement = undefined let defaultPin: BaseUIElement = undefined
if (this.label === undefined) { if (this.label === undefined) {
defaultPin = Svg.teardrop_with_hole_green_svg() defaultPin = Svg.teardrop_with_hole_green_svg()

View file

@ -1,20 +1,23 @@
import {Translation, TypedTranslation} from "../../UI/i18n/Translation" import { Translation, TypedTranslation } from "../../UI/i18n/Translation"
import {TagsFilter} from "../../Logic/Tags/TagsFilter" import { TagsFilter } from "../../Logic/Tags/TagsFilter"
import Translations from "../../UI/i18n/Translations" import Translations from "../../UI/i18n/Translations"
import {TagUtils, UploadableTag} from "../../Logic/Tags/TagUtils" import { TagUtils, UploadableTag } from "../../Logic/Tags/TagUtils"
import {And} from "../../Logic/Tags/And" import { And } from "../../Logic/Tags/And"
import {Utils} from "../../Utils" import { Utils } from "../../Utils"
import {Tag} from "../../Logic/Tags/Tag" import { Tag } from "../../Logic/Tags/Tag"
import BaseUIElement from "../../UI/BaseUIElement" import BaseUIElement from "../../UI/BaseUIElement"
import Combine from "../../UI/Base/Combine" import Combine from "../../UI/Base/Combine"
import Title from "../../UI/Base/Title" import Title from "../../UI/Base/Title"
import Link from "../../UI/Base/Link" import Link from "../../UI/Base/Link"
import List from "../../UI/Base/List" import List from "../../UI/Base/List"
import {MappingConfigJson, QuestionableTagRenderingConfigJson,} from "./Json/QuestionableTagRenderingConfigJson" import {
import {FixedUiElement} from "../../UI/Base/FixedUiElement" MappingConfigJson,
import {Paragraph} from "../../UI/Base/Paragraph" QuestionableTagRenderingConfigJson,
} from "./Json/QuestionableTagRenderingConfigJson"
import { FixedUiElement } from "../../UI/Base/FixedUiElement"
import { Paragraph } from "../../UI/Base/Paragraph"
import Svg from "../../Svg" import Svg from "../../Svg"
import Validators, {ValidatorType} from "../../UI/InputElement/Validators"; import Validators, { ValidatorType } from "../../UI/InputElement/Validators"
export interface Mapping { export interface Mapping {
readonly if: UploadableTag readonly if: UploadableTag
@ -118,9 +121,9 @@ export default class TagRenderingConfig {
this.question = Translations.T(json.question, translationKey + ".question") this.question = Translations.T(json.question, translationKey + ".question")
this.questionhint = Translations.T(json.questionHint, translationKey + ".questionHint") this.questionhint = Translations.T(json.questionHint, translationKey + ".questionHint")
this.description = Translations.T(json.description, translationKey + ".description") this.description = Translations.T(json.description, translationKey + ".description")
this.condition = TagUtils.Tag(json.condition ?? {and: []}, `${context}.condition`) this.condition = TagUtils.Tag(json.condition ?? { and: [] }, `${context}.condition`)
this.metacondition = TagUtils.Tag( this.metacondition = TagUtils.Tag(
json.metacondition ?? {and: []}, json.metacondition ?? { and: [] },
`${context}.metacondition` `${context}.metacondition`
) )
if (json.freeform) { if (json.freeform) {
@ -537,11 +540,8 @@ export default class TagRenderingConfig {
} }
} }
if ( if (this.freeform?.key === undefined || tags[this.freeform.key] !== undefined) {
this.freeform?.key === undefined || return { then: this.render }
tags[this.freeform.key] !== undefined
) {
return {then: this.render}
} }
return undefined return undefined
@ -681,13 +681,13 @@ export default class TagRenderingConfig {
) )
} }
const and = TagUtils.FlattenMultiAnswer([...selectedMappings, ...unselectedMappings]) const and = TagUtils.FlattenMultiAnswer([...selectedMappings, ...unselectedMappings])
if(and.and.length === 0){ if (and.and.length === 0) {
return undefined return undefined
} }
return and return and
} else { } else {
// Is at least one mapping shown in the answer? // Is at least one mapping shown in the answer?
const someMappingIsShown = this.mappings.some(m => { const someMappingIsShown = this.mappings.some((m) => {
if (typeof m.hideInAnswer === "boolean") { if (typeof m.hideInAnswer === "boolean") {
return !m.hideInAnswer return !m.hideInAnswer
} }
@ -695,7 +695,9 @@ export default class TagRenderingConfig {
return !isHidden return !isHidden
}) })
// If all mappings are hidden for the current tags, we can safely assume that we should use the freeform key // If all mappings are hidden for the current tags, we can safely assume that we should use the freeform key
const useFreeform = freeformValue !== undefined && (singleSelectedMapping === this.mappings.length || !someMappingIsShown) const useFreeform =
freeformValue !== undefined &&
(singleSelectedMapping === this.mappings.length || !someMappingIsShown)
if (useFreeform) { if (useFreeform) {
return new And([ return new And([
new Tag(this.freeform.key, freeformValue), new Tag(this.freeform.key, freeformValue),
@ -711,7 +713,7 @@ export default class TagRenderingConfig {
freeformValue, freeformValue,
singleSelectedMapping, singleSelectedMapping,
multiSelectedMapping, multiSelectedMapping,
currentProperties currentProperties,
}) })
return undefined return undefined
} }

View file

@ -21,7 +21,10 @@ export default class WithContextLoader {
if (deflt === undefined) { if (deflt === undefined) {
return undefined return undefined
} }
return new TagRenderingConfig(deflt, `${translationContext ?? this._context}.${key}.default value`) return new TagRenderingConfig(
deflt,
`${translationContext ?? this._context}.${key}.default value`
)
} }
if (typeof v === "string") { if (typeof v === "string") {
const shared = SharedTagRenderings.SharedTagRendering.get(v) const shared = SharedTagRenderings.SharedTagRendering.get(v)

View file

@ -1,23 +1,27 @@
import LayoutConfig from "./ThemeConfig/LayoutConfig" import LayoutConfig from "./ThemeConfig/LayoutConfig"
import {SpecialVisualizationState} from "../UI/SpecialVisualization" import { SpecialVisualizationState } from "../UI/SpecialVisualization"
import {Changes} from "../Logic/Osm/Changes" import { Changes } from "../Logic/Osm/Changes"
import {ImmutableStore, Store, UIEventSource} from "../Logic/UIEventSource" import { ImmutableStore, Store, UIEventSource } from "../Logic/UIEventSource"
import {FeatureSource, IndexedFeatureSource, WritableFeatureSource,} from "../Logic/FeatureSource/FeatureSource" import {
import {OsmConnection} from "../Logic/Osm/OsmConnection" FeatureSource,
import {ExportableMap, MapProperties} from "./MapProperties" IndexedFeatureSource,
WritableFeatureSource,
} from "../Logic/FeatureSource/FeatureSource"
import { OsmConnection } from "../Logic/Osm/OsmConnection"
import { ExportableMap, MapProperties } from "./MapProperties"
import LayerState from "../Logic/State/LayerState" import LayerState from "../Logic/State/LayerState"
import {Feature, Point, Polygon} from "geojson" import { Feature, Point, Polygon } from "geojson"
import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource" import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource"
import {Map as MlMap} from "maplibre-gl" import { Map as MlMap } from "maplibre-gl"
import InitialMapPositioning from "../Logic/Actors/InitialMapPositioning" import InitialMapPositioning from "../Logic/Actors/InitialMapPositioning"
import {MapLibreAdaptor} from "../UI/Map/MapLibreAdaptor" import { MapLibreAdaptor } from "../UI/Map/MapLibreAdaptor"
import {GeoLocationState} from "../Logic/State/GeoLocationState" import { GeoLocationState } from "../Logic/State/GeoLocationState"
import FeatureSwitchState from "../Logic/State/FeatureSwitchState" import FeatureSwitchState from "../Logic/State/FeatureSwitchState"
import {QueryParameters} from "../Logic/Web/QueryParameters" import { QueryParameters } from "../Logic/Web/QueryParameters"
import UserRelatedState from "../Logic/State/UserRelatedState" import UserRelatedState from "../Logic/State/UserRelatedState"
import LayerConfig from "./ThemeConfig/LayerConfig" import LayerConfig from "./ThemeConfig/LayerConfig"
import GeoLocationHandler from "../Logic/Actors/GeoLocationHandler" import GeoLocationHandler from "../Logic/Actors/GeoLocationHandler"
import {AvailableRasterLayers, RasterLayerPolygon, RasterLayerUtils} from "./RasterLayers" import { AvailableRasterLayers, RasterLayerPolygon, RasterLayerUtils } from "./RasterLayers"
import LayoutSource from "../Logic/FeatureSource/Sources/LayoutSource" import LayoutSource from "../Logic/FeatureSource/Sources/LayoutSource"
import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource" import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource"
import FeaturePropertiesStore from "../Logic/FeatureSource/Actors/FeaturePropertiesStore" import FeaturePropertiesStore from "../Logic/FeatureSource/Actors/FeaturePropertiesStore"
@ -28,24 +32,24 @@ import TitleHandler from "../Logic/Actors/TitleHandler"
import ChangeToElementsActor from "../Logic/Actors/ChangeToElementsActor" import ChangeToElementsActor from "../Logic/Actors/ChangeToElementsActor"
import PendingChangesUploader from "../Logic/Actors/PendingChangesUploader" import PendingChangesUploader from "../Logic/Actors/PendingChangesUploader"
import SelectedElementTagsUpdater from "../Logic/Actors/SelectedElementTagsUpdater" import SelectedElementTagsUpdater from "../Logic/Actors/SelectedElementTagsUpdater"
import {BBox} from "../Logic/BBox" import { BBox } from "../Logic/BBox"
import Constants from "./Constants" import Constants from "./Constants"
import Hotkeys from "../UI/Base/Hotkeys" import Hotkeys from "../UI/Base/Hotkeys"
import Translations from "../UI/i18n/Translations" import Translations from "../UI/i18n/Translations"
import {GeoIndexedStoreForLayer} from "../Logic/FeatureSource/Actors/GeoIndexedStore" import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore"
import {LastClickFeatureSource} from "../Logic/FeatureSource/Sources/LastClickFeatureSource" import { LastClickFeatureSource } from "../Logic/FeatureSource/Sources/LastClickFeatureSource"
import {MenuState} from "./MenuState" import { MenuState } from "./MenuState"
import MetaTagging from "../Logic/MetaTagging" import MetaTagging from "../Logic/MetaTagging"
import ChangeGeometryApplicator from "../Logic/FeatureSource/Sources/ChangeGeometryApplicator" import ChangeGeometryApplicator from "../Logic/FeatureSource/Sources/ChangeGeometryApplicator"
import {NewGeometryFromChangesFeatureSource} from "../Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource" import { NewGeometryFromChangesFeatureSource } from "../Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource"
import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader" import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader"
import ShowOverlayRasterLayer from "../UI/Map/ShowOverlayRasterLayer" import ShowOverlayRasterLayer from "../UI/Map/ShowOverlayRasterLayer"
import {Utils} from "../Utils" import { Utils } from "../Utils"
import {EliCategory} from "./RasterLayerProperties" import { EliCategory } from "./RasterLayerProperties"
import BackgroundLayerResetter from "../Logic/Actors/BackgroundLayerResetter" import BackgroundLayerResetter from "../Logic/Actors/BackgroundLayerResetter"
import SaveFeatureSourceToLocalStorage from "../Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage" import SaveFeatureSourceToLocalStorage from "../Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage"
import BBoxFeatureSource from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource" import BBoxFeatureSource from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource"
import ThemeViewStateHashActor from "../Logic/Web/ThemeViewStateHashActor"; import ThemeViewStateHashActor from "../Logic/Web/ThemeViewStateHashActor"
/** /**
* *
@ -146,7 +150,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
rasterInfo.defaultState ?? true, rasterInfo.defaultState ?? true,
"Wether or not overlayer layer " + rasterInfo.id + " is shown" "Wether or not overlayer layer " + rasterInfo.id + " is shown"
) )
const state = {isDisplayed} const state = { isDisplayed }
overlayLayerStates.set(rasterInfo.id, state) overlayLayerStates.set(rasterInfo.id, state)
new ShowOverlayRasterLayer(rasterInfo, this.map, this.mapProperties, state) new ShowOverlayRasterLayer(rasterInfo, this.map, this.mapProperties, state)
} }
@ -158,8 +162,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
* A bit tricky, as this is heavily intertwined with the 'changes'-element, which generate a stream of new and changed features too * A bit tricky, as this is heavily intertwined with the 'changes'-element, which generate a stream of new and changed features too
*/ */
if (this.layout.layers.some((l) => l._needsFullNodeDatabase)) {
if (this.layout.layers.some(l => l._needsFullNodeDatabase)) {
this.fullNodeDatabase = new FullNodeDatabaseSource() this.fullNodeDatabase = new FullNodeDatabaseSource()
} }
@ -180,14 +183,14 @@ export default class ThemeViewState implements SpecialVisualizationState {
return empty return empty
} }
currentViewIndex++ currentViewIndex++
return <Feature[]>[bbox.asGeoJson({ return <Feature[]>[
bbox.asGeoJson({
zoom: this.mapProperties.zoom.data, zoom: this.mapProperties.zoom.data,
...this.mapProperties.location.data, ...this.mapProperties.location.data,
id: "current_view" id: "current_view",
} }),
)]; ]
} })
)
) )
this.featuresInView = new BBoxFeatureSource(layoutSource, this.mapProperties.bounds) this.featuresInView = new BBoxFeatureSource(layoutSource, this.mapProperties.bounds)
this.dataIsLoading = layoutSource.isLoading this.dataIsLoading = layoutSource.isLoading
@ -355,7 +358,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
private initHotkeys() { private initHotkeys() {
Hotkeys.RegisterHotkey( Hotkeys.RegisterHotkey(
{nomod: "Escape", onUp: true}, { nomod: "Escape", onUp: true },
Translations.t.hotkeyDocumentation.closeSidebar, Translations.t.hotkeyDocumentation.closeSidebar,
() => { () => {
this.selectedElement.setData(undefined) this.selectedElement.setData(undefined)
@ -376,7 +379,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
) )
Hotkeys.RegisterHotkey( Hotkeys.RegisterHotkey(
{shift: "O"}, { shift: "O" },
Translations.t.hotkeyDocumentation.selectMapnik, Translations.t.hotkeyDocumentation.selectMapnik,
() => { () => {
this.mapProperties.rasterLayer.setData(AvailableRasterLayers.osmCarto) this.mapProperties.rasterLayer.setData(AvailableRasterLayers.osmCarto)
@ -395,17 +398,17 @@ export default class ThemeViewState implements SpecialVisualizationState {
} }
Hotkeys.RegisterHotkey( Hotkeys.RegisterHotkey(
{nomod: "O"}, { nomod: "O" },
Translations.t.hotkeyDocumentation.selectOsmbasedmap, Translations.t.hotkeyDocumentation.selectOsmbasedmap,
() => setLayerCategory("osmbasedmap") () => setLayerCategory("osmbasedmap")
) )
Hotkeys.RegisterHotkey({nomod: "M"}, Translations.t.hotkeyDocumentation.selectMap, () => Hotkeys.RegisterHotkey({ nomod: "M" }, Translations.t.hotkeyDocumentation.selectMap, () =>
setLayerCategory("map") setLayerCategory("map")
) )
Hotkeys.RegisterHotkey( Hotkeys.RegisterHotkey(
{nomod: "P"}, { nomod: "P" },
Translations.t.hotkeyDocumentation.selectAerial, Translations.t.hotkeyDocumentation.selectAerial,
() => setLayerCategory("photo") () => setLayerCategory("photo")
) )
@ -473,10 +476,10 @@ export default class ThemeViewState implements SpecialVisualizationState {
), ),
range: new StaticFeatureSource( range: new StaticFeatureSource(
this.mapProperties.maxbounds.map((bbox) => this.mapProperties.maxbounds.map((bbox) =>
bbox === undefined ? empty : <Feature[]>[bbox.asGeoJson({id: "range"})] bbox === undefined ? empty : <Feature[]>[bbox.asGeoJson({ id: "range" })]
) )
), ),
current_view: this.currentView current_view: this.currentView,
} }
if (this.layout?.lockLocation) { if (this.layout?.lockLocation) {
const bbox = new BBox(this.layout.lockLocation) const bbox = new BBox(this.layout.lockLocation)
@ -487,12 +490,19 @@ export default class ThemeViewState implements SpecialVisualizationState {
this.featureSwitches.featureSwitchIsTesting this.featureSwitches.featureSwitchIsTesting
) )
} }
const currentViewLayer = this.layout.layers.find(l => l.id === "current_view") const currentViewLayer = this.layout.layers.find((l) => l.id === "current_view")
if (currentViewLayer?.tagRenderings?.length > 0) { if (currentViewLayer?.tagRenderings?.length > 0) {
const params = MetaTagging.createExtraFuncParams(this) const params = MetaTagging.createExtraFuncParams(this)
this.featureProperties.trackFeatureSource(specialLayers.current_view) this.featureProperties.trackFeatureSource(specialLayers.current_view)
specialLayers.current_view.features.addCallbackAndRunD(features => { specialLayers.current_view.features.addCallbackAndRunD((features) => {
MetaTagging.addMetatags(features, params, currentViewLayer, this.layout, this.osmObjectDownloader, this.featureProperties) MetaTagging.addMetatags(
features,
params,
currentViewLayer,
this.layout,
this.osmObjectDownloader,
this.featureProperties
)
}) })
} }
@ -537,7 +547,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
}) })
} }
{ {
this.selectedElement.addCallback(selected => { this.selectedElement.addCallback((selected) => {
if (selected === undefined) { if (selected === undefined) {
// We did _unselect_ an item - we always remove the lastclick-object // We did _unselect_ an item - we always remove the lastclick-object
this.lastClickObject.features.setData([]) this.lastClickObject.features.setData([])

View file

@ -123,8 +123,15 @@ export class Unit {
) )
) )
if(json.defaultInput && !applicable.some(denom => denom.canonical.trim() === json.defaultInput)){ if (
throw `${ctx}: no denomination has the specified default denomination. The default denomination is '${json.defaultInput}', but the available denominations are ${applicable.map(denom => denom.canonical).join(", ")}` json.defaultInput &&
!applicable.some((denom) => denom.canonical.trim() === json.defaultInput)
) {
throw `${ctx}: no denomination has the specified default denomination. The default denomination is '${
json.defaultInput
}', but the available denominations are ${applicable
.map((denom) => denom.canonical)
.join(", ")}`
} }
return new Unit(appliesTo, applicable, json.eraseInvalidValues ?? false) return new Unit(appliesTo, applicable, json.eraseInvalidValues ?? false)
} }

View file

@ -1,4 +1,4 @@
import SvelteUIElement from "./UI/Base/SvelteUIElement"; import SvelteUIElement from "./UI/Base/SvelteUIElement"
import StylesheetTestGui from "./UI/StylesheetTestGui.svelte"; import StylesheetTestGui from "./UI/StylesheetTestGui.svelte"
new SvelteUIElement(StylesheetTestGui, {}).AttachTo("main") new SvelteUIElement(StylesheetTestGui, {}).AttachTo("main")

View file

@ -9,8 +9,8 @@ import IndexText from "./BigComponents/IndexText"
import { LoginToggle } from "./Popup/LoginButton" import { LoginToggle } from "./Popup/LoginButton"
import { ImmutableStore } from "../Logic/UIEventSource" import { ImmutableStore } from "../Logic/UIEventSource"
import { OsmConnection } from "../Logic/Osm/OsmConnection" import { OsmConnection } from "../Logic/Osm/OsmConnection"
import {QueryParameters} from "../Logic/Web/QueryParameters"; import { QueryParameters } from "../Logic/Web/QueryParameters"
import {OsmConnectionFeatureSwitches} from "../Logic/State/FeatureSwitchState"; import { OsmConnectionFeatureSwitches } from "../Logic/State/FeatureSwitchState"
export default class AllThemesGui { export default class AllThemesGui {
setup() { setup() {
@ -27,9 +27,10 @@ export default class AllThemesGui {
}) })
const state = new UserRelatedState(osmConnection) const state = new UserRelatedState(osmConnection)
const intro = new Combine([ const intro = new Combine([
new LanguagePicker(Translations.t.index.title.SupportedLanguages(), state.language).SetClass( new LanguagePicker(
"flex absolute top-2 right-3" Translations.t.index.title.SupportedLanguages(),
), state.language
).SetClass("flex absolute top-2 right-3"),
new IndexText(), new IndexText(),
]) ])
new Combine([ new Combine([

View file

@ -3,15 +3,18 @@
* Wrapper around 'subtleButton' with an arrow pointing to the right * Wrapper around 'subtleButton' with an arrow pointing to the right
* See also: NextButton * See also: NextButton
*/ */
import SubtleButton from "./SubtleButton.svelte"; import SubtleButton from "./SubtleButton.svelte"
import {ChevronLeftIcon} from "@rgossiaux/svelte-heroicons/solid"; import { ChevronLeftIcon } from "@rgossiaux/svelte-heroicons/solid"
import {createEventDispatcher} from "svelte"; import { createEventDispatcher } from "svelte"
const dispatch = createEventDispatcher<{ click }>() const dispatch = createEventDispatcher<{ click }>()
export let clss = "" export let clss = ""
</script> </script>
<SubtleButton on:click={() => dispatch("click")} options={{extraClasses:clss+ " flex items-center"}}> <SubtleButton
<ChevronLeftIcon class="w-12 h-12" slot="image"/> on:click={() => dispatch("click")}
<slot slot="message"/> options={{ extraClasses: clss + " flex items-center" }}
>
<ChevronLeftIcon class="w-12 h-12" slot="image" />
<slot slot="message" />
</SubtleButton> </SubtleButton>

View file

@ -1,13 +1,12 @@
<script lang="ts"> <script lang="ts">
import { UIEventSource } from "../../Logic/UIEventSource.js"; import { UIEventSource } from "../../Logic/UIEventSource.js"
/** /**
* For some stupid reason, it is very hard to bind inputs * For some stupid reason, it is very hard to bind inputs
*/ */
export let selected: UIEventSource<boolean>; export let selected: UIEventSource<boolean>
let _c: boolean = selected.data ?? true; let _c: boolean = selected.data ?? true
$: selected.setData(_c) $: selected.setData(_c)
</script> </script>
<input type="checkbox" bind:checked={_c} /> <input type="checkbox" bind:checked={_c} />

View file

@ -3,37 +3,37 @@
* This overlay element will regularly show a hand that swipes over the underlying element. * This overlay element will regularly show a hand that swipes over the underlying element.
* This element will hide as soon as the Store 'hideSignal' receives a change (which is not undefined) * This element will hide as soon as the Store 'hideSignal' receives a change (which is not undefined)
*/ */
import { Store } from "../../Logic/UIEventSource"; import { Store } from "../../Logic/UIEventSource"
import { onDestroy } from "svelte"; import { onDestroy } from "svelte"
let mainElem: HTMLElement; let mainElem: HTMLElement
export let hideSignal: Store<any>; export let hideSignal: Store<any>
function hide(){ function hide() {
mainElem.style.visibility = "hidden"; mainElem.style.visibility = "hidden"
} }
if (hideSignal) { if (hideSignal) {
onDestroy(hideSignal.addCallbackD(() => { onDestroy(
hideSignal.addCallbackD(() => {
console.log("Received hide signal") console.log("Received hide signal")
hide() hide()
return true; return true
})); })
)
} }
$: { $: {
mainElem?.addEventListener("click",_ => hide()) mainElem?.addEventListener("click", (_) => hide())
mainElem?.addEventListener("touchstart",_ => hide()) mainElem?.addEventListener("touchstart", (_) => hide())
} }
</script> </script>
<div bind:this={mainElem} class="absolute bottom-0 right-0 w-full h-full"> <div bind:this={mainElem} class="absolute bottom-0 right-0 w-full h-full">
<div id="hand-container" class="pointer-events-none"> <div id="hand-container" class="pointer-events-none">
<img src="./assets/svg/hand.svg"/> <img src="./assets/svg/hand.svg" />
</div> </div>
</div> </div>
<style> <style>
@keyframes hand-drag-animation { @keyframes hand-drag-animation {
/* This is the animation on the little extra hand on the location input. If fades in, invites the user to interact/drag the map */ /* This is the animation on the little extra hand on the location input. If fades in, invites the user to interact/drag the map */
0% { 0% {
@ -72,7 +72,6 @@ $: {
} }
} }
#hand-container { #hand-container {
position: absolute; position: absolute;
width: 2rem; width: 2rem;
@ -82,5 +81,4 @@ $: {
animation: hand-drag-animation 4s ease-in-out infinite; animation: hand-drag-animation 4s ease-in-out infinite;
transform-origin: 50% 125%; transform-origin: 50% 125%;
} }
</style> </style>

View file

@ -1,15 +1,14 @@
<script lang="ts"> <script lang="ts">
import { UIEventSource } from "../../Logic/UIEventSource.js"; import { UIEventSource } from "../../Logic/UIEventSource.js"
/** /**
* For some stupid reason, it is very hard to bind inputs * For some stupid reason, it is very hard to bind inputs
*/ */
export let value: UIEventSource<number>; export let value: UIEventSource<number>
let i: number = value.data; let i: number = value.data
$: value.setData(i) $: value.setData(i)
</script> </script>
<select bind:value={i} > <select bind:value={i}>
<slot></slot> <slot />
</select> </select>

View file

@ -1,31 +1,36 @@
<script lang="ts"> <script lang="ts">
import {createEventDispatcher} from "svelte"; import { createEventDispatcher } from "svelte"
import {XCircleIcon} from "@rgossiaux/svelte-heroicons/solid"; import { XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
/** /**
* The slotted element will be shown on top, with a lower-opacity border * The slotted element will be shown on top, with a lower-opacity border
*/ */
const dispatch = createEventDispatcher<{ close }>(); const dispatch = createEventDispatcher<{ close }>()
</script> </script>
<div class="absolute top-0 right-0 w-screen h-screen p-4 md:p-6" style="background-color: #00000088"> <div
class="absolute top-0 right-0 w-screen h-screen p-4 md:p-6"
style="background-color: #00000088"
>
<div class="content normal-background"> <div class="content normal-background">
<div class="rounded-xl h-full"> <div class="rounded-xl h-full">
<slot></slot> <slot />
</div> </div>
<slot name="close-button"> <slot name="close-button">
<!-- The close button is placed _after_ the default slot in order to always paint it on top --> <!-- The close button is placed _after_ the default slot in order to always paint it on top -->
<div class="w-8 h-8 absolute right-10 top-10 cursor-pointer" on:click={() => dispatch("close")}> <div
<XCircleIcon/> class="w-8 h-8 absolute right-10 top-10 cursor-pointer"
on:click={() => dispatch("close")}
>
<XCircleIcon />
</div> </div>
</slot> </slot>
</div> </div>
</div> </div>
<style> <style>
.content { .content {
height: calc( 100vh - 2rem ); height: calc(100vh - 2rem);
border-radius: 0.5rem; border-radius: 0.5rem;
overflow-x: auto; overflow-x: auto;
box-shadow: 0 0 1rem #00000088; box-shadow: 0 0 1rem #00000088;

View file

@ -3,11 +3,11 @@
* Given an HTML string, properly shows this * Given an HTML string, properly shows this
*/ */
export let src: string; export let src: string
let htmlElem: HTMLElement; let htmlElem: HTMLElement
$: { $: {
if (htmlElem) { if (htmlElem) {
htmlElem.innerHTML = src; htmlElem.innerHTML = src
} }
} }
@ -15,6 +15,5 @@
</script> </script>
{#if src !== undefined} {#if src !== undefined}
<span bind:this={htmlElem} class={clss}></span> <span bind:this={htmlElem} class={clss} />
{/if} {/if}

View file

@ -1,23 +1,24 @@
<script lang="ts"> <script lang="ts">
import { UIEventSource } from "../../Logic/UIEventSource"; import { UIEventSource } from "../../Logic/UIEventSource"
import { onDestroy } from "svelte"; import { onDestroy } from "svelte"
/** /**
* For some stupid reason, it is very hard to let {#if} work together with UIEventSources, so we wrap then here * For some stupid reason, it is very hard to let {#if} work together with UIEventSources, so we wrap then here
*/ */
export let condition: UIEventSource<boolean>; export let condition: UIEventSource<boolean>
let _c = condition.data; let _c = condition.data
onDestroy(condition.addCallback(c => { onDestroy(
condition.addCallback((c) => {
/* Do _not_ abbreviate this as `.addCallback(c => _c = c)`. This is the same as writing `.addCallback(c => {return _c = c})`, /* Do _not_ abbreviate this as `.addCallback(c => _c = c)`. This is the same as writing `.addCallback(c => {return _c = c})`,
which will _unregister_ the callback if `c = true`! */ which will _unregister_ the callback if `c = true`! */
_c = c; _c = c
return false return false
})) })
)
</script> </script>
{#if _c} {#if _c}
<slot></slot> <slot />
{:else} {:else}
<slot name="else"></slot> <slot name="else" />
{/if} {/if}

View file

@ -1,33 +1,34 @@
<script lang="ts"> <script lang="ts">
import {UIEventSource} from "../../Logic/UIEventSource"; import { UIEventSource } from "../../Logic/UIEventSource"
import {onDestroy} from "svelte"; import { onDestroy } from "svelte"
/** /**
* Functions as 'If', but uses 'display:hidden' instead. * Functions as 'If', but uses 'display:hidden' instead.
*/ */
export let condition: UIEventSource<boolean>; export let condition: UIEventSource<boolean>
let _c = condition.data; let _c = condition.data
let hasBeenShownPositive = false let hasBeenShownPositive = false
let hasBeenShownNegative = false let hasBeenShownNegative = false
onDestroy(condition.addCallbackAndRun(c => { onDestroy(
condition.addCallbackAndRun((c) => {
/* Do _not_ abbreviate this as `.addCallback(c => _c = c)`. This is the same as writing `.addCallback(c => {return _c = c})`, /* Do _not_ abbreviate this as `.addCallback(c => _c = c)`. This is the same as writing `.addCallback(c => {return _c = c})`,
which will _unregister_ the callback if `c = true`! */ which will _unregister_ the callback if `c = true`! */
hasBeenShownPositive = hasBeenShownPositive || c hasBeenShownPositive = hasBeenShownPositive || c
hasBeenShownNegative = hasBeenShownNegative || !c hasBeenShownNegative = hasBeenShownNegative || !c
_c = c; _c = c
return false return false
})) })
)
</script> </script>
{#if hasBeenShownPositive} {#if hasBeenShownPositive}
<span class={_c ? "" : "hidden"}> <span class={_c ? "" : "hidden"}>
<slot/> <slot />
</span> </span>
{/if} {/if}
{#if hasBeenShownNegative} {#if hasBeenShownNegative}
<span class={_c ? "hidden" : ""}> <span class={_c ? "hidden" : ""}>
<slot name="else"/> <slot name="else" />
</span> </span>
{/if} {/if}

View file

@ -1,18 +1,20 @@
<script lang="ts"> <script lang="ts">
import { UIEventSource } from "../../Logic/UIEventSource"; import { UIEventSource } from "../../Logic/UIEventSource"
import { onDestroy } from "svelte"; import { onDestroy } from "svelte"
/** /**
* For some stupid reason, it is very hard to let {#if} work together with UIEventSources, so we wrap then here * For some stupid reason, it is very hard to let {#if} work together with UIEventSources, so we wrap then here
*/ */
export let condition: UIEventSource<boolean>; export let condition: UIEventSource<boolean>
let _c = !condition.data; let _c = !condition.data
onDestroy(condition.addCallback(c => { onDestroy(
_c = !c; condition.addCallback((c) => {
_c = !c
return false return false
})) })
)
</script> </script>
{#if _c} {#if _c}
<slot></slot> <slot />
{/if} {/if}

View file

@ -1,13 +1,13 @@
<script> <script>
import ToSvelte from "./ToSvelte.svelte"; import ToSvelte from "./ToSvelte.svelte"
import Svg from "../../Svg"; import Svg from "../../Svg"
</script> </script>
<div class="pl-2 p-1 flex"> <div class="pl-2 p-1 flex">
<div class="animate-spin self-center w-6 h-6 min-w-6"> <div class="animate-spin self-center w-6 h-6 min-w-6">
<ToSvelte construct={Svg.loading_svg()}></ToSvelte> <ToSvelte construct={Svg.loading_svg()} />
</div> </div>
<div class="ml-2"> <div class="ml-2">
<slot></slot> <slot />
</div> </div>
</div> </div>

View file

@ -1,17 +1,17 @@
<script lang="ts"> <script lang="ts">
import {OsmConnection} from "../../Logic/Osm/OsmConnection"; import { OsmConnection } from "../../Logic/Osm/OsmConnection"
import Translations from "../i18n/Translations.js"; import Translations from "../i18n/Translations.js"
import Tr from "./Tr.svelte"; import Tr from "./Tr.svelte"
import ToSvelte from "./ToSvelte.svelte"; import ToSvelte from "./ToSvelte.svelte"
import Svg from "../../Svg"; import Svg from "../../Svg"
export let osmConnection: OsmConnection export let osmConnection: OsmConnection
export let clss = "" export let clss = ""
</script> </script>
<button class={clss} on:click={() => osmConnection.AttemptLogin()}> <button class={clss} on:click={() => osmConnection.AttemptLogin()}>
<ToSvelte construct={Svg.login_svg().SetClass("w-12 m-1")}/> <ToSvelte construct={Svg.login_svg().SetClass("w-12 m-1")} />
<slot name="message"> <slot name="message">
<Tr t={Translations.t.general.loginWithOpenStreetMap}/> <Tr t={Translations.t.general.loginWithOpenStreetMap} />
</slot> </slot>
</button> </button>

View file

@ -1,47 +1,45 @@
<script lang="ts"> <script lang="ts">
import Loading from "./Loading.svelte"; import Loading from "./Loading.svelte"
import type { OsmServiceState } from "../../Logic/Osm/OsmConnection"; import type { OsmServiceState } from "../../Logic/Osm/OsmConnection"
import { Translation } from "../i18n/Translation"; import { Translation } from "../i18n/Translation"
import Translations from "../i18n/Translations"; import Translations from "../i18n/Translations"
import Tr from "./Tr.svelte"; import Tr from "./Tr.svelte"
import {OsmConnection} from "../../Logic/Osm/OsmConnection"; import { OsmConnection } from "../../Logic/Osm/OsmConnection"
import {ImmutableStore, UIEventSource} from "../../Logic/UIEventSource"; import { ImmutableStore, UIEventSource } from "../../Logic/UIEventSource"
export let state: {osmConnection: OsmConnection, featureSwitches?: { featureSwitchUserbadge?: UIEventSource<boolean>}}; export let state: {
osmConnection: OsmConnection
featureSwitches?: { featureSwitchUserbadge?: UIEventSource<boolean> }
}
/** /**
* If set, 'loading' will act as if we are already logged in. * If set, 'loading' will act as if we are already logged in.
*/ */
export let ignoreLoading: boolean = false export let ignoreLoading: boolean = false
let loadingStatus = state.osmConnection.loadingStatus; let loadingStatus = state.osmConnection.loadingStatus
let badge = state.featureSwitches?.featureSwitchUserbadge ?? new ImmutableStore(true); let badge = state.featureSwitches?.featureSwitchUserbadge ?? new ImmutableStore(true)
const t = Translations.t.general; const t = Translations.t.general
const offlineModes: Partial<Record<OsmServiceState, Translation>> = { const offlineModes: Partial<Record<OsmServiceState, Translation>> = {
offline: t.loginFailedOfflineMode, offline: t.loginFailedOfflineMode,
unreachable: t.loginFailedUnreachableMode, unreachable: t.loginFailedUnreachableMode,
unknown: t.loginFailedUnreachableMode, unknown: t.loginFailedUnreachableMode,
readonly: t.loginFailedReadonlyMode readonly: t.loginFailedReadonlyMode,
}; }
const apiState = state.osmConnection.apiIsOnline; const apiState = state.osmConnection.apiIsOnline
</script> </script>
{#if $badge} {#if $badge}
{#if !ignoreLoading && $loadingStatus === "loading"} {#if !ignoreLoading && $loadingStatus === "loading"}
<slot name="loading"> <slot name="loading">
<Loading></Loading> <Loading />
</slot> </slot>
{:else if $loadingStatus === "error"} {:else if $loadingStatus === "error"}
<div class="flex items-center alert max-w-64"> <div class="flex items-center alert max-w-64">
<img src="./assets/svg/invalid.svg" class="w-8 h-8 m-2 shrink-0"> <img src="./assets/svg/invalid.svg" class="w-8 h-8 m-2 shrink-0" />
<Tr t={offlineModes[$apiState]} /> <Tr t={offlineModes[$apiState]} />
</div> </div>
{:else if $loadingStatus === "logged-in"} {:else if $loadingStatus === "logged-in"}
<slot></slot> <slot />
{:else if $loadingStatus === "not-attempted"} {:else if $loadingStatus === "not-attempted"}
<slot name="not-logged-in"> <slot name="not-logged-in" />
</slot>
{/if} {/if}
{/if} {/if}

View file

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte"
/** /**
* A round button with an icon and possible a small text, which hovers above the map * A round button with an icon and possible a small text, which hovers above the map
@ -8,7 +8,9 @@
export let cls = "" export let cls = ""
</script> </script>
<button
<button on:click={e => dispatch("click", e)} class={"rounded-full h-fit w-fit m-0.5 md:m-1 p-0.5 sm:p-1 pointer-events-auto "+cls} > on:click={(e) => dispatch("click", e)}
<slot/> class={"rounded-full h-fit w-fit m-0.5 md:m-1 p-0.5 sm:p-1 pointer-events-auto " + cls}
>
<slot />
</button> </button>

View file

@ -1,20 +1,26 @@
<script lang="ts"> <script lang="ts">
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte"
import { XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"; import { XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
/** /**
* The slotted element will be shown on the right side * The slotted element will be shown on the right side
*/ */
const dispatch = createEventDispatcher<{ close }>(); const dispatch = createEventDispatcher<{ close }>()
</script> </script>
<div class="absolute top-0 right-0 h-screen overflow-auto w-full md:w-6/12 lg:w-5/12 xl:w-4/12 drop-shadow-2xl" style="max-width: 100vw; max-height: 100vh"> <div
class="absolute top-0 right-0 h-screen overflow-auto w-full md:w-6/12 lg:w-5/12 xl:w-4/12 drop-shadow-2xl"
style="max-width: 100vw; max-height: 100vh"
>
<div class="flex flex-col m-0 normal-background"> <div class="flex flex-col m-0 normal-background">
<slot name="close-button"> <slot name="close-button">
<div class="w-8 h-8 absolute right-10 top-10 cursor-pointer" on:click={() => dispatch("close")}> <div
class="w-8 h-8 absolute right-10 top-10 cursor-pointer"
on:click={() => dispatch("close")}
>
<XCircleIcon /> <XCircleIcon />
</div> </div>
</slot> </slot>
<slot></slot> <slot />
</div> </div>
</div> </div>

View file

@ -3,19 +3,22 @@
* Wrapper around 'subtleButton' with an arrow pointing to the right * Wrapper around 'subtleButton' with an arrow pointing to the right
* See also: BackButton * See also: BackButton
*/ */
import SubtleButton from "./SubtleButton.svelte"; import SubtleButton from "./SubtleButton.svelte"
import {ChevronRightIcon} from "@rgossiaux/svelte-heroicons/solid"; import { ChevronRightIcon } from "@rgossiaux/svelte-heroicons/solid"
import {createEventDispatcher} from "svelte"; import { createEventDispatcher } from "svelte"
const dispatch = createEventDispatcher<{ click }>() const dispatch = createEventDispatcher<{ click }>()
export let clss : string= "" export let clss: string = ""
</script> </script>
<SubtleButton on:click={() => dispatch("click")} options={{extraClasses: clss+" flex items-center"}}> <SubtleButton
<slot name="image" slot="image"/> on:click={() => dispatch("click")}
options={{ extraClasses: clss + " flex items-center" }}
>
<slot name="image" slot="image" />
<div class="w-full flex justify-between items-center" slot="message"> <div class="w-full flex justify-between items-center" slot="message">
<slot/> <slot />
<ChevronRightIcon class="w-12 h-12"/> <ChevronRightIcon class="w-12 h-12" />
</div> </div>
</SubtleButton> </SubtleButton>

View file

@ -1,17 +1,16 @@
<script lang="ts"> <script lang="ts">
import ToSvelte from "./ToSvelte.svelte"
import Svg from "../../Svg"
import ToSvelte from "./ToSvelte.svelte"; export let generateShareData: () => {
import Svg from "../../Svg";
export let generateShareData: () => {
text: string text: string
title: string title: string
url: string url: string
} }
function share(){ function share() {
if (!navigator.share) { if (!navigator.share) {
console.log("web share not supported") console.log("web share not supported")
return; return
} }
navigator navigator
.share(generateShareData()) .share(generateShareData())
@ -21,13 +20,11 @@ function share(){
.catch((err) => { .catch((err) => {
console.log(`Couldn't share because of`, err.message) console.log(`Couldn't share because of`, err.message)
}) })
} }
</script> </script>
<button on:click={share} class="secondary w-8 h-8 m-0 p-0"> <button on:click={share} class="secondary w-8 h-8 m-0 p-0">
<slot name="content"> <slot name="content">
<ToSvelte construct={Svg.share_svg().SetClass("w-7 h-7 p-1")}/> <ToSvelte construct={Svg.share_svg().SetClass("w-7 h-7 p-1")} />
</slot> </slot>
</button> </button>

View file

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import {createEventDispatcher} from "svelte"; import { createEventDispatcher } from "svelte"
import BaseUIElement from "../BaseUIElement"; import BaseUIElement from "../BaseUIElement"
import Img from "./Img"; import Img from "./Img"
export let imageUrl: string | BaseUIElement = undefined export let imageUrl: string | BaseUIElement = undefined
export let message: string | BaseUIElement = undefined export let message: string | BaseUIElement = undefined
@ -10,22 +10,22 @@
extraClasses?: string extraClasses?: string
} = {} } = {}
let imgClasses = "block justify-center shrink-0 mr-4 " + (options?.imgSize ?? "h-11 w-11"); let imgClasses = "block justify-center shrink-0 mr-4 " + (options?.imgSize ?? "h-11 w-11")
const dispatch = createEventDispatcher<{click}>() const dispatch = createEventDispatcher<{ click }>()
</script> </script>
<button <button
class={(options.extraClasses??"") + ' secondary no-image-background'} class={(options.extraClasses ?? "") + " secondary no-image-background"}
target={options?.newTab ? "_blank" : ""} target={options?.newTab ? "_blank" : ""}
on:click={(e) => dispatch("click", e)} on:click={(e) => dispatch("click", e)}
> >
<slot name="image"> <slot name="image">
{#if imageUrl !== undefined} {#if imageUrl !== undefined}
{#if typeof imageUrl === "string"} {#if typeof imageUrl === "string"}
<Img src={imageUrl} class={imgClasses}></Img> <Img src={imageUrl} class={imgClasses} />
{/if} {/if}
{/if} {/if}
</slot> </slot>
<slot name="message"/> <slot name="message" />
</button> </button>

View file

@ -5,10 +5,10 @@ import { VariableUiElement } from "./VariableUIElement"
import Lazy from "./Lazy" import Lazy from "./Lazy"
import Loading from "./Loading" import Loading from "./Loading"
import SvelteUIElement from "./SvelteUIElement" import SvelteUIElement from "./SvelteUIElement"
import SubtleLink from "./SubtleLink.svelte"; import SubtleLink from "./SubtleLink.svelte"
import Translations from "../i18n/Translations"; import Translations from "../i18n/Translations"
import Combine from "./Combine"; import Combine from "./Combine"
import Img from "./Img"; import Img from "./Img"
/** /**
* @deprecated * @deprecated
@ -40,26 +40,28 @@ export class SubtleButton extends UIElement {
} }
protected InnerRender(): string | BaseUIElement { protected InnerRender(): string | BaseUIElement {
if(this.options.url !== undefined){ if (this.options.url !== undefined) {
return new SvelteUIElement(SubtleLink, {href: this.options.url, newTab: this.options.newTab}) return new SvelteUIElement(SubtleLink, {
href: this.options.url,
newTab: this.options.newTab,
})
} }
const classes = "button"; const classes = "button"
const message = Translations.W(this.message)?.SetClass("block overflow-ellipsis no-images flex-shrink"); const message = Translations.W(this.message)?.SetClass(
let img; "block overflow-ellipsis no-images flex-shrink"
const imgClasses = "block justify-center flex-none mr-4 " + (this.options?.imgSize ?? "h-11 w-11") )
let img
const imgClasses =
"block justify-center flex-none mr-4 " + (this.options?.imgSize ?? "h-11 w-11")
if ((this.imageUrl ?? "") === "") { if ((this.imageUrl ?? "") === "") {
img = undefined; img = undefined
} else if (typeof (this.imageUrl) === "string") { } else if (typeof this.imageUrl === "string") {
img = new Img(this.imageUrl)?.SetClass(imgClasses) img = new Img(this.imageUrl)?.SetClass(imgClasses)
} else { } else {
img = this.imageUrl?.SetClass(imgClasses); img = this.imageUrl?.SetClass(imgClasses)
} }
const button = new Combine([ const button = new Combine([img, message]).SetClass("flex items-center group w-full")
img,
message
]).SetClass("flex items-center group w-full")
this.SetClass(classes) this.SetClass(classes)
return button return button

View file

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import {onMount} from "svelte"; import { onMount } from "svelte"
import BaseUIElement from "../BaseUIElement"; import BaseUIElement from "../BaseUIElement"
import Img from "./Img"; import Img from "./Img"
export let imageUrl: string | BaseUIElement = undefined export let imageUrl: string | BaseUIElement = undefined
export let href: string export let href: string
@ -11,9 +11,8 @@
// extraClasses?: string // extraClasses?: string
} = {} } = {}
let imgElem: HTMLElement
let imgElem: HTMLElement; let imgClasses = "block justify-center shrink-0 mr-4 " + (options?.imgSize ?? "h-11 w-11")
let imgClasses = "block justify-center shrink-0 mr-4 " + (options?.imgSize ?? "h-11 w-11");
onMount(() => { onMount(() => {
// Image // Image
@ -27,24 +26,23 @@
} }
if (img) imgElem.replaceWith(img.ConstructElement()) if (img) imgElem.replaceWith(img.ConstructElement())
} }
}) })
</script> </script>
<a <a
class={(options.extraClasses??"") + ' button text-ellipsis'} class={(options.extraClasses ?? "") + " button text-ellipsis"}
{href} {href}
target={(newTab ? "_blank" : undefined) } target={newTab ? "_blank" : undefined}
> >
<slot name="image"> <slot name="image">
{#if imageUrl !== undefined} {#if imageUrl !== undefined}
{#if typeof imageUrl === "string"} {#if typeof imageUrl === "string"}
<Img src={imageUrl} class={imgClasses}></Img> <Img src={imageUrl} class={imgClasses} />
{:else } {:else}
<template bind:this={imgElem} /> <template bind:this={imgElem} />
{/if} {/if}
{/if} {/if}
</slot> </slot>
<slot/> <slot />
</a> </a>

View file

@ -3,12 +3,12 @@
* Thin wrapper around 'TabGroup' which binds the state * Thin wrapper around 'TabGroup' which binds the state
*/ */
import {Tab, TabGroup, TabList, TabPanel, TabPanels} from "@rgossiaux/svelte-headlessui"; import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@rgossiaux/svelte-headlessui"
import {UIEventSource} from "../../Logic/UIEventSource"; import { UIEventSource } from "../../Logic/UIEventSource"
export let tab: UIEventSource<number>; export let tab: UIEventSource<number>
let tabElements: HTMLElement[] = []; let tabElements: HTMLElement[] = []
$: tabElements[$tab]?.click(); $: tabElements[$tab]?.click()
$: { $: {
if (tabElements[tab.data]) { if (tabElements[tab.data]) {
window.setTimeout(() => tabElements[tab.data].click(), 50) window.setTimeout(() => tabElements[tab.data].click(), 50)
@ -17,52 +17,56 @@
</script> </script>
<div class="tabbedgroup w-full h-full"> <div class="tabbedgroup w-full h-full">
<TabGroup class="h-full w-full flex flex-col" defaultIndex={1} <TabGroup
on:change={(e) =>{if(e.detail >= 0){tab.setData( e.detail); }} }> class="h-full w-full flex flex-col"
defaultIndex={1}
on:change={(e) => {
if (e.detail >= 0) {
tab.setData(e.detail)
}
}}
>
<div class="interactive flex items-center justify-between sticky top-0"> <div class="interactive flex items-center justify-between sticky top-0">
<TabList class="flex flex-wrap"> <TabList class="flex flex-wrap">
{#if $$slots.title1} {#if $$slots.title1}
<Tab class={({selected}) => "tab "+(selected ? "primary" : "")}> <Tab class={({ selected }) => "tab " + (selected ? "primary" : "")}>
<div bind:this={tabElements[0]} class="flex"> <div bind:this={tabElements[0]} class="flex">
<slot name="title0"> <slot name="title0">Tab 0</slot>
Tab 0
</slot>
</div> </div>
</Tab> </Tab>
{/if} {/if}
{#if $$slots.title1} {#if $$slots.title1}
<Tab class={({selected}) => "tab "+(selected ? "primary" : "")}> <Tab class={({ selected }) => "tab " + (selected ? "primary" : "")}>
<div bind:this={tabElements[1]} class="flex"> <div bind:this={tabElements[1]} class="flex">
<slot name="title1"/> <slot name="title1" />
</div> </div>
</Tab> </Tab>
{/if} {/if}
{#if $$slots.title2} {#if $$slots.title2}
<Tab class={({selected}) => "tab "+(selected ? "primary" : "")}> <Tab class={({ selected }) => "tab " + (selected ? "primary" : "")}>
<div bind:this={tabElements[2]} class="flex"> <div bind:this={tabElements[2]} class="flex">
<slot name="title2"/> <slot name="title2" />
</div> </div>
</Tab> </Tab>
{/if} {/if}
{#if $$slots.title3} {#if $$slots.title3}
<Tab class={({selected}) => "tab "+(selected ? "primary" : "")}> <Tab class={({ selected }) => "tab " + (selected ? "primary" : "")}>
<div bind:this={tabElements[3]} class="flex"> <div bind:this={tabElements[3]} class="flex">
<slot name="title3"/> <slot name="title3" />
</div> </div>
</Tab> </Tab>
{/if} {/if}
{#if $$slots.title4} {#if $$slots.title4}
<Tab class={({selected}) => "tab "+(selected ? "primary" : "")}> <Tab class={({ selected }) => "tab " + (selected ? "primary" : "")}>
<div bind:this={tabElements[4]} class="flex"> <div bind:this={tabElements[4]} class="flex">
<slot name="title4"/> <slot name="title4" />
</div> </div>
</Tab> </Tab>
{/if} {/if}
</TabList> </TabList>
<slot name="post-tablist"/> <slot name="post-tablist" />
</div> </div>
<div class="overflow-y-auto normal-background"> <div class="overflow-y-auto normal-background">
<TabPanels defaultIndex={$tab}> <TabPanels defaultIndex={$tab}>
<TabPanel> <TabPanel>
<slot name="content0"> <slot name="content0">
@ -70,21 +74,22 @@
</slot> </slot>
</TabPanel> </TabPanel>
<TabPanel> <TabPanel>
<slot name="content1"/> <slot name="content1" />
</TabPanel> </TabPanel>
<TabPanel> <TabPanel>
<slot name="content2"/> <slot name="content2" />
</TabPanel> </TabPanel>
<TabPanel> <TabPanel>
<slot name="content3"/> <slot name="content3" />
</TabPanel> </TabPanel>
<TabPanel> <TabPanel>
<slot name="content4"/> <slot name="content4" />
</TabPanel> </TabPanel>
</TabPanels> </TabPanels>
</div> </div>
</TabGroup> </TabGroup>
</div> </div>
<style> <style>
.tabbedgroup { .tabbedgroup {
max-height: 100vh; max-height: 100vh;

View file

@ -78,7 +78,7 @@ export default class Table extends BaseUIElement {
for (let j = 0; j < row.length; j++) { for (let j = 0; j < row.length; j++) {
try { try {
let elem = row[j] let elem = row[j]
if(elem?.ConstructElement === undefined){ if (elem?.ConstructElement === undefined) {
continue continue
} }
const htmlElem = elem?.ConstructElement() const htmlElem = elem?.ConstructElement()

View file

@ -1,23 +1,21 @@
<script lang="ts"> <script lang="ts">
import BaseUIElement from "../BaseUIElement.js"; import BaseUIElement from "../BaseUIElement.js"
import { onDestroy, onMount } from "svelte"; import { onDestroy, onMount } from "svelte"
export let construct: BaseUIElement | (() => BaseUIElement); export let construct: BaseUIElement | (() => BaseUIElement)
let elem: HTMLElement; let elem: HTMLElement
let html: HTMLElement; let html: HTMLElement
onMount(() => { onMount(() => {
const uiElem = typeof construct === "function" const uiElem = typeof construct === "function" ? construct() : construct
? construct() : construct; html = uiElem?.ConstructElement()
html =uiElem?.ConstructElement();
if (html !== undefined) { if (html !== undefined) {
elem.replaceWith(html); elem.replaceWith(html)
} }
}); })
onDestroy(() => { onDestroy(() => {
html?.remove(); html?.remove()
}); })
</script> </script>
<span bind:this={elem} /> <span bind:this={elem} />

View file

@ -2,36 +2,37 @@
/** /**
* Properly renders a translation * Properly renders a translation
*/ */
import { Translation } from "../i18n/Translation"; import { Translation } from "../i18n/Translation"
import { onDestroy } from "svelte"; import { onDestroy } from "svelte"
import Locale from "../i18n/Locale"; import Locale from "../i18n/Locale"
import { Utils } from "../../Utils"; import { Utils } from "../../Utils"
import FromHtml from "./FromHtml.svelte"; import FromHtml from "./FromHtml.svelte"
import WeblateLink from "./WeblateLink.svelte"; import WeblateLink from "./WeblateLink.svelte"
export let t: Translation; export let t: Translation
export let cls: string = "" export let cls: string = ""
export let tags: Record<string, string> | undefined = undefined; export let tags: Record<string, string> | undefined = undefined
// Text for the current language // Text for the current language
let txt: string | undefined; let txt: string | undefined
$: onDestroy(Locale.language.addCallbackAndRunD(l => { $: onDestroy(
const translation = t?.textFor(l); Locale.language.addCallbackAndRunD((l) => {
const translation = t?.textFor(l)
if (translation === undefined) { if (translation === undefined) {
return; return
} }
if (tags) { if (tags) {
txt = Utils.SubstituteKeys(txt, tags); txt = Utils.SubstituteKeys(txt, tags)
} else { } else {
txt = translation; txt = translation
} }
})); })
)
</script> </script>
{#if t} {#if t}
<span class={cls}> <span class={cls}>
<FromHtml src={txt}></FromHtml> <FromHtml src={txt} />
<WeblateLink context={t.context}></WeblateLink> <WeblateLink context={t.context} />
</span> </span>
{/if} {/if}

View file

@ -1,25 +1,32 @@
<script lang="ts"> <script lang="ts">
import Locale from "../i18n/Locale"; import Locale from "../i18n/Locale"
import LinkToWeblate from "./LinkToWeblate"; import LinkToWeblate from "./LinkToWeblate"
/** /**
* Shows a small icon which will open up weblate; a contributor can translate the item for 'context' there * Shows a small icon which will open up weblate; a contributor can translate the item for 'context' there
*/ */
export let context : string export let context: string
let linkToWeblate = Locale.showLinkToWeblate; let linkToWeblate = Locale.showLinkToWeblate
let linkOnMobile = Locale.showLinkOnMobile; let linkOnMobile = Locale.showLinkOnMobile
let language = Locale.language; let language = Locale.language
</script> </script>
{#if !!context && context.indexOf(":") > 0} {#if !!context && context.indexOf(":") > 0}
{#if $linkOnMobile} {#if $linkOnMobile}
<a href={LinkToWeblate.hrefToWeblate($language, context)} target="_blank" class="mx-1 weblate-link"> <a
href={LinkToWeblate.hrefToWeblate($language, context)}
target="_blank"
class="mx-1 weblate-link"
>
<img src="./assets/svg/translate.svg" class="font-gray" /> <img src="./assets/svg/translate.svg" class="font-gray" />
</a> </a>
{:else if $linkToWeblate} {:else if $linkToWeblate}
<a href={LinkToWeblate.hrefToWeblate($language, context)} class="weblate-link hidden-on-mobile mx-1" target="_blank"> <a
href={LinkToWeblate.hrefToWeblate($language, context)}
class="weblate-link hidden-on-mobile mx-1"
target="_blank"
>
<img src="./assets/svg/translate.svg" class="font-gray inline-block" /> <img src="./assets/svg/translate.svg" class="font-gray inline-block" />
</a> </a>
{/if} {/if}

View file

@ -1,13 +1,13 @@
<script lang="ts"> <script lang="ts">
import {Store, UIEventSource} from "../../Logic/UIEventSource"; import { Store, UIEventSource } from "../../Logic/UIEventSource"
import type {RasterLayerPolygon} from "../../Models/RasterLayers"; import type { RasterLayerPolygon } from "../../Models/RasterLayers"
import {AvailableRasterLayers} from "../../Models/RasterLayers"; import { AvailableRasterLayers } from "../../Models/RasterLayers"
import {createEventDispatcher, onDestroy} from "svelte"; import { createEventDispatcher, onDestroy } from "svelte"
import Svg from "../../Svg"; import Svg from "../../Svg"
import {Map as MlMap} from "maplibre-gl" import { Map as MlMap } from "maplibre-gl"
import type {MapProperties} from "../../Models/MapProperties"; import type { MapProperties } from "../../Models/MapProperties"
import OverlayMap from "../Map/OverlayMap.svelte"; import OverlayMap from "../Map/OverlayMap.svelte"
import RasterLayerPicker from "../Map/RasterLayerPicker.svelte"; import RasterLayerPicker from "../Map/RasterLayerPicker.svelte"
export let mapproperties: MapProperties export let mapproperties: MapProperties
export let normalMap: UIEventSource<MlMap> export let normalMap: UIEventSource<MlMap>
@ -18,9 +18,11 @@
let rasterLayer: UIEventSource<RasterLayerPolygon | undefined> = mapproperties.rasterLayer let rasterLayer: UIEventSource<RasterLayerPolygon | undefined> = mapproperties.rasterLayer
let name = rasterLayer.data?.properties?.name let name = rasterLayer.data?.properties?.name
let icon = Svg.satellite_svg() let icon = Svg.satellite_svg()
onDestroy(rasterLayer.addCallback(polygon => { onDestroy(
rasterLayer.addCallback((polygon) => {
name = polygon.properties?.name name = polygon.properties?.name
})) })
)
/** /**
* The layers that this component can offer as a choice. * The layers that this component can offer as a choice.
*/ */
@ -36,8 +38,8 @@
const available = availableRasterLayers.data const available = availableRasterLayers.data
const current = rasterLayer.data const current = rasterLayer.data
const defaultLayer = AvailableRasterLayers.maplibre const defaultLayer = AvailableRasterLayers.maplibre
const firstOther = available.find(l => l !== defaultLayer) const firstOther = available.find((l) => l !== defaultLayer)
const secondOther = available.find(l => l !== defaultLayer && l !== firstOther) const secondOther = available.find((l) => l !== defaultLayer && l !== firstOther)
raster0.setData(firstOther === current ? defaultLayer : firstOther) raster0.setData(firstOther === current ? defaultLayer : firstOther)
raster1.setData(secondOther === current ? defaultLayer : secondOther) raster1.setData(secondOther === current ? defaultLayer : secondOther)
} }
@ -46,35 +48,41 @@
onDestroy(mapproperties.rasterLayer.addCallbackAndRunD(updatedAltLayer)) onDestroy(mapproperties.rasterLayer.addCallbackAndRunD(updatedAltLayer))
onDestroy(availableRasterLayers.addCallbackAndRunD(updatedAltLayer)) onDestroy(availableRasterLayers.addCallbackAndRunD(updatedAltLayer))
function use(rasterLayer: UIEventSource<RasterLayerPolygon>): (() => void) { function use(rasterLayer: UIEventSource<RasterLayerPolygon>): () => void {
return () => { return () => {
currentLayer = undefined currentLayer = undefined
mapproperties.rasterLayer.setData(rasterLayer.data) mapproperties.rasterLayer.setData(rasterLayer.data)
} }
} }
const dispatch = createEventDispatcher<{ copyright_clicked }>() const dispatch = createEventDispatcher<{ copyright_clicked }>()
</script> </script>
<div class="flex items-end opacity-50 hover:opacity-100"> <div class="flex items-end opacity-50 hover:opacity-100">
<div class="flex flex-col md:flex-row"> <div class="flex flex-col md:flex-row">
<button class="w-16 h-12 md:w-16 md:h-16 overflow-hidden m-0 p-0" <button class="w-16 h-12 md:w-16 md:h-16 overflow-hidden m-0 p-0" on:click={use(raster0)}>
on:click={use(raster0)}> <OverlayMap
<OverlayMap placedOverMap={normalMap} placedOverMapProperties={mapproperties} rasterLayer={raster0}/> placedOverMap={normalMap}
placedOverMapProperties={mapproperties}
rasterLayer={raster0}
/>
</button> </button>
<button class="w-16 h-12 md:w-16 md:h-16 overflow-hidden m-0 p-0 " on:click={use(raster1)}> <button class="w-16 h-12 md:w-16 md:h-16 overflow-hidden m-0 p-0 " on:click={use(raster1)}>
<OverlayMap placedOverMap={normalMap} placedOverMapProperties={mapproperties} rasterLayer={raster1}/> <OverlayMap
placedOverMap={normalMap}
placedOverMapProperties={mapproperties}
rasterLayer={raster1}
/>
</button> </button>
</div> </div>
<div class="text-sm flex flex-col gap-y-1 h-fit ml-1"> <div class="text-sm flex flex-col gap-y-1 h-fit ml-1">
<div class="low-interaction rounded p-1 w-64"> <div class="low-interaction rounded p-1 w-64">
<RasterLayerPicker availableLayers={availableRasterLayers} value={mapproperties.rasterLayer}></RasterLayerPicker> <RasterLayerPicker
availableLayers={availableRasterLayers}
value={mapproperties.rasterLayer}
/>
</div> </div>
<button class="small" on:click={() => dispatch("copyright_clicked")}> <button class="small" on:click={() => dispatch("copyright_clicked")}>© OpenStreetMap</button>
© OpenStreetMap
</button>
</div> </div>
</div> </div>

View file

@ -1,25 +1,25 @@
import Combine from "../Base/Combine" import Combine from "../Base/Combine"
import Translations from "../i18n/Translations" import Translations from "../i18n/Translations"
import {Store} from "../../Logic/UIEventSource" import { Store } from "../../Logic/UIEventSource"
import {FixedUiElement} from "../Base/FixedUiElement" import { FixedUiElement } from "../Base/FixedUiElement"
import licenses from "../../assets/generated/license_info.json" import licenses from "../../assets/generated/license_info.json"
import SmallLicense from "../../Models/smallLicense" import SmallLicense from "../../Models/smallLicense"
import {Utils} from "../../Utils" import { Utils } from "../../Utils"
import Link from "../Base/Link" import Link from "../Base/Link"
import {VariableUiElement} from "../Base/VariableUIElement" import { VariableUiElement } from "../Base/VariableUIElement"
import contributors from "../../assets/contributors.json" import contributors from "../../assets/contributors.json"
import translators from "../../assets/translators.json" import translators from "../../assets/translators.json"
import BaseUIElement from "../BaseUIElement" import BaseUIElement from "../BaseUIElement"
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
import Title from "../Base/Title" import Title from "../Base/Title"
import {BBox} from "../../Logic/BBox" import { BBox } from "../../Logic/BBox"
import {OsmConnection} from "../../Logic/Osm/OsmConnection" import { OsmConnection } from "../../Logic/Osm/OsmConnection"
import Constants from "../../Models/Constants" import Constants from "../../Models/Constants"
import ContributorCount from "../../Logic/ContributorCount" import ContributorCount from "../../Logic/ContributorCount"
import Img from "../Base/Img" import Img from "../Base/Img"
import {TypedTranslation} from "../i18n/Translation" import { TypedTranslation } from "../i18n/Translation"
import GeoIndexedStore from "../../Logic/FeatureSource/Actors/GeoIndexedStore" import GeoIndexedStore from "../../Logic/FeatureSource/Actors/GeoIndexedStore"
import {RasterLayerPolygon} from "../../Models/RasterLayers"; import { RasterLayerPolygon } from "../../Models/RasterLayers"
/** /**
* The attribution panel in the theme menu. * The attribution panel in the theme menu.
@ -29,7 +29,10 @@ export default class CopyrightPanel extends Combine {
constructor(state: { constructor(state: {
layout: LayoutConfig layout: LayoutConfig
mapProperties: { readonly bounds: Store<BBox>, readonly rasterLayer: Store<RasterLayerPolygon> } mapProperties: {
readonly bounds: Store<BBox>
readonly rasterLayer: Store<RasterLayerPolygon>
}
osmConnection: OsmConnection osmConnection: OsmConnection
dataIsLoading: Store<boolean> dataIsLoading: Store<boolean>
perLayer: ReadonlyMap<string, GeoIndexedStore> perLayer: ReadonlyMap<string, GeoIndexedStore>
@ -90,27 +93,34 @@ export default class CopyrightPanel extends Combine {
new Title(t.attributionTitle), new Title(t.attributionTitle),
t.attributionContent, t.attributionContent,
new VariableUiElement(state.mapProperties.rasterLayer.mapD(layer => { new VariableUiElement(
state.mapProperties.rasterLayer.mapD((layer) => {
const props = layer.properties const props = layer.properties
const attrUrl = props.attribution?.url const attrUrl = props.attribution?.url
const attrText = props.attribution?.text const attrText = props.attribution?.text
let bgAttr: BaseUIElement | string = undefined let bgAttr: BaseUIElement | string = undefined
if(attrText && attrUrl){ if (attrText && attrUrl) {
bgAttr = "<a href='"+attrUrl+"' target='_blank'>"+attrText+"</a>" bgAttr =
}else if(attrUrl){ "<a href='" + attrUrl + "' target='_blank'>" + attrText + "</a>"
} else if (attrUrl) {
bgAttr = attrUrl bgAttr = attrUrl
}else{ } else {
bgAttr = attrText bgAttr = attrText
} }
if(bgAttr){ if (bgAttr) {
return Translations.t.general.attribution.attributionBackgroundLayerWithCopyright.Subs({ return Translations.t.general.attribution.attributionBackgroundLayerWithCopyright.Subs(
{
name: props.name, name: props.name,
copyright: bgAttr copyright: bgAttr,
})
} }
return Translations.t.general.attribution.attributionBackgroundLayer.Subs(props) )
})), }
return Translations.t.general.attribution.attributionBackgroundLayer.Subs(
props
)
})
),
maintainer, maintainer,
dataContributors, dataContributors,

View file

@ -1,21 +1,21 @@
import {UIElement} from "../UIElement" import { UIElement } from "../UIElement"
import BaseUIElement from "../BaseUIElement" import BaseUIElement from "../BaseUIElement"
import {Store} from "../../Logic/UIEventSource" import { Store } from "../../Logic/UIEventSource"
import ExtraLinkConfig from "../../Models/ThemeConfig/ExtraLinkConfig" import ExtraLinkConfig from "../../Models/ThemeConfig/ExtraLinkConfig"
import Img from "../Base/Img" import Img from "../Base/Img"
import {SubtleButton} from "../Base/SubtleButton" import { SubtleButton } from "../Base/SubtleButton"
import Toggle from "../Input/Toggle" import Toggle from "../Input/Toggle"
import Locale from "../i18n/Locale" import Locale from "../i18n/Locale"
import {Utils} from "../../Utils" import { Utils } from "../../Utils"
import Svg from "../../Svg" import Svg from "../../Svg"
import Translations from "../i18n/Translations" import Translations from "../i18n/Translations"
import {Translation} from "../i18n/Translation" import { Translation } from "../i18n/Translation"
interface ExtraLinkButtonState { interface ExtraLinkButtonState {
layout: { id: string; title: Translation } layout: { id: string; title: Translation }
featureSwitches: { featureSwitchWelcomeMessage: Store<boolean> }, featureSwitches: { featureSwitchWelcomeMessage: Store<boolean> }
mapProperties: { mapProperties: {
location: Store<{ lon: number, lat: number }>; location: Store<{ lon: number; lat: number }>
zoom: Store<number> zoom: Store<number>
} }
} }
@ -23,10 +23,7 @@ export default class ExtraLinkButton extends UIElement {
private readonly _config: ExtraLinkConfig private readonly _config: ExtraLinkConfig
private readonly state: ExtraLinkButtonState private readonly state: ExtraLinkButtonState
constructor( constructor(state: ExtraLinkButtonState, config: ExtraLinkConfig) {
state: ExtraLinkButtonState,
config: ExtraLinkConfig
) {
super() super()
this.state = state this.state = state
this._config = config this._config = config
@ -51,7 +48,8 @@ export default class ExtraLinkButton extends UIElement {
let link: BaseUIElement let link: BaseUIElement
const theme = this.state.layout?.id ?? "" const theme = this.state.layout?.id ?? ""
const basepath = window.location.host const basepath = window.location.host
const href = this.state.mapProperties.location.map((loc) => { const href = this.state.mapProperties.location.map(
(loc) => {
const subs = { const subs = {
...loc, ...loc,
theme: theme, theme: theme,
@ -59,7 +57,9 @@ export default class ExtraLinkButton extends UIElement {
language: Locale.language.data, language: Locale.language.data,
} }
return Utils.SubstituteKeys(c.href, subs) return Utils.SubstituteKeys(c.href, subs)
}, [this.state.mapProperties.zoom]) },
[this.state.mapProperties.zoom]
)
let img: BaseUIElement = Svg.pop_out_svg() let img: BaseUIElement = Svg.pop_out_svg()
if (c.icon !== undefined) { if (c.icon !== undefined) {
@ -81,11 +81,19 @@ export default class ExtraLinkButton extends UIElement {
}) })
if (c.requirements?.has("no-welcome-message")) { if (c.requirements?.has("no-welcome-message")) {
link = new Toggle(undefined, link, this.state.featureSwitches.featureSwitchWelcomeMessage) link = new Toggle(
undefined,
link,
this.state.featureSwitches.featureSwitchWelcomeMessage
)
} }
if (c.requirements?.has("welcome-message")) { if (c.requirements?.has("welcome-message")) {
link = new Toggle(link, undefined, this.state.featureSwitches.featureSwitchWelcomeMessage) link = new Toggle(
link,
undefined,
this.state.featureSwitches.featureSwitchWelcomeMessage
)
} }
return link return link

View file

@ -1,107 +1,110 @@
<script lang="ts">/** <script lang="ts">
/**
* The FilterView shows the various options to enable/disable a single layer or to only show a subset of the data. * The FilterView shows the various options to enable/disable a single layer or to only show a subset of the data.
*/ */
import type FilteredLayer from "../../Models/FilteredLayer"; import type FilteredLayer from "../../Models/FilteredLayer"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import ToSvelte from "../Base/ToSvelte.svelte"; import ToSvelte from "../Base/ToSvelte.svelte"
import Checkbox from "../Base/Checkbox.svelte"; import Checkbox from "../Base/Checkbox.svelte"
import FilterConfig from "../../Models/ThemeConfig/FilterConfig"; import FilterConfig from "../../Models/ThemeConfig/FilterConfig"
import type {Writable} from "svelte/store"; import type { Writable } from "svelte/store"
import If from "../Base/If.svelte"; import If from "../Base/If.svelte"
import Dropdown from "../Base/Dropdown.svelte"; import Dropdown from "../Base/Dropdown.svelte"
import {onDestroy} from "svelte"; import { onDestroy } from "svelte"
import {ImmutableStore, Store} from "../../Logic/UIEventSource"; import { ImmutableStore, Store } from "../../Logic/UIEventSource"
import FilterviewWithFields from "./FilterviewWithFields.svelte"; import FilterviewWithFields from "./FilterviewWithFields.svelte"
import Tr from "../Base/Tr.svelte"; import Tr from "../Base/Tr.svelte"
import Translations from "../i18n/Translations"; import Translations from "../i18n/Translations"
export let filteredLayer: FilteredLayer; export let filteredLayer: FilteredLayer
export let highlightedLayer: Store<string | undefined> = new ImmutableStore(undefined); export let highlightedLayer: Store<string | undefined> = new ImmutableStore(undefined)
export let zoomlevel: Store<number> = new ImmutableStore(22); export let zoomlevel: Store<number> = new ImmutableStore(22)
let layer: LayerConfig = filteredLayer.layerDef; let layer: LayerConfig = filteredLayer.layerDef
let isDisplayed: Store<boolean> = filteredLayer.isDisplayed; let isDisplayed: Store<boolean> = filteredLayer.isDisplayed
/** /**
* Gets a UIEventSource as boolean for the given option, to be used with a checkbox * Gets a UIEventSource as boolean for the given option, to be used with a checkbox
*/ */
function getBooleanStateFor(option: FilterConfig): Writable<boolean> { function getBooleanStateFor(option: FilterConfig): Writable<boolean> {
const state = filteredLayer.appliedFilters.get(option.id); const state = filteredLayer.appliedFilters.get(option.id)
return state.sync(f => f === 0, [], (b) => b ? 0 : undefined); return state.sync(
} (f) => f === 0,
[],
(b) => (b ? 0 : undefined)
)
}
/** /**
* Gets a UIEventSource as number for the given option, to be used with a dropdown or radiobutton * Gets a UIEventSource as number for the given option, to be used with a dropdown or radiobutton
*/ */
function getStateFor(option: FilterConfig): Writable<number> { function getStateFor(option: FilterConfig): Writable<number> {
return filteredLayer.appliedFilters.get(option.id); return filteredLayer.appliedFilters.get(option.id)
} }
let mainElem: HTMLElement; let mainElem: HTMLElement
$: onDestroy( $: onDestroy(
highlightedLayer.addCallbackAndRun(highlightedLayer => { highlightedLayer.addCallbackAndRun((highlightedLayer) => {
if (highlightedLayer === filteredLayer.layerDef.id) { if (highlightedLayer === filteredLayer.layerDef.id) {
mainElem?.classList?.add("glowing-shadow"); mainElem?.classList?.add("glowing-shadow")
} else { } else {
mainElem?.classList?.remove("glowing-shadow"); mainElem?.classList?.remove("glowing-shadow")
} }
}) })
); )
</script> </script>
{#if filteredLayer.layerDef.name} {#if filteredLayer.layerDef.name}
<div bind:this={mainElem} class="mb-1.5"> <div bind:this={mainElem} class="mb-1.5">
<label class="flex gap-1 no-image-background"> <label class="flex gap-1 no-image-background">
<Checkbox selected={isDisplayed}/> <Checkbox selected={isDisplayed} />
<If condition={filteredLayer.isDisplayed}> <If condition={filteredLayer.isDisplayed}>
<ToSvelte <ToSvelte
construct={() => layer.defaultIcon()?.SetClass("block h-6 w-6 no-image-background")}></ToSvelte> construct={() => layer.defaultIcon()?.SetClass("block h-6 w-6 no-image-background")}
<ToSvelte slot="else" />
construct={() => layer.defaultIcon()?.SetClass("block h-6 w-6 no-image-background opacity-50")}></ToSvelte> <ToSvelte
slot="else"
construct={() =>
layer.defaultIcon()?.SetClass("block h-6 w-6 no-image-background opacity-50")}
/>
</If> </If>
{filteredLayer.layerDef.name} {filteredLayer.layerDef.name}
{#if $zoomlevel < layer.minzoom} {#if $zoomlevel < layer.minzoom}
<span class="alert"> <span class="alert">
<Tr t={Translations.t.general.layerSelection.zoomInToSeeThisLayer}/> <Tr t={Translations.t.general.layerSelection.zoomInToSeeThisLayer} />
</span> </span>
{/if} {/if}
</label> </label>
{#if $isDisplayed && filteredLayer.layerDef.filters?.length > 0} {#if $isDisplayed && filteredLayer.layerDef.filters?.length > 0}
<div id="subfilters" class="flex flex-col gap-y-1 ml-4"> <div id="subfilters" class="flex flex-col gap-y-1 ml-4">
{#each filteredLayer.layerDef.filters as filter} {#each filteredLayer.layerDef.filters as filter}
<div> <div>
<!-- There are three (and a half) modes of filters: a single checkbox, a radio button/dropdown or with searchable fields --> <!-- There are three (and a half) modes of filters: a single checkbox, a radio button/dropdown or with searchable fields -->
{#if filter.options.length === 1 && filter.options[0].fields.length === 0} {#if filter.options.length === 1 && filter.options[0].fields.length === 0}
<label> <label>
<Checkbox selected={getBooleanStateFor(filter)}/> <Checkbox selected={getBooleanStateFor(filter)} />
{filter.options[0].question} {filter.options[0].question}
</label> </label>
{/if} {/if}
{#if filter.options.length === 1 && filter.options[0].fields.length > 0} {#if filter.options.length === 1 && filter.options[0].fields.length > 0}
<FilterviewWithFields id={filter.id} filteredLayer={filteredLayer} <FilterviewWithFields id={filter.id} {filteredLayer} option={filter.options[0]} />
option={filter.options[0]}></FilterviewWithFields>
{/if} {/if}
{#if filter.options.length > 1} {#if filter.options.length > 1}
<Dropdown value={getStateFor(filter)}> <Dropdown value={getStateFor(filter)}>
{#each filter.options as option, i} {#each filter.options as option, i}
<option value={i}> <option value={i}>
{ option.question} {option.question}
</option> </option>
{/each} {/each}
</Dropdown> </Dropdown>
{/if} {/if}
</div> </div>
{/each} {/each}
</div> </div>
{/if} {/if}
</div> </div>
{/if} {/if}

View file

@ -1,49 +1,50 @@
<script lang="ts"> <script lang="ts">
import FilteredLayer from "../../Models/FilteredLayer"; import FilteredLayer from "../../Models/FilteredLayer"
import type {FilterConfigOption} from "../../Models/ThemeConfig/FilterConfig"; import type { FilterConfigOption } from "../../Models/ThemeConfig/FilterConfig"
import Locale from "../i18n/Locale"; import Locale from "../i18n/Locale"
import ValidatedInput from "../InputElement/ValidatedInput.svelte"; import ValidatedInput from "../InputElement/ValidatedInput.svelte"
import {UIEventSource} from "../../Logic/UIEventSource"; import { UIEventSource } from "../../Logic/UIEventSource"
import {onDestroy} from "svelte"; import { onDestroy } from "svelte"
import {Utils} from "../../Utils"; import { Utils } from "../../Utils"
export let filteredLayer: FilteredLayer; export let filteredLayer: FilteredLayer
export let option: FilterConfigOption; export let option: FilterConfigOption
export let id: string; export let id: string
let parts: ({ message: string } | { subs: string })[]; let parts: ({ message: string } | { subs: string })[]
let language = Locale.language; let language = Locale.language
$: { $: {
const template = option.question.textFor($language) const template = option.question.textFor($language)
parts = Utils.splitIntoSubstitutionParts(template) parts = Utils.splitIntoSubstitutionParts(template)
} }
let fieldValues: Record<string, UIEventSource<string>> = {}; let fieldValues: Record<string, UIEventSource<string>> = {}
let fieldTypes: Record<string, string> = {}; let fieldTypes: Record<string, string> = {}
let appliedFilter = <UIEventSource<string>>filteredLayer.appliedFilters.get(id); let appliedFilter = <UIEventSource<string>>filteredLayer.appliedFilters.get(id)
let initialState: Record<string, string> = JSON.parse(appliedFilter?.data ?? "{}"); let initialState: Record<string, string> = JSON.parse(appliedFilter?.data ?? "{}")
function setFields() { function setFields() {
const properties: Record<string, string> = {}; const properties: Record<string, string> = {}
for (const key in fieldValues) { for (const key in fieldValues) {
const v = fieldValues[key].data; const v = fieldValues[key].data
if (v === undefined) { if (v === undefined) {
properties[key] = undefined; properties[key] = undefined
} else { } else {
properties[key] = v; properties[key] = v
} }
} }
appliedFilter?.setData(FilteredLayer.fieldsToString(properties)); appliedFilter?.setData(FilteredLayer.fieldsToString(properties))
} }
for (const field of option.fields) { for (const field of option.fields) {
// A bit of cheating: the 'parts' will have '}' suffixed for fields // A bit of cheating: the 'parts' will have '}' suffixed for fields
const src = new UIEventSource<string>(initialState[field.name] ?? ""); const src = new UIEventSource<string>(initialState[field.name] ?? "")
fieldTypes[field.name] = field.type; fieldTypes[field.name] = field.type
fieldValues[field.name] = src; fieldValues[field.name] = src
onDestroy(src.stabilized(200).addCallback(() => { onDestroy(
setFields(); src.stabilized(200).addCallback(() => {
})); setFields()
})
)
} }
</script> </script>
<div> <div>
@ -51,7 +52,7 @@
{#if part.subs} {#if part.subs}
<!-- This is a field! --> <!-- This is a field! -->
<span class="mx-1"> <span class="mx-1">
<ValidatedInput value={fieldValues[part.subs]} type={fieldTypes[part.subs]}/> <ValidatedInput value={fieldValues[part.subs]} type={fieldTypes[part.subs]} />
</span> </span>
{:else} {:else}
{part.message} {part.message}

View file

@ -1,119 +1,116 @@
<script lang="ts"> <script lang="ts">
import { UIEventSource } from "../../Logic/UIEventSource"
import type { Feature } from "geojson"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import ToSvelte from "../Base/ToSvelte.svelte"
import Svg from "../../Svg.js"
import Translations from "../i18n/Translations"
import Loading from "../Base/Loading.svelte"
import Hotkeys from "../Base/Hotkeys"
import { Geocoding } from "../../Logic/Osm/Geocoding"
import { BBox } from "../../Logic/BBox"
import { GeoIndexedStoreForLayer } from "../../Logic/FeatureSource/Actors/GeoIndexedStore"
import { createEventDispatcher, onDestroy } from "svelte"
import {UIEventSource} from "../../Logic/UIEventSource"; export let perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer> | undefined = undefined
import type {Feature} from "geojson"; export let bounds: UIEventSource<BBox>
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; export let selectedElement: UIEventSource<Feature> | undefined = undefined
import ToSvelte from "../Base/ToSvelte.svelte"; export let selectedLayer: UIEventSource<LayerConfig> | undefined = undefined
import Svg from "../../Svg.js";
import Translations from "../i18n/Translations";
import Loading from "../Base/Loading.svelte";
import Hotkeys from "../Base/Hotkeys";
import {Geocoding} from "../../Logic/Osm/Geocoding";
import {BBox} from "../../Logic/BBox";
import {GeoIndexedStoreForLayer} from "../../Logic/FeatureSource/Actors/GeoIndexedStore";
import {createEventDispatcher, onDestroy} from "svelte";
export let perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer> | undefined = undefined;
export let bounds: UIEventSource<BBox>;
export let selectedElement: UIEventSource<Feature> | undefined = undefined;
export let selectedLayer: UIEventSource<LayerConfig> | undefined = undefined;
export let clearAfterView: boolean = true export let clearAfterView: boolean = true
let searchContents: string = "" let searchContents: string = ""
export let triggerSearch: UIEventSource<any> = new UIEventSource<any>(undefined) export let triggerSearch: UIEventSource<any> = new UIEventSource<any>(undefined)
onDestroy(triggerSearch.addCallback(_ => { onDestroy(
triggerSearch.addCallback((_) => {
performSearch() performSearch()
})) })
)
let isRunning: boolean = false; let isRunning: boolean = false
let inputElement: HTMLInputElement; let inputElement: HTMLInputElement
let feedback: string = undefined; let feedback: string = undefined
Hotkeys.RegisterHotkey( Hotkeys.RegisterHotkey({ ctrl: "F" }, Translations.t.hotkeyDocumentation.selectSearch, () => {
{ctrl: "F"}, inputElement?.focus()
Translations.t.hotkeyDocumentation.selectSearch, inputElement?.select()
() => { })
inputElement?.focus();
inputElement?.select();
}
);
const dispatch = createEventDispatcher<{ searchCompleted, searchIsValid: boolean }>() const dispatch = createEventDispatcher<{ searchCompleted; searchIsValid: boolean }>()
$: { $: {
if (!searchContents?.trim()) { if (!searchContents?.trim()) {
dispatch("searchIsValid", false) dispatch("searchIsValid", false)
}else{ } else {
dispatch("searchIsValid", true) dispatch("searchIsValid", true)
} }
} }
async function performSearch() { async function performSearch() {
try { try {
isRunning = true; isRunning = true
searchContents = searchContents?.trim() ?? ""; searchContents = searchContents?.trim() ?? ""
if (searchContents === "") { if (searchContents === "") {
return; return
} }
const result = await Geocoding.Search(searchContents, bounds.data); const result = await Geocoding.Search(searchContents, bounds.data)
if (result.length == 0) { if (result.length == 0) {
feedback = Translations.t.general.search.nothing.txt; feedback = Translations.t.general.search.nothing.txt
return; return
} }
const poi = result[0]; const poi = result[0]
const [lat0, lat1, lon0, lon1] = poi.boundingbox; const [lat0, lat1, lon0, lon1] = poi.boundingbox
bounds.set(new BBox([[lon0, lat0], [lon1, lat1]]).pad(0.01)); bounds.set(
new BBox([
[lon0, lat0],
[lon1, lat1],
]).pad(0.01)
)
if (perLayer !== undefined) { if (perLayer !== undefined) {
const id = poi.osm_type + "/" + poi.osm_id; const id = poi.osm_type + "/" + poi.osm_id
const layers = Array.from(perLayer?.values() ?? []); const layers = Array.from(perLayer?.values() ?? [])
for (const layer of layers) { for (const layer of layers) {
const found = layer.features.data.find(f => f.properties.id === id); const found = layer.features.data.find((f) => f.properties.id === id)
selectedElement?.setData(found); selectedElement?.setData(found)
selectedLayer?.setData(layer.layer.layerDef); selectedLayer?.setData(layer.layer.layerDef)
} }
} }
if(clearAfterView){ if (clearAfterView) {
searchContents = "" searchContents = ""
} }
dispatch("searchIsValid", false) dispatch("searchIsValid", false)
dispatch("searchCompleted") dispatch("searchCompleted")
} catch (e) { } catch (e) {
console.error(e); console.error(e)
feedback = Translations.t.general.search.error.txt; feedback = Translations.t.general.search.error.txt
} finally { } finally {
isRunning = false; isRunning = false
} }
} }
</script> </script>
<div class="flex normal-background rounded-full pl-2 justify-between"> <div class="flex normal-background rounded-full pl-2 justify-between">
<form class="w-full"> <form class="w-full">
{#if isRunning} {#if isRunning}
<Loading>{Translations.t.general.search.searching}</Loading> <Loading>{Translations.t.general.search.searching}</Loading>
{:else if feedback !== undefined} {:else if feedback !== undefined}
<div class="alert" on:click={() => feedback = undefined}> <div class="alert" on:click={() => (feedback = undefined)}>
{feedback} {feedback}
</div> </div>
{:else } {:else}
<input <input
type="search" type="search"
class="w-full" class="w-full"
bind:this={inputElement} bind:this={inputElement}
on:keypress={keypr => keypr.key === "Enter" ? performSearch() : undefined} on:keypress={(keypr) => (keypr.key === "Enter" ? performSearch() : undefined)}
bind:value={searchContents} bind:value={searchContents}
placeholder={Translations.t.general.search.search}> placeholder={Translations.t.general.search.search}
/>
{/if} {/if}
</form> </form>
<div class="w-6 h-6 self-end" on:click={performSearch}> <div class="w-6 h-6 self-end" on:click={performSearch}>
<ToSvelte construct={Svg.search_svg}></ToSvelte> <ToSvelte construct={Svg.search_svg} />
</div> </div>
</div> </div>

View file

@ -1,21 +1,20 @@
<script lang="ts"> <script lang="ts">
import {OsmConnection} from "../../Logic/Osm/OsmConnection" import { OsmConnection } from "../../Logic/Osm/OsmConnection"
import {UIEventSource} from "../../Logic/UIEventSource" import { UIEventSource } from "../../Logic/UIEventSource"
import * as themeOverview from "../../assets/generated/theme_overview.json" import * as themeOverview from "../../assets/generated/theme_overview.json"
import {Utils} from "../../Utils" import { Utils } from "../../Utils"
import ThemesList from "./ThemesList.svelte" import ThemesList from "./ThemesList.svelte"
import Translations from "../i18n/Translations" import Translations from "../i18n/Translations"
import {LayoutInformation} from "../../Models/ThemeConfig/LayoutConfig" import { LayoutInformation } from "../../Models/ThemeConfig/LayoutConfig"
import LoginToggle from "../Base/LoginToggle.svelte"; import LoginToggle from "../Base/LoginToggle.svelte"
export let search: UIEventSource<string> export let search: UIEventSource<string>
export let state: { osmConnection: OsmConnection } export let state: { osmConnection: OsmConnection }
export let onMainScreen: boolean = true export let onMainScreen: boolean = true
const prefix = "mapcomplete-hidden-theme-" const prefix = "mapcomplete-hidden-theme-"
const hiddenThemes: LayoutInformation[] = (themeOverview["default"] ?? themeOverview)?.filter( const hiddenThemes: LayoutInformation[] =
(layout) => layout.hideFromOverview (themeOverview["default"] ?? themeOverview)?.filter((layout) => layout.hideFromOverview) ?? []
) ?? []
const userPreferences = state.osmConnection.preferencesHandler.preferences const userPreferences = state.osmConnection.preferencesHandler.preferences
const t = Translations.t.general.morescreen const t = Translations.t.general.morescreen
@ -30,7 +29,6 @@
</script> </script>
<LoginToggle {state}> <LoginToggle {state}>
<ThemesList <ThemesList
hideThemes={false} hideThemes={false}
isCustom={false} isCustom={false}
@ -49,5 +47,4 @@
</p> </p>
</svelte:fragment> </svelte:fragment>
</ThemesList> </ThemesList>
</LoginToggle> </LoginToggle>

View file

@ -2,28 +2,31 @@
/** /**
* Shows a 'floorSelector' and maps the selected floor onto a global filter * Shows a 'floorSelector' and maps the selected floor onto a global filter
*/ */
import LayerState from "../../Logic/State/LayerState"; import LayerState from "../../Logic/State/LayerState"
import FloorSelector from "../InputElement/Helpers/FloorSelector.svelte"; import FloorSelector from "../InputElement/Helpers/FloorSelector.svelte"
import { Store, UIEventSource } from "../../Logic/UIEventSource"; import { Store, UIEventSource } from "../../Logic/UIEventSource"
export let layerState: LayerState; export let layerState: LayerState
export let floors: Store<string[]>; export let floors: Store<string[]>
export let zoom: Store<number>; export let zoom: Store<number>
const maxZoom = 16 const maxZoom = 16
let selectedFloor: UIEventSource<string> = new UIEventSource<string>(undefined); let selectedFloor: UIEventSource<string> = new UIEventSource<string>(undefined)
selectedFloor.stabilized(5).map(floor => { selectedFloor.stabilized(5).map(
if(floors.data === undefined || floors.data.length <= 1 || zoom.data < maxZoom){ (floor) => {
if (floors.data === undefined || floors.data.length <= 1 || zoom.data < maxZoom) {
// Only a single floor is visible -> disable the 'level' global filter // Only a single floor is visible -> disable the 'level' global filter
// OR we might have zoomed out to much ant want to show all // OR we might have zoomed out to much ant want to show all
layerState.setLevelFilter(undefined) layerState.setLevelFilter(undefined)
}else{ } else {
layerState.setLevelFilter(floor) layerState.setLevelFilter(floor)
} }
}, [floors, zoom]) },
[floors, zoom]
)
</script> </script>
{#if $zoom >= maxZoom} {#if $zoom >= maxZoom}
<FloorSelector {floors} value={selectedFloor} /> <FloorSelector {floors} value={selectedFloor} />
{/if} {/if}

View file

@ -1,31 +1,29 @@
<script lang="ts"> <script lang="ts">
import Translations from "../i18n/Translations" import Translations from "../i18n/Translations"
import Svg from "../../Svg" import Svg from "../../Svg"
import {Store} from "../../Logic/UIEventSource"; import { Store } from "../../Logic/UIEventSource"
import Tr from "../Base/Tr.svelte"; import Tr from "../Base/Tr.svelte"
import ToSvelte from "../Base/ToSvelte.svelte"; import ToSvelte from "../Base/ToSvelte.svelte"
/* /*
A subtleButton which opens mapillary in a new tab at the current location A subtleButton which opens mapillary in a new tab at the current location
*/ */
export let mapProperties: { export let mapProperties: {
readonly zoom: Store<number>, readonly zoom: Store<number>
readonly location: Store<{ lon: number, lat: number }> readonly location: Store<{ lon: number; lat: number }>
} }
let location = mapProperties.location let location = mapProperties.location
let zoom = mapProperties.zoom let zoom = mapProperties.zoom
let mapillaryLink = `https://www.mapillary.com/app/?focus=map&lat=${ let mapillaryLink = `https://www.mapillary.com/app/?focus=map&lat=${$location?.lat ?? 0}&lng=${
$location?.lat ?? 0 $location?.lon ?? 0
}&lng=${$location?.lon ?? 0}&z=${Math.max(($zoom ?? 2) - 1, 1)}` }&z=${Math.max(($zoom ?? 2) - 1, 1)}`
</script> </script>
<a class="flex button items-center" href={mapillaryLink} target="_blank"> <a class="flex button items-center" href={mapillaryLink} target="_blank">
<ToSvelte construct={() =>Svg.mapillary_black_svg().SetClass("w-12 h-12 m-2 mr-4 shrink-0")}/> <ToSvelte construct={() => Svg.mapillary_black_svg().SetClass("w-12 h-12 m-2 mr-4 shrink-0")} />
<div class="flex flex-col"> <div class="flex flex-col">
<Tr t={Translations.t.general.attribution.openMapillary}/> <Tr t={Translations.t.general.attribution.openMapillary} />
<Tr cls="subtle" t={ Translations.t.general.attribution.mapillaryHelp}/> <Tr cls="subtle" t={Translations.t.general.attribution.mapillaryHelp} />
</div> </div>
</a> </a>

View file

@ -1,12 +1,12 @@
import Svg from "../../Svg" import Svg from "../../Svg"
import Combine from "../Base/Combine" import Combine from "../Base/Combine"
import Translations from "../i18n/Translations" import Translations from "../i18n/Translations"
import LayoutConfig, {LayoutInformation} from "../../Models/ThemeConfig/LayoutConfig" import LayoutConfig, { LayoutInformation } from "../../Models/ThemeConfig/LayoutConfig"
import {ImmutableStore, Store} from "../../Logic/UIEventSource" import { ImmutableStore, Store } from "../../Logic/UIEventSource"
import UserRelatedState from "../../Logic/State/UserRelatedState" import UserRelatedState from "../../Logic/State/UserRelatedState"
import {Utils} from "../../Utils" import { Utils } from "../../Utils"
import themeOverview from "../../assets/generated/theme_overview.json" import themeOverview from "../../assets/generated/theme_overview.json"
import {TextField} from "../Input/TextField" import { TextField } from "../Input/TextField"
import Locale from "../i18n/Locale" import Locale from "../i18n/Locale"
import SvelteUIElement from "../Base/SvelteUIElement" import SvelteUIElement from "../Base/SvelteUIElement"
import ThemesList from "./ThemesList.svelte" import ThemesList from "./ThemesList.svelte"
@ -29,7 +29,7 @@ export default class MoreScreen extends Combine {
}) })
search.enterPressed.addCallbackD((searchTerm) => { search.enterPressed.addCallbackD((searchTerm) => {
searchTerm = searchTerm.toLowerCase() searchTerm = searchTerm.toLowerCase()
if(!searchTerm){ if (!searchTerm) {
return return
} }
if (searchTerm === "personal") { if (searchTerm === "personal") {

View file

@ -1,17 +1,20 @@
<script lang="ts"> <script lang="ts">
import type {SpecialVisualizationState} from "../SpecialVisualization"; import type { SpecialVisualizationState } from "../SpecialVisualization"
import LocationInput from "../InputElement/Helpers/LocationInput.svelte"; import LocationInput from "../InputElement/Helpers/LocationInput.svelte"
import {UIEventSource} from "../../Logic/UIEventSource"; import { UIEventSource } from "../../Logic/UIEventSource"
import {Tiles} from "../../Models/TileRange"; import { Tiles } from "../../Models/TileRange"
import {Map as MlMap} from "maplibre-gl"; import { Map as MlMap } from "maplibre-gl"
import {BBox} from "../../Logic/BBox"; import { BBox } from "../../Logic/BBox"
import type {MapProperties} from "../../Models/MapProperties"; import type { MapProperties } from "../../Models/MapProperties"
import ShowDataLayer from "../Map/ShowDataLayer"; import ShowDataLayer from "../Map/ShowDataLayer"
import type {FeatureSource, FeatureSourceForLayer} from "../../Logic/FeatureSource/FeatureSource"; import type {
import SnappingFeatureSource from "../../Logic/FeatureSource/Sources/SnappingFeatureSource"; FeatureSource,
import FeatureSourceMerger from "../../Logic/FeatureSource/Sources/FeatureSourceMerger"; FeatureSourceForLayer,
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; } from "../../Logic/FeatureSource/FeatureSource"
import {Utils} from "../../Utils"; import SnappingFeatureSource from "../../Logic/FeatureSource/Sources/SnappingFeatureSource"
import FeatureSourceMerger from "../../Logic/FeatureSource/Sources/FeatureSourceMerger"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import { Utils } from "../../Utils"
/** /**
* An advanced location input, which has support to: * An advanced location input, which has support to:
@ -20,67 +23,67 @@
* *
* This one is mostly used to insert new points, including when importing * This one is mostly used to insert new points, including when importing
*/ */
export let state: SpecialVisualizationState; export let state: SpecialVisualizationState
/** /**
* The start coordinate * The start coordinate
*/ */
export let coordinate: { lon: number, lat: number }; export let coordinate: { lon: number; lat: number }
export let snapToLayers: string[] | undefined; export let snapToLayers: string[] | undefined
export let targetLayer: LayerConfig; export let targetLayer: LayerConfig
export let maxSnapDistance: number = undefined; export let maxSnapDistance: number = undefined
export let snappedTo: UIEventSource<string | undefined>; export let snappedTo: UIEventSource<string | undefined>
export let value: UIEventSource<{ lon: number, lat: number }>; export let value: UIEventSource<{ lon: number; lat: number }>
if (value.data === undefined) { if (value.data === undefined) {
value.setData(coordinate); value.setData(coordinate)
} }
let preciseLocation: UIEventSource<{ lon: number, lat: number }> = new UIEventSource<{ let preciseLocation: UIEventSource<{ lon: number; lat: number }> = new UIEventSource<{
lon: number; lon: number
lat: number lat: number
}>(undefined); }>(undefined)
const xyz = Tiles.embedded_tile(coordinate.lat, coordinate.lon, 16); const xyz = Tiles.embedded_tile(coordinate.lat, coordinate.lon, 16)
const map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined); const map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined)
let initialMapProperties: Partial<MapProperties> = { let initialMapProperties: Partial<MapProperties> = {
zoom: new UIEventSource<number>(19), zoom: new UIEventSource<number>(19),
maxbounds: new UIEventSource(undefined), maxbounds: new UIEventSource(undefined),
/*If no snapping needed: the value is simply the map location; /*If no snapping needed: the value is simply the map location;
* If snapping is needed: the value will be set later on by the snapping feature source * If snapping is needed: the value will be set later on by the snapping feature source
* */ * */
location: snapToLayers?.length > 0 ? new UIEventSource<{ lon: number; lat: number }>(coordinate) : value, location:
snapToLayers?.length > 0
? new UIEventSource<{ lon: number; lat: number }>(coordinate)
: value,
bounds: new UIEventSource<BBox>(undefined), bounds: new UIEventSource<BBox>(undefined),
allowMoving: new UIEventSource<boolean>(true), allowMoving: new UIEventSource<boolean>(true),
allowZooming: new UIEventSource<boolean>(true), allowZooming: new UIEventSource<boolean>(true),
minzoom: new UIEventSource<number>(18), minzoom: new UIEventSource<number>(18),
rasterLayer: UIEventSource.feedFrom(state.mapProperties.rasterLayer) rasterLayer: UIEventSource.feedFrom(state.mapProperties.rasterLayer),
}; }
const featuresForLayer = state.perLayer.get(targetLayer.id) const featuresForLayer = state.perLayer.get(targetLayer.id)
if(featuresForLayer){ if (featuresForLayer) {
new ShowDataLayer(map, { new ShowDataLayer(map, {
layer: targetLayer, layer: targetLayer,
features: featuresForLayer features: featuresForLayer,
}) })
} }
if (snapToLayers?.length > 0) { if (snapToLayers?.length > 0) {
const snapSources: FeatureSource[] = []
const snapSources: FeatureSource[] = []; for (const layerId of snapToLayers ?? []) {
for (const layerId of (snapToLayers ?? [])) { const layer: FeatureSourceForLayer = state.perLayer.get(layerId)
const layer: FeatureSourceForLayer = state.perLayer.get(layerId); snapSources.push(layer)
snapSources.push(layer);
if (layer.features === undefined) { if (layer.features === undefined) {
continue; continue
} }
new ShowDataLayer(map, { new ShowDataLayer(map, {
layer: layer.layer.layerDef, layer: layer.layer.layerDef,
zoomToFeatures: false, zoomToFeatures: false,
features: layer features: layer,
}); })
} }
const snappedLocation = new SnappingFeatureSource( const snappedLocation = new SnappingFeatureSource(
new FeatureSourceMerger(...Utils.NoNull(snapSources)), new FeatureSourceMerger(...Utils.NoNull(snapSources)),
@ -90,19 +93,21 @@
maxDistance: maxSnapDistance ?? 15, maxDistance: maxSnapDistance ?? 15,
allowUnsnapped: true, allowUnsnapped: true,
snappedTo, snappedTo,
snapLocation: value snapLocation: value,
} }
); )
new ShowDataLayer(map, { new ShowDataLayer(map, {
layer: targetLayer, layer: targetLayer,
features: snappedLocation features: snappedLocation,
}); })
} }
</script> </script>
<LocationInput
<LocationInput {map} mapProperties={initialMapProperties} {map}
value={preciseLocation} initialCoordinate={coordinate} maxDistanceInMeters=50 /> mapProperties={initialMapProperties}
value={preciseLocation}
initialCoordinate={coordinate}
maxDistanceInMeters="50"
/>

View file

@ -12,11 +12,11 @@
</script> </script>
<script lang="ts"> <script lang="ts">
import {UIEventSource} from "../../Logic/UIEventSource" import { UIEventSource } from "../../Logic/UIEventSource"
import Svg from "../../Svg" import Svg from "../../Svg"
import ToSvelte from "../Base/ToSvelte.svelte" import ToSvelte from "../Base/ToSvelte.svelte"
import Translations from "../i18n/Translations" import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte"; import Tr from "../Base/Tr.svelte"
export let search: UIEventSource<string> export let search: UIEventSource<string>
@ -26,10 +26,9 @@
<div class="w-full"> <div class="w-full">
<h5>{t.noMatchingThemes.toString()}</h5> <h5>{t.noMatchingThemes.toString()}</h5>
<div class="flex justify-center"> <div class="flex justify-center">
<button on:click={() => search.setData("")}> <button on:click={() => search.setData("")}>
<ToSvelte construct={Svg.search_disable_svg().SetClass("w-6 mr-2")}/> <ToSvelte construct={Svg.search_disable_svg().SetClass("w-6 mr-2")} />
<Tr slot="message" t={t.noSearch}/> <Tr slot="message" t={t.noSearch} />
</button> </button>
</div> </div>
</div> </div>

View file

@ -1,12 +1,11 @@
<script lang="ts"> <script lang="ts">
import { Square3Stack3dIcon } from "@babeard/svelte-heroicons/solid"
import {Square3Stack3dIcon} from "@babeard/svelte-heroicons/solid"; import MapControlButton from "../Base/MapControlButton.svelte"
import MapControlButton from "../Base/MapControlButton.svelte"; import ThemeViewState from "../../Models/ThemeViewState"
import ThemeViewState from "../../Models/ThemeViewState";
export let state: ThemeViewState export let state: ThemeViewState
</script> </script>
<MapControlButton on:click={() => state.guistate.backgroundLayerSelectionIsOpened.setData(true)}> <MapControlButton on:click={() => state.guistate.backgroundLayerSelectionIsOpened.setData(true)}>
<Square3Stack3dIcon class="w-6 h-6"/> <Square3Stack3dIcon class="w-6 h-6" />
</MapControlButton> </MapControlButton>

View file

@ -1,8 +1,8 @@
<script lang="ts"> <script lang="ts">
import {Store} from "../../Logic/UIEventSource"; import { Store } from "../../Logic/UIEventSource"
import {PencilIcon} from "@babeard/svelte-heroicons/solid"; import { PencilIcon } from "@babeard/svelte-heroicons/solid"
import Translations from "../i18n/Translations"; import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte"; import Tr from "../Base/Tr.svelte"
export let mapProperties: { location: Store<{ lon: number; lat: number }>; zoom: Store<number> } export let mapProperties: { location: Store<{ lon: number; lat: number }>; zoom: Store<number> }
let location = mapProperties.location let location = mapProperties.location
@ -21,13 +21,12 @@
elementSelect = "&" + tp + "=" + parts[1] elementSelect = "&" + tp + "=" + parts[1]
} }
} }
const idLink = `https://www.openstreetmap.org/edit?editor=id${elementSelect}#map=${ const idLink = `https://www.openstreetmap.org/edit?editor=id${elementSelect}#map=${$zoom ?? 0}/${
$zoom ?? 0 $location?.lat ?? 0
}/${$location?.lat ?? 0}/${$location?.lon ?? 0}` }/${$location?.lon ?? 0}`
</script> </script>
<a class="flex button items-center" target="_blank" href={idLink}> <a class="flex button items-center" target="_blank" href={idLink}>
<PencilIcon class="w-12 h-12 p-2 pr-4"/> <PencilIcon class="w-12 h-12 p-2 pr-4" />
<Tr t={ Translations.t.general.attribution.editId}/> <Tr t={Translations.t.general.attribution.editId} />
</a> </a>

Some files were not shown because too many files have changed in this diff Show more