forked from MapComplete/MapComplete
Favourites: include _all_ tagRenderings
This commit is contained in:
parent
eb444ab849
commit
473931891c
20 changed files with 436 additions and 154 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -6,6 +6,7 @@ scratch
|
|||
assets/editor-layer-index.json
|
||||
assets/generated/*
|
||||
src/assets/generated/
|
||||
assets/layers/favourite/favourite.json
|
||||
public/*.webmanifest
|
||||
/*.html
|
||||
!/index.html
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"#":"no-translations",
|
||||
"pointRendering": [
|
||||
{
|
||||
"location": [
|
||||
|
@ -37,35 +38,9 @@
|
|||
"render": {
|
||||
"en": "Favourite location",
|
||||
"nl": "Favoriete locatie"
|
||||
},
|
||||
"mappings": [
|
||||
{
|
||||
"if": "name~*",
|
||||
"then": {
|
||||
"*": "{name}"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tagRenderings": [
|
||||
{
|
||||
"id": "Explanation",
|
||||
"classes": "thanks",
|
||||
"icon": {
|
||||
"class": "large",
|
||||
"path": "heart"
|
||||
},
|
||||
"render": {
|
||||
"en": "You marked this location as a personal favourite. As such, it is shown on every map you load.",
|
||||
"nl": "Je hebt deze locatie als persoonlijke favoriet aangeduid en worden op alle MapComplete-kaarten getoond."
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "show_images",
|
||||
"render": {
|
||||
"*": "{image_carousel()}"
|
||||
}
|
||||
},
|
||||
"all_tags"
|
||||
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "mapcomplete",
|
||||
"version": "0.35.0",
|
||||
"version": "0.36.0",
|
||||
"repository": "https://github.com/pietervdvn/MapComplete",
|
||||
"description": "A small website to edit OSM easily",
|
||||
"bugs": "https://github.com/pietervdvn/MapComplete/issues",
|
||||
|
|
|
@ -2,24 +2,242 @@ import Script from "./Script"
|
|||
import { LayerConfigJson } from "../src/Models/ThemeConfig/Json/LayerConfigJson"
|
||||
import { readFileSync, writeFileSync } from "fs"
|
||||
import { AllSharedLayers } from "../src/Customizations/AllSharedLayers"
|
||||
import { AllKnownLayoutsLazy } from "../src/Customizations/AllKnownLayouts"
|
||||
import { Utils } from "../src/Utils"
|
||||
import { AddEditingElements } from "../src/Models/ThemeConfig/Conversion/PrepareLayer"
|
||||
import {
|
||||
MappingConfigJson,
|
||||
QuestionableTagRenderingConfigJson,
|
||||
} from "../src/Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
|
||||
import { TagConfigJson } from "../src/Models/ThemeConfig/Json/TagConfigJson"
|
||||
import { TagUtils } from "../src/Logic/Tags/TagUtils"
|
||||
import { TagRenderingConfigJson } from "../src/Models/ThemeConfig/Json/TagRenderingConfigJson"
|
||||
import { Translatable } from "../src/Models/ThemeConfig/Json/Translatable"
|
||||
|
||||
export class GenerateFavouritesLayer extends Script {
|
||||
private readonly layers: LayerConfigJson[] = []
|
||||
|
||||
class PrepareFavouritesLayerJson extends Script {
|
||||
constructor() {
|
||||
super("Prepares the 'favourites'-layer")
|
||||
const allThemes = new AllKnownLayoutsLazy(false).values()
|
||||
for (const theme of allThemes) {
|
||||
if (theme.hideFromOverview) {
|
||||
continue
|
||||
}
|
||||
for (const layer of theme.layers) {
|
||||
if (!layer.source) {
|
||||
continue
|
||||
}
|
||||
if (layer.source.geojsonSource) {
|
||||
continue
|
||||
}
|
||||
const layerConfig = AllSharedLayers.getSharedLayersConfigs().get(layer.id)
|
||||
if (!layerConfig) {
|
||||
continue
|
||||
}
|
||||
this.layers.push(layerConfig)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private addTagRenderings(proto: LayerConfigJson) {
|
||||
const blacklistedIds = new Set([
|
||||
"images",
|
||||
"questions",
|
||||
"mapillary",
|
||||
"leftover-questions",
|
||||
"last_edit",
|
||||
"minimap",
|
||||
"move-button",
|
||||
"delete-button",
|
||||
"all-tags",
|
||||
...AddEditingElements.addedElements,
|
||||
])
|
||||
|
||||
const generateTagRenderings: (string | QuestionableTagRenderingConfigJson)[] = []
|
||||
const trPerId = new Map<
|
||||
string,
|
||||
{ conditions: TagConfigJson[]; tr: QuestionableTagRenderingConfigJson }
|
||||
>()
|
||||
for (const layerConfig of this.layers) {
|
||||
if (!layerConfig.tagRenderings) {
|
||||
continue
|
||||
}
|
||||
for (const tagRendering of layerConfig.tagRenderings) {
|
||||
if (typeof tagRendering === "string") {
|
||||
if (blacklistedIds.has(tagRendering)) {
|
||||
continue
|
||||
}
|
||||
generateTagRenderings.push(tagRendering)
|
||||
blacklistedIds.add(tagRendering)
|
||||
continue
|
||||
}
|
||||
if (tagRendering["builtin"]) {
|
||||
continue
|
||||
}
|
||||
const id = tagRendering.id
|
||||
if (blacklistedIds.has(id)) {
|
||||
continue
|
||||
}
|
||||
if (trPerId.has(id)) {
|
||||
const old = trPerId.get(id).tr
|
||||
|
||||
// We need to figure out if this was a 'recycled' tag rendering or just happens to have the same id
|
||||
function isSame(fieldName: string) {
|
||||
return old[fieldName]?.["en"] === tagRendering[fieldName]?.["en"]
|
||||
}
|
||||
|
||||
const sameQuestion = isSame("question") && isSame("render")
|
||||
if (!sameQuestion) {
|
||||
const newTr = <QuestionableTagRenderingConfigJson>Utils.Clone(tagRendering)
|
||||
newTr.id = layerConfig.id + "_" + newTr.id
|
||||
if (blacklistedIds.has(newTr.id)) {
|
||||
continue
|
||||
}
|
||||
newTr.condition = {
|
||||
and: Utils.NoNull([(newTr.condition, layerConfig.source["osmTags"])]),
|
||||
}
|
||||
generateTagRenderings.push(newTr)
|
||||
blacklistedIds.add(newTr.id)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if (!trPerId.has(id)) {
|
||||
const newTr = <QuestionableTagRenderingConfigJson>Utils.Clone(tagRendering)
|
||||
generateTagRenderings.push(newTr)
|
||||
trPerId.set(newTr.id, { tr: newTr, conditions: [] })
|
||||
}
|
||||
const conditions = trPerId.get(id).conditions
|
||||
if (tagRendering["condition"]) {
|
||||
conditions.push({
|
||||
and: [tagRendering["condition"], layerConfig.source["osmTags"]],
|
||||
})
|
||||
} else {
|
||||
conditions.push(layerConfig.source["osmTags"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const { tr, conditions } of Array.from(trPerId.values())) {
|
||||
const optimized = TagUtils.optimzeJson({ or: conditions })
|
||||
if (optimized === true) {
|
||||
continue
|
||||
}
|
||||
if (optimized === false) {
|
||||
throw "Optimized into 'false', this is weird..."
|
||||
}
|
||||
tr.condition = optimized
|
||||
}
|
||||
|
||||
const allTags: QuestionableTagRenderingConfigJson = {
|
||||
id: "all-tags",
|
||||
render: { "*": "{all_tags()}" },
|
||||
|
||||
metacondition: {
|
||||
or: [
|
||||
"__featureSwitchIsDebugging=true",
|
||||
"mapcomplete-show_tags=full",
|
||||
"mapcomplete-show_debug=yes",
|
||||
],
|
||||
},
|
||||
}
|
||||
proto.tagRenderings = [
|
||||
...generateTagRenderings,
|
||||
...proto.tagRenderings,
|
||||
"questions",
|
||||
allTags,
|
||||
]
|
||||
}
|
||||
|
||||
private addTitle(proto: LayerConfigJson) {
|
||||
const mappings: MappingConfigJson[] = []
|
||||
for (const layer of this.layers) {
|
||||
const t = layer.title
|
||||
const tags: TagConfigJson = layer.source["osmTags"]
|
||||
if (!t) {
|
||||
continue
|
||||
}
|
||||
if (typeof t === "string") {
|
||||
mappings.push({ if: tags, then: t })
|
||||
} else if (t["render"] !== undefined || t["mappings"] !== undefined) {
|
||||
const tr = <TagRenderingConfigJson>t
|
||||
for (let i = 0; i < (tr.mappings ?? []).length; i++) {
|
||||
const mapping = tr.mappings[i]
|
||||
const optimized = TagUtils.optimzeJson({
|
||||
and: [mapping.if, tags],
|
||||
})
|
||||
if (optimized === false) {
|
||||
console.warn(
|
||||
"The following tags yielded 'false':",
|
||||
JSON.stringify(mapping.if),
|
||||
JSON.stringify(tags)
|
||||
)
|
||||
continue
|
||||
}
|
||||
if (optimized === true) {
|
||||
console.error(
|
||||
"The following tags yielded 'false':",
|
||||
JSON.stringify(mapping.if),
|
||||
JSON.stringify(tags)
|
||||
)
|
||||
throw "Tags for title optimized to true"
|
||||
}
|
||||
|
||||
if (!mapping.then) {
|
||||
throw (
|
||||
"The title has a missing 'then' for mapping " +
|
||||
i +
|
||||
" in layer " +
|
||||
layer.id
|
||||
)
|
||||
}
|
||||
mappings.push({
|
||||
if: optimized,
|
||||
then: mapping.then,
|
||||
})
|
||||
}
|
||||
if (tr.render) {
|
||||
mappings.push({
|
||||
if: tags,
|
||||
then: <Translatable>tr.render,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
mappings.push({ if: tags, then: <Record<string, string>>t })
|
||||
}
|
||||
}
|
||||
|
||||
if (proto.title["mappings"]) {
|
||||
mappings.unshift(...proto.title["mappings"])
|
||||
}
|
||||
if (proto.title["render"]) {
|
||||
mappings.push({
|
||||
if: "id~*",
|
||||
then: proto.title["render"],
|
||||
})
|
||||
}
|
||||
|
||||
proto.title = {
|
||||
mappings,
|
||||
}
|
||||
}
|
||||
|
||||
async main(args: string[]): Promise<void> {
|
||||
const allConfigs = AllSharedLayers.getSharedLayersConfigs()
|
||||
console.log("Generating the favourite layer: stealing _all_ tagRenderings")
|
||||
const proto = this.readLayer("favourite/favourite.proto.json")
|
||||
const questions = allConfigs.get("questions")
|
||||
proto.tagRenderings.push(...questions.tagRenderings)
|
||||
|
||||
this.addTagRenderings(proto)
|
||||
this.addTitle(proto)
|
||||
writeFileSync("./assets/layers/favourite/favourite.json", JSON.stringify(proto, null, " "))
|
||||
}
|
||||
|
||||
private readLayer(path: string): LayerConfigJson {
|
||||
try {
|
||||
return JSON.parse(readFileSync("./assets/layers/" + path, "utf8"))
|
||||
} catch (e) {
|
||||
console.error("Could not read ./assets/layers/" + path)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new PrepareFavouritesLayerJson().run()
|
||||
new GenerateFavouritesLayer().run()
|
||||
|
|
|
@ -28,6 +28,7 @@ import { QuestionableTagRenderingConfigJson } from "../src/Models/ThemeConfig/Js
|
|||
import LayerConfig from "../src/Models/ThemeConfig/LayerConfig"
|
||||
import PointRenderingConfig from "../src/Models/ThemeConfig/PointRenderingConfig"
|
||||
import { ConversionContext } from "../src/Models/ThemeConfig/Conversion/ConversionContext"
|
||||
import { GenerateFavouritesLayer } from "./generateFavouritesLayer"
|
||||
|
||||
// This scripts scans 'src/assets/layers/*.json' for layer definition files and 'src/assets/themes/*.json' for theme definition files.
|
||||
// It spits out an overview of those to be used to load them
|
||||
|
@ -381,16 +382,11 @@ class LayerOverviewUtils extends Script {
|
|||
forceReload
|
||||
)
|
||||
|
||||
writeFileSync(
|
||||
"./src/assets/generated/known_themes.json",
|
||||
JSON.stringify({
|
||||
themes: Array.from(sharedThemes.values()),
|
||||
})
|
||||
)
|
||||
|
||||
writeFileSync(
|
||||
"./src/assets/generated/known_layers.json",
|
||||
JSON.stringify({ layers: Array.from(sharedLayers.values()) })
|
||||
JSON.stringify({
|
||||
layers: Array.from(sharedLayers.values()).filter((l) => l.id !== "favourite"),
|
||||
})
|
||||
)
|
||||
|
||||
const mcChangesPath = "./assets/themes/mapcomplete-changes/mapcomplete-changes.json"
|
||||
|
@ -428,6 +424,19 @@ class LayerOverviewUtils extends Script {
|
|||
ConversionContext.construct([], [])
|
||||
)
|
||||
|
||||
for (const [_, theme] of sharedThemes) {
|
||||
theme.layers = theme.layers.filter(
|
||||
(l) => Constants.added_by_default.indexOf(l["id"]) < 0
|
||||
)
|
||||
}
|
||||
|
||||
writeFileSync(
|
||||
"./src/assets/generated/known_themes.json",
|
||||
JSON.stringify({
|
||||
themes: Array.from(sharedThemes.values()),
|
||||
})
|
||||
)
|
||||
|
||||
const end = new Date()
|
||||
const millisNeeded = end.getTime() - start.getTime()
|
||||
if (AllSharedLayers.getSharedLayersConfigs().size == 0) {
|
||||
|
@ -791,4 +800,5 @@ class LayerOverviewUtils extends Script {
|
|||
}
|
||||
}
|
||||
|
||||
new GenerateFavouritesLayer().run()
|
||||
new LayerOverviewUtils().run()
|
||||
|
|
|
@ -1,45 +1,54 @@
|
|||
import known_themes from "../assets/generated/known_themes.json"
|
||||
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"
|
||||
import favourite from "../assets/generated/layers/favourite.json"
|
||||
import { LayoutConfigJson } from "../Models/ThemeConfig/Json/LayoutConfigJson"
|
||||
import { AllSharedLayers } from "./AllSharedLayers"
|
||||
import Constants from "../Models/Constants"
|
||||
|
||||
/**
|
||||
* Somewhat of a dictionary, which lazily parses needed themes
|
||||
*/
|
||||
export class AllKnownLayoutsLazy {
|
||||
private readonly dict: Map<string, { data: LayoutConfig } | { func: () => LayoutConfig }> =
|
||||
new Map()
|
||||
constructor() {
|
||||
private readonly raw: Map<string, LayoutConfigJson> = new Map()
|
||||
private readonly dict: Map<string, LayoutConfig> = new Map()
|
||||
|
||||
constructor(includeFavouriteLayer = true) {
|
||||
for (const layoutConfigJson of known_themes["themes"]) {
|
||||
this.dict.set(layoutConfigJson.id, {
|
||||
func: () => {
|
||||
const layout = new LayoutConfig(<LayoutConfigJson>layoutConfigJson, true)
|
||||
for (let i = 0; i < layout.layers.length; i++) {
|
||||
let layer = layout.layers[i]
|
||||
if (typeof layer === "string") {
|
||||
throw "Layer " + layer + " was not expanded in " + layout.id
|
||||
for (const layerId of Constants.added_by_default) {
|
||||
if (layerId === "favourite") {
|
||||
if (includeFavouriteLayer) {
|
||||
layoutConfigJson.layers.push(favourite)
|
||||
}
|
||||
continue
|
||||
}
|
||||
return layout
|
||||
},
|
||||
})
|
||||
const defaultLayer = AllSharedLayers.getSharedLayersConfigs().get(layerId)
|
||||
if (defaultLayer === undefined) {
|
||||
console.error("Could not find builtin layer", layerId)
|
||||
continue
|
||||
}
|
||||
layoutConfigJson.layers.push(defaultLayer)
|
||||
}
|
||||
this.raw.set(layoutConfigJson.id, layoutConfigJson)
|
||||
}
|
||||
}
|
||||
|
||||
public getConfig(key: string): LayoutConfigJson {
|
||||
return this.raw.get(key)
|
||||
}
|
||||
|
||||
public get(key: string): LayoutConfig {
|
||||
const thunk = this.dict.get(key)
|
||||
if (thunk === undefined) {
|
||||
return undefined
|
||||
const cached = this.dict.get(key)
|
||||
if (cached !== undefined) {
|
||||
return cached
|
||||
}
|
||||
if (thunk["data"]) {
|
||||
return thunk["data"]
|
||||
}
|
||||
const layout = thunk["func"]()
|
||||
this.dict.set(key, { data: layout })
|
||||
|
||||
const layout = new LayoutConfig(this.getConfig(key))
|
||||
this.dict.set(key, layout)
|
||||
return layout
|
||||
}
|
||||
|
||||
public keys() {
|
||||
return this.dict.keys()
|
||||
return this.raw.keys()
|
||||
}
|
||||
|
||||
public values() {
|
||||
|
|
|
@ -173,7 +173,6 @@ export default class GeoLocationHandler {
|
|||
properties[k] = location[k]
|
||||
}
|
||||
}
|
||||
console.debug("Current location object:", location)
|
||||
properties["_all"] = JSON.stringify(location)
|
||||
|
||||
const feature = <Feature>{
|
||||
|
|
|
@ -16,6 +16,11 @@ export default class FavouritesFeatureSource extends StaticFeatureSource {
|
|||
private readonly _osmConnection: OsmConnection
|
||||
private readonly _detectedIds: Store<string[]>
|
||||
|
||||
/**
|
||||
* All favourites, including the ones which are filtered away because they are already displayed
|
||||
*/
|
||||
public readonly allFavourites: Store<Feature[]>
|
||||
|
||||
constructor(
|
||||
connection: OsmConnection,
|
||||
indexedSource: FeaturePropertiesStore,
|
||||
|
@ -53,6 +58,7 @@ export default class FavouritesFeatureSource extends StaticFeatureSource {
|
|||
)
|
||||
|
||||
super(featuresWithoutAlreadyPresent)
|
||||
this.allFavourites = features
|
||||
|
||||
this._osmConnection = connection
|
||||
this._detectedIds = Stores.ListStabilized(
|
||||
|
@ -76,6 +82,7 @@ export default class FavouritesFeatureSource extends StaticFeatureSource {
|
|||
const geometry = <[number, number]>JSON.parse(prefs[key])
|
||||
const properties = FavouritesFeatureSource.getPropertiesFor(prefs, id)
|
||||
properties._orig_layer = prefs[FavouritesFeatureSource.prefix + id + "-layer"]
|
||||
properties._orig_theme = prefs[FavouritesFeatureSource.prefix + id + "-theme"]
|
||||
|
||||
properties.id = osmId
|
||||
properties._favourite = "yes"
|
||||
|
|
|
@ -2,6 +2,7 @@ import { LayerConfigJson } from "../Json/LayerConfigJson"
|
|||
import { Utils } from "../../../Utils"
|
||||
import { QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson"
|
||||
import { ConversionContext } from "./ConversionContext"
|
||||
import { T } from "vitest/dist/types-aac763a5"
|
||||
|
||||
export interface DesugaringContext {
|
||||
tagRenderings: Map<string, QuestionableTagRenderingConfigJson>
|
||||
|
@ -81,18 +82,36 @@ export class Pure<TIn, TOut> extends Conversion<TIn, TOut> {
|
|||
}
|
||||
}
|
||||
|
||||
export class Bypass<T> extends DesugaringStep<T> {
|
||||
private readonly _applyIf: (t: T) => boolean
|
||||
private readonly _step: DesugaringStep<T>
|
||||
constructor(applyIf: (t: T) => boolean, step: DesugaringStep<T>) {
|
||||
super("Applies the step on the object, if the object satisfies the predicate", [], "Bypass")
|
||||
this._applyIf = applyIf
|
||||
this._step = step
|
||||
}
|
||||
|
||||
convert(json: T, context: ConversionContext): T {
|
||||
if (!this._applyIf(json)) {
|
||||
return json
|
||||
}
|
||||
return this._step.convert(json, context)
|
||||
}
|
||||
}
|
||||
|
||||
export class Each<X, Y> extends Conversion<X[], Y[]> {
|
||||
private readonly _step: Conversion<X, Y>
|
||||
private readonly _msg: string
|
||||
private readonly _filter: (x: X) => boolean
|
||||
|
||||
constructor(step: Conversion<X, Y>, msg?: string) {
|
||||
constructor(step: Conversion<X, Y>, options?: { msg?: string }) {
|
||||
super(
|
||||
"Applies the given step on every element of the list",
|
||||
[],
|
||||
"OnEach(" + step.name + ")"
|
||||
)
|
||||
this._step = step
|
||||
this._msg = msg
|
||||
this._msg = options?.msg
|
||||
}
|
||||
|
||||
convert(values: X[], context: ConversionContext): Y[] {
|
||||
|
|
|
@ -85,7 +85,7 @@ export default class CreateNoteImportLayer extends Conversion<LayerConfigJson, L
|
|||
description: trs(t.description, { title: layer.title.render }),
|
||||
source: {
|
||||
osmTags: {
|
||||
and: ["id~*"],
|
||||
and: ["id~[0-9]+", "comment_url~.*notes/[0-9]*g.json"],
|
||||
},
|
||||
geoJson:
|
||||
"https://api.openstreetmap.org/api/0.6/notes.json?limit=10000&closed=" +
|
||||
|
|
|
@ -566,6 +566,16 @@ export class AddQuestionBox extends DesugaringStep<LayerConfigJson> {
|
|||
}
|
||||
|
||||
export class AddEditingElements extends DesugaringStep<LayerConfigJson> {
|
||||
static addedElements: string[] = [
|
||||
"minimap",
|
||||
"just_created",
|
||||
"split_button",
|
||||
"move_button",
|
||||
"delete_button",
|
||||
"last_edit",
|
||||
"favourite_state",
|
||||
"all_tags",
|
||||
]
|
||||
private readonly _desugaring: DesugaringContext
|
||||
|
||||
constructor(desugaring: DesugaringContext) {
|
||||
|
@ -1210,7 +1220,7 @@ class AddFavouriteBadges extends DesugaringStep<LayerConfigJson> {
|
|||
}
|
||||
|
||||
convert(json: LayerConfigJson, context: ConversionContext): LayerConfigJson {
|
||||
if (json.id === "favourite") {
|
||||
if (json.source === "special" || json.source === "special:library") {
|
||||
return json
|
||||
}
|
||||
const pr = json.pointRendering?.[0]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Conversion, DesugaringStep, Each, Fuse, On, Pipe, Pure } from "./Conversion"
|
||||
import { Bypass, Conversion, DesugaringStep, Each, Fuse, On } from "./Conversion"
|
||||
import { LayerConfigJson } from "../Json/LayerConfigJson"
|
||||
import LayerConfig from "../LayerConfig"
|
||||
import { Utils } from "../../../Utils"
|
||||
|
@ -11,7 +11,6 @@ import { TagUtils } from "../../../Logic/Tags/TagUtils"
|
|||
import { ExtractImages } from "./FixImages"
|
||||
import { And } from "../../../Logic/Tags/And"
|
||||
import Translations from "../../../UI/i18n/Translations"
|
||||
import Svg from "../../../Svg"
|
||||
import FilterConfigJson from "../Json/FilterConfigJson"
|
||||
import DeleteConfig from "../DeleteConfig"
|
||||
import { QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson"
|
||||
|
@ -276,9 +275,9 @@ export class ValidateThemeAndLayers extends Fuse<LayoutConfigJson> {
|
|||
new On(
|
||||
"layers",
|
||||
new Each(
|
||||
new Pipe(
|
||||
new ValidateLayer(undefined, isBuiltin, doesImageExist, false, true),
|
||||
new Pure((x) => x?.raw)
|
||||
new Bypass(
|
||||
(layer) => Constants.added_by_default.indexOf(<any>layer.id) < 0,
|
||||
new ValidateLayerConfig(undefined, isBuiltin, doesImageExist, false, true)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -968,7 +967,7 @@ export class ValidateTagRenderings extends Fuse<TagRenderingConfigJson> {
|
|||
"Various validation on tagRenderingConfigs",
|
||||
new DetectShadowedMappings(layerConfig),
|
||||
new DetectConflictingAddExtraTags(),
|
||||
// new DetectNonErasedKeysInMappings(),
|
||||
// TODO enable new DetectNonErasedKeysInMappings(),
|
||||
new DetectMappingsWithImages(doesImageExist),
|
||||
new On("render", new ValidatePossibleLinks()),
|
||||
new On("question", new ValidatePossibleLinks()),
|
||||
|
@ -1350,6 +1349,29 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
|
|||
}
|
||||
}
|
||||
|
||||
export class ValidateLayerConfig extends DesugaringStep<LayerConfigJson> {
|
||||
private readonly validator: ValidateLayer
|
||||
constructor(
|
||||
path: string,
|
||||
isBuiltin: boolean,
|
||||
doesImageExist: DoesImageExist,
|
||||
studioValidations: boolean = false,
|
||||
skipDefaultLayers: boolean = false
|
||||
) {
|
||||
super("Thin wrapper around 'ValidateLayer", [], "ValidateLayerConfig")
|
||||
this.validator = new ValidateLayer(
|
||||
path,
|
||||
isBuiltin,
|
||||
doesImageExist,
|
||||
studioValidations,
|
||||
skipDefaultLayers
|
||||
)
|
||||
}
|
||||
|
||||
convert(json: LayerConfigJson, context: ConversionContext): LayerConfigJson {
|
||||
return this.validator.convert(json, context).raw
|
||||
}
|
||||
}
|
||||
export class ValidateLayer extends Conversion<
|
||||
LayerConfigJson,
|
||||
{ parsed: LayerConfig; raw: LayerConfigJson }
|
||||
|
|
|
@ -462,6 +462,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
* @private
|
||||
*/
|
||||
private selectClosestAtCenter(i: number = 0) {
|
||||
this.mapProperties.lastKeyNavigation.setData(Date.now() / 1000)
|
||||
const toSelect = this.closestFeatures.features.data[i]
|
||||
if (!toSelect) {
|
||||
return
|
||||
|
@ -567,46 +568,6 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
})
|
||||
}
|
||||
|
||||
private addLastClick(last_click: LastClickFeatureSource) {
|
||||
// The last_click gets a _very_ special treatment as it interacts with various parts
|
||||
|
||||
this.featureProperties.trackFeatureSource(last_click)
|
||||
this.indexedFeatures.addSource(last_click)
|
||||
|
||||
last_click.features.addCallbackAndRunD((features) => {
|
||||
if (this.selectedLayer.data?.id === "last_click") {
|
||||
// The last-click location moved, but we have selected the last click of the previous location
|
||||
// So, we update _after_ clearing the selection to make sure no stray data is sticking around
|
||||
this.selectedElement.setData(undefined)
|
||||
this.selectedElement.setData(features[0])
|
||||
}
|
||||
})
|
||||
|
||||
new ShowDataLayer(this.map, {
|
||||
features: new FilteringFeatureSource(this.newPointDialog, last_click),
|
||||
doShowLayer: this.featureSwitches.featureSwitchEnableLogin,
|
||||
layer: this.newPointDialog.layerDef,
|
||||
selectedElement: this.selectedElement,
|
||||
selectedLayer: this.selectedLayer,
|
||||
metaTags: this.userRelatedState.preferencesAsTags,
|
||||
onClick: (feature: Feature) => {
|
||||
if (this.mapProperties.zoom.data < Constants.minZoomLevelToAddNewPoint) {
|
||||
this.map.data.flyTo({
|
||||
zoom: Constants.minZoomLevelToAddNewPoint,
|
||||
center: this.mapProperties.lastClickLocation.data,
|
||||
})
|
||||
return
|
||||
}
|
||||
// We first clear the selection to make sure no weird state is around
|
||||
this.selectedLayer.setData(undefined)
|
||||
this.selectedElement.setData(undefined)
|
||||
|
||||
this.selectedElement.setData(feature)
|
||||
this.selectedLayer.setData(this.newPointDialog.layerDef)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the special layers to the map
|
||||
*/
|
||||
|
@ -663,9 +624,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
}
|
||||
|
||||
const rangeFLayer: FilteredLayer = this.layerState.filteredLayers.get("range")
|
||||
|
||||
const rangeIsDisplayed = rangeFLayer?.isDisplayed
|
||||
|
||||
if (
|
||||
!QueryParameters.wasInitialized(FilteredLayer.queryParameterKey(rangeFLayer.layerDef))
|
||||
) {
|
||||
|
|
|
@ -121,9 +121,9 @@ export default class UploadTraceToOsmUI extends LoginToggle {
|
|||
]).SetClass("flex p-2 rounded-xl border-2 subtle-border items-center"),
|
||||
new Toggle(
|
||||
confirmPanel,
|
||||
new SubtleButton(new SvelteUIElement(Upload), t.title).onClick(() =>
|
||||
clicked.setData(true)
|
||||
),
|
||||
new SubtleButton(new SvelteUIElement(Upload), t.title)
|
||||
.onClick(() => clicked.setData(true))
|
||||
.SetClass("w-full"),
|
||||
clicked
|
||||
),
|
||||
uploadFinished
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
<script lang="ts">
|
||||
|
||||
export let properties: Record<string, string>
|
||||
</script>
|
||||
|
||||
<div>
|
||||
{JSON.stringify(properties)}
|
||||
{properties?.id ?? "undefined"}
|
||||
<a href={properties._backend +"/"+ properties?.id}>OSM</a>
|
||||
</div>
|
|
@ -1,8 +1,19 @@
|
|||
<script lang="ts">
|
||||
import { SpecialVisualization } from "../SpecialVisualization";
|
||||
import type { SpecialVisualizationState } from "../SpecialVisualization";
|
||||
import FavouriteSummary from "./FavouriteSummary.svelte";
|
||||
|
||||
/**
|
||||
* A panel showing all your favourites
|
||||
*/
|
||||
export let state: SpecialVisualizationState
|
||||
export let state: SpecialVisualizationState;
|
||||
let favourites = state.favourites.allFavourites;
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col">
|
||||
You marked {$favourites.length} locations as a favourite location.
|
||||
|
||||
This list is only visible to you
|
||||
{#each $favourites as f}
|
||||
<FavouriteSummary properties={f.properties} />
|
||||
{/each}
|
||||
</div>
|
||||
|
|
|
@ -31,7 +31,9 @@ export class ExportAsGpxViz implements SpecialVisualization {
|
|||
t.downloadFeatureAsGpx.SetClass("font-bold text-lg"),
|
||||
t.downloadGpxHelper.SetClass("subtle"),
|
||||
]).SetClass("flex flex-col")
|
||||
).onClick(() => {
|
||||
)
|
||||
.SetClass("w-full")
|
||||
.onClick(() => {
|
||||
console.log("Exporting as GPX!")
|
||||
const tags = tagSource.data
|
||||
const title = layer.title?.GetRenderValue(tags)?.Subs(tags)?.txt ?? "gpx_track"
|
||||
|
|
|
@ -534,6 +534,9 @@ export default class SpecialVisualizations {
|
|||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
): BaseUIElement {
|
||||
if (!layer.deletion) {
|
||||
return undefined
|
||||
}
|
||||
return new SvelteUIElement(DeleteWizard, {
|
||||
tags: tagSource,
|
||||
deleteConfig: layer.deletion,
|
||||
|
@ -873,7 +876,8 @@ export default class SpecialVisualizations {
|
|||
t.downloadFeatureAsGeojson.SetClass("font-bold text-lg"),
|
||||
t.downloadGeoJsonHelper.SetClass("subtle"),
|
||||
]).SetClass("flex flex-col")
|
||||
).onClick(() => {
|
||||
)
|
||||
.onClick(() => {
|
||||
console.log("Exporting as Geojson")
|
||||
const tags = tagSource.data
|
||||
const title =
|
||||
|
@ -887,6 +891,7 @@ export default class SpecialVisualizations {
|
|||
}
|
||||
)
|
||||
})
|
||||
.SetClass("w-full")
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
import type { MapProperties } from "../Models/MapProperties";
|
||||
import Geosearch from "./BigComponents/Geosearch.svelte";
|
||||
import Translations from "./i18n/Translations";
|
||||
import { CogIcon, EyeIcon, MenuIcon, XCircleIcon } from "@rgossiaux/svelte-heroicons/solid";
|
||||
import { CogIcon, EyeIcon, HeartIcon, MenuIcon, XCircleIcon } from "@rgossiaux/svelte-heroicons/solid";
|
||||
import Tr from "./Base/Tr.svelte";
|
||||
import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte";
|
||||
import FloatOver from "./Base/FloatOver.svelte";
|
||||
|
@ -64,6 +64,8 @@
|
|||
import Community from "../assets/svg/Community.svelte";
|
||||
import Download from "../assets/svg/Download.svelte";
|
||||
import Share from "../assets/svg/Share.svelte";
|
||||
import FavouriteSummary from "./Favourites/FavouriteSummary.svelte";
|
||||
import Favourites from "./Favourites/Favourites.svelte";
|
||||
|
||||
export let state: ThemeViewState;
|
||||
let layout = state.layout;
|
||||
|
@ -493,22 +495,31 @@
|
|||
</div>
|
||||
|
||||
<div class="flex" slot="title2">
|
||||
<HeartIcon class="h-6 w-6" />
|
||||
Your favourites
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col" slot="content2">
|
||||
<h3>Your favourite locations</h3>
|
||||
<Favourites {state}/>
|
||||
</div>
|
||||
<div class="flex" slot="title3">
|
||||
<Community class="w-6 h-6"/>
|
||||
<Tr t={Translations.t.communityIndex.title} />
|
||||
</div>
|
||||
<div class="m-2" slot="content2">
|
||||
<div class="m-2" slot="content3">
|
||||
<CommunityIndexView location={state.mapProperties.location} />
|
||||
</div>
|
||||
<div class="flex" slot="title3">
|
||||
<div class="flex" slot="title4">
|
||||
<EyeIcon class="w-6" />
|
||||
<Tr t={Translations.t.privacy.title} />
|
||||
</div>
|
||||
<div class="m-2" slot="content3">
|
||||
<div class="m-2" slot="content4">
|
||||
<ToSvelte construct={() => new PrivacyPolicy()} />
|
||||
</div>
|
||||
|
||||
<Tr slot="title4" t={Translations.t.advanced.title} />
|
||||
<div class="m-2 flex flex-col" slot="content4">
|
||||
<Tr slot="title5" t={Translations.t.advanced.title} />
|
||||
<div class="m-2 flex flex-col" slot="content5">
|
||||
<If condition={featureSwitches.featureSwitchEnableLogin}>
|
||||
<OpenIdEditor mapProperties={state.mapProperties} />
|
||||
<OpenJosm {state}/>
|
||||
|
|
|
@ -179,7 +179,21 @@ describe("PrepareTheme", () => {
|
|||
id: "layer-example",
|
||||
name: null,
|
||||
minzoom: 18,
|
||||
pointRendering: [{ location: ["point"], label: "xyz" }],
|
||||
pointRendering: [
|
||||
{
|
||||
location: ["point"],
|
||||
label: "xyz",
|
||||
iconBadges: [
|
||||
{
|
||||
if: "_favourite=yes",
|
||||
then: {
|
||||
id: "circlewhiteheartred",
|
||||
render: "circle:white;heart:red",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
lineRendering: [{ width: 1 }],
|
||||
titleIcons: [],
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue