Chore: reformat all files with prettier
This commit is contained in:
parent
5757ae5dea
commit
d008dcb54d
214 changed files with 8926 additions and 8196 deletions
|
@ -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)
|
||||
|
|
|
@ -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]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -30,7 +30,7 @@ export default class FeatureSourceMerger implements IndexedFeatureSource {
|
|||
}
|
||||
|
||||
public addSource(source: FeatureSource) {
|
||||
if(!source){
|
||||
if (!source) {
|
||||
return
|
||||
}
|
||||
this._sources.push(source)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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>) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 []
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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[]) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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" &&
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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])
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
/**
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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)) ||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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 ?? []))
|
||||
|
|
|
@ -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: [
|
||||
{
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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) {}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -417,7 +417,6 @@ export default class LayerConfig extends WithContextLoader {
|
|||
)
|
||||
}
|
||||
this.popupInFloatover = json.popupInFloatover ?? false
|
||||
|
||||
}
|
||||
|
||||
public defaultIcon(): BaseUIElement | undefined {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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([])
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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([
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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}
|
||||
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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") {
|
||||
|
|
|
@ -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"
|
||||
/>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue