diff --git a/.github/workflows/deploy_prod.yml b/.github/workflows/deploy_prod.yml
index 1334f196c0..faf288f2d8 100644
--- a/.github/workflows/deploy_prod.yml
+++ b/.github/workflows/deploy_prod.yml
@@ -26,7 +26,7 @@ jobs:
shell: bash
- name: create dependencies
- run: npm run generate:licenses; npm run generate:images; npm run generate:charging-stations; npm run generate:service-worker; npm run generate:editor-layer-index
+ run: npm run generate:licenses; npm run generate:images; npm run generate:charging-stations; npm run generate:service-worker; npm run download:editor-layer-index
shell: bash
- name: sync translations
diff --git a/assets/layers/atm/atm.json b/assets/layers/atm/atm.json
index c9f3bb7534..36cd15328e 100644
--- a/assets/layers/atm/atm.json
+++ b/assets/layers/atm/atm.json
@@ -613,7 +613,6 @@
}
],
"filter": [
- "open_now",
{
"id": "speech_output",
"options": [
diff --git a/assets/layers/bike_shop/bike_shop.json b/assets/layers/bike_shop/bike_shop.json
index 757c3ae9c4..d9a6196052 100644
--- a/assets/layers/bike_shop/bike_shop.json
+++ b/assets/layers/bike_shop/bike_shop.json
@@ -893,7 +893,6 @@
"description"
],
"filter": [
- "open_now",
{
"id": "sells_second-hand",
"options": [
diff --git a/assets/layers/elongated_coin/elongated_coin.json b/assets/layers/elongated_coin/elongated_coin.json
index d76da9ad76..e56b47ddfd 100644
--- a/assets/layers/elongated_coin/elongated_coin.json
+++ b/assets/layers/elongated_coin/elongated_coin.json
@@ -435,7 +435,6 @@
"check_date"
],
"filter": [
- "open_now",
"accepts_debit_cards",
"accepts_credit_cards"
],
diff --git a/assets/layers/etymology/etymology.json b/assets/layers/etymology/etymology.json
index f522c2eddc..3c0eb2e1d0 100644
--- a/assets/layers/etymology/etymology.json
+++ b/assets/layers/etymology/etymology.json
@@ -288,7 +288,8 @@
"cs": "Pojmenováno po {name:etymology}"
},
"freeform": {
- "key": "name:etymology"
+ "key": "name:etymology",
+ "type": "text"
},
"mappings": [
{
diff --git a/assets/layers/food/food.json b/assets/layers/food/food.json
index 1077a5fde6..9719ebcb6a 100644
--- a/assets/layers/food/food.json
+++ b/assets/layers/food/food.json
@@ -1304,10 +1304,10 @@
}
]
},
- "has_organic",
- "sugar_free",
- "gluten_free",
- "lactose_free",
+ "filters.has_organic",
+ "filters.sugar_free",
+ "filters.gluten_free",
+ "filters.lactose_free",
"accepts_cash",
"accepts_cards",
"dogs"
diff --git a/assets/layers/questions/questions.json b/assets/layers/questions/questions.json
index 00dfea69fd..38dfa9b3a7 100644
--- a/assets/layers/questions/questions.json
+++ b/assets/layers/questions/questions.json
@@ -632,7 +632,8 @@
"de": "Hunde sind nur draußen erlaubt"
}
}
- ]
+ ],
+ "filter": ["filters.dogs"]
},
{
"id": "description",
@@ -756,7 +757,8 @@
},
"hideInAnswer": true
}
- ]
+ ],
+ "filter": ["filters.open_now"]
},
{
"id": "opening_hours_24_7",
@@ -1040,7 +1042,8 @@
"cs": "Platba QR kódem je zde možná"
}
}
- ]
+ ],
+ "filter": ["filters.accepts_cash","filters.accepts_cards"]
},
{
"id": "payment-options-split",
@@ -2120,7 +2123,8 @@
"pl": "To miejsce oferuje przewodowy dostęp do Internetu"
}
}
- ]
+ ],
+ "filter": ["filters.has_internet"]
},
{
"id": "internet-fee",
@@ -2451,7 +2455,8 @@
"cs": "Tento obchod nemá žádnou nabídku bez cukru"
}
}
- ]
+ ],
+ "filter": ["filters.sugar_free"]
},
{
"id": "lactose_free",
@@ -2496,7 +2501,9 @@
"cs": "Žádná nabídka bez laktózy"
}
}
- ]
+ ],
+ "filter": ["filters.lactose_free"]
+
},
{
"id": "gluten_free",
@@ -2541,7 +2548,9 @@
"cs": "Tento obchod nemá bezlepkovou nabídku"
}
}
- ]
+ ],
+ "filter": ["filters.gluten_free"]
+
},
{
"id": "vegan",
diff --git a/assets/layers/shops/shops.json b/assets/layers/shops/shops.json
index a3983508c6..a794639e55 100644
--- a/assets/layers/shops/shops.json
+++ b/assets/layers/shops/shops.json
@@ -663,9 +663,6 @@
}
]
},
- "accepts_cash",
- "accepts_cards",
- "has_organic",
{
"id": "second_hand",
"options": [
@@ -686,9 +683,7 @@
}
]
},
- "sugar_free",
- "gluten_free",
- "lactose_free"
+ "filters.has_organic"
],
"deletion": {
"softDeletionTags": {
diff --git a/langs/en.json b/langs/en.json
index 15fb70b067..18eaa8f49d 100644
--- a/langs/en.json
+++ b/langs/en.json
@@ -245,7 +245,7 @@
"licenseInfo": "
Copyright notice
The provided data is available under ODbL. Reusing it is gratis for any purpose, but - the attribution © OpenStreetMap contributors must be shown
- Any change must be published under the same license
Please read the full copyright notice for details.",
"noDataLoaded": "No data is loaded yet. Download will be available soon",
"pdf": {
- "current_view_generic": "Export a PDF off the current view for {paper_size} in {orientation} orientation"
+ "current_view_generic": "Export a PDF of the current view for {paper_size} in {orientation} orientation"
},
"title": "Download",
"toMuch": "There are to many features to download them all",
diff --git a/langs/nl.json b/langs/nl.json
index 883c5f165e..2be0a59cb7 100644
--- a/langs/nl.json
+++ b/langs/nl.json
@@ -45,6 +45,9 @@
"useSomethingElse": "Gebruik een ander OpenStreetMap-bewerkprogramma om dit object te verwijderen",
"whyDelete": "Waarom moet dit object van de kaart verwijderd worden?"
},
+ "external": {
+ "error": "Kon geen gestructureerde informatie uit de website ophalen"
+ },
"favourite": {
"loginNeeded": "Log in
Je moet je aanmelden met OpenStreetMap om een persoonlijk thema te gebruiken",
"panelIntro": "Jouw persoonlijke thema
Activeer je favorite lagen van alle andere themas",
@@ -151,6 +154,7 @@
"editId": "Hier bewerken met de OpenStreetMap online editor",
"editJosm": "Hier bewerken met JOSM",
"followOnMastodon": "Volg MapComplete op Mastodon",
+ "gotoSourceCode": "Bekijk de broncode",
"iconAttribution": {
"title": "Iconen en afbeeldingen"
},
@@ -163,6 +167,7 @@
"openIssueTracker": "Geef een fout in de software door",
"openMapillary": "Open Mapillary op deze locatie",
"openOsmcha": "Bekijk de laatste bijdragen gemaakt met {theme}",
+ "openOsmchaLastWeek": "Bekijk aanpassingen van de voorbije 7 dagen",
"themeBy": "Thema gemaakt door {author}",
"title": "Copyright en attributie",
"translatedBy": "MapComplete werd vertaald door {contributors} en {hiddenCount} meer vertalers"
diff --git a/langs/zh_Hant.json b/langs/zh_Hant.json
index 01527a2f18..7e81c7bc7d 100644
--- a/langs/zh_Hant.json
+++ b/langs/zh_Hant.json
@@ -245,7 +245,7 @@
"licenseInfo": "著作權聲明
提供的資料採用 ODbL 授權釋出。可以用任何目標再利用資料,但是需 請閱讀完整的 著作權聲明。",
"noDataLoaded": "還未載入資料,之後能夠下載",
"pdf": {
- "current_view_generic": "匯出目前檢視為 {paper_size} 的 {orientation} 方向 PDF"
+ "current_view_generic": "以 {orientation} 方向匯出 {paper_size} 大小的目前檢視 PDF"
},
"title": "下載",
"toMuch": "有很多圖徵可以下載了",
diff --git a/scripts/generateFavouritesLayer.ts b/scripts/generateFavouritesLayer.ts
index 41b8fd035a..fa592b0f63 100644
--- a/scripts/generateFavouritesLayer.ts
+++ b/scripts/generateFavouritesLayer.ts
@@ -236,8 +236,11 @@ export class GenerateFavouritesLayer extends Script {
if (seenTitleIcons.has(titleIcon.id)) {
continue
}
+ if(titleIcon.id === undefined){
+ continue
+ }
seenTitleIcons.add(titleIcon.id)
- console.log("Adding ", titleIcon.id)
+ console.log("Adding title icon", titleIcon.id)
titleIcons.push(titleIcon)
}
}
diff --git a/scripts/generateLayerOverview.ts b/scripts/generateLayerOverview.ts
index cc496437ab..b0df401c49 100644
--- a/scripts/generateLayerOverview.ts
+++ b/scripts/generateLayerOverview.ts
@@ -332,6 +332,7 @@ class LayerOverviewUtils extends Script {
return sharedQuestions.tagRenderings
}
+
return this.getSharedTagRenderings(
doesImageExist,
dict,
diff --git a/src/Logic/FeatureSource/TiledFeatureSource/SummaryTileSource.ts b/src/Logic/FeatureSource/TiledFeatureSource/SummaryTileSource.ts
index 150b7ed9a8..74ffa97457 100644
--- a/src/Logic/FeatureSource/TiledFeatureSource/SummaryTileSource.ts
+++ b/src/Logic/FeatureSource/TiledFeatureSource/SummaryTileSource.ts
@@ -114,7 +114,7 @@ export class SummaryTileSource extends DynamicTileSource {
): Store[]> {
const [z, x, y] = Tiles.tile_from_index(tileIndex)
let coordinates = Tiles.centerPointOf(z, x, y)
- const url = `${cacheserver}/${layersSummed}/${z}/${x}/${y}.json`
+ const url = `${cacheserver}/summary/${layersSummed}/${z}/${x}/${y}.json`
const count = UIEventSource.FromPromiseWithErr(Utils.downloadJson(url))
return count.mapD((count) => {
if (count["error"] !== undefined) {
diff --git a/src/Models/ThemeConfig/Conversion/PrepareLayer.ts b/src/Models/ThemeConfig/Conversion/PrepareLayer.ts
index 7a859a5a6f..2199ff44b0 100644
--- a/src/Models/ThemeConfig/Conversion/PrepareLayer.ts
+++ b/src/Models/ThemeConfig/Conversion/PrepareLayer.ts
@@ -62,8 +62,37 @@ class ExpandFilter extends DesugaringStep {
const newFilters: FilterConfigJson[] = []
const filters = <(FilterConfigJson | string)[]>json.filter
+
+ for (let i = 0; i < json.tagRenderings?.length; i++){
+ const tagRendering = json.tagRenderings[i]
+ if(!tagRendering.filter){
+ continue
+ }
+ for (const filterName of tagRendering.filter ?? []) {
+ if(typeof filterName !== "string"){
+ context.enters("tagRenderings",i,"filter").err("Not a string: "+ filterName)
+ }
+ const exists = filters.some(existing => {
+ const id : string = existing["id"] ?? existing
+ return filterName === id || (filterName.startsWith("filters.") && filterName.endsWith("."+id))
+ })
+ if(exists){
+ continue
+ }
+ if(!filterName){
+ context.err("Got undefined as filter expansion in "+tagRendering["id"])
+ continue
+ }
+ console.log("Adding filter",filterName," due to", tagRendering["id"])
+ filters.push(filterName)
+ }
+ }
+
for (let i = 0; i < filters.length; i++) {
const filter = filters[i]
+ if(filter === undefined){
+ continue
+ }
if (typeof filter !== "string") {
newFilters.push(filter)
continue
@@ -85,7 +114,7 @@ class ExpandFilter extends DesugaringStep {
osmTags: mapping.if,
}))
options.unshift({
- question: {
+ question: matchingTr["question"] ?? {
en: "All types",
},
osmTags: undefined,
@@ -113,7 +142,11 @@ class ExpandFilter extends DesugaringStep {
const expandedFilter = (<(FilterConfigJson | string)[]>layer.filter).find(
(f) => typeof f !== "string" && f.id === expectedId
)
- newFilters.push(expandedFilter)
+ if(expandedFilter === undefined){
+ context.err("Did not find filter with name "+filter)
+ }else{
+ newFilters.push(expandedFilter)
+ }
} else {
// This is a bootstrapping-run, we can safely ignore this
}
diff --git a/src/Models/ThemeConfig/Conversion/Validation.ts b/src/Models/ThemeConfig/Conversion/Validation.ts
index b309c13777..f8cddc46c8 100644
--- a/src/Models/ThemeConfig/Conversion/Validation.ts
+++ b/src/Models/ThemeConfig/Conversion/Validation.ts
@@ -1761,6 +1761,10 @@ export class ValidateFilter extends DesugaringStep {
// Calling another filter, we skip
return filter
}
+ if(filter === undefined){
+ context.err("Trying to validate a filter, but this filter is undefined")
+ return undefined
+ }
for (const option of filter.options) {
for (let i = 0; i < option.fields?.length ?? 0; i++) {
const field = option.fields[i]
diff --git a/src/Models/ThemeConfig/Json/TagRenderingConfigJson.ts b/src/Models/ThemeConfig/Json/TagRenderingConfigJson.ts
index 5d037b9c8c..645b0eaca3 100644
--- a/src/Models/ThemeConfig/Json/TagRenderingConfigJson.ts
+++ b/src/Models/ThemeConfig/Json/TagRenderingConfigJson.ts
@@ -222,4 +222,9 @@ export interface TagRenderingConfigJson {
* Values are split on ` ` (space)
*/
classes?: string
+
+ /**
+ * This tagRendering can introduce this builtin filter
+ */
+ filter?: string[]
}
diff --git a/src/UI/BigComponents/Filterview.svelte b/src/UI/BigComponents/Filterview.svelte
index 0ff7115436..1aa5957a3f 100644
--- a/src/UI/BigComponents/Filterview.svelte
+++ b/src/UI/BigComponents/Filterview.svelte
@@ -43,16 +43,12 @@
{#if filteredLayer.layerDef.name}
-
+
layer.defaultIcon()?.SetClass("block h-6 w-6 no-image-background")}
+ construct={() => layer.defaultIcon()}
/>
-
- layer.defaultIcon()?.SetClass("block h-6 w-6 no-image-background opacity-50")}
- />
-
+
+
diff --git a/src/UI/InputElement/Validators/StringValidator.ts b/src/UI/InputElement/Validators/StringValidator.ts
index 9da065c9a3..6b62e0f590 100644
--- a/src/UI/InputElement/Validators/StringValidator.ts
+++ b/src/UI/InputElement/Validators/StringValidator.ts
@@ -1,7 +1,24 @@
import { Validator } from "../Validator"
+import { Translation } from "../../i18n/Translation"
+import Translations from "../../i18n/Translations"
export default class StringValidator extends Validator {
- constructor() {
- super("string", "A simple piece of text")
+
+ constructor(type?: string, doc?: string, inputmode?: "none" | "text" | "tel" | "url" | "email" | "numeric" | "decimal" | "search", textArea?: boolean) {
+ super(type ?? "string",
+ doc ?? "A simple piece of text which is at most 255 characters long",
+ inputmode,
+ textArea)
+ }
+
+ isValid(s: string): boolean {
+ return s.length <= 255
+ }
+
+ getFeedback(s: string, getCountry?: () => string): Translation | undefined {
+ if (s.length > 255) {
+ return Translations.t.validation.tooLong.Subs({ count: s.length })
+ }
+ return super.getFeedback(s, getCountry)
}
}
diff --git a/src/UI/InputElement/Validators/TextValidator.ts b/src/UI/InputElement/Validators/TextValidator.ts
index d1bc1e1604..a60368ee76 100644
--- a/src/UI/InputElement/Validators/TextValidator.ts
+++ b/src/UI/InputElement/Validators/TextValidator.ts
@@ -1,6 +1,6 @@
-import { Validator } from "../Validator"
+import StringValidator from "./StringValidator"
-export default class TextValidator extends Validator {
+export default class TextValidator extends StringValidator {
constructor() {
super(
"text",