Chore: reformat all files with prettier
This commit is contained in:
parent
5757ae5dea
commit
d008dcb54d
214 changed files with 8926 additions and 8196 deletions
|
@ -1,6 +1,6 @@
|
|||
import { Store, UIEventSource } from "../UIEventSource"
|
||||
import { Utils } from "../../Utils"
|
||||
import {RasterLayerPolygon, RasterLayerUtils,} from "../../Models/RasterLayers"
|
||||
import { 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.
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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[]>([])
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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>) {
|
||||
|
|
|
@ -72,7 +72,9 @@ export default class DynamicGeoJsonTileSource extends DynamicTileSource {
|
|||
if (!isWhiteListed) {
|
||||
console.debug(
|
||||
"Not downloading tile",
|
||||
zxy,"for layer",layer.id,
|
||||
zxy,
|
||||
"for layer",
|
||||
layer.id,
|
||||
"as it is not on the whitelist"
|
||||
)
|
||||
return undefined
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { OsmNode, OsmObject, OsmWay } from "../../Osm/OsmObject"
|
||||
import { UIEventSource } from "../../UIEventSource"
|
||||
import {BBox} from "../../BBox";
|
||||
import StaticFeatureSource from "../Sources/StaticFeatureSource";
|
||||
import {Tiles} from "../../../Models/TileRange";
|
||||
import { 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[]>>()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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[],
|
||||
|
|
|
@ -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[]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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[]) {
|
||||
|
|
|
@ -316,7 +316,6 @@ export default class SimpleMetaTaggers {
|
|||
(feature) => {
|
||||
Utils.AddLazyProperty(feature.properties, "_surface", () => {
|
||||
return "" + GeoOperations.surfaceAreaInSqMeters(feature)
|
||||
|
||||
})
|
||||
|
||||
return true
|
||||
|
|
|
@ -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" &&
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -39,7 +39,6 @@ export class RegexTag extends TagsFilter {
|
|||
return fromTag === possibleRegex
|
||||
}
|
||||
return possibleRegex.test(fromTag)
|
||||
|
||||
}
|
||||
|
||||
private static source(r: string | RegExp) {
|
||||
|
|
|
@ -698,7 +698,11 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
|
|||
* srcSeen // => 21
|
||||
* lastSeen // => 42
|
||||
*/
|
||||
public map<J>(f: (t: T) => J, extraSources: Store<any>[] = [], onDestroy?: (f : () => void ) => void): Store<J> {
|
||||
public map<J>(
|
||||
f: (t: T) => J,
|
||||
extraSources: Store<any>[] = [],
|
||||
onDestroy?: (f: () => void) => void
|
||||
): Store<J> {
|
||||
return new MappedStore(this, f, extraSources, this._callbacks, f(this.data), onDestroy)
|
||||
}
|
||||
/**
|
||||
|
|
|
@ -76,7 +76,8 @@ export default class FeatureReviews {
|
|||
) {
|
||||
const centerLonLat = GeoOperations.centerpointCoordinates(feature)
|
||||
;[this._lon, this._lat] = centerLonLat
|
||||
this._identity = mangroveIdentity ?? new MangroveIdentity(new UIEventSource<string>(undefined))
|
||||
this._identity =
|
||||
mangroveIdentity ?? new MangroveIdentity(new UIEventSource<string>(undefined))
|
||||
const nameKey = options?.nameKey ?? "name"
|
||||
|
||||
if (feature.geometry.type === "Point") {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -47,8 +47,8 @@ export class AvailableRasterLayers {
|
|||
type: "vector",
|
||||
attribution: {
|
||||
text: "Maptiler",
|
||||
url: "https://www.maptiler.com/copyright/"
|
||||
}
|
||||
url: "https://www.maptiler.com/copyright/",
|
||||
},
|
||||
},
|
||||
geometry: BBox.global.asGeometry(),
|
||||
}
|
||||
|
@ -63,8 +63,8 @@ export class AvailableRasterLayers {
|
|||
type: "vector",
|
||||
attribution: {
|
||||
text: "Americana",
|
||||
url: "https://github.com/ZeLonewolf/openstreetmap-americana/"
|
||||
}
|
||||
url: "https://github.com/ZeLonewolf/openstreetmap-americana/",
|
||||
},
|
||||
},
|
||||
geometry: BBox.global.asGeometry(),
|
||||
}
|
||||
|
@ -151,7 +151,14 @@ export interface EditorLayerIndexProperties extends RasterLayerProperties {
|
|||
* Whether the imagery name should be translated
|
||||
*/
|
||||
readonly i18n?: boolean
|
||||
readonly type: "tms" | "wms" | "bing" | "scanex" | "wms_endpoint" | "wmts" | "vector" /* Vector is not actually part of the ELI-spec, we add it for vector layers */
|
||||
readonly type:
|
||||
| "tms"
|
||||
| "wms"
|
||||
| "bing"
|
||||
| "scanex"
|
||||
| "wms_endpoint"
|
||||
| "wmts"
|
||||
| "vector" /* Vector is not actually part of the ELI-spec, we add it for vector layers */
|
||||
/**
|
||||
* A rough categorisation of different types of layers. See https://github.com/osmlab/editor-layer-index/blob/gh-pages/CONTRIBUTING.md#categories for a description of the individual categories.
|
||||
*/
|
||||
|
|
|
@ -297,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 ?? []))
|
||||
|
|
|
@ -184,7 +184,7 @@ export default class CreateNoteImportLayer extends Conversion<LayerConfigJson, L
|
|||
"mapcomplete-show_debug=yes",
|
||||
],
|
||||
},
|
||||
}
|
||||
},
|
||||
],
|
||||
mapRendering: [
|
||||
{
|
||||
|
|
|
@ -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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -103,7 +103,11 @@ export default class DependencyCalculator {
|
|||
currentLine = i // Leak the state...
|
||||
currentKey = key
|
||||
try {
|
||||
const func = new Function("feat", "{"+ExtraFunctions.types.join(",")+"}", "return " + code + ";")
|
||||
const func = new Function(
|
||||
"feat",
|
||||
"{" + ExtraFunctions.types.join(",") + "}",
|
||||
"return " + code + ";"
|
||||
)
|
||||
const result = func(obj, helpers)
|
||||
obj.properties[key] = JSON.stringify(result)
|
||||
} catch (e) {}
|
||||
|
|
|
@ -31,7 +31,6 @@ export default interface LineRenderingConfigJson {
|
|||
*/
|
||||
lineCap?: "round" | "square" | "butt" | string | TagRenderingConfigJson
|
||||
|
||||
|
||||
/**
|
||||
* The color to fill a polygon with.
|
||||
* If undefined, this will be slightly more opaque version of the stroke line.
|
||||
|
|
|
@ -417,7 +417,6 @@ export default class LayerConfig extends WithContextLoader {
|
|||
)
|
||||
}
|
||||
this.popupInFloatover = json.popupInFloatover ?? false
|
||||
|
||||
}
|
||||
|
||||
public defaultIcon(): BaseUIElement | undefined {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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([])
|
||||
|
|
|
@ -123,8 +123,15 @@ export class Unit {
|
|||
)
|
||||
)
|
||||
|
||||
if(json.defaultInput && !applicable.some(denom => denom.canonical.trim() === json.defaultInput)){
|
||||
throw `${ctx}: no denomination has the specified default denomination. The default denomination is '${json.defaultInput}', but the available denominations are ${applicable.map(denom => denom.canonical).join(", ")}`
|
||||
if (
|
||||
json.defaultInput &&
|
||||
!applicable.some((denom) => denom.canonical.trim() === json.defaultInput)
|
||||
) {
|
||||
throw `${ctx}: no denomination has the specified default denomination. The default denomination is '${
|
||||
json.defaultInput
|
||||
}', but the available denominations are ${applicable
|
||||
.map((denom) => denom.canonical)
|
||||
.join(", ")}`
|
||||
}
|
||||
return new Unit(appliesTo, applicable, json.eraseInvalidValues ?? false)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import SvelteUIElement from "./UI/Base/SvelteUIElement";
|
||||
import StylesheetTestGui from "./UI/StylesheetTestGui.svelte";
|
||||
import SvelteUIElement from "./UI/Base/SvelteUIElement"
|
||||
import StylesheetTestGui from "./UI/StylesheetTestGui.svelte"
|
||||
|
||||
new SvelteUIElement(StylesheetTestGui, {}).AttachTo("main")
|
||||
|
|
|
@ -9,8 +9,8 @@ import IndexText from "./BigComponents/IndexText"
|
|||
import { LoginToggle } from "./Popup/LoginButton"
|
||||
import { ImmutableStore } from "../Logic/UIEventSource"
|
||||
import { OsmConnection } from "../Logic/Osm/OsmConnection"
|
||||
import {QueryParameters} from "../Logic/Web/QueryParameters";
|
||||
import {OsmConnectionFeatureSwitches} from "../Logic/State/FeatureSwitchState";
|
||||
import { QueryParameters } from "../Logic/Web/QueryParameters"
|
||||
import { OsmConnectionFeatureSwitches } from "../Logic/State/FeatureSwitchState"
|
||||
|
||||
export default class AllThemesGui {
|
||||
setup() {
|
||||
|
@ -27,9 +27,10 @@ export default class AllThemesGui {
|
|||
})
|
||||
const state = new UserRelatedState(osmConnection)
|
||||
const intro = new Combine([
|
||||
new LanguagePicker(Translations.t.index.title.SupportedLanguages(), state.language).SetClass(
|
||||
"flex absolute top-2 right-3"
|
||||
),
|
||||
new LanguagePicker(
|
||||
Translations.t.index.title.SupportedLanguages(),
|
||||
state.language
|
||||
).SetClass("flex absolute top-2 right-3"),
|
||||
new IndexText(),
|
||||
])
|
||||
new Combine([
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
<script lang="ts">
|
||||
import { UIEventSource } from "../../Logic/UIEventSource.js";
|
||||
import { UIEventSource } from "../../Logic/UIEventSource.js"
|
||||
|
||||
/**
|
||||
* For some stupid reason, it is very hard to bind inputs
|
||||
*/
|
||||
export let selected: UIEventSource<boolean>;
|
||||
let _c: boolean = selected.data ?? true;
|
||||
export let selected: UIEventSource<boolean>
|
||||
let _c: boolean = selected.data ?? true
|
||||
$: selected.setData(_c)
|
||||
|
||||
</script>
|
||||
|
||||
<input type="checkbox" bind:checked={_c} />
|
||||
|
|
|
@ -3,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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
* Given an HTML string, properly shows this
|
||||
*/
|
||||
|
||||
export let src: string;
|
||||
let htmlElem: HTMLElement;
|
||||
export let src: string
|
||||
let htmlElem: HTMLElement
|
||||
$: {
|
||||
if (htmlElem) {
|
||||
htmlElem.innerHTML = src;
|
||||
htmlElem.innerHTML = src
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,5 @@
|
|||
</script>
|
||||
|
||||
{#if src !== undefined}
|
||||
<span bind:this={htmlElem} class={clss}></span>
|
||||
<span bind:this={htmlElem} class={clss} />
|
||||
{/if}
|
||||
|
||||
|
|
|
@ -1,23 +1,24 @@
|
|||
<script lang="ts">
|
||||
import { UIEventSource } from "../../Logic/UIEventSource";
|
||||
import { onDestroy } from "svelte";
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { onDestroy } from "svelte"
|
||||
|
||||
/**
|
||||
* For some stupid reason, it is very hard to let {#if} work together with UIEventSources, so we wrap then here
|
||||
*/
|
||||
export let condition: UIEventSource<boolean>;
|
||||
let _c = condition.data;
|
||||
onDestroy(condition.addCallback(c => {
|
||||
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}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<script>
|
||||
import ToSvelte from "./ToSvelte.svelte";
|
||||
import Svg from "../../Svg";
|
||||
import ToSvelte from "./ToSvelte.svelte"
|
||||
import Svg from "../../Svg"
|
||||
</script>
|
||||
|
||||
<div class="pl-2 p-1 flex">
|
||||
<div class="animate-spin self-center w-6 h-6 min-w-6">
|
||||
<ToSvelte construct={Svg.loading_svg()}></ToSvelte>
|
||||
<ToSvelte construct={Svg.loading_svg()} />
|
||||
</div>
|
||||
<div class="ml-2">
|
||||
<slot></slot>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,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 = ""
|
||||
|
|
|
@ -1,47 +1,45 @@
|
|||
<script lang="ts">
|
||||
import Loading from "./Loading.svelte";
|
||||
import type { OsmServiceState } from "../../Logic/Osm/OsmConnection";
|
||||
import { Translation } from "../i18n/Translation";
|
||||
import Translations from "../i18n/Translations";
|
||||
import Tr from "./Tr.svelte";
|
||||
import {OsmConnection} from "../../Logic/Osm/OsmConnection";
|
||||
import {ImmutableStore, UIEventSource} from "../../Logic/UIEventSource";
|
||||
import Loading from "./Loading.svelte"
|
||||
import type { OsmServiceState } from "../../Logic/Osm/OsmConnection"
|
||||
import { Translation } from "../i18n/Translation"
|
||||
import Translations from "../i18n/Translations"
|
||||
import Tr from "./Tr.svelte"
|
||||
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
|
||||
import { ImmutableStore, UIEventSource } from "../../Logic/UIEventSource"
|
||||
|
||||
export let state: {osmConnection: OsmConnection, featureSwitches?: { featureSwitchUserbadge?: UIEventSource<boolean>}};
|
||||
export let state: {
|
||||
osmConnection: OsmConnection
|
||||
featureSwitches?: { featureSwitchUserbadge?: UIEventSource<boolean> }
|
||||
}
|
||||
/**
|
||||
* If set, 'loading' will act as if we are already logged in.
|
||||
*/
|
||||
export let ignoreLoading: boolean = false
|
||||
let loadingStatus = state.osmConnection.loadingStatus;
|
||||
let badge = state.featureSwitches?.featureSwitchUserbadge ?? new ImmutableStore(true);
|
||||
const t = Translations.t.general;
|
||||
let loadingStatus = state.osmConnection.loadingStatus
|
||||
let badge = state.featureSwitches?.featureSwitchUserbadge ?? new ImmutableStore(true)
|
||||
const t = Translations.t.general
|
||||
const offlineModes: Partial<Record<OsmServiceState, Translation>> = {
|
||||
offline: t.loginFailedOfflineMode,
|
||||
unreachable: t.loginFailedUnreachableMode,
|
||||
unknown: t.loginFailedUnreachableMode,
|
||||
readonly: t.loginFailedReadonlyMode
|
||||
};
|
||||
const apiState = state.osmConnection.apiIsOnline;
|
||||
|
||||
|
||||
readonly: t.loginFailedReadonlyMode,
|
||||
}
|
||||
const apiState = state.osmConnection.apiIsOnline
|
||||
</script>
|
||||
|
||||
{#if $badge}
|
||||
{#if !ignoreLoading && $loadingStatus === "loading"}
|
||||
<slot name="loading">
|
||||
<Loading></Loading>
|
||||
<Loading />
|
||||
</slot>
|
||||
{:else if $loadingStatus === "error"}
|
||||
<div class="flex items-center alert max-w-64">
|
||||
<img src="./assets/svg/invalid.svg" class="w-8 h-8 m-2 shrink-0">
|
||||
<img src="./assets/svg/invalid.svg" class="w-8 h-8 m-2 shrink-0" />
|
||||
<Tr t={offlineModes[$apiState]} />
|
||||
</div>
|
||||
|
||||
{:else if $loadingStatus === "logged-in"}
|
||||
<slot></slot>
|
||||
<slot />
|
||||
{:else if $loadingStatus === "not-attempted"}
|
||||
<slot name="not-logged-in">
|
||||
|
||||
</slot>
|
||||
<slot name="not-logged-in" />
|
||||
{/if}
|
||||
{/if}
|
||||
|
|
|
@ -1,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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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")} />
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,23 +1,21 @@
|
|||
<script lang="ts">
|
||||
import BaseUIElement from "../BaseUIElement.js";
|
||||
import { onDestroy, onMount } from "svelte";
|
||||
import BaseUIElement from "../BaseUIElement.js"
|
||||
import { onDestroy, onMount } from "svelte"
|
||||
|
||||
export let construct: BaseUIElement | (() => BaseUIElement);
|
||||
let elem: HTMLElement;
|
||||
let html: HTMLElement;
|
||||
export let construct: BaseUIElement | (() => BaseUIElement)
|
||||
let elem: HTMLElement
|
||||
let html: HTMLElement
|
||||
onMount(() => {
|
||||
const uiElem = typeof construct === "function"
|
||||
? construct() : construct;
|
||||
html =uiElem?.ConstructElement();
|
||||
const uiElem = typeof construct === "function" ? construct() : construct
|
||||
html = uiElem?.ConstructElement()
|
||||
if (html !== undefined) {
|
||||
elem.replaceWith(html);
|
||||
elem.replaceWith(html)
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
html?.remove();
|
||||
});
|
||||
|
||||
html?.remove()
|
||||
})
|
||||
</script>
|
||||
|
||||
<span bind:this={elem} />
|
||||
|
|
|
@ -2,36 +2,37 @@
|
|||
/**
|
||||
* Properly renders a translation
|
||||
*/
|
||||
import { Translation } from "../i18n/Translation";
|
||||
import { onDestroy } from "svelte";
|
||||
import Locale from "../i18n/Locale";
|
||||
import { Utils } from "../../Utils";
|
||||
import FromHtml from "./FromHtml.svelte";
|
||||
import WeblateLink from "./WeblateLink.svelte";
|
||||
import { Translation } from "../i18n/Translation"
|
||||
import { onDestroy } from "svelte"
|
||||
import Locale from "../i18n/Locale"
|
||||
import { Utils } from "../../Utils"
|
||||
import FromHtml from "./FromHtml.svelte"
|
||||
import WeblateLink from "./WeblateLink.svelte"
|
||||
|
||||
export let t: Translation;
|
||||
export let t: Translation
|
||||
export let cls: string = ""
|
||||
export let tags: Record<string, string> | undefined = undefined;
|
||||
export let tags: Record<string, string> | undefined = undefined
|
||||
// Text for the current language
|
||||
let txt: string | undefined;
|
||||
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}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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"
|
||||
/>
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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}
|
||||
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -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(/>/g, ">")
|
||||
?.replace(/</g, "<");
|
||||
|
||||
}));
|
||||
?.replace(/</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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue