Chore: reformat all files with prettier

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

View file

@ -1,6 +1,6 @@
import { Store, UIEventSource } from "../UIEventSource"
import { Utils } from "../../Utils"
import {RasterLayerPolygon, RasterLayerUtils,} from "../../Models/RasterLayers"
import { 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.

View file

@ -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()

View file

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

View file

@ -5,7 +5,7 @@ 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 { 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
}
@ -158,7 +160,10 @@ 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
}
@ -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 ||
@ -468,7 +479,15 @@ export class ExtraFunctions {
.SetClass("flex-col")
.AsMarkdown()
static readonly types = ["distanceTo", "overlapWith", "enclosingFeatures", "intersectionsWith", "closest", "closestn", "get"] as const
static readonly types = [
"distanceTo",
"overlapWith",
"enclosingFeatures",
"intersectionsWith",
"closest",
"closestn",
"get",
] as const
private static readonly allFuncs = [
new DistanceToFunc(),
new OverlapFunc(),
@ -479,8 +498,9 @@ export class ExtraFunctions {
new GetParsed(),
]
public static constructHelpers(params: ExtraFuncParams): Record<ExtraFuncType, (feature: Feature) => Function> {
public static constructHelpers(
params: ExtraFuncParams
): Record<ExtraFuncType, (feature: Feature) => Function> {
const record: Record<string, (feature: GeoJSONFeature) => Function> = {}
for (const f of ExtraFunctions.allFuncs) {
if (this.types.indexOf(<any>f._name) < 0) {

View file

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

View file

@ -60,7 +60,10 @@ export default class PerLayerFeatureSourceSplitter<T extends FeatureSource = Fea
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)
console.error(
"PerLayerFeatureSourceSplitter got a layer without a source:",
layer.layerDef.id
)
continue
}
if (layer.layerDef.source.osmTags.matchesProperties(f.properties)) {

View file

@ -6,7 +6,7 @@ import { UIEventSource } from "../../UIEventSource"
import { FeatureSource, IndexedFeatureSource } from "../FeatureSource"
import { ChangeDescription, ChangeDescriptionTools } from "../../Osm/Actions/ChangeDescription"
import { Feature } from "geojson"
import {Utils} from "../../../Utils";
import { Utils } from "../../../Utils"
export default class ChangeGeometryApplicator implements FeatureSource {
public readonly features: UIEventSource<Feature[]> = new UIEventSource<Feature[]>([])

View file

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

View file

@ -10,7 +10,7 @@ import FeatureSourceMerger from "./FeatureSourceMerger"
import DynamicGeoJsonTileSource from "../TiledFeatureSource/DynamicGeoJsonTileSource"
import { BBox } from "../../BBox"
import LocalStorageFeatureSource from "../TiledFeatureSource/LocalStorageFeatureSource"
import FullNodeDatabaseSource from "../TiledFeatureSource/FullNodeDatabaseSource";
import FullNodeDatabaseSource from "../TiledFeatureSource/FullNodeDatabaseSource"
/**
* This source will fetch the needed data from various sources for the given layout.
@ -41,7 +41,7 @@ export default class LayoutSource extends FeatureSourceMerger {
(l) =>
new LocalStorageFeatureSource(backend, l.id, 15, mapProperties, {
isActive: isDisplayed(l.id),
maxAge: l.maxAgeOfCache
maxAge: l.maxAgeOfCache,
})
)
@ -127,7 +127,7 @@ export default class LayoutSource extends FeatureSourceMerger {
backend,
isActive,
patchRelations: true,
fullNodeDatabase
fullNodeDatabase,
})
}

View file

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

View file

@ -8,13 +8,7 @@ import {Feature} from "geojson"
export default class StaticFeatureSource<T extends Feature = Feature> implements FeatureSource<T> {
public readonly features: Store<T[]>
constructor(
features:
| Store<T[]>
| T[]
| { features: T[] }
| { features: Store<T[]> }
) {
constructor(features: Store<T[]> | T[] | { features: T[] } | { features: Store<T[]> }) {
if (features === undefined) {
throw "Static feature source received undefined as source"
}

View file

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

View file

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

View file

@ -1,11 +1,10 @@
import { OsmNode, OsmObject, OsmWay } from "../../Osm/OsmObject"
import { UIEventSource } from "../../UIEventSource"
import {BBox} from "../../BBox";
import StaticFeatureSource from "../Sources/StaticFeatureSource";
import {Tiles} from "../../../Models/TileRange";
import { 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[]>>()

View file

@ -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) => {
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]+/));
}
)
return features.filter((f) => !f.properties.id.match(/(node|way)\/-[0-9]+/))
})
),
mapProperties,
options

View file

@ -408,7 +408,10 @@ export class GeoOperations {
/**
* Calculates line intersection between two features.
*/
public static LineIntersections(feature: Feature<LineString | MultiLineString | Polygon | MultiPolygon>, otherFeature: Feature<LineString | MultiLineString | Polygon | MultiPolygon>): [number, number][] {
public static LineIntersections(
feature: Feature<LineString | MultiLineString | Polygon | MultiPolygon>,
otherFeature: Feature<LineString | MultiLineString | Polygon | MultiPolygon>
): [number, number][] {
return turf
.lineIntersect(feature, otherFeature)
.features.map((p) => <[number, number]>p.geometry.coordinates)
@ -421,7 +424,6 @@ export class GeoOperations {
* @param zoomlevel
*/
public static spreadIntoBboxes(features: Feature[], zoomlevel: number): Map<number, Feature[]> {
const perBbox = new Map<number, Feature[]>()
for (const feature of features) {
@ -707,14 +709,14 @@ export class GeoOperations {
type: "Feature",
geometry: {
type: "LineString",
coordinates: [a, b]
}
}, distanceMeter, {units: "meters"}
coordinates: [a, b],
},
},
distanceMeter,
{ units: "meters" }
).geometry.coordinates
}
/**
* Returns 'true' if one feature contains the other feature
*

View file

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

View file

@ -7,8 +7,8 @@ import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"
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
@ -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,7 +205,8 @@ export default class MetaTagging {
* @param layerId
* @private
*/
private static createFunctionForFeature([key, code, isStrict]: [string, string, boolean],
private static createFunctionForFeature(
[key, code, isStrict]: [string, string, boolean],
helperFunctions: Record<ExtraFuncType, (feature: Feature) => Function>,
layerId: string = "unkown layer"
): ((feature: Feature, propertiesStore?: UIEventSource<any>) => void) | undefined {
@ -210,10 +214,16 @@ export default class MetaTagging {
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
}
@ -276,9 +286,12 @@ export default class MetaTagging {
return undefined
}
let functions: ((feature: Feature, propertiesStore?: UIEventSource<any>) => void)[] = MetaTagging.retaggingFuncCache.get(layer.id)
let functions: ((feature: Feature, propertiesStore?: UIEventSource<any>) => void)[] =
MetaTagging.retaggingFuncCache.get(layer.id)
if (functions === undefined) {
functions = calculatedTags.map(spec => this.createFunctionForFeature(spec, helpers, layer.id))
functions = calculatedTags.map((spec) =>
this.createFunctionForFeature(spec, helpers, layer.id)
)
MetaTagging.retaggingFuncCache.set(layer.id, functions)
}

View file

@ -7,14 +7,17 @@ import CreateWayWithPointReuseAction, {MergePointConfig} from "./CreateWayWithPo
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 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[],

View file

@ -9,9 +9,9 @@ import {FeatureSource, IndexedFeatureSource} from "../../FeatureSource/FeatureSo
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[]

View file

@ -8,7 +8,7 @@ import { And } from "../../Tags/And"
import { Tag } from "../../Tags/Tag"
import { OsmId } from "../../../Models/OsmFeature"
import { Utils } from "../../../Utils"
import OsmObjectDownloader from "../OsmObjectDownloader";
import OsmObjectDownloader from "../OsmObjectDownloader"
export default class DeleteAction extends OsmChangeAction {
private readonly _softDeletionTags: TagsFilter
@ -72,7 +72,9 @@ 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") {
// already deleted in the meantime - no more changes necessary

View file

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

View file

@ -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,

View file

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

View file

@ -198,7 +198,6 @@ export class OsmNode extends OsmObject {
this.LoadData(extraData)
}
/**
*
* const obj = new OsmNode(1234)
@ -213,11 +212,11 @@ export class OsmNode extends OsmObject {
*/
ChangesetXML(changesetId: string, header?: string): string {
let tags = this.TagsXML()
return (
` <node id="${this.id}" ${header ?? ""} ${changesetId ? (' changeset="' + changesetId+ '" ') : ""}${this.VersionXML()} lat="${this.lat}" lon="${this.lon}">
return ` <node id="${this.id}" ${header ?? ""} ${
changesetId ? ' changeset="' + changesetId + '" ' : ""
}${this.VersionXML()} lat="${this.lat}" lon="${this.lon}">
${tags} </node>
`
)
}
SaveExtraData(element) {
@ -269,11 +268,11 @@ export class OsmWay extends OsmObject {
nds += ' <nd ref="' + this.nodes[node] + '"/>\n'
}
return (
` <way id="${this.id}" ${header ?? ""} ${changesetId ? ('changeset="' + changesetId + '" ') : ""} ${this.VersionXML()}>
return ` <way id="${this.id}" ${header ?? ""} ${
changesetId ? 'changeset="' + changesetId + '" ' : ""
} ${this.VersionXML()}>
${nds}${tags} </way>
`
)
}
SaveExtraData(element, allNodes: OsmNode[]) {

View file

@ -316,7 +316,6 @@ export default class SimpleMetaTaggers {
(feature) => {
Utils.AddLazyProperty(feature.properties, "_surface", () => {
return "" + GeoOperations.surfaceAreaInSqMeters(feature)
})
return true

View file

@ -8,17 +8,9 @@ import Constants from "../../Models/Constants"
import { Utils } from "../../Utils"
class FeatureSwitchUtils {
static initSwitch(
key: string,
deflt:boolean,
documentation: string
): UIEventSource<boolean> {
static initSwitch(key: string, deflt: boolean, documentation: string): UIEventSource<boolean> {
const defaultValue = deflt
const queryParam = QueryParameters.GetQueryParameter(
key,
"" + defaultValue,
documentation
)
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(
@ -26,14 +18,10 @@ class FeatureSwitchUtils {
[],
(b) => (b == defaultValue ? undefined : "" + b)
)
}
}
export class OsmConnectionFeatureSwitches {
public readonly featureSwitchFakeUser: UIEventSource<boolean>
public readonly featureSwitchApiURL: UIEventSource<string>
@ -52,7 +40,6 @@ export class OsmConnectionFeatureSwitches {
}
}
export default class FeatureSwitchState extends OsmConnectionFeatureSwitches {
/**
* The layout that is being used in this run
@ -154,7 +141,6 @@ export default class FeatureSwitchState extends OsmConnectionFeatureSwitches{
"Enable the export as GeoJSON and CSV button"
)
let testingDefaultValue = false
if (
this.featureSwitchApiURL.data !== "osm-test" &&

View file

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

View file

@ -14,7 +14,7 @@ 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,
@ -35,7 +35,7 @@ export default class UserRelatedState {
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 readonly showTags: UIEventSource<"no" | undefined | "always" | "yes" | "full">
public readonly homeLocation: FeatureSource
public readonly language: UIEventSource<string>
/**
@ -103,11 +103,7 @@ export default class UserRelatedState {
private static initUserRelatedState(): LayerConfig {
try {
return new LayerConfig(
<LayerConfigJson>usersettings,
"userinformationpanel"
)
return new LayerConfig(<LayerConfigJson>usersettings, "userinformationpanel")
} catch (e) {
return undefined
}
@ -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) {

View file

@ -39,7 +39,6 @@ export class RegexTag extends TagsFilter {
return fromTag === possibleRegex
}
return possibleRegex.test(fromTag)
}
private static source(r: string | RegExp) {

View file

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

View file

@ -76,7 +76,8 @@ export default class FeatureReviews {
) {
const centerLonLat = GeoOperations.centerpointCoordinates(feature)
;[this._lon, this._lat] = centerLonLat
this._identity = mangroveIdentity ?? new MangroveIdentity(new UIEventSource<string>(undefined))
this._identity =
mangroveIdentity ?? new MangroveIdentity(new UIEventSource<string>(undefined))
const nameKey = options?.nameKey ?? "name"
if (feature.geometry.type === "Point") {

View file

@ -1,8 +1,8 @@
import ThemeViewState from "../../Models/ThemeViewState";
import Hash from "./Hash";
import ThemeViewState from "../../Models/ThemeViewState"
import Hash from "./Hash"
export default class ThemeViewStateHashActor {
private readonly _state: ThemeViewState;
private readonly _state: ThemeViewState
/**
* Converts the hash to the appropriate themeview state and, vice versa, sets the hash.
@ -15,22 +15,22 @@ 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
@ -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.selectedElement.addCallback((_) => this.setHash())
state.guistate.allToggles.forEach(({ toggle, submenu }) => {
submenu?.addCallback(_ => this.setHash())
toggle.addCallback(_ => this.setHash());
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()
}
/**
@ -104,7 +102,6 @@ export default class ThemeViewStateHashActor {
const state = this._state
const parts = hash.split(";")
outer: for (const { toggle, name, showOverOthers, submenu } of state.guistate.allToggles) {
for (const part of parts) {
if (part === name) {
toggle.setData(true)
@ -125,8 +122,6 @@ export default class ThemeViewStateHashActor {
// If we arrive here, the loop above has not found any match
toggle.setData(false)
}
}
private setHash() {
@ -135,7 +130,7 @@ export default class ThemeViewStateHashActor {
for (const { toggle, showOverOthers, name, submenu } of s.guistate.allToggles) {
if (showOverOthers || !toggle.data) {
continue;
continue
}
h = name
if (submenu?.data) {
@ -149,7 +144,7 @@ export default class ThemeViewStateHashActor {
for (const { toggle, showOverOthers, name, submenu } of s.guistate.allToggles) {
if (!showOverOthers || !toggle.data) {
continue;
continue
}
if (h) {
h += ";" + name
@ -161,7 +156,6 @@ export default class ThemeViewStateHashActor {
}
}
Hash.hash.setData(h)
}
private back() {
@ -176,5 +170,4 @@ export default class ThemeViewStateHashActor {
return
}
}
}

View file

@ -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 {

View file

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

View file

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

View file

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

View file

@ -297,7 +297,7 @@ 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)){
if (r.result["tagRenderings"]?.some((tr) => tr === undefined)) {
throw step.name + " introduced an undefined tagRendering"
}
errors.push(...(r.errors ?? []))

View file

@ -184,7 +184,7 @@ export default class CreateNoteImportLayer extends Conversion<LayerConfigJson, L
"mapcomplete-show_debug=yes",
],
},
}
},
],
mapRendering: [
{

View file

@ -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 => {
const needsSpecial =
json.tagRenderings?.some((tr) => {
if (typeof tr === "string") {
return false
}
const specs = ValidationUtils.getSpecialVisualisations(<TagRenderingConfigJson>tr)
return specs?.some(sp => sp.needsNodeDatabase)
return specs?.some((sp) => sp.needsNodeDatabase)
}) ?? false
if (!needsSpecial) {
return { result: json }
}
return {
result: { ...json, fullNodeDatabase: true },
information: ["Layer " + json.id + " needs the fullNodeDatabase"]
};
information: ["Layer " + json.id + " needs the fullNodeDatabase"],
}
}
}

View file

@ -15,10 +15,11 @@ export default class ValidationUtils {
return false
}
const spec = ValidationUtils.getSpecialVisualisations(<TagRenderingConfigJson>tagRendering)
return spec.some((vis) => vis.funcName === specialVisualisation);
}
) ?? false
const spec = ValidationUtils.getSpecialVisualisations(
<TagRenderingConfigJson>tagRendering
)
return spec.some((vis) => vis.funcName === specialVisualisation)
}) ?? false
)
}

View file

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

View file

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

View file

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

View file

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

View file

@ -10,11 +10,14 @@ 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 {
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
@ -537,10 +540,7 @@ export default class TagRenderingConfig {
}
}
if (
this.freeform?.key === undefined ||
tags[this.freeform.key] !== undefined
) {
if (this.freeform?.key === undefined || tags[this.freeform.key] !== undefined) {
return { then: this.render }
}
@ -687,7 +687,7 @@ export default class TagRenderingConfig {
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
}

View file

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

View file

@ -2,7 +2,11 @@ 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 {
FeatureSource,
IndexedFeatureSource,
WritableFeatureSource,
} from "../Logic/FeatureSource/FeatureSource"
import { OsmConnection } from "../Logic/Osm/OsmConnection"
import { ExportableMap, MapProperties } from "./MapProperties"
import LayerState from "../Logic/State/LayerState"
@ -45,7 +49,7 @@ 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"
/**
*
@ -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()
}
@ -180,14 +183,14 @@ export default class ThemeViewState implements SpecialVisualizationState {
return empty
}
currentViewIndex++
return <Feature[]>[bbox.asGeoJson({
return <Feature[]>[
bbox.asGeoJson({
zoom: this.mapProperties.zoom.data,
...this.mapProperties.location.data,
id: "current_view"
}
)];
}
)
id: "current_view",
}),
]
})
)
this.featuresInView = new BBoxFeatureSource(layoutSource, this.mapProperties.bounds)
this.dataIsLoading = layoutSource.isLoading
@ -476,7 +479,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
bbox === undefined ? empty : <Feature[]>[bbox.asGeoJson({ id: "range" })]
)
),
current_view: this.currentView
current_view: this.currentView,
}
if (this.layout?.lockLocation) {
const bbox = new BBox(this.layout.lockLocation)
@ -487,12 +490,19 @@ export default class ThemeViewState implements SpecialVisualizationState {
this.featureSwitches.featureSwitchIsTesting
)
}
const currentViewLayer = this.layout.layers.find(l => l.id === "current_view")
const currentViewLayer = this.layout.layers.find((l) => l.id === "current_view")
if (currentViewLayer?.tagRenderings?.length > 0) {
const params = MetaTagging.createExtraFuncParams(this)
this.featureProperties.trackFeatureSource(specialLayers.current_view)
specialLayers.current_view.features.addCallbackAndRunD(features => {
MetaTagging.addMetatags(features, params, currentViewLayer, this.layout, this.osmObjectDownloader, this.featureProperties)
specialLayers.current_view.features.addCallbackAndRunD((features) => {
MetaTagging.addMetatags(
features,
params,
currentViewLayer,
this.layout,
this.osmObjectDownloader,
this.featureProperties
)
})
}
@ -537,7 +547,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
})
}
{
this.selectedElement.addCallback(selected => {
this.selectedElement.addCallback((selected) => {
if (selected === undefined) {
// We did _unselect_ an item - we always remove the lastclick-object
this.lastClickObject.features.setData([])

View file

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

View file

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

View file

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

View file

@ -3,15 +3,18 @@
* 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";
import SubtleButton from "./SubtleButton.svelte"
import { ChevronLeftIcon } from "@rgossiaux/svelte-heroicons/solid"
import { createEventDispatcher } from "svelte"
const dispatch = createEventDispatcher<{ click }>()
export let clss = ""
</script>
<SubtleButton on:click={() => dispatch("click")} options={{extraClasses:clss+ " flex items-center"}}>
<SubtleButton
on:click={() => dispatch("click")}
options={{ extraClasses: clss + " flex items-center" }}
>
<ChevronLeftIcon class="w-12 h-12" slot="image" />
<slot slot="message" />
</SubtleButton>

View file

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

View file

@ -3,29 +3,30 @@
* 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>;
let mainElem: HTMLElement
export let hideSignal: Store<any>
function hide() {
mainElem.style.visibility = "hidden";
mainElem.style.visibility = "hidden"
}
if (hideSignal) {
onDestroy(hideSignal.addCallbackD(() => {
onDestroy(
hideSignal.addCallbackD(() => {
console.log("Received hide signal")
hide()
return true;
}));
return true
})
)
}
$: {
mainElem?.addEventListener("click",_ => hide())
mainElem?.addEventListener("touchstart",_ => hide())
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" />
@ -33,7 +34,6 @@ $: {
</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% {
@ -72,7 +72,6 @@ $: {
}
}
#hand-container {
position: absolute;
width: 2rem;
@ -82,5 +81,4 @@ $: {
animation: hand-drag-animation 4s ease-in-out infinite;
transform-origin: 50% 125%;
}
</style>

View file

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

View file

@ -1,28 +1,33 @@
<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 }>();
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="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>
<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")}>
<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);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,9 +1,9 @@
<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 = ""

View file

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

View file

@ -1,5 +1,5 @@
<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
@ -8,7 +8,9 @@
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} >
<button
on:click={(e) => dispatch("click", e)}
class={"rounded-full h-fit w-fit m-0.5 md:m-1 p-0.5 sm:p-1 pointer-events-auto " + cls}
>
<slot />
</button>

View file

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

View file

@ -3,16 +3,19 @@
* 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";
import SubtleButton from "./SubtleButton.svelte"
import { ChevronRightIcon } from "@rgossiaux/svelte-heroicons/solid"
import { createEventDispatcher } from "svelte"
const dispatch = createEventDispatcher<{ click }>()
export let clss: string = ""
</script>
<SubtleButton on:click={() => dispatch("click")} options={{extraClasses: clss+" flex items-center"}}>
<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 />

View file

@ -1,7 +1,6 @@
<script lang="ts">
import ToSvelte from "./ToSvelte.svelte";
import Svg from "../../Svg";
import ToSvelte from "./ToSvelte.svelte"
import Svg from "../../Svg"
export let generateShareData: () => {
text: string
@ -11,7 +10,7 @@ export let generateShareData: () => {
function share() {
if (!navigator.share) {
console.log("web share not supported")
return;
return
}
navigator
.share(generateShareData())
@ -22,10 +21,8 @@ function share(){
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")} />

View file

@ -1,7 +1,7 @@
<script lang="ts">
import {createEventDispatcher} from "svelte";
import BaseUIElement from "../BaseUIElement";
import Img from "./Img";
import { createEventDispatcher } from "svelte"
import BaseUIElement from "../BaseUIElement"
import Img from "./Img"
export let imageUrl: string | BaseUIElement = undefined
export let message: string | BaseUIElement = undefined
@ -10,19 +10,19 @@
extraClasses?: string
} = {}
let imgClasses = "block justify-center shrink-0 mr-4 " + (options?.imgSize ?? "h-11 w-11");
let imgClasses = "block justify-center shrink-0 mr-4 " + (options?.imgSize ?? "h-11 w-11")
const dispatch = createEventDispatcher<{ click }>()
</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>

View file

@ -5,10 +5,10 @@ import { VariableUiElement } from "./VariableUIElement"
import Lazy from "./Lazy"
import Loading from "./Loading"
import SvelteUIElement from "./SvelteUIElement"
import SubtleLink from "./SubtleLink.svelte";
import Translations from "../i18n/Translations";
import Combine from "./Combine";
import Img from "./Img";
import SubtleLink from "./SubtleLink.svelte"
import Translations from "../i18n/Translations"
import Combine from "./Combine"
import Img from "./Img"
/**
* @deprecated
@ -41,25 +41,27 @@ 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})
return new SvelteUIElement(SubtleLink, {
href: this.options.url,
newTab: this.options.newTab,
})
}
const classes = "button";
const message = Translations.W(this.message)?.SetClass("block overflow-ellipsis no-images flex-shrink");
let img;
const imgClasses = "block justify-center flex-none mr-4 " + (this.options?.imgSize ?? "h-11 w-11")
const classes = "button"
const message = Translations.W(this.message)?.SetClass(
"block overflow-ellipsis no-images flex-shrink"
)
let img
const imgClasses =
"block justify-center flex-none mr-4 " + (this.options?.imgSize ?? "h-11 w-11")
if ((this.imageUrl ?? "") === "") {
img = undefined;
} else if (typeof (this.imageUrl) === "string") {
img = undefined
} else if (typeof this.imageUrl === "string") {
img = new Img(this.imageUrl)?.SetClass(imgClasses)
} else {
img = this.imageUrl?.SetClass(imgClasses);
img = this.imageUrl?.SetClass(imgClasses)
}
const button = new Combine([
img,
message
]).SetClass("flex items-center group w-full")
const button = new Combine([img, message]).SetClass("flex items-center group w-full")
this.SetClass(classes)
return button

View file

@ -1,7 +1,7 @@
<script lang="ts">
import {onMount} from "svelte";
import BaseUIElement from "../BaseUIElement";
import Img from "./Img";
import { onMount } from "svelte"
import BaseUIElement from "../BaseUIElement"
import Img from "./Img"
export let imageUrl: string | BaseUIElement = undefined
export let href: string
@ -11,9 +11,8 @@
// 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,19 +26,18 @@
}
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>
<Img src={imageUrl} class={imgClasses} />
{:else}
<template bind:this={imgElem} />
{/if}

View file

@ -3,12 +3,12 @@
* 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();
export let tab: UIEventSource<number>
let tabElements: HTMLElement[] = []
$: tabElements[$tab]?.click()
$: {
if (tabElements[tab.data]) {
window.setTimeout(() => tabElements[tab.data].click(), 50)
@ -17,16 +17,21 @@
</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); }} }>
<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>
<slot name="title0">Tab 0</slot>
</div>
</Tab>
{/if}
@ -62,7 +67,6 @@
<slot name="post-tablist" />
</div>
<div class="overflow-y-auto normal-background">
<TabPanels defaultIndex={$tab}>
<TabPanel>
<slot name="content0">
@ -85,6 +89,7 @@
</div>
</TabGroup>
</div>
<style>
.tabbedgroup {
max-height: 100vh;

View file

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

View file

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

View file

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

View file

@ -1,13 +1,13 @@
<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 { 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 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>
@ -18,9 +18,11 @@
let rasterLayer: UIEventSource<RasterLayerPolygon | undefined> = mapproperties.rasterLayer
let name = rasterLayer.data?.properties?.name
let icon = Svg.satellite_svg()
onDestroy(rasterLayer.addCallback(polygon => {
onDestroy(
rasterLayer.addCallback((polygon) => {
name = polygon.properties?.name
}))
})
)
/**
* The layers that this component can offer as a choice.
*/
@ -36,8 +38,8 @@
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)
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)
}
@ -46,35 +48,41 @@
onDestroy(mapproperties.rasterLayer.addCallbackAndRunD(updatedAltLayer))
onDestroy(availableRasterLayers.addCallbackAndRunD(updatedAltLayer))
function use(rasterLayer: UIEventSource<RasterLayerPolygon>): (() => void) {
function use(rasterLayer: UIEventSource<RasterLayerPolygon>): () => void {
return () => {
currentLayer = undefined
mapproperties.rasterLayer.setData(rasterLayer.data)
}
}
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 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}/>
<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>
<RasterLayerPicker
availableLayers={availableRasterLayers}
value={mapproperties.rasterLayer}
/>
</div>
<button class="small" on:click={() => dispatch("copyright_clicked")}>
© OpenStreetMap
</button>
<button class="small" on:click={() => dispatch("copyright_clicked")}>© OpenStreetMap</button>
</div>
</div>

View file

@ -19,7 +19,7 @@ import ContributorCount from "../../Logic/ContributorCount"
import Img from "../Base/Img"
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 => {
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>"
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({
return Translations.t.general.attribution.attributionBackgroundLayerWithCopyright.Subs(
{
name: props.name,
copyright: bgAttr
})
copyright: bgAttr,
}
return Translations.t.general.attribution.attributionBackgroundLayer.Subs(props)
})),
)
}
return Translations.t.general.attribution.attributionBackgroundLayer.Subs(
props
)
})
),
maintainer,
dataContributors,

View file

@ -13,9 +13,9 @@ 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
@ -51,7 +48,8 @@ export default class ExtraLinkButton extends UIElement {
let link: BaseUIElement
const theme = this.state.layout?.id ?? ""
const basepath = window.location.host
const href = this.state.mapProperties.location.map((loc) => {
const href = this.state.mapProperties.location.map(
(loc) => {
const subs = {
...loc,
theme: theme,
@ -59,7 +57,9 @@ export default class ExtraLinkButton extends UIElement {
language: Locale.language.data,
}
return Utils.SubstituteKeys(c.href, subs)
}, [this.state.mapProperties.zoom])
},
[this.state.mapProperties.zoom]
)
let img: BaseUIElement = Svg.pop_out_svg()
if (c.icon !== undefined) {
@ -81,11 +81,19 @@ export default class ExtraLinkButton extends UIElement {
})
if (c.requirements?.has("no-welcome-message")) {
link = new Toggle(undefined, link, this.state.featureSwitches.featureSwitchWelcomeMessage)
link = new Toggle(
undefined,
link,
this.state.featureSwitches.featureSwitchWelcomeMessage
)
}
if (c.requirements?.has("welcome-message")) {
link = new Toggle(link, undefined, this.state.featureSwitches.featureSwitchWelcomeMessage)
link = new Toggle(
link,
undefined,
this.state.featureSwitches.featureSwitchWelcomeMessage
)
}
return link

View file

@ -1,61 +1,71 @@
<script lang="ts">/**
<script lang="ts">
/**
* The FilterView shows the various options to enable/disable a single layer or to only show a subset of the data.
*/
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";
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);
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);
return filteredLayer.appliedFilters.get(option.id)
}
let mainElem: HTMLElement;
let mainElem: HTMLElement
$: onDestroy(
highlightedLayer.addCallbackAndRun(highlightedLayer => {
highlightedLayer.addCallbackAndRun((highlightedLayer) => {
if (highlightedLayer === filteredLayer.layerDef.id) {
mainElem?.classList?.add("glowing-shadow");
mainElem?.classList?.add("glowing-shadow")
} else {
mainElem?.classList?.remove("glowing-shadow");
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>
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}
@ -65,14 +75,12 @@ $: onDestroy(
<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>
@ -82,9 +90,7 @@ $: onDestroy(
{/if}
{#if filter.options.length === 1 && filter.options[0].fields.length > 0}
<FilterviewWithFields id={filter.id} filteredLayer={filteredLayer}
option={filter.options[0]}></FilterviewWithFields>
<FilterviewWithFields id={filter.id} {filteredLayer} option={filter.options[0]} />
{/if}
{#if filter.options.length > 1}
@ -96,12 +102,9 @@ $: onDestroy(
{/each}
</Dropdown>
{/if}
</div>
{/each}
</div>
{/if}
</div>
{/if}

View file

@ -1,49 +1,50 @@
<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;
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 ?? "{}");
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> = {};
const properties: Record<string, string> = {}
for (const key in fieldValues) {
const v = fieldValues[key].data;
const v = fieldValues[key].data
if (v === undefined) {
properties[key] = undefined;
properties[key] = undefined
} else {
properties[key] = v;
properties[key] = v
}
}
appliedFilter?.setData(FilteredLayer.fieldsToString(properties));
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();
}));
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>

View file

@ -1,47 +1,44 @@
<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
let searchContents: string = ""
export let triggerSearch: UIEventSource<any> = new UIEventSource<any>(undefined)
onDestroy(triggerSearch.addCallback(_ => {
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();
}
);
Hotkeys.RegisterHotkey({ ctrl: "F" }, Translations.t.hotkeyDocumentation.selectSearch, () => {
inputElement?.focus()
inputElement?.select()
})
const dispatch = createEventDispatcher<{ searchCompleted, searchIsValid: boolean }>()
const dispatch = createEventDispatcher<{ searchCompleted; searchIsValid: boolean }>()
$: {
if (!searchContents?.trim()) {
dispatch("searchIsValid", false)
@ -50,31 +47,34 @@
}
}
async function performSearch() {
try {
isRunning = true;
searchContents = searchContents?.trim() ?? "";
isRunning = true
searchContents = searchContents?.trim() ?? ""
if (searchContents === "") {
return;
return
}
const result = await Geocoding.Search(searchContents, bounds.data);
const result = await Geocoding.Search(searchContents, bounds.data)
if (result.length == 0) {
feedback = Translations.t.general.search.nothing.txt;
return;
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));
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() ?? []);
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);
const found = layer.features.data.find((f) => f.properties.id === id)
selectedElement?.setData(found)
selectedLayer?.setData(layer.layer.layerDef)
}
}
if (clearAfterView) {
@ -83,22 +83,20 @@
dispatch("searchIsValid", false)
dispatch("searchCompleted")
} catch (e) {
console.error(e);
feedback = Translations.t.general.search.error.txt;
console.error(e)
feedback = Translations.t.general.search.error.txt
} finally {
isRunning = false;
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}>
<div class="alert" on:click={() => (feedback = undefined)}>
{feedback}
</div>
{:else}
@ -106,14 +104,13 @@
type="search"
class="w-full"
bind:this={inputElement}
on:keypress={keypr => keypr.key === "Enter" ? performSearch() : undefined}
on:keypress={(keypr) => (keypr.key === "Enter" ? performSearch() : undefined)}
bind:value={searchContents}
placeholder={Translations.t.general.search.search}>
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>
<ToSvelte construct={Svg.search_svg} />
</div>
</div>

View file

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

View file

@ -2,18 +2,19 @@
/**
* 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);
let selectedFloor: UIEventSource<string> = new UIEventSource<string>(undefined)
selectedFloor.stabilized(5).map(floor => {
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
@ -21,9 +22,11 @@
} else {
layerState.setLevelFilter(floor)
}
}, [floors, zoom])
},
[floors, zoom]
)
</script>
{#if $zoom >= maxZoom}
<FloorSelector {floors} value={selectedFloor} />
{/if}

View file

@ -1,25 +1,23 @@
<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 { 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 }>
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)}`
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">

View file

@ -1,17 +1,20 @@
<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:
@ -20,67 +23,67 @@
*
* This one is mostly used to insert new points, including when importing
*/
export let state: SpecialVisualizationState;
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 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 snappedTo: UIEventSource<string | undefined>
export let value: UIEventSource<{ lon: number, lat: number }>;
export let value: UIEventSource<{ lon: number; lat: number }>
if (value.data === undefined) {
value.setData(coordinate);
value.setData(coordinate)
}
let preciseLocation: UIEventSource<{ lon: number, lat: number }> = new UIEventSource<{
lon: number;
let preciseLocation: UIEventSource<{ lon: number; lat: number }> = new UIEventSource<{
lon: number
lat: number
}>(undefined);
}>(undefined)
const xyz = Tiles.embedded_tile(coordinate.lat, coordinate.lon, 16);
const map: UIEventSource<MlMap> = new UIEventSource<MlMap>(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,
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)
};
rasterLayer: UIEventSource.feedFrom(state.mapProperties.rasterLayer),
}
const featuresForLayer = state.perLayer.get(targetLayer.id)
if (featuresForLayer) {
new ShowDataLayer(map, {
layer: targetLayer,
features: featuresForLayer
features: featuresForLayer,
})
}
if (snapToLayers?.length > 0) {
const snapSources: FeatureSource[] = [];
for (const layerId of (snapToLayers ?? [])) {
const layer: FeatureSourceForLayer = state.perLayer.get(layerId);
snapSources.push(layer);
const snapSources: FeatureSource[] = []
for (const layerId of snapToLayers ?? []) {
const layer: FeatureSourceForLayer = state.perLayer.get(layerId)
snapSources.push(layer)
if (layer.features === undefined) {
continue;
continue
}
new ShowDataLayer(map, {
layer: layer.layer.layerDef,
zoomToFeatures: false,
features: layer
});
features: layer,
})
}
const snappedLocation = new SnappingFeatureSource(
new FeatureSourceMerger(...Utils.NoNull(snapSources)),
@ -90,19 +93,21 @@
maxDistance: maxSnapDistance ?? 15,
allowUnsnapped: true,
snappedTo,
snapLocation: value
snapLocation: value,
}
);
)
new ShowDataLayer(map, {
layer: targetLayer,
features: snappedLocation
});
features: snappedLocation,
})
}
</script>
<LocationInput {map} mapProperties={initialMapProperties}
value={preciseLocation} initialCoordinate={coordinate} maxDistanceInMeters=50 />
<LocationInput
{map}
mapProperties={initialMapProperties}
value={preciseLocation}
initialCoordinate={coordinate}
maxDistanceInMeters="50"
/>

View file

@ -16,7 +16,7 @@
import Svg from "../../Svg"
import ToSvelte from "../Base/ToSvelte.svelte"
import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte";
import Tr from "../Base/Tr.svelte"
export let search: UIEventSource<string>
@ -26,7 +26,6 @@
<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} />

View file

@ -1,8 +1,7 @@
<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
</script>

View file

@ -1,8 +1,8 @@
<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
@ -21,12 +21,11 @@
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} />

View file

@ -1,14 +1,14 @@
import Combine from "../Base/Combine";
import {OsmConnection} from "../../Logic/Osm/OsmConnection";
import {Store, UIEventSource} from "../../Logic/UIEventSource";
import {BBox} from "../../Logic/BBox";
import Translations from "../i18n/Translations";
import {VariableUiElement} from "../Base/VariableUIElement";
import Toggle from "../Input/Toggle";
import {SubtleButton} from "../Base/SubtleButton";
import Svg from "../../Svg";
import {Utils} from "../../Utils";
import Constants from "../../Models/Constants";
import Combine from "../Base/Combine"
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import { BBox } from "../../Logic/BBox"
import Translations from "../i18n/Translations"
import { VariableUiElement } from "../Base/VariableUIElement"
import Toggle from "../Input/Toggle"
import { SubtleButton } from "../Base/SubtleButton"
import Svg from "../../Svg"
import { Utils } from "../../Utils"
import Constants from "../../Models/Constants"
export class OpenJosm extends Combine {
constructor(osmConnection: OsmConnection, bounds: Store<BBox>, iconStyle?: string) {
@ -32,7 +32,8 @@ export class OpenJosm extends Combine {
)
const toggle = new Toggle(
new SubtleButton(Svg.josm_logo_svg().SetStyle(iconStyle), t.editJosm).onClick(() => {
new SubtleButton(Svg.josm_logo_svg().SetStyle(iconStyle), t.editJosm)
.onClick(() => {
const bbox = bounds.data
if (bbox === undefined) {
return
@ -45,7 +46,8 @@ export class OpenJosm extends Combine {
Utils.download(josmLink)
.then((answer) => josmState.setData(answer.replace(/\n/g, "").trim()))
.catch((_) => josmState.setData("ERROR"))
}).SetClass("w-full"),
})
.SetClass("w-full"),
undefined,
osmConnection.userDetails.map(
(ud) => ud.loggedIn && ud.csCount >= Constants.userJourney.historyLinkVisible

View file

@ -1,37 +1,40 @@
<script lang="ts">/**
<script lang="ts">
/**
* The OverlayToggle shows a single toggle to enable or disable an overlay
*/
import Checkbox from "../Base/Checkbox.svelte";
import { onDestroy } from "svelte";
import { UIEventSource } from "../../Logic/UIEventSource";
import Tr from "../Base/Tr.svelte";
import Translations from "../i18n/Translations";
import { Translation } from "../i18n/Translation";
import type { RasterLayerProperties } from "../../Models/RasterLayerProperties";
import Checkbox from "../Base/Checkbox.svelte"
import { onDestroy } from "svelte"
import { UIEventSource } from "../../Logic/UIEventSource"
import Tr from "../Base/Tr.svelte"
import Translations from "../i18n/Translations"
import { Translation } from "../i18n/Translation"
import type { RasterLayerProperties } from "../../Models/RasterLayerProperties"
export let layerproperties: RasterLayerProperties
export let state: {isDisplayed: UIEventSource<boolean>};
export let zoomlevel: UIEventSource<number>;
export let highlightedLayer: UIEventSource<string> | undefined;
export let state: { isDisplayed: UIEventSource<boolean> }
export let zoomlevel: UIEventSource<number>
export let highlightedLayer: UIEventSource<string> | undefined
let isDisplayed: boolean = state.isDisplayed.data;
onDestroy(state.isDisplayed.addCallbackAndRunD(d => {
isDisplayed = d;
return false;
}));
let isDisplayed: boolean = state.isDisplayed.data
onDestroy(
state.isDisplayed.addCallbackAndRunD((d) => {
isDisplayed = d
return false
})
)
let mainElem: HTMLElement;
let mainElem: HTMLElement
$: onDestroy(
highlightedLayer.addCallbackAndRun(highlightedLayer => {
highlightedLayer.addCallbackAndRun((highlightedLayer) => {
if (highlightedLayer === layerproperties.id) {
mainElem?.classList?.add("glowing-shadow");
mainElem?.classList?.add("glowing-shadow")
} else {
mainElem?.classList?.remove("glowing-shadow");
mainElem?.classList?.remove("glowing-shadow")
}
})
);
)
</script>
{#if layerproperties.name}
<div bind:this={mainElem}>
<label class="flex gap-1">

View file

@ -1,57 +1,69 @@
<script lang="ts">
import type {Feature} from "geojson";
import {UIEventSource} from "../../Logic/UIEventSource";
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
import type {SpecialVisualizationState} from "../SpecialVisualization";
import TagRenderingAnswer from "../Popup/TagRendering/TagRenderingAnswer.svelte";
import {onDestroy} from "svelte";
import Translations from "../i18n/Translations";
import Tr from "../Base/Tr.svelte";
import {XCircleIcon} from "@rgossiaux/svelte-heroicons/solid";
import type { Feature } from "geojson"
import { UIEventSource } from "../../Logic/UIEventSource"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import type { SpecialVisualizationState } from "../SpecialVisualization"
import TagRenderingAnswer from "../Popup/TagRendering/TagRenderingAnswer.svelte"
import { onDestroy } from "svelte"
import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte"
import { XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
export let state: SpecialVisualizationState;
export let layer: LayerConfig;
export let selectedElement: Feature;
export let tags: UIEventSource<Record<string, string>>;
export let state: SpecialVisualizationState
export let layer: LayerConfig
export let selectedElement: Feature
export let tags: UIEventSource<Record<string, string>>
let _tags: Record<string, string>
onDestroy(
tags.addCallbackAndRun((tags) => {
_tags = tags
})
)
let _tags: Record<string, string>;
onDestroy(tags.addCallbackAndRun(tags => {
_tags = tags;
}));
let _metatags: Record<string, string>;
onDestroy(state.userRelatedState.preferencesAsTags.addCallbackAndRun(tags => {
_metatags = tags;
}));
let _metatags: Record<string, string>
onDestroy(
state.userRelatedState.preferencesAsTags.addCallbackAndRun((tags) => {
_metatags = tags
})
)
</script>
{#if _tags._deleted === "yes"}
<Tr t={Translations.t.delete.isDeleted} />
{:else}
<div class="flex border-b-2 border-black drop-shadow-md justify-between items-center low-interaction px-3">
<div
class="flex border-b-2 border-black drop-shadow-md justify-between items-center low-interaction px-3"
>
<div class="flex flex-col">
<!-- Title element-->
<h3>
<TagRenderingAnswer config={layer.title} {selectedElement} {state} {tags}
{layer}></TagRenderingAnswer>
<TagRenderingAnswer config={layer.title} {selectedElement} {state} {tags} {layer} />
</h3>
<div class="no-weblate title-icons flex flex-row flex-wrap pt-0.5 sm:pt-1 items-center mr-2 gap-x-0.5 p-1 links-as-button">
<div
class="no-weblate title-icons flex flex-row flex-wrap pt-0.5 sm:pt-1 items-center mr-2 gap-x-0.5 p-1 links-as-button"
>
{#each layer.titleIcons as titleIconConfig}
{#if (titleIconConfig.condition?.matchesProperties(_tags) ?? true) && (titleIconConfig.metacondition?.matchesProperties( { ..._metatags, ..._tags } ) ?? true) && titleIconConfig.IsKnown(_tags)}
<div class="w-8 h-8 flex items-center">
<TagRenderingAnswer config={titleIconConfig} {tags} {selectedElement} {state}
{layer} extraClasses="h-full justify-center"></TagRenderingAnswer>
<TagRenderingAnswer
config={titleIconConfig}
{tags}
{selectedElement}
{state}
{layer}
extraClasses="h-full justify-center"
/>
</div>
{/if}
{/each}
</div>
</div>
<XCircleIcon class="w-8 h-8 cursor-pointer" on:click={() => state.selectedElement.setData(undefined)}/>
<XCircleIcon
class="w-8 h-8 cursor-pointer"
on:click={() => state.selectedElement.setData(undefined)}
/>
</div>
{/if}

View file

@ -1,32 +1,34 @@
<script lang="ts">
import type {Feature} from "geojson";
import {UIEventSource} from "../../Logic/UIEventSource";
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
import type {SpecialVisualizationState} from "../SpecialVisualization";
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte";
import {onDestroy} from "svelte";
import Translations from "../i18n/Translations";
import Tr from "../Base/Tr.svelte";
import type { Feature } from "geojson"
import { UIEventSource } from "../../Logic/UIEventSource"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import type { SpecialVisualizationState } from "../SpecialVisualization"
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte"
import { onDestroy } from "svelte"
import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte"
export let state: SpecialVisualizationState;
export let layer: LayerConfig;
export let selectedElement: Feature;
export let tags: UIEventSource<Record<string, string>>;
export let highlightedRendering: UIEventSource<string> = undefined;
export let state: SpecialVisualizationState
export let layer: LayerConfig
export let selectedElement: Feature
export let tags: UIEventSource<Record<string, string>>
export let highlightedRendering: UIEventSource<string> = undefined
let _tags: Record<string, string>
onDestroy(
tags.addCallbackAndRun((tags) => {
_tags = tags
})
)
let _tags: Record<string, string>;
onDestroy(tags.addCallbackAndRun(tags => {
_tags = tags;
}));
let _metatags: Record<string, string>;
onDestroy(state.userRelatedState.preferencesAsTags.addCallbackAndRun(tags => {
_metatags = tags;
}));
let _metatags: Record<string, string>
onDestroy(
state.userRelatedState.preferencesAsTags.addCallbackAndRun((tags) => {
_metatags = tags
})
)
</script>
{#if _tags._deleted === "yes"}
<Tr t={Translations.t.delete.isDeleted} />
<button class="w-full" on:click={() => state.selectedElement.setData(undefined)}>
@ -37,8 +39,14 @@
{#each layer.tagRenderings as config (config.id)}
{#if (config.condition === undefined || config.condition.matchesProperties(_tags)) && (config.metacondition === undefined || config.metacondition.matchesProperties( { ..._tags, ..._metatags } ))}
{#if config.IsKnown(_tags)}
<TagRenderingEditable {tags} {config} {state} {selectedElement} {layer}
{highlightedRendering}></TagRenderingEditable>
<TagRenderingEditable
{tags}
{config}
{state}
{selectedElement}
{layer}
{highlightedRendering}
/>
{/if}
{/if}
{/each}

View file

@ -60,8 +60,9 @@ export class ShareScreen extends Combine{
return "layer-" + flayer.layerDef.id + "=" + flayer.isDisplayed.data
}
const currentLayer: Store<{ id: string; name: string | Record<string, string> } | undefined> =
state.mapProperties.rasterLayer.map((l) => l?.properties)
const currentLayer: Store<
{ id: string; name: string | Record<string, string> } | undefined
> = state.mapProperties.rasterLayer.map((l) => l?.properties)
const currentBackground = new VariableUiElement(
currentLayer.map((layer) => {
return tr.fsIncludeCurrentBackgroundMap.Subs({ name: layer?.name ?? "" })
@ -96,7 +97,9 @@ export class ShareScreen extends Combine{
return null
}
},
Array.from(state.layerState.filteredLayers.values()).map((flayer) => flayer.isDisplayed)
Array.from(state.layerState.filteredLayers.values()).map(
(flayer) => flayer.isDisplayed
)
)
)

View file

@ -1,15 +1,15 @@
<script lang="ts">
import Translations from "../i18n/Translations";
import Svg from "../../Svg";
import Tr from "../Base/Tr.svelte";
import NextButton from "../Base/NextButton.svelte";
import Geosearch from "./Geosearch.svelte";
import IfNot from "../Base/IfNot.svelte";
import ToSvelte from "../Base/ToSvelte.svelte";
import ThemeViewState from "../../Models/ThemeViewState";
import If from "../Base/If.svelte";
import {UIEventSource} from "../../Logic/UIEventSource";
import {SearchIcon} from "@rgossiaux/svelte-heroicons/solid";
import Translations from "../i18n/Translations"
import Svg from "../../Svg"
import Tr from "../Base/Tr.svelte"
import NextButton from "../Base/NextButton.svelte"
import Geosearch from "./Geosearch.svelte"
import IfNot from "../Base/IfNot.svelte"
import ToSvelte from "../Base/ToSvelte.svelte"
import ThemeViewState from "../../Models/ThemeViewState"
import If from "../Base/If.svelte"
import { UIEventSource } from "../../Logic/UIEventSource"
import { SearchIcon } from "@rgossiaux/svelte-heroicons/solid"
/**
* The theme introduction panel
@ -19,7 +19,6 @@
let selectedElement = state.selectedElement
let selectedLayer = state.selectedLayer
let triggerSearch: UIEventSource<any> = new UIEventSource<any>(undefined)
let searchEnabled = false
@ -35,12 +34,10 @@
glstate.requestPermission()
return
}
}
</script>
<Tr t={layout.description}></Tr>
<Tr t={layout.description} />
<Tr t={Translations.t.general.welcomeExplanation.general} />
{#if layout.layers.some((l) => l.presets?.length > 0)}
<If condition={state.featureSwitches.featureSwitchAddNew}>
@ -51,7 +48,7 @@
<!--toTheMap,
loginStatus.SetClass("block mt-6 pt-2 md:border-t-2 border-dotted border-gray-400"),
-->
<Tr t={layout.descriptionTail}></Tr>
<Tr t={layout.descriptionTail} />
<NextButton clss="primary w-full" on:click={() => state.guistate.themeIsOpened.setData(false)}>
<div class="flex justify-center w-full text-2xl">
<Tr t={Translations.t.general.openTheMap} />
@ -59,7 +56,7 @@ loginStatus.SetClass("block mt-6 pt-2 md:border-t-2 border-dotted border-gray-40
</NextButton>
<div class="flex w-full flex-wrap sm:flex-nowrap">
<IfNot condition={state.geolocation.geolocationState.permission.map(p => p === "denied")}>
<IfNot condition={state.geolocation.geolocationState.permission.map((p) => p === "denied")}>
<button class="flex w-full gap-x-2 items-center" on:click={jumpToCurrentLocation}>
<ToSvelte construct={Svg.crosshair_svg().SetClass("w-8 h-8")} />
<Tr t={Translations.t.general.openTheMapAtGeolocation} />
@ -68,20 +65,24 @@ loginStatus.SetClass("block mt-6 pt-2 md:border-t-2 border-dotted border-gray-40
<div class="flex gap-x-2 items-center w-full border rounded .button p-2 m-1 low-interaction">
<div class="w-full">
<Geosearch bounds={state.mapProperties.bounds}
<Geosearch
bounds={state.mapProperties.bounds}
on:searchCompleted={() => state.guistate.themeIsOpened.setData(false)}
on:searchIsValid={isValid => {searchEnabled= isValid}}
on:searchIsValid={(isValid) => {
searchEnabled = isValid
}}
perLayer={state.perLayer}
{selectedElement}
{selectedLayer}
{triggerSearch}>
</Geosearch>
{triggerSearch}
/>
</div>
<button class={"flex gap-x-2 justify-between items-center "+(searchEnabled ? "" : "disabled")}
on:click={() => triggerSearch.ping()}>
<button
class={"flex gap-x-2 justify-between items-center " + (searchEnabled ? "" : "disabled")}
on:click={() => triggerSearch.ping()}
>
<Tr t={Translations.t.general.search.searchShort} />
<SearchIcon class="w-6 h-6"></SearchIcon>
<SearchIcon class="w-6 h-6" />
</button>
</div>
</div>

View file

@ -30,7 +30,6 @@
</div>
{:else}
<div>
{#each filteredThemes as theme (theme.id)}
{#if theme !== undefined && !(hideThemes && theme?.hideFromOverview)}
<ThemeButton {theme} {isCustom} userDetails={state.osmConnection.userDetails} {state} />

View file

@ -1,31 +1,32 @@
<script lang="ts">
import UserDetails, { OsmConnection } from "../../Logic/Osm/OsmConnection";
import { UIEventSource } from "../../Logic/UIEventSource";
import { PencilAltIcon, UserCircleIcon } from "@rgossiaux/svelte-heroicons/solid";
import { onDestroy } from "svelte";
import Showdown from "showdown";
import FromHtml from "../Base/FromHtml.svelte";
import Tr from "../Base/Tr.svelte";
import Translations from "../i18n/Translations.js";
import UserDetails, { OsmConnection } from "../../Logic/Osm/OsmConnection"
import { UIEventSource } from "../../Logic/UIEventSource"
import { PencilAltIcon, UserCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
import { onDestroy } from "svelte"
import Showdown from "showdown"
import FromHtml from "../Base/FromHtml.svelte"
import Tr from "../Base/Tr.svelte"
import Translations from "../i18n/Translations.js"
/**
* This panel shows information about the logged-in user, showing account name, profile pick, description and an edit-button
*/
export let osmConnection: OsmConnection;
let userdetails: UIEventSource<UserDetails> = osmConnection.userDetails;
let description: string;
onDestroy(userdetails.addCallbackAndRunD(userdetails => {
export let osmConnection: OsmConnection
let userdetails: UIEventSource<UserDetails> = osmConnection.userDetails
let description: string
onDestroy(
userdetails.addCallbackAndRunD((userdetails) => {
description = new Showdown.Converter()
.makeHtml(userdetails.description)
?.replace(/&gt;/g, ">")
?.replace(/&lt;/g, "<");
}));
?.replace(/&lt;/g, "<")
})
)
</script>
<div class="flex border border-gray-600 border-dashed m-1 p-1 rounded-md link-underline">
{#if $userdetails.img}
<img src={$userdetails.img} class="rounded-full w-12 h-12 m-4">
<img src={$userdetails.img} class="rounded-full w-12 h-12 m-4" />
{:else}
<UserCircleIcon class="w-12 h-12" />
{/if}
@ -33,11 +34,14 @@
<h3>{$userdetails.name}</h3>
{#if description}
<FromHtml src={description} />
<a href={osmConnection.Backend() + "/profile/edit"} target="_blank" class="link-no-underline flex items-center self-end">
<a
href={osmConnection.Backend() + "/profile/edit"}
target="_blank"
class="link-no-underline flex items-center self-end"
>
<PencilAltIcon slot="image" class="p-2 w-8 h-8" />
<Tr slot="message" t={Translations.t.userinfo.editDescription} />
</a>
{:else}
<Tr t={Translations.t.userinfo.noDescription} />
<a href={osmConnection.Backend() + "/profile/edit"} target="_blank" class="flex items-center">
@ -45,6 +49,5 @@
<Tr slot="message" t={Translations.t.userinfo.noDescriptionCallToAction} />
</a>
{/if}
</div>
</div>

View file

@ -8,33 +8,33 @@
*
* This component is _not_ responsible for the rest of the flow, e.g. the confirm button
*/
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
import type { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson";
import split_point from "../../assets/layers/split_point/split_point.json";
import split_road from "../../assets/layers/split_road/split_road.json";
import { UIEventSource } from "../../Logic/UIEventSource";
import { Map as MlMap } from "maplibre-gl";
import type { MapProperties } from "../../Models/MapProperties";
import { MapLibreAdaptor } from "../Map/MapLibreAdaptor";
import MaplibreMap from "../Map/MaplibreMap.svelte";
import { OsmWay } from "../../Logic/Osm/OsmObject";
import ShowDataLayer from "../Map/ShowDataLayer";
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource";
import { GeoOperations } from "../../Logic/GeoOperations";
import { BBox } from "../../Logic/BBox";
import type { Feature, LineString, Point } from "geojson";
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import type { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson"
import split_point from "../../assets/layers/split_point/split_point.json"
import split_road from "../../assets/layers/split_road/split_road.json"
import { UIEventSource } from "../../Logic/UIEventSource"
import { Map as MlMap } from "maplibre-gl"
import type { MapProperties } from "../../Models/MapProperties"
import { MapLibreAdaptor } from "../Map/MapLibreAdaptor"
import MaplibreMap from "../Map/MaplibreMap.svelte"
import { OsmWay } from "../../Logic/Osm/OsmObject"
import ShowDataLayer from "../Map/ShowDataLayer"
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"
import { GeoOperations } from "../../Logic/GeoOperations"
import { BBox } from "../../Logic/BBox"
import type { Feature, LineString, Point } from "geojson"
const splitpoint_style = new LayerConfig(
<LayerConfigJson>split_point,
"(BUILTIN) SplitRoadWizard.ts",
true
) as const;
) as const
const splitroad_style = new LayerConfig(
<LayerConfigJson>split_road,
"(BUILTIN) SplitRoadWizard.ts",
true
) as const;
) as const
/**
* The way to focus on
@ -48,10 +48,10 @@
/**
* Optional: use these properties to set e.g. background layer
*/
export let mapProperties: undefined | Partial<MapProperties> = undefined;
export let mapProperties: undefined | Partial<MapProperties> = undefined
let map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined);
let adaptor = new MapLibreAdaptor(map, mapProperties);
let map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined)
let adaptor = new MapLibreAdaptor(map, mapProperties)
const wayGeojson: Feature<LineString> = GeoOperations.forceLineString(osmWay.asGeoJson())
adaptor.location.setData(GeoOperations.centerpointCoordinatesObj(wayGeojson))
@ -61,10 +61,11 @@
new ShowDataLayer(map, {
features: new StaticFeatureSource([wayGeojson]),
drawMarkers: false,
layer: layer
layer: layer,
})
export let splitPoints: UIEventSource< Feature<
export let splitPoints: UIEventSource<
Feature<
Point,
{
id: number
@ -72,7 +73,8 @@
dist: number
location: number
}
>[]> = new UIEventSource([])
>[]
> = new UIEventSource([])
const splitPointsFS = new StaticFeatureSource(splitPoints)
new ShowDataLayer(map, {
@ -80,13 +82,13 @@
features: splitPointsFS,
onClick: (clickedFeature: Feature) => {
console.log("Clicked feature is", clickedFeature, splitPoints.data)
const i = splitPoints.data.findIndex(f => f === clickedFeature)
const i = splitPoints.data.findIndex((f) => f === clickedFeature)
if (i < 0) {
return
}
splitPoints.data.splice(i, 1)
splitPoints.ping()
}
},
})
let id = 0
adaptor.lastClickLocation.addCallbackD(({ lon, lat }) => {
@ -97,8 +99,8 @@
splitPoints.data.push(<any>projected)
splitPoints.ping()
})
</script>
<div class="w-full h-full">
<MaplibreMap {map}></MaplibreMap>
<MaplibreMap {map} />
</div>

View file

@ -1,22 +1,25 @@
<script lang="ts">
import type {SpecialVisualizationState} from "../SpecialVisualization";
import {ArrowDownTrayIcon} from "@babeard/svelte-heroicons/mini";
import Tr from "../Base/Tr.svelte";
import Translations from "../i18n/Translations";
import type {FeatureCollection} from "geojson";
import Loading from "../Base/Loading.svelte";
import {Translation} from "../i18n/Translation";
import DownloadHelper from "./DownloadHelper";
import {Utils} from "../../Utils";
import type {PriviligedLayerType} from "../../Models/Constants";
import {UIEventSource} from "../../Logic/UIEventSource";
import type { SpecialVisualizationState } from "../SpecialVisualization"
import { ArrowDownTrayIcon } from "@babeard/svelte-heroicons/mini"
import Tr from "../Base/Tr.svelte"
import Translations from "../i18n/Translations"
import type { FeatureCollection } from "geojson"
import Loading from "../Base/Loading.svelte"
import { Translation } from "../i18n/Translation"
import DownloadHelper from "./DownloadHelper"
import { Utils } from "../../Utils"
import type { PriviligedLayerType } from "../../Models/Constants"
import { UIEventSource } from "../../Logic/UIEventSource"
export let state: SpecialVisualizationState
export let extension: string
export let mimetype: string
export let construct: (geojsonCleaned: FeatureCollection, title: string, status?: UIEventSource<string>) => (Blob | string) | Promise<void>
export let construct: (
geojsonCleaned: FeatureCollection,
title: string,
status?: UIEventSource<string>
) => (Blob | string) | Promise<void>
export let mainText: Translation
export let helperText: Translation
export let metaIsIncluded: boolean
@ -31,9 +34,7 @@
async function clicked() {
isExporting = true
const gpsLayer = state.layerState.filteredLayers.get(
<PriviligedLayerType>"gps_location"
)
const gpsLayer = state.layerState.filteredLayers.get(<PriviligedLayerType>"gps_location")
state.lastClickObject.features.setData([])
const gpsIsDisplayed = gpsLayer.isDisplayed.data
@ -42,13 +43,15 @@
const geojson: FeatureCollection = downloadHelper.getCleanGeoJson(metaIsIncluded)
const name = state.layout.id
const title = `MapComplete_${name}_export_${new Date().toISOString().substr(0, 19)}.${extension}`
const title = `MapComplete_${name}_export_${new Date()
.toISOString()
.substr(0, 19)}.${extension}`
const promise = construct(geojson, title, status)
let data: Blob | string
if (typeof promise === "string") {
data = promise
} else if (typeof promise["then"] === "function") {
data = await <Promise<Blob | string>>promise
data = await (<Promise<Blob | string>>promise)
} else {
data = <Blob>promise
}
@ -56,13 +59,9 @@
return
}
console.log("Got data", data)
Utils.offerContentsAsDownloadableFile(
data,
title,
{
Utils.offerContentsAsDownloadableFile(data, title, {
mimetype,
}
)
})
} catch (e) {
isError = true
console.error(e)
@ -70,9 +69,7 @@
isExporting = false
gpsLayer.isDisplayed.setData(gpsIsDisplayed)
}
}
</script>
{#if isError}

View file

@ -1,8 +1,8 @@
import {SpecialVisualizationState} from "../SpecialVisualization";
import {Feature, FeatureCollection} from "geojson";
import {BBox} from "../../Logic/BBox";
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
import {Utils} from "../../Utils";
import { SpecialVisualizationState } from "../SpecialVisualization"
import { Feature, FeatureCollection } from "geojson"
import { BBox } from "../../Logic/BBox"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import { Utils } from "../../Utils"
import SimpleMetaTagger from "../../Logic/SimpleMetaTagger"
import geojson2svg from "geojson2svg"
@ -10,11 +10,10 @@ import geojson2svg from "geojson2svg"
* Exposes the download-functionality
*/
export default class DownloadHelper {
private readonly _state: SpecialVisualizationState;
private readonly _state: SpecialVisualizationState
constructor(state: SpecialVisualizationState) {
this._state = state;
this._state = state
}
/**
@ -46,9 +45,7 @@ export default class DownloadHelper {
return f
}
public getCleanGeoJson(
includeMetaData: boolean
): FeatureCollection {
public getCleanGeoJson(includeMetaData: boolean): FeatureCollection {
const featuresPerLayer = this.getCleanGeoJsonPerLayer(includeMetaData)
const features = [].concat(...Array.from(featuresPerLayer.values()))
return {
@ -79,15 +76,13 @@ export default class DownloadHelper {
* perLayer.set("testlayer", features)
* new DownloadHelper(<any> {perLayer}).asSvg().replace(/\n/g, "") // => `<svg width="1000px" height="1000px" viewBox="0 0 1000 1000"> <g id="testlayer" inkscape:groupmode="layer" inkscape:label="testlayer"> <path d="M0,27.77777777777778 1000,472.22222222222223" style="fill:none;stroke-width:1" stroke="#ff0000"/> </g></svg>`
*/
public asSvg(
options?: {
public asSvg(options?: {
layers?: LayerConfig[]
width?: 1000 | number
height?: 1000 | number
mapExtent?: BBox
unit?: "px" | "mm" | string
}
) {
}) {
const perLayer = this._state.perLayer
options = options ?? {}
const width = options.width ?? 1000
@ -154,9 +149,7 @@ export default class DownloadHelper {
return header + "\n" + elements.join("\n") + "\n</svg>"
}
public getCleanGeoJsonPerLayer(
includeMetaData: boolean
): Map<string, Feature[]> {
public getCleanGeoJsonPerLayer(includeMetaData: boolean): Map<string, Feature[]> {
const state = this._state
const featuresPerLayer = new Map<string, any[]>()
const neededLayers = state.layout.layers.filter((l) => l.source !== null).map((l) => l.id)
@ -188,15 +181,20 @@ export default class DownloadHelper {
createImage(key: string, width: string, height: string): HTMLImageElement {
const img = document.createElement("img")
const sources = {
"layouticon":this._state.layout.icon
layouticon: this._state.layout.icon,
}
img.src = sources[key]
if (!img.src) {
throw "Invalid key for 'createImage': "+key+"; try one of: "+Object.keys(sources).join(", ")
throw (
"Invalid key for 'createImage': " +
key +
"; try one of: " +
Object.keys(sources).join(", ")
)
}
img.style.width = width
img.style.height = height
console.log("Fetching an image with src", img.src)
return img;
return img
}
}

View file

@ -1,14 +1,13 @@
<script lang="ts">
import Loading from "../Base/Loading.svelte";
import Translations from "../i18n/Translations";
import Tr from "../Base/Tr.svelte";
import DownloadHelper from "./DownloadHelper";
import DownloadButton from "./DownloadButton.svelte";
import {GeoOperations} from "../../Logic/GeoOperations";
import {SvgToPdf} from "../../Utils/svgToPdf";
import ThemeViewState from "../../Models/ThemeViewState";
import DownloadPdf from "./DownloadPdf.svelte";
import Loading from "../Base/Loading.svelte"
import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte"
import DownloadHelper from "./DownloadHelper"
import DownloadButton from "./DownloadButton.svelte"
import { GeoOperations } from "../../Logic/GeoOperations"
import { SvgToPdf } from "../../Utils/svgToPdf"
import ThemeViewState from "../../Models/ThemeViewState"
import DownloadPdf from "./DownloadPdf.svelte"
export let state: ThemeViewState
let isLoading = state.dataIsLoading
@ -20,7 +19,6 @@
let metaIsIncluded = false
const name = state.layout.id
function offerSvg(): string {
const maindiv = document.getElementById("maindiv")
const layers = state.layout.layers.filter((l) => l.source !== null)
@ -31,43 +29,44 @@
height: maindiv.offsetHeight,
})
}
</script>
{#if $isLoading}
<Loading />
{:else}
<div class="w-full flex flex-col"></div>
<div class="w-full flex flex-col" />
<h3>
<Tr t={t.title} />
</h3>
<DownloadButton {state}
<DownloadButton
{state}
extension="geojson"
mimetype="application/vnd.geo+json"
construct={(geojson) => JSON.stringify(geojson)}
mainText={t.downloadGeojson}
helperText={t.downloadGeoJsonHelper}
{metaIsIncluded}/>
{metaIsIncluded}
/>
<DownloadButton {state}
<DownloadButton
{state}
extension="csv"
mimetype="text/csv"
construct={(geojson) => GeoOperations.toCSV(geojson)}
mainText={t.downloadCSV}
helperText={t.downloadCSVHelper}
{metaIsIncluded}/>
{metaIsIncluded}
/>
<label class="mb-8 mt-2">
<input type="checkbox" bind:value={metaIsIncluded}>
<input type="checkbox" bind:value={metaIsIncluded} />
<Tr t={t.includeMetaData} />
</label>
<DownloadButton {state} {metaIsIncluded}
<DownloadButton
{state}
{metaIsIncluded}
extension="svg"
mimetype="image/svg+xml"
mainText={t.downloadAsSvg}
@ -75,15 +74,16 @@
construct={offerSvg}
/>
<DownloadButton {state} {metaIsIncluded}
<DownloadButton
{state}
{metaIsIncluded}
extension="png"
mimetype="image/png"
mainText={t.downloadAsPng}
helperText={t.downloadAsPngHelper}
construct={_ => state.mapProperties.exportAsPng(4)}
construct={(_) => state.mapProperties.exportAsPng(4)}
/>
<div class="flex flex-col">
{#each Object.keys(SvgToPdf.templates) as key}
{#if SvgToPdf.templates[key].isPublic}
@ -92,7 +92,5 @@
{/each}
</div>
<Tr cls="link-underline" t={t.licenseInfo} />
{/if}

View file

@ -1,36 +1,40 @@
<script lang="ts">
import DownloadButton from "./DownloadButton.svelte";
import ThemeViewState from "../../Models/ThemeViewState";
import {SvgToPdf} from "../../Utils/svgToPdf";
import type {PdfTemplateInfo} from "../../Utils/svgToPdf";
import Translations from "../i18n/Translations";
import {Translation} from "../i18n/Translation";
import {Utils} from "../../Utils";
import {AvailableRasterLayers} from "../../Models/RasterLayers";
import Constants from "../../Models/Constants";
import Locale from "../i18n/Locale";
import {UIEventSource} from "../../Logic/UIEventSource";
import DownloadHelper from "./DownloadHelper";
import DownloadButton from "./DownloadButton.svelte"
import ThemeViewState from "../../Models/ThemeViewState"
import { SvgToPdf } from "../../Utils/svgToPdf"
import type { PdfTemplateInfo } from "../../Utils/svgToPdf"
import Translations from "../i18n/Translations"
import { Translation } from "../i18n/Translation"
import { Utils } from "../../Utils"
import { AvailableRasterLayers } from "../../Models/RasterLayers"
import Constants from "../../Models/Constants"
import Locale from "../i18n/Locale"
import { UIEventSource } from "../../Logic/UIEventSource"
import DownloadHelper from "./DownloadHelper"
export let templateName: string
export let state: ThemeViewState
const template: PdfTemplateInfo = SvgToPdf.templates[templateName]
console.log("template", template)
let mainText: Translation = typeof template.description === "string" ? new Translation(template.description) : template.description
let mainText: Translation =
typeof template.description === "string"
? new Translation(template.description)
: template.description
let t = Translations.t.general.download
const downloadHelper = new DownloadHelper(state)
async function constructPdf(_, title: string, status: UIEventSource<string>) {
title=title.substring(0, title.length - 4)+"_"+template.format+"_"+template.orientation
title =
title.substring(0, title.length - 4) + "_" + template.format + "_" + template.orientation
const templateUrls = SvgToPdf.templates[templateName].pages
const templates: string[] = await Promise.all(templateUrls.map(url => Utils.download(url)))
const templates: string[] = await Promise.all(templateUrls.map((url) => Utils.download(url)))
console.log("Templates are", templates)
const bg = state.mapProperties.rasterLayer.data ?? AvailableRasterLayers.maplibre
const creator = new SvgToPdf(title, templates, {
state,
freeComponentId: "belowmap",
createImage: (key: string, width: string, height: string) => downloadHelper.createImage(key, width, height),
createImage: (key: string, width: string, height: string) =>
downloadHelper.createImage(key, width, height),
textSubstitutions: <Record<string, string>>{
"layout.title": state.layout.title,
layoutid: state.layout.id,
@ -38,13 +42,13 @@
layoutImg: state.layout.icon,
version: Constants.vNumber,
date: new Date().toISOString().substring(0, 16),
background: new Translation(bg.properties.name).txt
}
background: new Translation(bg.properties.name).txt,
},
})
const unsub = creator.status.addCallbackAndRunD(s => {
const unsub = creator.status.addCallbackAndRunD((s) => {
console.log("SVG creator status:", s)
status?.setData(s);
status?.setData(s)
})
await creator.ExportPdf(Locale.language.data)
unsub()
@ -52,8 +56,8 @@
}
</script>
<DownloadButton construct={constructPdf}
<DownloadButton
construct={constructPdf}
extension="pdf"
helperText={t.downloadAsPdfHelper}
metaIsIncluded={false}

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