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 {Utils} from "../../Utils"
import {RasterLayerPolygon, RasterLayerUtils,} from "../../Models/RasterLayers"
import { Store, UIEventSource } from "../UIEventSource"
import { Utils } from "../../Utils"
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.
@ -35,7 +35,7 @@ export default class BackgroundLayerResetter {
availableLayers,
currentBgPolygon?.properties?.category
)
if(!availableInSameCat){
if (!availableInSameCat) {
return
}
console.log("Selecting a different layer:", availableInSameCat.properties.id)

View file

@ -1,15 +1,15 @@
import {QueryParameters} from "../Web/QueryParameters"
import {BBox} from "../BBox"
import { QueryParameters } from "../Web/QueryParameters"
import { BBox } from "../BBox"
import Constants from "../../Models/Constants"
import {GeoLocationState} from "../State/GeoLocationState"
import {UIEventSource} from "../UIEventSource"
import {Feature, LineString, Point} from "geojson"
import {FeatureSource, WritableFeatureSource} from "../FeatureSource/FeatureSource"
import {LocalStorageSource} from "../Web/LocalStorageSource"
import {GeoOperations} from "../GeoOperations"
import {OsmTags} from "../../Models/OsmFeature"
import { GeoLocationState } from "../State/GeoLocationState"
import { UIEventSource } from "../UIEventSource"
import { Feature, LineString, Point } from "geojson"
import { FeatureSource, WritableFeatureSource } from "../FeatureSource/FeatureSource"
import { LocalStorageSource } from "../Web/LocalStorageSource"
import { GeoOperations } from "../GeoOperations"
import { OsmTags } from "../../Models/OsmFeature"
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.
@ -39,7 +39,9 @@ export default class GeoLocationHandler {
/**
* 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 mapProperties?: MapProperties
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!
self.MoveMapToCurrentLocation()
}
if (timeSinceLastRequest < Constants.zoomToLocationTimeout &&
(this.mapHasMoved.data === undefined || this.mapHasMoved.data.getTime() < geolocationState.requestMoment.data?.getTime() )
if (
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
self.MoveMapToCurrentLocation()
@ -154,8 +159,8 @@ export default class GeoLocationHandler {
return
}
const properties = {
id: "gps-"+i,
const properties = {
id: "gps-" + i,
"user:location": "yes",
date: new Date().toISOString(),
}
@ -164,7 +169,7 @@ export default class GeoLocationHandler {
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}
// As such, they are copied here
if(location[k]){
if (location[k]) {
properties[k] = location[k]
}
}

View file

@ -20,14 +20,15 @@ import { FixImages } from "../Models/ThemeConfig/Conversion/FixImages"
import Svg from "../Svg"
import {
DoesImageExist,
PrevalidateTheme, ValidateTagRenderings,
PrevalidateTheme,
ValidateTagRenderings,
ValidateThemeAndLayers,
} from "../Models/ThemeConfig/Conversion/Validation"
import {DesugaringContext, Each, On} from "../Models/ThemeConfig/Conversion/Conversion";
import {PrepareLayer, RewriteSpecial} from "../Models/ThemeConfig/Conversion/PrepareLayer";
import {AllSharedLayers} from "../Customizations/AllSharedLayers";
import {TagRenderingConfigJson} from "../Models/ThemeConfig/Json/TagRenderingConfigJson";
import questions from "../assets/tagRenderings/questions.json";
import { DesugaringContext, Each, On } from "../Models/ThemeConfig/Conversion/Conversion"
import { PrepareLayer, RewriteSpecial } from "../Models/ThemeConfig/Conversion/PrepareLayer"
import { AllSharedLayers } from "../Customizations/AllSharedLayers"
import { TagRenderingConfigJson } from "../Models/ThemeConfig/Json/TagRenderingConfigJson"
import questions from "../assets/tagRenderings/questions.json"
export default class DetermineLayout {
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 BaseUIElement from "../UI/BaseUIElement"
import List from "../UI/Base/List"
import Title from "../UI/Base/Title"
import {BBox} from "./BBox"
import {Feature, Geometry, MultiPolygon, Polygon} from "geojson"
import {GeoJSONFeature} from "maplibre-gl";
import { BBox } from "./BBox"
import { Feature, Geometry, MultiPolygon, Polygon } from "geojson"
import { GeoJSONFeature } from "maplibre-gl"
export interface ExtraFuncParams {
/**
@ -13,7 +13,10 @@ export interface ExtraFuncParams {
* Note that more features then requested can be given back.
* 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>>
}
@ -55,7 +58,6 @@ class EnclosingFunc implements ExtraFunction {
}
for (const otherFeatures of otherFeaturess) {
for (const otherFeature of otherFeatures) {
if (seenIds.has(otherFeature.properties.id)) {
continue
}
@ -72,7 +74,7 @@ class EnclosingFunc implements ExtraFunction {
<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 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) {
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 uniqueTag: string | undefined = options?.uniqueTag
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") {
const name = features
const bbox = GeoOperations.bbox(
@ -272,7 +284,6 @@ class ClosestNObjectFunc implements ExtraFunction {
let closestFeatures: { feat: any; distance: number }[] = []
for (const feats of allFeatures) {
for (const otherFeature of feats) {
if (
otherFeature === feature ||
@ -333,7 +344,7 @@ class ClosestNObjectFunc implements ExtraFunction {
const uniqueTagsMatch =
otherFeature.properties[uniqueTag] !== undefined &&
closestFeature.feat.properties[uniqueTag] ===
otherFeature.properties[uniqueTag]
otherFeature.properties[uniqueTag]
if (uniqueTagsMatch) {
targetIndex = -1
if (closestFeature.distance > distance) {
@ -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')
// AT this point, we have found a closer segment with the same, identical tag
// so we replace directly
closestFeatures[i] = {feat: otherFeature, distance: distance}
closestFeatures[i] = { feat: otherFeature, distance: distance }
}
break
}
@ -468,7 +479,15 @@ export class ExtraFunctions {
.SetClass("flex-col")
.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 = [
new DistanceToFunc(),
new OverlapFunc(),
@ -479,8 +498,9 @@ export class ExtraFunctions {
new GetParsed(),
]
public static constructHelpers(params: ExtraFuncParams): Record<ExtraFuncType, (feature: Feature) => Function> {
public static constructHelpers(
params: ExtraFuncParams
): Record<ExtraFuncType, (feature: Feature) => Function> {
const record: Record<string, (feature: GeoJSONFeature) => Function> = {}
for (const f of ExtraFunctions.allFuncs) {
if (this.types.indexOf(<any>f._name) < 0) {

View file

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

View file

@ -1,5 +1,5 @@
import {IdbLocalStorage} from "../../Web/IdbLocalStorage"
import {UIEventSource} from "../../UIEventSource"
import { IdbLocalStorage } from "../../Web/IdbLocalStorage"
import { UIEventSource } from "../../UIEventSource"
/**
* 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 inUse = new UIEventSource(false)
private readonly cachedSources: Record<number, UIEventSource<T> & { flush: () => void }> = {}
private readonly _maxAgeSeconds: number;
private readonly _maxAgeSeconds: number
private constructor(layername: string, maxAgeSeconds: number) {
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 cached = TileLocalStorage.perLayer[key]
if (cached) {
@ -59,7 +63,10 @@ export default class TileLocalStorage<T> {
await this.inUse.AsPromise((inUse) => !inUse)
this.inUse.setData(true)
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)
} catch (e) {
@ -80,7 +87,9 @@ export default class TileLocalStorage<T> {
if (!TileLocalStorage.useIndexedDb) {
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 timeDiff = Date.now() - date
if (timeDiff >= maxAge) {

View file

@ -1,8 +1,8 @@
import {FeatureSource} from "./FeatureSource"
import { FeatureSource } from "./FeatureSource"
import FilteredLayer from "../../Models/FilteredLayer"
import SimpleFeatureSource from "./Sources/SimpleFeatureSource"
import {Feature} from "geojson"
import {UIEventSource} from "../UIEventSource"
import { Feature } from "geojson"
import { UIEventSource } from "../UIEventSource"
/**
* 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
for (let i = 0; i < layers.length; i++) {
const layer = layers[i]
if(!layer.layerDef?.source){
console.error("PerLayerFeatureSourceSplitter got a layer without a source:", layer.layerDef.id)
if (!layer.layerDef?.source) {
console.error(
"PerLayerFeatureSourceSplitter got a layer without a source:",
layer.layerDef.id
)
continue
}
if (layer.layerDef.source.osmTags.matchesProperties(f.properties)) {

View file

@ -6,7 +6,7 @@ import { UIEventSource } from "../../UIEventSource"
import { FeatureSource, IndexedFeatureSource } from "../FeatureSource"
import { ChangeDescription, ChangeDescriptionTools } from "../../Osm/Actions/ChangeDescription"
import { Feature } from "geojson"
import {Utils} from "../../../Utils";
import { Utils } from "../../../Utils"
export default class ChangeGeometryApplicator implements FeatureSource {
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
const change = changesForFeature[changesForFeature.length - 1]
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
newFeatures.push(feature)
continue

View file

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

View file

@ -1,14 +1,14 @@
/**
* Fetches a geojson file somewhere and passes it along
*/
import {Store, UIEventSource} from "../../UIEventSource"
import {Utils} from "../../../Utils"
import {FeatureSource} from "../FeatureSource"
import {BBox} from "../../BBox"
import {GeoOperations} from "../../GeoOperations"
import {Feature} from "geojson"
import { Store, UIEventSource } from "../../UIEventSource"
import { Utils } from "../../../Utils"
import { FeatureSource } from "../FeatureSource"
import { BBox } from "../../BBox"
import { GeoOperations } from "../../GeoOperations"
import { Feature } from "geojson"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
import {Tiles} from "../../../Models/TileRange"
import { Tiles } from "../../../Models/TileRange"
export default class GeoJsonSource implements FeatureSource {
public readonly features: Store<Feature[]>
@ -65,13 +65,13 @@ export default class GeoJsonSource implements FeatureSource {
return
}
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))
return true // data is loaded, we can safely unregister
})
} else {
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))
}
this.features = eventsource
@ -105,7 +105,7 @@ export default class GeoJsonSource implements FeatureSource {
let i = 0
let skipped = 0
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
feature.geometry.coordinates = feature.geometry.coordinates.map(Number)
}

View file

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

View file

@ -1,16 +1,16 @@
import GeoJsonSource from "./GeoJsonSource"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
import {FeatureSource} from "../FeatureSource"
import {Or} from "../../Tags/Or"
import { FeatureSource } from "../FeatureSource"
import { Or } from "../../Tags/Or"
import FeatureSwitchState from "../../State/FeatureSwitchState"
import OverpassFeatureSource from "./OverpassFeatureSource"
import {Store, UIEventSource} from "../../UIEventSource"
import { Store, UIEventSource } from "../../UIEventSource"
import OsmFeatureSource from "./OsmFeatureSource"
import FeatureSourceMerger from "./FeatureSourceMerger"
import DynamicGeoJsonTileSource from "../TiledFeatureSource/DynamicGeoJsonTileSource"
import {BBox} from "../../BBox"
import { BBox } from "../../BBox"
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.
@ -41,7 +41,7 @@ export default class LayoutSource extends FeatureSourceMerger {
(l) =>
new LocalStorageFeatureSource(backend, l.id, 15, mapProperties, {
isActive: isDisplayed(l.id),
maxAge: l.maxAgeOfCache
maxAge: l.maxAgeOfCache,
})
)
@ -127,7 +127,7 @@ export default class LayoutSource extends FeatureSourceMerger {
backend,
isActive,
patchRelations: true,
fullNodeDatabase
fullNodeDatabase,
})
}

View file

@ -7,7 +7,7 @@ import { TagsFilter } from "../../Tags/TagsFilter"
import { Feature } from "geojson"
import FeatureSourceMerger from "../Sources/FeatureSourceMerger"
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'
@ -24,16 +24,16 @@ export default class OsmFeatureSource extends FeatureSourceMerger {
/**
* If given: this featureSwitch will not update if the store contains 'false'
*/
isActive?: Store<boolean>,
patchRelations?: true | boolean,
isActive?: Store<boolean>
patchRelations?: true | boolean
fullNodeDatabase?: FullNodeDatabaseSource
};
}
public readonly isRunning: UIEventSource<boolean> = new UIEventSource<boolean>(false)
private readonly _downloadedTiles: Set<number> = new Set<number>()
private readonly _downloadedData: Feature[][] = []
private readonly _patchRelations: boolean;
private readonly _patchRelations: boolean
/**
* Downloads data directly from the OSM-api within the given bounds.
* 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'
*/
isActive?: Store<boolean>,
patchRelations?: true | boolean,
isActive?: Store<boolean>
patchRelations?: true | boolean
fullNodeDatabase?: FullNodeDatabaseSource
}) {
super()
this.options = options;
this.options = options
this._bounds = options.bounds
this.allowedTags = options.allowedFeatures
this.isActive = options.isActive ?? new ImmutableStore(true)

View file

@ -1,6 +1,6 @@
import {FeatureSource} from "../FeatureSource"
import {ImmutableStore, Store} from "../../UIEventSource"
import {Feature} from "geojson"
import { FeatureSource } from "../FeatureSource"
import { ImmutableStore, Store } from "../../UIEventSource"
import { Feature } from "geojson"
/**
* 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> {
public readonly features: Store<T[]>
constructor(
features:
| Store<T[]>
| T[]
| { features: T[] }
| { features: Store<T[]> }
) {
constructor(features: Store<T[]> | T[] | { features: T[] } | { features: Store<T[]> }) {
if (features === undefined) {
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 {BBox} from "../../BBox"
import { BBox } from "../../BBox"
import FilteredLayer from "../../../Models/FilteredLayer"
import {Store} from "../../UIEventSource"
import {Feature} from "geojson";
import { Store } from "../../UIEventSource"
import { Feature } from "geojson"
/**
* 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
constructor(features: FeatureSourceForLayer<T>, mustTouch: Store<BBox>) {

View file

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

View file

@ -1,11 +1,10 @@
import {OsmNode, OsmObject, OsmWay} from "../../Osm/OsmObject"
import {UIEventSource} from "../../UIEventSource"
import {BBox} from "../../BBox";
import StaticFeatureSource from "../Sources/StaticFeatureSource";
import {Tiles} from "../../../Models/TileRange";
import { OsmNode, OsmObject, OsmWay } from "../../Osm/OsmObject"
import { UIEventSource } from "../../UIEventSource"
import { BBox } from "../../BBox"
import StaticFeatureSource from "../Sources/StaticFeatureSource"
import { Tiles } from "../../../Models/TileRange"
export default class FullNodeDatabaseSource {
private readonly loadedTiles = new Map<number, Map<number, OsmNode>>()
private readonly nodeByIds = new Map<number, OsmNode>()
private readonly parentWays = new Map<number, UIEventSource<OsmWay[]>>()
@ -13,7 +12,7 @@ export default class FullNodeDatabaseSource {
private smallestZoom = 99
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 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
* @param bbox
*/
getNodesWithin(bbox: BBox) : Map<number, OsmNode>{
getNodesWithin(bbox: BBox): Map<number, OsmNode> {
const allById = new Map<number, OsmNode>()
for (let z = this.smallestZoom; z < this.largestZoom; 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 nodesById = this.loadedTiles.get(tileId)
nodesById?.forEach((v,k) => allById.set(k,v))
nodesById?.forEach((v, k) => allById.set(k, v))
})
}
return allById

View file

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

View file

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

View file

@ -1,10 +1,10 @@
import { Mapillary } from "./Mapillary";
import { WikimediaImageProvider } from "./WikimediaImageProvider";
import { Imgur } from "./Imgur";
import GenericImageProvider from "./GenericImageProvider";
import { Store, UIEventSource } from "../UIEventSource";
import ImageProvider, { ProvidedImage } from "./ImageProvider";
import { WikidataImageProvider } from "./WikidataImageProvider";
import { Mapillary } from "./Mapillary"
import { WikimediaImageProvider } from "./WikimediaImageProvider"
import { Imgur } from "./Imgur"
import GenericImageProvider from "./GenericImageProvider"
import { Store, UIEventSource } from "../UIEventSource"
import ImageProvider, { ProvidedImage } from "./ImageProvider"
import { WikidataImageProvider } from "./WikidataImageProvider"
/**
* A generic 'from the interwebz' image picker, without attribution
@ -44,7 +44,10 @@ export default class AllImageProviders {
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) {
return undefined
}

View file

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

View file

@ -1,14 +1,14 @@
import SimpleMetaTaggers, {MetataggingState, SimpleMetaTagger} from "./SimpleMetaTagger"
import {ExtraFuncParams, ExtraFunctions, ExtraFuncType} from "./ExtraFunctions"
import SimpleMetaTaggers, { MetataggingState, SimpleMetaTagger } from "./SimpleMetaTagger"
import { ExtraFuncParams, ExtraFunctions, ExtraFuncType } from "./ExtraFunctions"
import LayerConfig from "../Models/ThemeConfig/LayerConfig"
import {Feature} from "geojson"
import { Feature } from "geojson"
import FeaturePropertiesStore from "./FeatureSource/Actors/FeaturePropertiesStore"
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"
import {GeoIndexedStoreForLayer} from "./FeatureSource/Actors/GeoIndexedStore"
import {IndexedFeatureSource} from "./FeatureSource/FeatureSource"
import { GeoIndexedStoreForLayer } from "./FeatureSource/Actors/GeoIndexedStore"
import { IndexedFeatureSource } from "./FeatureSource/FeatureSource"
import OsmObjectDownloader from "./Osm/OsmObjectDownloader"
import {Utils} from "../Utils";
import {UIEventSource} from "./UIEventSource";
import { Utils } from "../Utils"
import { UIEventSource } from "./UIEventSource"
/**
* 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 {
private static errorPrintCount = 0
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: {
layout: LayoutConfig
@ -96,7 +99,7 @@ export default class MetaTagging {
// The calculated functions - per layer - which add the new keys
// Calculated functions are defined by the layer
const layerFuncs = this.createRetaggingFunc(layer, ExtraFunctions.constructHelpers(params))
const state: MetataggingState = {layout, osmObjectDownloader}
const state: MetataggingState = { layout, osmObjectDownloader }
let atLeastOneFeatureChanged = false
let strictlyEvaluated = 0
@ -177,20 +180,20 @@ export default class MetaTagging {
}
public static createExtraFuncParams(state: {
indexedFeatures: IndexedFeatureSource,
indexedFeatures: IndexedFeatureSource
perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer>
}) {
return {
getFeatureById: (id) => state.indexedFeatures.featuresById.data.get(id),
getFeaturesWithin: (layerId, bbox) => {
if (layerId === '*' || layerId === null || layerId === undefined) {
if (layerId === "*" || layerId === null || layerId === undefined) {
const feats: Feature[][] = []
state.perLayer.forEach((layer) => {
feats.push(layer.GetFeaturesWithin(bbox))
})
return feats
}
return [state.perLayer.get(layerId).GetFeaturesWithin(bbox)];
return [state.perLayer.get(layerId).GetFeaturesWithin(bbox)]
},
}
}
@ -202,23 +205,30 @@ export default class MetaTagging {
* @param layerId
* @private
*/
private static createFunctionForFeature([key, code, isStrict]: [string, string, boolean],
helperFunctions: Record<ExtraFuncType, (feature: Feature) => Function>,
layerId: string = "unkown layer"
private static createFunctionForFeature(
[key, code, isStrict]: [string, string, boolean],
helperFunctions: Record<ExtraFuncType, (feature: Feature) => Function>,
layerId: string = "unkown layer"
): ((feature: Feature, propertiesStore?: UIEventSource<any>) => void) | undefined {
if (code === undefined) {
return undefined
}
const calculateAndAssign: ((feat: Feature, store?: UIEventSource<any>) => string | any) = (feat, store) => {
const calculateAndAssign: (feat: Feature, store?: UIEventSource<any>) => string | any = (
feat,
store
) => {
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 === "") {
result = undefined
}
const oldValue= feat.properties[key]
if(oldValue == result){
const oldValue = feat.properties[key]
if (oldValue == result) {
return oldValue
}
delete feat.properties[key]
@ -229,16 +239,16 @@ export default class MetaTagging {
if (MetaTagging.errorPrintCount < MetaTagging.stopErrorOutputAt) {
console.warn(
"Could not calculate a " +
(isStrict ? "strict " : "") +
" calculated tag for key " +
key +
" defined by " +
code +
" (in layer" +
layerId +
") due to \n" +
e +
"\n. Are you the theme creator? Doublecheck your code. Note that the metatags might not be stable on new features",
(isStrict ? "strict " : "") +
" calculated tag for key " +
key +
" defined by " +
code +
" (in layer" +
layerId +
") due to \n" +
e +
"\n. Are you the theme creator? Doublecheck your code. Note that the metatags might not be stable on new features",
e,
e.stack
)
@ -276,9 +286,12 @@ export default class MetaTagging {
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) {
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)
}

View file

@ -1,20 +1,23 @@
import {OsmCreateAction, PreviewableAction} from "./OsmChangeAction"
import {Tag} from "../../Tags/Tag"
import {Changes} from "../Changes"
import {ChangeDescription} from "./ChangeDescription"
import { OsmCreateAction, PreviewableAction } from "./OsmChangeAction"
import { Tag } from "../../Tags/Tag"
import { Changes } from "../Changes"
import { ChangeDescription } from "./ChangeDescription"
import CreateNewWayAction from "./CreateNewWayAction"
import CreateWayWithPointReuseAction, {MergePointConfig} from "./CreateWayWithPointReuseAction"
import {And} from "../../Tags/And"
import {TagUtils} from "../../Tags/TagUtils"
import {FeatureSource, IndexedFeatureSource} from "../../FeatureSource/FeatureSource"
import LayoutConfig from "../../../Models/ThemeConfig/LayoutConfig";
import {Position} from "geojson";
import FullNodeDatabaseSource from "../../FeatureSource/TiledFeatureSource/FullNodeDatabaseSource";
import CreateWayWithPointReuseAction, { MergePointConfig } from "./CreateWayWithPointReuseAction"
import { And } from "../../Tags/And"
import { TagUtils } from "../../Tags/TagUtils"
import { FeatureSource, IndexedFeatureSource } from "../../FeatureSource/FeatureSource"
import LayoutConfig from "../../../Models/ThemeConfig/LayoutConfig"
import { Position } from "geojson"
import FullNodeDatabaseSource from "../../FeatureSource/TiledFeatureSource/FullNodeDatabaseSource"
/**
* 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 newElementIdNumber: number = undefined
private readonly _tags: Tag[]
@ -29,9 +32,9 @@ export default class CreateMultiPolygonWithPointReuseAction extends OsmCreateAct
outerRingCoordinates: Position[],
innerRingsCoordinates: Position[][],
state: {
layout: LayoutConfig;
changes: Changes;
indexedFeatures: IndexedFeatureSource,
layout: LayoutConfig
changes: Changes
indexedFeatures: IndexedFeatureSource
fullNodeDatabase?: FullNodeDatabaseSource
},
config: MergePointConfig[],
@ -43,7 +46,7 @@ export default class CreateMultiPolygonWithPointReuseAction extends OsmCreateAct
this.theme = state?.layout?.id ?? ""
this.createOuterWay = new CreateWayWithPointReuseAction(
[],
<[number,number][]> outerRingCoordinates,
<[number, number][]>outerRingCoordinates,
state,
config
)

View file

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

View file

@ -1,17 +1,17 @@
import {OsmCreateAction, PreviewableAction} from "./OsmChangeAction"
import {Tag} from "../../Tags/Tag"
import {Changes} from "../Changes"
import {ChangeDescription} from "./ChangeDescription"
import {BBox} from "../../BBox"
import {TagsFilter} from "../../Tags/TagsFilter"
import {GeoOperations} from "../../GeoOperations"
import {FeatureSource, IndexedFeatureSource} from "../../FeatureSource/FeatureSource"
import { OsmCreateAction, PreviewableAction } from "./OsmChangeAction"
import { Tag } from "../../Tags/Tag"
import { Changes } from "../Changes"
import { ChangeDescription } from "./ChangeDescription"
import { BBox } from "../../BBox"
import { TagsFilter } from "../../Tags/TagsFilter"
import { GeoOperations } from "../../GeoOperations"
import { FeatureSource, IndexedFeatureSource } from "../../FeatureSource/FeatureSource"
import StaticFeatureSource from "../../FeatureSource/Sources/StaticFeatureSource"
import CreateNewNodeAction from "./CreateNewNodeAction"
import CreateNewWayAction from "./CreateNewWayAction"
import LayoutConfig from "../../../Models/ThemeConfig/LayoutConfig";
import FullNodeDatabaseSource from "../../FeatureSource/TiledFeatureSource/FullNodeDatabaseSource";
import {Position} from "geojson";
import LayoutConfig from "../../../Models/ThemeConfig/LayoutConfig"
import FullNodeDatabaseSource from "../../FeatureSource/TiledFeatureSource/FullNodeDatabaseSource"
import { Position } from "geojson"
export interface MergePointConfig {
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
*/
export default class CreateWayWithPointReuseAction extends OsmCreateAction implements PreviewableAction {
export default class CreateWayWithPointReuseAction
extends OsmCreateAction
implements PreviewableAction
{
public newElementId: string = undefined
public newElementIdNumber: number = undefined
private readonly _tags: Tag[]
@ -66,9 +69,9 @@ export default class CreateWayWithPointReuseAction extends OsmCreateAction imple
*/
private readonly _coordinateInfo: CoordinateInfo[]
private readonly _state: {
layout: LayoutConfig;
changes: Changes;
indexedFeatures: IndexedFeatureSource,
layout: LayoutConfig
changes: Changes
indexedFeatures: IndexedFeatureSource
fullNodeDatabase?: FullNodeDatabaseSource
}
private readonly _config: MergePointConfig[]
@ -77,9 +80,9 @@ export default class CreateWayWithPointReuseAction extends OsmCreateAction imple
tags: Tag[],
coordinates: Position[],
state: {
layout: LayoutConfig;
changes: Changes;
indexedFeatures: IndexedFeatureSource,
layout: LayoutConfig
changes: Changes
indexedFeatures: IndexedFeatureSource
fullNodeDatabase?: FullNodeDatabaseSource
},
config: MergePointConfig[]
@ -90,7 +93,7 @@ export default class CreateWayWithPointReuseAction extends OsmCreateAction imple
this._config = config
// 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> {
@ -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, {
@ -321,7 +324,7 @@ export default class CreateWayWithPointReuseAction extends OsmCreateAction imple
if (!config.ifMatches.matchesProperties(node.properties)) {
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 { OsmId } from "../../../Models/OsmFeature"
import { Utils } from "../../../Utils"
import OsmObjectDownloader from "../OsmObjectDownloader";
import OsmObjectDownloader from "../OsmObjectDownloader"
export default class DeleteAction extends OsmChangeAction {
private readonly _softDeletionTags: TagsFilter
@ -72,9 +72,11 @@ export default class DeleteAction extends OsmChangeAction {
changes: Changes,
object?: OsmObject
): 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
return []
}

View file

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

View file

@ -1,21 +1,21 @@
import OsmChangeAction, {PreviewableAction} from "./OsmChangeAction"
import {Changes} from "../Changes"
import {ChangeDescription} from "./ChangeDescription"
import {Tag} from "../../Tags/Tag"
import {FeatureSource} from "../../FeatureSource/FeatureSource"
import {OsmNode, OsmObject, OsmWay} from "../OsmObject"
import {GeoOperations} from "../../GeoOperations"
import OsmChangeAction, { PreviewableAction } from "./OsmChangeAction"
import { Changes } from "../Changes"
import { ChangeDescription } from "./ChangeDescription"
import { Tag } from "../../Tags/Tag"
import { FeatureSource } from "../../FeatureSource/FeatureSource"
import { OsmNode, OsmObject, OsmWay } from "../OsmObject"
import { GeoOperations } from "../../GeoOperations"
import StaticFeatureSource from "../../FeatureSource/Sources/StaticFeatureSource"
import CreateNewNodeAction from "./CreateNewNodeAction"
import ChangeTagAction from "./ChangeTagAction"
import {And} from "../../Tags/And"
import {Utils} from "../../../Utils"
import {OsmConnection} from "../OsmConnection"
import {Feature} from "@turf/turf"
import {Geometry, LineString, Point} from "geojson"
import { And } from "../../Tags/And"
import { Utils } from "../../../Utils"
import { OsmConnection } from "../OsmConnection"
import { Feature } from "@turf/turf"
import { Geometry, LineString, Point } from "geojson"
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
*/
@ -45,7 +45,7 @@ export default class ReplaceGeometryAction extends OsmChangeAction implements Pr
public readonly newElementId: string
constructor(
state: {
osmConnection: OsmConnection,
osmConnection: OsmConnection
fullNodeDatabase?: FullNodeDatabaseSource
},
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) {
const addExtraTags = new ChangeTagAction(
this.wayToReplaceId,

View file

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

View file

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

View file

@ -131,7 +131,8 @@ export class ChangesetHandler {
const changeset = generateChangeXML(csId, this._remappings)
console.log(
"Opened a new changeset (openChangeset.data is undefined):",
changeset, extraMetaTags
changeset,
extraMetaTags
)
const changes = await this.UploadChange(csId, changeset)
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 OsmToGeoJson from "osmtogeojson"
import {OsmFeature, OsmId, OsmTags, WayId} from "../../Models/OsmFeature"
import {Feature, LineString, Polygon} from "geojson"
import { OsmFeature, OsmId, OsmTags, WayId } from "../../Models/OsmFeature"
import { Feature, LineString, Polygon } from "geojson"
export abstract class OsmObject {
private static defaultBackend = "https://www.openstreetmap.org/"
@ -198,7 +198,6 @@ export class OsmNode extends OsmObject {
this.LoadData(extraData)
}
/**
*
* const obj = new OsmNode(1234)
@ -213,11 +212,11 @@ export class OsmNode extends OsmObject {
*/
ChangesetXML(changesetId: string, header?: string): string {
let tags = this.TagsXML()
return (
` <node id="${this.id}" ${header ?? ""} ${changesetId ? (' changeset="' + changesetId+ '" ') : ""}${this.VersionXML()} lat="${this.lat}" lon="${this.lon}">
return ` <node id="${this.id}" ${header ?? ""} ${
changesetId ? ' changeset="' + changesetId + '" ' : ""
}${this.VersionXML()} lat="${this.lat}" lon="${this.lon}">
${tags} </node>
`
)
}
SaveExtraData(element) {
@ -269,11 +268,11 @@ export class OsmWay extends OsmObject {
nds += ' <nd ref="' + this.nodes[node] + '"/>\n'
}
return (
` <way id="${this.id}" ${header ?? ""} ${changesetId ? ('changeset="' + changesetId + '" ') : ""} ${this.VersionXML()}>
return ` <way id="${this.id}" ${header ?? ""} ${
changesetId ? 'changeset="' + changesetId + '" ' : ""
} ${this.VersionXML()}>
${nds}${tags} </way>
`
)
}
SaveExtraData(element, allNodes: OsmNode[]) {

View file

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

View file

@ -2,38 +2,26 @@
* 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 {UIEventSource} from "../UIEventSource"
import {QueryParameters} from "../Web/QueryParameters"
import { UIEventSource } from "../UIEventSource"
import { QueryParameters } from "../Web/QueryParameters"
import Constants from "../../Models/Constants"
import {Utils} from "../../Utils"
import { Utils } from "../../Utils"
class FeatureSwitchUtils {
static initSwitch(
key: string,
deflt:boolean,
documentation: string
): UIEventSource<boolean> {
const defaultValue = deflt
const queryParam = QueryParameters.GetQueryParameter(
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
return queryParam.sync(
(str) => (str === undefined ? defaultValue : str !== "false"),
[],
(b) => (b == defaultValue ? undefined : "" + b)
)
}
static initSwitch(key: string, deflt: boolean, documentation: string): UIEventSource<boolean> {
const defaultValue = deflt
const queryParam = QueryParameters.GetQueryParameter(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
return queryParam.sync(
(str) => (str === undefined ? defaultValue : str !== "false"),
[],
(b) => (b == defaultValue ? undefined : "" + b)
)
}
}
export class OsmConnectionFeatureSwitches {
public readonly featureSwitchFakeUser: UIEventSource<boolean>
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
*/
@ -154,7 +141,6 @@ export default class FeatureSwitchState extends OsmConnectionFeatureSwitches{
"Enable the export as GeoJSON and CSV button"
)
let testingDefaultValue = false
if (
this.featureSwitchApiURL.data !== "osm-test" &&

View file

@ -21,7 +21,9 @@ export class GeoLocationState {
* 'granted' means that it is granted
* '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

View file

@ -69,7 +69,7 @@ export default class LayerState {
new Tag("level", "" + 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'
}
console.log("Setting levels filter to", conditionsOrred)

View file

@ -1,20 +1,20 @@
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
import {OsmConnection} from "../Osm/OsmConnection"
import {MangroveIdentity} from "../Web/MangroveReviews"
import {Store, Stores, UIEventSource} from "../UIEventSource"
import { OsmConnection } from "../Osm/OsmConnection"
import { MangroveIdentity } from "../Web/MangroveReviews"
import { Store, Stores, UIEventSource } from "../UIEventSource"
import StaticFeatureSource from "../FeatureSource/Sources/StaticFeatureSource"
import {FeatureSource} from "../FeatureSource/FeatureSource"
import {Feature} from "geojson"
import {Utils} from "../../Utils"
import { FeatureSource } from "../FeatureSource/FeatureSource"
import { Feature } from "geojson"
import { Utils } from "../../Utils"
import translators from "../../assets/translators.json"
import codeContributors from "../../assets/contributors.json"
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 Locale from "../../UI/i18n/Locale"
import LinkToWeblate from "../../UI/Base/LinkToWeblate"
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,
@ -34,8 +34,8 @@ export default class UserRelatedState {
public readonly mangroveIdentity: MangroveIdentity
public readonly installedUserThemes: Store<string[]>
public readonly showAllQuestionsAtOnce: UIEventSource<boolean>
public static readonly SHOW_TAGS_VALUES = ["always","yes","full"] as const
public readonly showTags: UIEventSource<"no" | undefined | "always" | "yes" | "full">;
public static readonly SHOW_TAGS_VALUES = ["always", "yes", "full"] as const
public readonly showTags: UIEventSource<"no" | undefined | "always" | "yes" | "full">
public readonly homeLocation: FeatureSource
public readonly language: UIEventSource<string>
/**
@ -102,26 +102,22 @@ export default class UserRelatedState {
}
private static initUserRelatedState(): LayerConfig {
try{
return new LayerConfig(
<LayerConfigJson>usersettings,
"userinformationpanel"
)
}catch(e){
try {
return new LayerConfig(<LayerConfigJson>usersettings, "userinformationpanel")
} catch (e) {
return undefined
}
}
public GetUnofficialTheme(id: string):
| {
id: string
icon: string
title: any
shortDescription: any
definition?: any
isOfficial: boolean
}
id: string
icon: string
title: any
shortDescription: any
definition?: any
isOfficial: boolean
}
| undefined {
console.log("GETTING UNOFFICIAL THEME")
const pref = this.osmConnection.GetLongPreference("unofficial-theme-" + id)
@ -146,8 +142,8 @@ export default class UserRelatedState {
} catch (e) {
console.warn(
"Removing theme " +
id +
" as it could not be parsed from the preferences; the content is:",
id +
" as it could not be parsed from the preferences; the content is:",
str
)
pref.setData(null)
@ -184,7 +180,7 @@ export default class UserRelatedState {
}
private InitializeLanguage(availableLanguages?: string[]) {
this.language.addCallbackAndRunD(language => Locale.language.setData(language))
this.language.addCallbackAndRunD((language) => Locale.language.setData(language))
Locale.language.addCallback((currentLanguage) => {
if (Locale.showLinkToWeblate.data) {
return true // Disable auto switching as we are in translators mode
@ -262,7 +258,8 @@ export default class UserRelatedState {
_theme: layout?.id,
_backend: this.osmConnection.Backend(),
_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) {
@ -297,13 +294,13 @@ export default class UserRelatedState {
const zenLinks: { link: string; id: string }[] = Utils.NoNull([
hasMissingTheme
? {
id: "theme:" + layout.id,
link: LinkToWeblate.hrefToWeblateZen(
language,
"themes",
layout.id
),
}
id: "theme:" + layout.id,
link: LinkToWeblate.hrefToWeblateZen(
language,
"themes",
layout.id
),
}
: undefined,
...missingLayers.map((id) => ({
id: "layer:" + id,
@ -375,7 +372,7 @@ export default class UserRelatedState {
// Language is managed seperately
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 {TagsFilter} from "./TagsFilter"
import { Tag } from "./Tag"
import { TagsFilter } from "./TagsFilter"
export class RegexTag extends TagsFilter {
public readonly key: RegExp | string
@ -39,7 +39,6 @@ export class RegexTag extends TagsFilter {
return fromTag === possibleRegex
}
return possibleRegex.test(fromTag)
}
private static source(r: string | RegExp) {
@ -155,7 +154,7 @@ export class RegexTag extends TagsFilter {
matchesProperties(tags: Record<string, string | number | boolean>): boolean {
if (typeof this.key === "string") {
let value = tags[this.key]
if(!value || value === ""){
if (!value || value === "") {
// No tag is known, so we assume the empty string
// If this regexTag matches the empty string, we return true, otherwise false
// (Note: if inverted, we must reverse this)
@ -176,7 +175,7 @@ export class RegexTag extends TagsFilter {
return !this.invert
}
}
if(typeof value !== "string"){
if (typeof value !== "string") {
value = JSON.stringify(value)
}
return RegexTag.doesMatch(value, this.value) != this.invert
@ -188,7 +187,7 @@ export class RegexTag extends TagsFilter {
}
if (RegexTag.doesMatch(key, this.key)) {
let value = tags[key] ?? ""
if(typeof value !== "string"){
if (typeof value !== "string") {
value = JSON.stringify(value)
}
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!
return true
}
if(typeof foundValue !== "string"){
if(foundValue === true && (this.value === "true" || this.value === "yes" )){
if (typeof foundValue !== "string") {
if (foundValue === true && (this.value === "true" || this.value === "yes")) {
return true
}
if(foundValue === false && (this.value === "false" || this.value === "no" )){
if (foundValue === false && (this.value === "false" || this.value === "no")) {
return true
}
foundValue = ""+foundValue
foundValue = "" + foundValue
}
return foundValue === this.value
}

View file

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

View file

@ -416,7 +416,7 @@ class MappedStore<TIn, T> extends Store<T> {
this._upstreamPingCount = upstreamListenerHandler?.pingCount
this._extraStores = extraStores
this.registerCallbacksToUpstream()
if(onDestroy !== undefined){
if (onDestroy !== undefined) {
onDestroy(() => this.unregisterFromUpstream())
}
}
@ -698,7 +698,11 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
* srcSeen // => 21
* 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)
}
/**

View file

@ -76,7 +76,8 @@ export default class FeatureReviews {
) {
const centerLonLat = GeoOperations.centerpointCoordinates(feature)
;[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"
if (feature.geometry.type === "Point") {
@ -103,7 +104,7 @@ export default class FeatureReviews {
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()

View file

@ -1,8 +1,8 @@
import ThemeViewState from "../../Models/ThemeViewState";
import Hash from "./Hash";
import ThemeViewState from "../../Models/ThemeViewState"
import Hash from "./Hash"
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.
@ -15,27 +15,27 @@ export default class ThemeViewStateHashActor {
* @param state
*/
constructor(state: ThemeViewState) {
this._state = state;
this._state = state
// First of all, try to recover the selected element
if (Hash.hash.data) {
const hash = Hash.hash.data
this.loadStateFromHash(hash)
Hash.hash.setData(hash) // reapply the previous hash
state.indexedFeatures.featuresById.addCallbackAndRunD(_ => {
let unregister = this.loadSelectedElementFromHash(hash);
state.indexedFeatures.featuresById.addCallbackAndRunD((_) => {
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
return unregister
})
}
// Register a hash change listener to correctly handle the back button
Hash.hash.addCallback(hash => {
Hash.hash.addCallback((hash) => {
if (!!hash) {
// There is still a hash
// We _only_ have to (at most) close the overlays in this case
const parts = hash.split(";")
if(parts.indexOf("background") < 0){
if (parts.indexOf("background") < 0) {
state.guistate.backgroundLayerSelectionIsOpened.setData(false)
}
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.
// Note: these should use 'addCallback', not 'addCallbackAndRun'
state.selectedElement.addCallback(_ => this.setHash())
state.guistate.allToggles.forEach(({toggle, submenu}) => {
submenu?.addCallback(_ => this.setHash())
toggle.addCallback(_ => this.setHash());
state.selectedElement.addCallback((_) => this.setHash())
state.guistate.allToggles.forEach(({ toggle, submenu }) => {
submenu?.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
this.setHash()
}
/**
@ -103,8 +101,7 @@ export default class ThemeViewStateHashActor {
private loadStateFromHash(hash: string) {
const state = this._state
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) {
if (part === name) {
toggle.setData(true)
@ -125,17 +122,15 @@ export default class ThemeViewStateHashActor {
// If we arrive here, the loop above has not found any match
toggle.setData(false)
}
}
private setHash() {
const s = this._state
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) {
continue;
continue
}
h = name
if (submenu?.data) {
@ -147,9 +142,9 @@ export default class ThemeViewStateHashActor {
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) {
continue;
continue
}
if (h) {
h += ";" + name
@ -161,7 +156,6 @@ export default class ThemeViewStateHashActor {
}
}
Hash.hash.setData(h)
}
private back() {
@ -176,5 +170,4 @@ export default class ThemeViewStateHashActor {
return
}
}
}

View file

@ -1,4 +1,4 @@
import {Utils} from "../Utils"
import { Utils } from "../Utils"
import * as meta from "../package.json"
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
*/
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
private static isRetina(): boolean {
@ -131,8 +143,8 @@ export default class Constants {
return (
(window.matchMedia &&
(window.matchMedia(
"only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx), only screen and (min-resolution: 75.6dpcm)"
).matches ||
"only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx), only screen and (min-resolution: 75.6dpcm)"
).matches ||
window.matchMedia(
"only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 2/1), only screen and (min--moz-device-pixel-ratio: 2), only screen and (min-device-pixel-ratio: 2)"
).matches)) ||

View file

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

View file

@ -1,8 +1,8 @@
import LayerConfig from "./ThemeConfig/LayerConfig"
import {UIEventSource} from "../Logic/UIEventSource"
import { UIEventSource } from "../Logic/UIEventSource"
import UserRelatedState from "../Logic/State/UserRelatedState"
import {Utils} from "../Utils"
import {LocalStorageSource} from "../Logic/Web/LocalStorageSource"
import { Utils } from "../Utils"
import { LocalStorageSource } from "../Logic/Web/LocalStorageSource"
export type ThemeViewTabStates = typeof MenuState._themeviewTabs[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
*/
export class MenuState {
public static readonly _themeviewTabs = ["intro", "filters", "download", "copyright","share"] as const
public static readonly _menuviewTabs = ["about", "settings", "community", "privacy","advanced"] as const
public static readonly _themeviewTabs = [
"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 themeViewTabIndex: UIEventSource<number>
public readonly themeViewTab: UIEventSource<ThemeViewTabStates>
@ -23,11 +35,13 @@ export class MenuState {
public readonly menuViewTabIndex: UIEventSource<number>
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: {
toggle: UIEventSource<boolean>, name: string,
submenu?: UIEventSource<string>,
toggle: UIEventSource<boolean>
name: string
submenu?: UIEventSource<string>
showOverOthers?: boolean
}[]
@ -81,16 +95,19 @@ export class MenuState {
{
toggle: this.menuIsOpened,
name: "menu",
submenu: this.menuViewTab
}, {
submenu: this.menuViewTab,
},
{
toggle: this.themeIsOpened,
name: "theme-menu",
submenu: this.themeViewTab
}, {
submenu: this.themeViewTab,
},
{
toggle: this.backgroundLayerSelectionIsOpened,
name: "background",
showOverOthers: true
}]
showOverOthers: true,
},
]
}
public openFilterView(highlightLayer?: LayerConfig | string) {
@ -128,10 +145,13 @@ export class MenuState {
* Returns 'true' if at least one menu was opened
*/
public closeAll(): boolean {
const toggles = [this.menuIsOpened, this.themeIsOpened, this.backgroundLayerSelectionIsOpened]
const somethingIsOpen = toggles.some(t => t.data)
toggles.forEach(t => t.setData(false))
const toggles = [
this.menuIsOpened,
this.themeIsOpened,
this.backgroundLayerSelectionIsOpened,
]
const somethingIsOpen = toggles.some((t) => t.data)
toggles.forEach((t) => t.setData(false))
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 globallayers from "../assets/global-raster-layers.json"
import {BBox} from "../Logic/BBox"
import {Store, Stores} from "../Logic/UIEventSource"
import {GeoOperations} from "../Logic/GeoOperations"
import {RasterLayerProperties} from "./RasterLayerProperties"
import { BBox } from "../Logic/BBox"
import { Store, Stores } from "../Logic/UIEventSource"
import { GeoOperations } from "../Logic/GeoOperations"
import { RasterLayerProperties } from "./RasterLayerProperties"
export class AvailableRasterLayers {
public static EditorLayerIndex: (Feature<Polygon, EditorLayerIndexProperties> &
@ -47,8 +47,8 @@ export class AvailableRasterLayers {
type: "vector",
attribution: {
text: "Maptiler",
url: "https://www.maptiler.com/copyright/"
}
url: "https://www.maptiler.com/copyright/",
},
},
geometry: BBox.global.asGeometry(),
}
@ -63,8 +63,8 @@ export class AvailableRasterLayers {
type: "vector",
attribution: {
text: "Americana",
url: "https://github.com/ZeLonewolf/openstreetmap-americana/"
}
url: "https://github.com/ZeLonewolf/openstreetmap-americana/",
},
},
geometry: BBox.global.asGeometry(),
}
@ -151,7 +151,14 @@ export interface EditorLayerIndexProperties extends RasterLayerProperties {
* Whether the imagery name should be translated
*/
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.
*/

View file

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

View file

@ -1,9 +1,9 @@
import {Conversion} from "./Conversion"
import { Conversion } from "./Conversion"
import LayerConfig from "../LayerConfig"
import {LayerConfigJson} from "../Json/LayerConfigJson"
import { LayerConfigJson } from "../Json/LayerConfigJson"
import Translations from "../../../UI/i18n/Translations"
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> {
/**
@ -101,7 +101,7 @@ export default class CreateNoteImportLayer extends Conversion<LayerConfigJson, L
geoJsonZoomLevel: 10,
maxCacheAge: 0,
},
/* We need to set 'pass_all_features'
/* We need to set 'pass_all_features'
There are probably many note_import-layers, and we don't want the first one to gobble up all notes and then discard them...
*/
passAllFeatures: true,
@ -175,7 +175,7 @@ export default class CreateNoteImportLayer extends Conversion<LayerConfigJson, L
render: tr(t.nearbyImagesIntro),
},
{
id:"all_tags",
id: "all_tags",
render: "{all_tags()}",
metacondition: {
or: [
@ -184,7 +184,7 @@ export default class CreateNoteImportLayer extends Conversion<LayerConfigJson, L
"mapcomplete-show_debug=yes",
],
},
}
},
],
mapRendering: [
{

View file

@ -24,8 +24,8 @@ import { TagConfigJson } from "../Json/TagConfigJson"
import PointRenderingConfigJson from "../Json/PointRenderingConfigJson"
import LineRenderingConfigJson from "../Json/LineRenderingConfigJson"
import ValidationUtils from "./ValidationUtils"
import {RenderingSpecification} from "../../../UI/SpecialVisualization"
import {QuestionableTagRenderingConfigJson} from "../Json/QuestionableTagRenderingConfigJson"
import { RenderingSpecification } from "../../../UI/SpecialVisualization"
import { QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson"
class ExpandFilter extends DesugaringStep<LayerConfigJson> {
private static readonly predefinedFilters = ExpandFilter.load_filters()
@ -446,11 +446,11 @@ class DetectInline extends DesugaringStep<QuestionableTagRenderingConfigJson> {
information?: string[]
} {
if (json.freeform === undefined) {
return {result: json}
return { result: json }
}
let spec: Record<string, string>
if (typeof json.render === "string") {
spec = {"*": json.render}
spec = { "*": json.render }
} else {
spec = <Record<string, string>>json.render
}
@ -459,7 +459,7 @@ class DetectInline extends DesugaringStep<QuestionableTagRenderingConfigJson> {
if (spec[key].indexOf("<a ") >= 0) {
// We have a link element, it probably contains something that needs to be substituted...
// Let's play this safe and not inline it
return {result: json}
return { result: json }
}
const fullSpecification = SpecialVisualizations.constructSpecification(spec[key])
if (fullSpecification.length > 1) {
@ -467,19 +467,19 @@ class DetectInline extends DesugaringStep<QuestionableTagRenderingConfigJson> {
if (json.freeform.inline === true) {
errors.push(
"At " +
context +
": 'inline' is set, but the rendering contains a special visualisation...\n " +
spec[key]
context +
": 'inline' is set, but the rendering contains a special visualisation...\n " +
spec[key]
)
}
json = JSON.parse(JSON.stringify(json))
json.freeform.inline = false
return {result: json, errors}
return { result: json, errors }
}
}
json = JSON.parse(JSON.stringify(json))
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.some((tr) => tr["id"] === "leftover-questions")
) {
return {result: json}
return { result: json }
}
json = JSON.parse(JSON.stringify(json))
const allSpecials: Exclude<RenderingSpecification, string>[] = []
@ -521,8 +521,8 @@ export class AddQuestionBox extends DesugaringStep<LayerConfigJson> {
if (noLabels.length > 1) {
errors.push(
"At " +
context +
": multiple 'questions'-visualisations found which would show _all_ questions. Don't do this"
context +
": multiple 'questions'-visualisations found which would show _all_ questions. Don't do this"
)
}
@ -546,24 +546,24 @@ export class AddQuestionBox extends DesugaringStep<LayerConfigJson> {
if (blacklisted?.length > 0 && used?.length > 0) {
errors.push(
"At " +
context +
": the {questions()}-special rendering only supports either a blacklist OR a whitelist, but not both." +
"\n Whitelisted: " +
used.join(", ") +
"\n Blacklisted: " +
blacklisted.join(", ")
context +
": the {questions()}-special rendering only supports either a blacklist OR a whitelist, but not both." +
"\n Whitelisted: " +
used.join(", ") +
"\n Blacklisted: " +
blacklisted.join(", ")
)
}
for (const usedLabel of used) {
if (!allLabels.has(usedLabel)) {
errors.push(
"At " +
context +
": this layers specifies a special question element for label `" +
usedLabel +
"`, but this label doesn't exist.\n" +
" Available labels are " +
Array.from(allLabels).join(", ")
context +
": this layers specifies a special question element for label `" +
usedLabel +
"`, but this label doesn't exist.\n" +
" Available labels are " +
Array.from(allLabels).join(", ")
)
}
seen.add(usedLabel)
@ -619,7 +619,7 @@ export class AddEditingElements extends DesugaringStep<LayerConfigJson> {
if (json.allowSplit && !ValidationUtils.hasSpecialVisualisation(json, "split_button")) {
json.tagRenderings.push({
id: "split-button",
render: {"*": "{split_button()}"},
render: { "*": "{split_button()}" },
})
delete json.allowSplit
}
@ -627,13 +627,13 @@ export class AddEditingElements extends DesugaringStep<LayerConfigJson> {
if (json.allowMove && !ValidationUtils.hasSpecialVisualisation(json, "move_button")) {
json.tagRenderings.push({
id: "move-button",
render: {"*": "{move_button()}"},
render: { "*": "{move_button()}" },
})
}
if (json.deletion && !ValidationUtils.hasSpecialVisualisation(json, "delete_button")) {
json.tagRenderings.push({
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")) {
const trc: TagRenderingConfigJson = {
id: "all-tags",
render: {"*": "{all_tags()}"},
render: { "*": "{all_tags()}" },
metacondition: {
or: [
@ -663,7 +663,7 @@ export class AddEditingElements extends DesugaringStep<LayerConfigJson> {
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> {
constructor() {
super("sets the fullNodeDatabase-bit if needed",
super(
"sets the fullNodeDatabase-bit if needed",
["fullNodeDatabase"],
"SetFullNodeDatabase")
"SetFullNodeDatabase"
)
}
convert(json: LayerConfigJson, context: string): {
result: LayerConfigJson;
errors?: string[];
warnings?: string[];
convert(
json: LayerConfigJson,
context: string
): {
result: LayerConfigJson
errors?: string[]
warnings?: string[]
information?: string[]
} {
const needsSpecial = json.tagRenderings?.some(tr => {
if (typeof tr === "string") {
return false
}
const specs = ValidationUtils.getSpecialVisualisations(<TagRenderingConfigJson>tr)
return specs?.some(sp => sp.needsNodeDatabase)
}) ?? false
const needsSpecial =
json.tagRenderings?.some((tr) => {
if (typeof tr === "string") {
return false
}
const specs = ValidationUtils.getSpecialVisualisations(<TagRenderingConfigJson>tr)
return specs?.some((sp) => sp.needsNodeDatabase)
}) ?? false
if (!needsSpecial) {
return {result: json}
return { result: json }
}
return {
result: {...json, fullNodeDatabase: true},
information: ["Layer " + json.id + " needs the fullNodeDatabase"]
};
result: { ...json, fullNodeDatabase: true },
information: ["Layer " + json.id + " needs the fullNodeDatabase"],
}
}
}
@ -1203,12 +1209,12 @@ export class AddMiniMap extends DesugaringStep<LayerConfigJson> {
convert(layerConfig: LayerConfigJson, context: string): { result: LayerConfigJson } {
if (!layerConfig.tagRenderings || layerConfig.source === "special") {
return {result: layerConfig}
return { result: layerConfig }
}
const state = this._state
const hasMinimap = ValidationUtils.hasSpecialVisualisation(layerConfig, "minimap")
if (!hasMinimap) {
layerConfig = {...layerConfig}
layerConfig = { ...layerConfig }
layerConfig.tagRenderings = [...layerConfig.tagRenderings]
const minimap = state.tagRenderings.get("minimap")
if (minimap === undefined) {

View file

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

View file

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

View file

@ -103,7 +103,11 @@ export default class DependencyCalculator {
currentLine = i // Leak the state...
currentKey = key
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)
obj.properties[key] = JSON.stringify(result)
} catch (e) {}

View file

@ -31,7 +31,6 @@ export default interface LineRenderingConfigJson {
*/
lineCap?: "round" | "square" | "butt" | string | TagRenderingConfigJson
/**
* The color to fill a polygon with.
* 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
}
public defaultIcon(): BaseUIElement | undefined {

View file

@ -1,16 +1,16 @@
import PointRenderingConfigJson from "./Json/PointRenderingConfigJson"
import TagRenderingConfig from "./TagRenderingConfig"
import {TagsFilter} from "../../Logic/Tags/TagsFilter"
import {TagUtils} from "../../Logic/Tags/TagUtils"
import {Utils} from "../../Utils"
import { TagsFilter } from "../../Logic/Tags/TagsFilter"
import { TagUtils } from "../../Logic/Tags/TagUtils"
import { Utils } from "../../Utils"
import Svg from "../../Svg"
import WithContextLoader from "./WithContextLoader"
import {Store} from "../../Logic/UIEventSource"
import { Store } from "../../Logic/UIEventSource"
import BaseUIElement from "../../UI/BaseUIElement"
import {FixedUiElement} from "../../UI/Base/FixedUiElement"
import { FixedUiElement } from "../../UI/Base/FixedUiElement"
import Img from "../../UI/Base/Img"
import Combine from "../../UI/Base/Combine"
import {VariableUiElement} from "../../UI/Base/VariableUIElement"
import { VariableUiElement } from "../../UI/Base/VariableUIElement"
export default class PointRenderingConfig extends WithContextLoader {
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)) {
const iconKey = iconPath.substr(Utils.assets_path.length)
if (Svg.All[iconKey] === undefined) {
@ -168,7 +168,7 @@ export default class PointRenderingConfig extends WithContextLoader {
noFullWidth?: boolean
}
): BaseUIElement {
tags = tags ?? {id: "node/-1"}
tags = tags ?? { id: "node/-1" }
let defaultPin: BaseUIElement = undefined
if (this.label === undefined) {
defaultPin = Svg.teardrop_with_hole_green_svg()

View file

@ -1,20 +1,23 @@
import {Translation, TypedTranslation} from "../../UI/i18n/Translation"
import {TagsFilter} from "../../Logic/Tags/TagsFilter"
import { Translation, TypedTranslation } from "../../UI/i18n/Translation"
import { TagsFilter } from "../../Logic/Tags/TagsFilter"
import Translations from "../../UI/i18n/Translations"
import {TagUtils, UploadableTag} from "../../Logic/Tags/TagUtils"
import {And} from "../../Logic/Tags/And"
import {Utils} from "../../Utils"
import {Tag} from "../../Logic/Tags/Tag"
import { TagUtils, UploadableTag } from "../../Logic/Tags/TagUtils"
import { And } from "../../Logic/Tags/And"
import { Utils } from "../../Utils"
import { Tag } from "../../Logic/Tags/Tag"
import BaseUIElement from "../../UI/BaseUIElement"
import Combine from "../../UI/Base/Combine"
import Title from "../../UI/Base/Title"
import Link from "../../UI/Base/Link"
import List from "../../UI/Base/List"
import {MappingConfigJson, QuestionableTagRenderingConfigJson,} from "./Json/QuestionableTagRenderingConfigJson"
import {FixedUiElement} from "../../UI/Base/FixedUiElement"
import {Paragraph} from "../../UI/Base/Paragraph"
import {
MappingConfigJson,
QuestionableTagRenderingConfigJson,
} from "./Json/QuestionableTagRenderingConfigJson"
import { FixedUiElement } from "../../UI/Base/FixedUiElement"
import { Paragraph } from "../../UI/Base/Paragraph"
import Svg from "../../Svg"
import Validators, {ValidatorType} from "../../UI/InputElement/Validators";
import Validators, { ValidatorType } from "../../UI/InputElement/Validators"
export interface Mapping {
readonly if: UploadableTag
@ -118,9 +121,9 @@ export default class TagRenderingConfig {
this.question = Translations.T(json.question, translationKey + ".question")
this.questionhint = Translations.T(json.questionHint, translationKey + ".questionHint")
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(
json.metacondition ?? {and: []},
json.metacondition ?? { and: [] },
`${context}.metacondition`
)
if (json.freeform) {
@ -537,11 +540,8 @@ export default class TagRenderingConfig {
}
}
if (
this.freeform?.key === undefined ||
tags[this.freeform.key] !== undefined
) {
return {then: this.render}
if (this.freeform?.key === undefined || tags[this.freeform.key] !== undefined) {
return { then: this.render }
}
return undefined
@ -681,13 +681,13 @@ export default class TagRenderingConfig {
)
}
const and = TagUtils.FlattenMultiAnswer([...selectedMappings, ...unselectedMappings])
if(and.and.length === 0){
if (and.and.length === 0) {
return undefined
}
return and
} else {
// 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") {
return !m.hideInAnswer
}
@ -695,7 +695,9 @@ export default class TagRenderingConfig {
return !isHidden
})
// 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) {
return new And([
new Tag(this.freeform.key, freeformValue),
@ -711,7 +713,7 @@ export default class TagRenderingConfig {
freeformValue,
singleSelectedMapping,
multiSelectedMapping,
currentProperties
currentProperties,
})
return undefined
}
@ -757,7 +759,7 @@ export default class TagRenderingConfig {
if (m.ifnot !== undefined) {
msgs.push(
"Unselecting this answer will add " +
m.ifnot.asHumanString(true, false, {})
m.ifnot.asHumanString(true, false, {})
)
}
return msgs
@ -787,12 +789,12 @@ export default class TagRenderingConfig {
this.description,
this.question !== undefined
? new Combine([
"The question is ",
new FixedUiElement(this.question.txt).SetClass("font-bold bold"),
])
"The question is ",
new FixedUiElement(this.question.txt).SetClass("font-bold bold"),
])
: new FixedUiElement(
"This tagrendering has no question and is thus read-only"
).SetClass("italic"),
"This tagrendering has no question and is thus read-only"
).SetClass("italic"),
new Combine(withRender),
mappings,
condition,

View file

@ -21,7 +21,10 @@ export default class WithContextLoader {
if (deflt === 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") {
const shared = SharedTagRenderings.SharedTagRendering.get(v)

View file

@ -1,23 +1,27 @@
import LayoutConfig from "./ThemeConfig/LayoutConfig"
import {SpecialVisualizationState} from "../UI/SpecialVisualization"
import {Changes} from "../Logic/Osm/Changes"
import {ImmutableStore, Store, UIEventSource} from "../Logic/UIEventSource"
import {FeatureSource, IndexedFeatureSource, WritableFeatureSource,} from "../Logic/FeatureSource/FeatureSource"
import {OsmConnection} from "../Logic/Osm/OsmConnection"
import {ExportableMap, MapProperties} from "./MapProperties"
import { SpecialVisualizationState } from "../UI/SpecialVisualization"
import { Changes } from "../Logic/Osm/Changes"
import { ImmutableStore, Store, UIEventSource } from "../Logic/UIEventSource"
import {
FeatureSource,
IndexedFeatureSource,
WritableFeatureSource,
} from "../Logic/FeatureSource/FeatureSource"
import { OsmConnection } from "../Logic/Osm/OsmConnection"
import { ExportableMap, MapProperties } from "./MapProperties"
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 {Map as MlMap} from "maplibre-gl"
import { Map as MlMap } from "maplibre-gl"
import InitialMapPositioning from "../Logic/Actors/InitialMapPositioning"
import {MapLibreAdaptor} from "../UI/Map/MapLibreAdaptor"
import {GeoLocationState} from "../Logic/State/GeoLocationState"
import { MapLibreAdaptor } from "../UI/Map/MapLibreAdaptor"
import { GeoLocationState } from "../Logic/State/GeoLocationState"
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 LayerConfig from "./ThemeConfig/LayerConfig"
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 StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource"
import FeaturePropertiesStore from "../Logic/FeatureSource/Actors/FeaturePropertiesStore"
@ -28,24 +32,24 @@ import TitleHandler from "../Logic/Actors/TitleHandler"
import ChangeToElementsActor from "../Logic/Actors/ChangeToElementsActor"
import PendingChangesUploader from "../Logic/Actors/PendingChangesUploader"
import SelectedElementTagsUpdater from "../Logic/Actors/SelectedElementTagsUpdater"
import {BBox} from "../Logic/BBox"
import { BBox } from "../Logic/BBox"
import Constants from "./Constants"
import Hotkeys from "../UI/Base/Hotkeys"
import Translations from "../UI/i18n/Translations"
import {GeoIndexedStoreForLayer} from "../Logic/FeatureSource/Actors/GeoIndexedStore"
import {LastClickFeatureSource} from "../Logic/FeatureSource/Sources/LastClickFeatureSource"
import {MenuState} from "./MenuState"
import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore"
import { LastClickFeatureSource } from "../Logic/FeatureSource/Sources/LastClickFeatureSource"
import { MenuState } from "./MenuState"
import MetaTagging from "../Logic/MetaTagging"
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 ShowOverlayRasterLayer from "../UI/Map/ShowOverlayRasterLayer"
import {Utils} from "../Utils"
import {EliCategory} from "./RasterLayerProperties"
import { Utils } from "../Utils"
import { EliCategory } from "./RasterLayerProperties"
import BackgroundLayerResetter from "../Logic/Actors/BackgroundLayerResetter"
import SaveFeatureSourceToLocalStorage from "../Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage"
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,
"Wether or not overlayer layer " + rasterInfo.id + " is shown"
)
const state = {isDisplayed}
const state = { isDisplayed }
overlayLayerStates.set(rasterInfo.id, 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
*/
if (this.layout.layers.some(l => l._needsFullNodeDatabase)) {
if (this.layout.layers.some((l) => l._needsFullNodeDatabase)) {
this.fullNodeDatabase = new FullNodeDatabaseSource()
}
@ -176,18 +179,18 @@ export default class ThemeViewState implements SpecialVisualizationState {
let currentViewIndex = 0
this.currentView = new StaticFeatureSource(
this.mapProperties.bounds.map((bbox) => {
if (!bbox) {
return empty
}
currentViewIndex++
return <Feature[]>[bbox.asGeoJson({
zoom: this.mapProperties.zoom.data,
...this.mapProperties.location.data,
id: "current_view"
}
)];
if (!bbox) {
return empty
}
)
currentViewIndex++
return <Feature[]>[
bbox.asGeoJson({
zoom: this.mapProperties.zoom.data,
...this.mapProperties.location.data,
id: "current_view",
}),
]
})
)
this.featuresInView = new BBoxFeatureSource(layoutSource, this.mapProperties.bounds)
this.dataIsLoading = layoutSource.isLoading
@ -355,7 +358,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
private initHotkeys() {
Hotkeys.RegisterHotkey(
{nomod: "Escape", onUp: true},
{ nomod: "Escape", onUp: true },
Translations.t.hotkeyDocumentation.closeSidebar,
() => {
this.selectedElement.setData(undefined)
@ -376,7 +379,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
)
Hotkeys.RegisterHotkey(
{shift: "O"},
{ shift: "O" },
Translations.t.hotkeyDocumentation.selectMapnik,
() => {
this.mapProperties.rasterLayer.setData(AvailableRasterLayers.osmCarto)
@ -395,17 +398,17 @@ export default class ThemeViewState implements SpecialVisualizationState {
}
Hotkeys.RegisterHotkey(
{nomod: "O"},
{ nomod: "O" },
Translations.t.hotkeyDocumentation.selectOsmbasedmap,
() => setLayerCategory("osmbasedmap")
)
Hotkeys.RegisterHotkey({nomod: "M"}, Translations.t.hotkeyDocumentation.selectMap, () =>
Hotkeys.RegisterHotkey({ nomod: "M" }, Translations.t.hotkeyDocumentation.selectMap, () =>
setLayerCategory("map")
)
Hotkeys.RegisterHotkey(
{nomod: "P"},
{ nomod: "P" },
Translations.t.hotkeyDocumentation.selectAerial,
() => setLayerCategory("photo")
)
@ -473,10 +476,10 @@ export default class ThemeViewState implements SpecialVisualizationState {
),
range: new StaticFeatureSource(
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) {
const bbox = new BBox(this.layout.lockLocation)
@ -487,12 +490,19 @@ export default class ThemeViewState implements SpecialVisualizationState {
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) {
const params = MetaTagging.createExtraFuncParams(this)
this.featureProperties.trackFeatureSource(specialLayers.current_view)
specialLayers.current_view.features.addCallbackAndRunD(features => {
MetaTagging.addMetatags(features, params, currentViewLayer, this.layout, this.osmObjectDownloader, this.featureProperties)
specialLayers.current_view.features.addCallbackAndRunD((features) => {
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) {
// We did _unselect_ an item - we always remove the lastclick-object
this.lastClickObject.features.setData([])

View file

@ -123,8 +123,15 @@ export class Unit {
)
)
if(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(", ")}`
if (
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)
}

View file

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

View file

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

View file

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

View file

@ -1,13 +1,12 @@
<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
*/
export let selected: UIEventSource<boolean>;
let _c: boolean = selected.data ?? true;
export let selected: UIEventSource<boolean>
let _c: boolean = selected.data ?? true
$: selected.setData(_c)
</script>
<input type="checkbox" bind:checked={_c} />

View file

@ -3,84 +3,82 @@
* 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)
*/
import { Store } from "../../Logic/UIEventSource";
import { onDestroy } from "svelte";
import { Store } from "../../Logic/UIEventSource"
import { onDestroy } from "svelte"
let mainElem: HTMLElement;
export let hideSignal: Store<any>;
function hide(){
mainElem.style.visibility = "hidden";
let mainElem: HTMLElement
export let hideSignal: Store<any>
function hide() {
mainElem.style.visibility = "hidden"
}
if (hideSignal) {
onDestroy(hideSignal.addCallbackD(() => {
console.log("Received hide signal")
hide()
return true;
}));
onDestroy(
hideSignal.addCallbackD(() => {
console.log("Received hide signal")
hide()
return true
})
)
}
$: {
mainElem?.addEventListener("click",_ => hide())
mainElem?.addEventListener("touchstart",_ => hide())
}
</script>
$: {
mainElem?.addEventListener("click", (_) => hide())
mainElem?.addEventListener("touchstart", (_) => hide())
}
</script>
<div bind:this={mainElem} class="absolute bottom-0 right-0 w-full h-full">
<div id="hand-container" class="pointer-events-none">
<img src="./assets/svg/hand.svg"/>
<img src="./assets/svg/hand.svg" />
</div>
</div>
<style>
@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 */
0% {
opacity: 0;
transform: rotate(-30deg);
}
6% {
opacity: 1;
transform: rotate(-30deg);
}
12% {
opacity: 1;
transform: rotate(-45deg);
}
24% {
opacity: 1;
transform: rotate(-00deg);
}
30% {
opacity: 1;
transform: rotate(-30deg);
}
36% {
opacity: 0;
transform: rotate(-30deg);
}
100% {
opacity: 0;
transform: rotate(-30deg);
}
@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 */
0% {
opacity: 0;
transform: rotate(-30deg);
}
6% {
opacity: 1;
transform: rotate(-30deg);
}
#hand-container {
position: absolute;
width: 2rem;
left: calc(50% + 4rem);
top: calc(50%);
opacity: 0.7;
animation: hand-drag-animation 4s ease-in-out infinite;
transform-origin: 50% 125%;
12% {
opacity: 1;
transform: rotate(-45deg);
}
24% {
opacity: 1;
transform: rotate(-00deg);
}
30% {
opacity: 1;
transform: rotate(-30deg);
}
36% {
opacity: 0;
transform: rotate(-30deg);
}
100% {
opacity: 0;
transform: rotate(-30deg);
}
}
#hand-container {
position: absolute;
width: 2rem;
left: calc(50% + 4rem);
top: calc(50%);
opacity: 0.7;
animation: hand-drag-animation 4s ease-in-out infinite;
transform-origin: 50% 125%;
}
</style>

View file

@ -1,15 +1,14 @@
<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
*/
export let value: UIEventSource<number>;
let i: number = value.data;
export let value: UIEventSource<number>
let i: number = value.data
$: value.setData(i)
</script>
<select bind:value={i} >
<slot></slot>
<select bind:value={i}>
<slot />
</select>

View file

@ -1,31 +1,36 @@
<script lang="ts">
import {createEventDispatcher} from "svelte";
import {XCircleIcon} from "@rgossiaux/svelte-heroicons/solid";
import { createEventDispatcher } from "svelte"
import { XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
/**
* The slotted element will be shown on top, with a lower-opacity border
*/
const dispatch = createEventDispatcher<{ close }>();
/**
* The slotted element will be shown on top, with a lower-opacity border
*/
const dispatch = createEventDispatcher<{ close }>()
</script>
<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="rounded-xl h-full">
<slot></slot>
</div>
<slot name="close-button">
<!-- 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")}>
<XCircleIcon/>
</div>
</slot>
<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="rounded-xl h-full">
<slot />
</div>
<slot name="close-button">
<!-- 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")}
>
<XCircleIcon />
</div>
</slot>
</div>
</div>
<style>
.content {
height: calc( 100vh - 2rem );
height: calc(100vh - 2rem);
border-radius: 0.5rem;
overflow-x: auto;
box-shadow: 0 0 1rem #00000088;

View file

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

View file

@ -1,23 +1,24 @@
<script lang="ts">
import { UIEventSource } from "../../Logic/UIEventSource";
import { onDestroy } from "svelte";
import { UIEventSource } from "../../Logic/UIEventSource"
import { onDestroy } from "svelte"
/**
* 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>;
let _c = condition.data;
onDestroy(condition.addCallback(c => {
/* Do _not_ abbreviate this as `.addCallback(c => _c = c)`. This is the same as writing `.addCallback(c => {return _c = c})`,
export let condition: UIEventSource<boolean>
let _c = condition.data
onDestroy(
condition.addCallback((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`! */
_c = c;
return false
}))
_c = c
return false
})
)
</script>
{#if _c}
<slot></slot>
{:else}
<slot name="else"></slot>
<slot />
{:else}
<slot name="else" />
{/if}

View file

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

View file

@ -1,18 +1,20 @@
<script lang="ts">
import { UIEventSource } from "../../Logic/UIEventSource";
import { onDestroy } from "svelte";
import { UIEventSource } from "../../Logic/UIEventSource"
import { onDestroy } from "svelte"
/**
* 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>;
let _c = !condition.data;
onDestroy(condition.addCallback(c => {
_c = !c;
return false
}))
export let condition: UIEventSource<boolean>
let _c = !condition.data
onDestroy(
condition.addCallback((c) => {
_c = !c
return false
})
)
</script>
{#if _c}
<slot></slot>
<slot />
{/if}

View file

@ -1,13 +1,13 @@
<script>
import ToSvelte from "./ToSvelte.svelte";
import Svg from "../../Svg";
import ToSvelte from "./ToSvelte.svelte"
import Svg from "../../Svg"
</script>
<div class="pl-2 p-1 flex">
<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 class="ml-2">
<slot></slot>
<slot />
</div>
</div>

View file

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

View file

@ -1,47 +1,45 @@
<script lang="ts">
import Loading from "./Loading.svelte";
import type { OsmServiceState } from "../../Logic/Osm/OsmConnection";
import { Translation } from "../i18n/Translation";
import Translations from "../i18n/Translations";
import Tr from "./Tr.svelte";
import {OsmConnection} from "../../Logic/Osm/OsmConnection";
import {ImmutableStore, UIEventSource} from "../../Logic/UIEventSource";
import Loading from "./Loading.svelte"
import type { OsmServiceState } from "../../Logic/Osm/OsmConnection"
import { Translation } from "../i18n/Translation"
import Translations from "../i18n/Translations"
import Tr from "./Tr.svelte"
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
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.
*/
export let ignoreLoading: boolean = false
let loadingStatus = state.osmConnection.loadingStatus;
let badge = state.featureSwitches?.featureSwitchUserbadge ?? new ImmutableStore(true);
const t = Translations.t.general;
let loadingStatus = state.osmConnection.loadingStatus
let badge = state.featureSwitches?.featureSwitchUserbadge ?? new ImmutableStore(true)
const t = Translations.t.general
const offlineModes: Partial<Record<OsmServiceState, Translation>> = {
offline: t.loginFailedOfflineMode,
unreachable: t.loginFailedUnreachableMode,
unknown: t.loginFailedUnreachableMode,
readonly: t.loginFailedReadonlyMode
};
const apiState = state.osmConnection.apiIsOnline;
readonly: t.loginFailedReadonlyMode,
}
const apiState = state.osmConnection.apiIsOnline
</script>
{#if $badge}
{#if !ignoreLoading && $loadingStatus === "loading"}
<slot name="loading">
<Loading></Loading>
<Loading />
</slot>
{:else if $loadingStatus === "error"}
<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]} />
</div>
{:else if $loadingStatus === "logged-in"}
<slot></slot>
<slot />
{:else if $loadingStatus === "not-attempted"}
<slot name="not-logged-in">
</slot>
<slot name="not-logged-in" />
{/if}
{/if}

View file

@ -1,14 +1,16 @@
<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
*/
const dispatch = createEventDispatcher()
const dispatch = createEventDispatcher()
export let cls = ""
</script>
<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} >
<slot/>
<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}
>
<slot />
</button>

View file

@ -1,20 +1,26 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";
import { XCircleIcon } from "@rgossiaux/svelte-heroicons/solid";
import { createEventDispatcher } from "svelte"
import { XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
/**
* The slotted element will be shown on the right side
*/
const dispatch = createEventDispatcher<{ close }>();
const dispatch = createEventDispatcher<{ close }>()
</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">
<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 />
</div>
</slot>
<slot></slot>
<slot />
</div>
</div>

View file

@ -1,21 +1,24 @@
<script lang="ts">
/**
* Wrapper around 'subtleButton' with an arrow pointing to the right
* See also: BackButton
*/
import SubtleButton from "./SubtleButton.svelte";
import {ChevronRightIcon} from "@rgossiaux/svelte-heroicons/solid";
import {createEventDispatcher} from "svelte";
/**
* Wrapper around 'subtleButton' with an arrow pointing to the right
* See also: BackButton
*/
import SubtleButton from "./SubtleButton.svelte"
import { ChevronRightIcon } from "@rgossiaux/svelte-heroicons/solid"
import { createEventDispatcher } from "svelte"
const dispatch = createEventDispatcher<{ click }>()
export let clss : string= ""
const dispatch = createEventDispatcher<{ click }>()
export let clss: string = ""
</script>
<SubtleButton 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">
<slot/>
<ChevronRightIcon class="w-12 h-12"/>
</div>
<SubtleButton
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">
<slot />
<ChevronRightIcon class="w-12 h-12" />
</div>
</SubtleButton>

View file

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

View file

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

View file

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

View file

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

View file

@ -1,121 +1,126 @@
<script lang="ts">
/**
* 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 {UIEventSource} from "../../Logic/UIEventSource";
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@rgossiaux/svelte-headlessui"
import { UIEventSource } from "../../Logic/UIEventSource"
export let tab: UIEventSource<number>;
let tabElements: HTMLElement[] = [];
$: tabElements[$tab]?.click();
$: {
if (tabElements[tab.data]) {
window.setTimeout(() => tabElements[tab.data].click(), 50)
}
export let tab: UIEventSource<number>
let tabElements: HTMLElement[] = []
$: tabElements[$tab]?.click()
$: {
if (tabElements[tab.data]) {
window.setTimeout(() => tabElements[tab.data].click(), 50)
}
}
</script>
<div class="tabbedgroup w-full h-full">
<TabGroup 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">
<TabList class="flex flex-wrap">
{#if $$slots.title1}
<Tab class={({selected}) => "tab "+(selected ? "primary" : "")}>
<div bind:this={tabElements[0]} class="flex">
<slot name="title0">
Tab 0
</slot>
</div>
</Tab>
{/if}
{#if $$slots.title1}
<Tab class={({selected}) => "tab "+(selected ? "primary" : "")}>
<div bind:this={tabElements[1]} class="flex">
<slot name="title1"/>
</div>
</Tab>
{/if}
{#if $$slots.title2}
<Tab class={({selected}) => "tab "+(selected ? "primary" : "")}>
<div bind:this={tabElements[2]} class="flex">
<slot name="title2"/>
</div>
</Tab>
{/if}
{#if $$slots.title3}
<Tab class={({selected}) => "tab "+(selected ? "primary" : "")}>
<div bind:this={tabElements[3]} class="flex">
<slot name="title3"/>
</div>
</Tab>
{/if}
{#if $$slots.title4}
<Tab class={({selected}) => "tab "+(selected ? "primary" : "")}>
<div bind:this={tabElements[4]} class="flex">
<slot name="title4"/>
</div>
</Tab>
{/if}
</TabList>
<slot name="post-tablist"/>
</div>
<div class="overflow-y-auto normal-background">
<TabPanels defaultIndex={$tab}>
<TabPanel>
<slot name="content0">
<div>Empty</div>
</slot>
</TabPanel>
<TabPanel>
<slot name="content1"/>
</TabPanel>
<TabPanel>
<slot name="content2"/>
</TabPanel>
<TabPanel>
<slot name="content3"/>
</TabPanel>
<TabPanel>
<slot name="content4"/>
</TabPanel>
</TabPanels>
</div>
</TabGroup>
<TabGroup
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">
<TabList class="flex flex-wrap">
{#if $$slots.title1}
<Tab class={({ selected }) => "tab " + (selected ? "primary" : "")}>
<div bind:this={tabElements[0]} class="flex">
<slot name="title0">Tab 0</slot>
</div>
</Tab>
{/if}
{#if $$slots.title1}
<Tab class={({ selected }) => "tab " + (selected ? "primary" : "")}>
<div bind:this={tabElements[1]} class="flex">
<slot name="title1" />
</div>
</Tab>
{/if}
{#if $$slots.title2}
<Tab class={({ selected }) => "tab " + (selected ? "primary" : "")}>
<div bind:this={tabElements[2]} class="flex">
<slot name="title2" />
</div>
</Tab>
{/if}
{#if $$slots.title3}
<Tab class={({ selected }) => "tab " + (selected ? "primary" : "")}>
<div bind:this={tabElements[3]} class="flex">
<slot name="title3" />
</div>
</Tab>
{/if}
{#if $$slots.title4}
<Tab class={({ selected }) => "tab " + (selected ? "primary" : "")}>
<div bind:this={tabElements[4]} class="flex">
<slot name="title4" />
</div>
</Tab>
{/if}
</TabList>
<slot name="post-tablist" />
</div>
<div class="overflow-y-auto normal-background">
<TabPanels defaultIndex={$tab}>
<TabPanel>
<slot name="content0">
<div>Empty</div>
</slot>
</TabPanel>
<TabPanel>
<slot name="content1" />
</TabPanel>
<TabPanel>
<slot name="content2" />
</TabPanel>
<TabPanel>
<slot name="content3" />
</TabPanel>
<TabPanel>
<slot name="content4" />
</TabPanel>
</TabPanels>
</div>
</TabGroup>
</div>
<style>
.tabbedgroup {
max-height: 100vh;
height: 100%;
}
.tabbedgroup {
max-height: 100vh;
height: 100%;
}
:global(.tab) {
margin: 0.25rem;
padding: 0.25rem;
padding-left: 0.75rem;
padding-right: 0.75rem;
border-radius: 1rem;
}
:global(.tab) {
margin: 0.25rem;
padding: 0.25rem;
padding-left: 0.75rem;
padding-right: 0.75rem;
border-radius: 1rem;
}
:global(.tab .flex) {
align-items: center;
gap: 0.25rem;
}
:global(.tab .flex) {
align-items: center;
gap: 0.25rem;
}
:global(.tab span|div) {
align-items: center;
gap: 0.25rem;
display: flex;
}
:global(.tab span|div) {
align-items: center;
gap: 0.25rem;
display: flex;
}
:global(.tab-selected svg) {
fill: var(--catch-detail-color-contrast);
}
:global(.tab-selected svg) {
fill: var(--catch-detail-color-contrast);
}
:global(.tab-unselected) {
background-color: var(--background-color) !important;
color: var(--foreground-color) !important;
}
:global(.tab-unselected) {
background-color: var(--background-color) !important;
color: var(--foreground-color) !important;
}
</style>

View file

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

View file

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

View file

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

View file

@ -1,25 +1,32 @@
<script lang="ts">
import Locale from "../i18n/Locale";
import LinkToWeblate from "./LinkToWeblate";
import Locale from "../i18n/Locale"
import LinkToWeblate from "./LinkToWeblate"
/**
* 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 linkOnMobile = Locale.showLinkOnMobile;
let language = Locale.language;
let linkToWeblate = Locale.showLinkToWeblate
let linkOnMobile = Locale.showLinkOnMobile
let language = Locale.language
</script>
{#if !!context && context.indexOf(":") > 0}
{#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" />
</a>
{: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" />
</a>
{/if}

View file

@ -1,80 +1,88 @@
<script lang="ts">
import {Store, UIEventSource} from "../../Logic/UIEventSource";
import type {RasterLayerPolygon} from "../../Models/RasterLayers";
import {AvailableRasterLayers} from "../../Models/RasterLayers";
import {createEventDispatcher, onDestroy} from "svelte";
import Svg from "../../Svg";
import {Map as MlMap} from "maplibre-gl"
import type {MapProperties} from "../../Models/MapProperties";
import OverlayMap from "../Map/OverlayMap.svelte";
import RasterLayerPicker from "../Map/RasterLayerPicker.svelte";
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import type { RasterLayerPolygon } from "../../Models/RasterLayers"
import { AvailableRasterLayers } from "../../Models/RasterLayers"
import { createEventDispatcher, onDestroy } from "svelte"
import Svg from "../../Svg"
import { Map as MlMap } from "maplibre-gl"
import type { MapProperties } from "../../Models/MapProperties"
import OverlayMap from "../Map/OverlayMap.svelte"
import RasterLayerPicker from "../Map/RasterLayerPicker.svelte"
export let mapproperties: MapProperties
export let normalMap: UIEventSource<MlMap>
/**
* The current background (raster) layer of the polygon.
* This is undefined if a vector layer is used
*/
let rasterLayer: UIEventSource<RasterLayerPolygon | undefined> = mapproperties.rasterLayer
let name = rasterLayer.data?.properties?.name
let icon = Svg.satellite_svg()
onDestroy(rasterLayer.addCallback(polygon => {
name = polygon.properties?.name
}))
/**
* The layers that this component can offer as a choice.
*/
export let availableRasterLayers: Store<RasterLayerPolygon[]>
export let mapproperties: MapProperties
export let normalMap: UIEventSource<MlMap>
/**
* The current background (raster) layer of the polygon.
* This is undefined if a vector layer is used
*/
let rasterLayer: UIEventSource<RasterLayerPolygon | undefined> = mapproperties.rasterLayer
let name = rasterLayer.data?.properties?.name
let icon = Svg.satellite_svg()
onDestroy(
rasterLayer.addCallback((polygon) => {
name = polygon.properties?.name
})
)
/**
* The layers that this component can offer as a choice.
*/
export let availableRasterLayers: Store<RasterLayerPolygon[]>
let raster0 = new UIEventSource<RasterLayerPolygon>(undefined)
let raster0 = new UIEventSource<RasterLayerPolygon>(undefined)
let raster1 = new UIEventSource<RasterLayerPolygon>(undefined)
let raster1 = new UIEventSource<RasterLayerPolygon>(undefined)
let currentLayer: RasterLayerPolygon
let currentLayer: RasterLayerPolygon
function updatedAltLayer() {
const available = availableRasterLayers.data
const current = rasterLayer.data
const defaultLayer = AvailableRasterLayers.maplibre
const firstOther = available.find(l => l !== defaultLayer)
const secondOther = available.find(l => l !== defaultLayer && l !== firstOther)
raster0.setData(firstOther === current ? defaultLayer : firstOther)
raster1.setData(secondOther === current ? defaultLayer : secondOther)
function updatedAltLayer() {
const available = availableRasterLayers.data
const current = rasterLayer.data
const defaultLayer = AvailableRasterLayers.maplibre
const firstOther = available.find((l) => l !== defaultLayer)
const secondOther = available.find((l) => l !== defaultLayer && l !== firstOther)
raster0.setData(firstOther === current ? defaultLayer : firstOther)
raster1.setData(secondOther === current ? defaultLayer : secondOther)
}
updatedAltLayer()
onDestroy(mapproperties.rasterLayer.addCallbackAndRunD(updatedAltLayer))
onDestroy(availableRasterLayers.addCallbackAndRunD(updatedAltLayer))
function use(rasterLayer: UIEventSource<RasterLayerPolygon>): () => void {
return () => {
currentLayer = undefined
mapproperties.rasterLayer.setData(rasterLayer.data)
}
}
updatedAltLayer()
onDestroy(mapproperties.rasterLayer.addCallbackAndRunD(updatedAltLayer))
onDestroy(availableRasterLayers.addCallbackAndRunD(updatedAltLayer))
function use(rasterLayer: UIEventSource<RasterLayerPolygon>): (() => void) {
return () => {
currentLayer = undefined
mapproperties.rasterLayer.setData(rasterLayer.data)
}
}
const dispatch = createEventDispatcher<{ copyright_clicked }>()
const dispatch = createEventDispatcher<{ copyright_clicked }>()
</script>
<div class="flex items-end opacity-50 hover:opacity-100">
<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"
on:click={use(raster0)}>
<OverlayMap placedOverMap={normalMap} placedOverMapProperties={mapproperties} rasterLayer={raster0}/>
</button>
<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}/>
</button>
</div>
<div class="text-sm flex flex-col gap-y-1 h-fit ml-1">
<div class="low-interaction rounded p-1 w-64">
<RasterLayerPicker availableLayers={availableRasterLayers} value={mapproperties.rasterLayer}></RasterLayerPicker>
</div>
<button class="small" on:click={() => dispatch("copyright_clicked")}>
© OpenStreetMap
</button>
<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" on:click={use(raster0)}>
<OverlayMap
placedOverMap={normalMap}
placedOverMapProperties={mapproperties}
rasterLayer={raster0}
/>
</button>
<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}
/>
</button>
</div>
<div class="text-sm flex flex-col gap-y-1 h-fit ml-1">
<div class="low-interaction rounded p-1 w-64">
<RasterLayerPicker
availableLayers={availableRasterLayers}
value={mapproperties.rasterLayer}
/>
</div>
<button class="small" on:click={() => dispatch("copyright_clicked")}>© OpenStreetMap</button>
</div>
</div>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,53 +1,50 @@
<script lang="ts">
import {OsmConnection} from "../../Logic/Osm/OsmConnection"
import {UIEventSource} from "../../Logic/UIEventSource"
import * as themeOverview from "../../assets/generated/theme_overview.json"
import {Utils} from "../../Utils"
import ThemesList from "./ThemesList.svelte"
import Translations from "../i18n/Translations"
import {LayoutInformation} from "../../Models/ThemeConfig/LayoutConfig"
import LoginToggle from "../Base/LoginToggle.svelte";
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
import { UIEventSource } from "../../Logic/UIEventSource"
import * as themeOverview from "../../assets/generated/theme_overview.json"
import { Utils } from "../../Utils"
import ThemesList from "./ThemesList.svelte"
import Translations from "../i18n/Translations"
import { LayoutInformation } from "../../Models/ThemeConfig/LayoutConfig"
import LoginToggle from "../Base/LoginToggle.svelte"
export let search: UIEventSource<string>
export let state: { osmConnection: OsmConnection }
export let onMainScreen: boolean = true
export let search: UIEventSource<string>
export let state: { osmConnection: OsmConnection }
export let onMainScreen: boolean = true
const prefix = "mapcomplete-hidden-theme-"
const hiddenThemes: LayoutInformation[] = (themeOverview["default"] ?? themeOverview)?.filter(
(layout) => layout.hideFromOverview
) ?? []
const userPreferences = state.osmConnection.preferencesHandler.preferences
const t = Translations.t.general.morescreen
const prefix = "mapcomplete-hidden-theme-"
const hiddenThemes: LayoutInformation[] =
(themeOverview["default"] ?? themeOverview)?.filter((layout) => layout.hideFromOverview) ?? []
const userPreferences = state.osmConnection.preferencesHandler.preferences
const t = Translations.t.general.morescreen
let knownThemesId: string[]
$: knownThemesId = Utils.NoNull(
Object.keys($userPreferences)
.filter((key) => key.startsWith(prefix))
.map((key) => key.substring(prefix.length, key.length - "-enabled".length))
)
$: console.log("Known theme ids:", knownThemesId)
$: knownThemes = hiddenThemes.filter((theme) => knownThemesId.includes(theme.id))
let knownThemesId: string[]
$: knownThemesId = Utils.NoNull(
Object.keys($userPreferences)
.filter((key) => key.startsWith(prefix))
.map((key) => key.substring(prefix.length, key.length - "-enabled".length))
)
$: console.log("Known theme ids:", knownThemesId)
$: knownThemes = hiddenThemes.filter((theme) => knownThemesId.includes(theme.id))
</script>
<LoginToggle {state}>
<ThemesList
hideThemes={false}
isCustom={false}
{onMainScreen}
{search}
{state}
themes={knownThemes}
>
<svelte:fragment slot="title">
<h3>{t.previouslyHiddenTitle.toString()}</h3>
<p>
{t.hiddenExplanation.Subs({
hidden_discovered: knownThemes.length.toString(),
total_hidden: hiddenThemes.length.toString(),
})}
</p>
</svelte:fragment>
</ThemesList>
<ThemesList
hideThemes={false}
isCustom={false}
{onMainScreen}
{search}
{state}
themes={knownThemes}
>
<svelte:fragment slot="title">
<h3>{t.previouslyHiddenTitle.toString()}</h3>
<p>
{t.hiddenExplanation.Subs({
hidden_discovered: knownThemes.length.toString(),
total_hidden: hiddenThemes.length.toString(),
})}
</p>
</svelte:fragment>
</ThemesList>
</LoginToggle>

View file

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

View file

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

View file

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

View file

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

View file

@ -1,35 +1,34 @@
<script context="module" lang="ts">
export interface Theme {
id: string
icon: string
title: any
shortDescription: any
definition?: any
mustHaveLanguage?: boolean
hideFromOverview: boolean
keywords?: any[]
}
export interface Theme {
id: string
icon: string
title: any
shortDescription: any
definition?: any
mustHaveLanguage?: boolean
hideFromOverview: boolean
keywords?: any[]
}
</script>
<script lang="ts">
import {UIEventSource} from "../../Logic/UIEventSource"
import Svg from "../../Svg"
import ToSvelte from "../Base/ToSvelte.svelte"
import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte";
import { UIEventSource } from "../../Logic/UIEventSource"
import Svg from "../../Svg"
import ToSvelte from "../Base/ToSvelte.svelte"
import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte"
export let search: UIEventSource<string>
export let search: UIEventSource<string>
const t = Translations.t.general.morescreen
const t = Translations.t.general.morescreen
</script>
<div class="w-full">
<h5>{t.noMatchingThemes.toString()}</h5>
<div class="flex justify-center">
<button on:click={() => search.setData("")}>
<ToSvelte construct={Svg.search_disable_svg().SetClass("w-6 mr-2")}/>
<Tr slot="message" t={t.noSearch}/>
</button>
</div>
<h5>{t.noMatchingThemes.toString()}</h5>
<div class="flex justify-center">
<button on:click={() => search.setData("")}>
<ToSvelte construct={Svg.search_disable_svg().SetClass("w-6 mr-2")} />
<Tr slot="message" t={t.noSearch} />
</button>
</div>
</div>

View file

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

View file

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

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