diff --git a/Docs/SpecialInputElements.md b/Docs/SpecialInputElements.md
index 3c05b4c87..5a70109de 100644
--- a/Docs/SpecialInputElements.md
+++ b/Docs/SpecialInputElements.md
@@ -24,7 +24,40 @@ A geographical length in meters (rounded at two points). Will give an extra mini
## wikidata
-A wikidata identifier, e.g. Q42. Input helper arguments: [ key: the value of this tag will initialize search (default: name), options: { removePrefixes: string[], removePostfixes: string[] } these prefixes and postfixes will be removed from the initial search value]
+A wikidata identifier, e.g. Q42.
+### Helper arguments
+
+
+
+name | doc
+------ | -----
+key | the value of this tag will initialize search (default: name)
+options | A JSON-object of type `{ removePrefixes: string[], removePostfixes: string[] }`.
+
+subarg | doc
+-------- | -----
+removePrefixes | remove these snippets of text from the start of the passed string to search
+removePostfixes | remove these snippets of text from the end of the passed string to search
+
+
+### Example usage
+
+ The following is the 'freeform'-part of a layer config which will trigger a search for the wikidata item corresponding with the name of the selected feature. It will also remove '-street', '-square', ... if found at the end of the name```"freeform": {
+ "key": "name:etymology:wikidata",
+ "type": "wikidata",
+ "helperArgs": [
+ "name",
+ {
+ "removePostfixes": [
+ "street",
+ "boulevard",
+ "path",
+ "square",
+ "plaza",
+ ]
+ }
+ ]
+ },```
## int
@@ -60,7 +93,38 @@ A phone number
## opening_hours
-Has extra elements to easily input when a POI is opened
+Has extra elements to easily input when a POI is opened.
+### Helper arguments
+
+
+
+name | doc
+------ | -----
+options | A JSON-object of type `{ prefix: string, postfix: string }`.
+
+subarg | doc
+-------- | -----
+prefix | Piece of text that will always be added to the front of the generated opening hours. If the OSM-data does not start with this, it will fail to parse
+postfix | Piece of text that will always be added to the end of the generated opening hours
+
+
+### Example usage
+
+ To add a conditional (based on time) access restriction:
+
+```
+ "freeform": {
+ "key": "access:conditional",
+ "type": "opening_hours",
+ "helperArgs": [
+ {
+ "prefix":"no @ (",
+ "postfix":")"
+ }
+ ]
+ },```
+
+*Don't forget to pass these in the rendering as well*: `{opening_hours_table(opening_hours,yes @ &LPARENS, &RPARENS )`
## color
diff --git a/Docs/SpecialRenderings.md b/Docs/SpecialRenderings.md
index 452b907da..48750973a 100644
--- a/Docs/SpecialRenderings.md
+++ b/Docs/SpecialRenderings.md
@@ -84,10 +84,12 @@ fallback | undefined | The identifier to use, if tags[subjectKey] as spec
name | default | description
------ | --------- | -------------
key | opening_hours | The tagkey from which the table is constructed.
+prefix | | Remove this string from the start of the value before parsing. __Note: use `&LPARENs` to indicate `(` if needed__
+postfix | | Remove this string from the end of the value before parsing. __Note: use `&RPARENs` to indicate `)` if needed__
#### Example usage
- `{opening_hours_table(opening_hours)}`
+ A normal opening hours table can be invoked with `{opening_hours_table()}`. A table for e.g. conditional access with opening hours can be `{opening_hours_table(access:conditional, no @ &LPARENS, &RPARENS)}`
### live
Downloads a JSON from the given URL, e.g. '{live(example.org/data.json, shorthand:x.y.z, other:a.b.c, shorthand)}' will download the given file, will create an object {shorthand: json[x][y][z], other: json[a][b][c] out of it and will return 'other' or 'json[a][b][c]. This is made to use in combination with tags, e.g. {live({url}, {url:format}, needed_value)}
diff --git a/UI/Input/InputElementMap.ts b/UI/Input/InputElementMap.ts
index 548e50363..a2a50f9d3 100644
--- a/UI/Input/InputElementMap.ts
+++ b/UI/Input/InputElementMap.ts
@@ -25,8 +25,8 @@ export default class InputElementMap extends InputElement {
const self = this;
this._value = inputElement.GetValue().map(
(t => {
- const currentX = self.GetValue()?.data;
const newX = toX(t);
+ const currentX = self.GetValue()?.data;
if (isSame(currentX, newX)) {
return currentX;
}
diff --git a/UI/Input/ValidatedTextField.ts b/UI/Input/ValidatedTextField.ts
index 78d543657..75cf02e69 100644
--- a/UI/Input/ValidatedTextField.ts
+++ b/UI/Input/ValidatedTextField.ts
@@ -19,6 +19,9 @@ import {FixedInputElement} from "./FixedInputElement";
import WikidataSearchBox from "../Wikipedia/WikidataSearchBox";
import Wikidata from "../../Logic/Web/Wikidata";
import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers";
+import Table from "../Base/Table";
+import Combine from "../Base/Combine";
+import Title from "../Base/Title";
interface TextFieldDef {
name: string,
@@ -28,12 +31,159 @@ interface TextFieldDef {
inputHelper?: (value: UIEventSource, options?: {
location: [number, number],
mapBackgroundLayer?: UIEventSource,
- args: (string | number | boolean)[]
+ args: (string | number | boolean | any)[]
feature?: any
}) => InputElement,
inputmode?: string
}
+class WikidataTextField implements TextFieldDef {
+ name = "wikidata"
+ explanation =
+ new Combine([
+ "A wikidata identifier, e.g. Q42.",
+ new Title("Helper arguments"),
+ new Table(["name", "doc"],
+ [
+ ["key", "the value of this tag will initialize search (default: name)"],
+ ["options", new Combine(["A JSON-object of type `{ removePrefixes: string[], removePostfixes: string[] }`.",
+ new Table(
+ ["subarg", "doc"],
+ [["removePrefixes", "remove these snippets of text from the start of the passed string to search"],
+ ["removePostfixes", "remove these snippets of text from the end of the passed string to search"],
+ ]
+ )])
+ ]]),
+ new Title("Example usage"),
+ "The following is the 'freeform'-part of a layer config which will trigger a search for the wikidata item corresponding with the name of the selected feature. It will also remove '-street', '-square', ... if found at the end of the name```" + `"freeform": {
+ "key": "name:etymology:wikidata",
+ "type": "wikidata",
+ "helperArgs": [
+ "name",
+ {
+ "removePostfixes": [
+ "street",
+ "boulevard",
+ "path",
+ "square",
+ "plaza",
+ ]
+ }
+ ]
+ },` + "```"
+ ]).AsMarkdown()
+
+
+ public isValid(str) {
+
+ if (str === undefined) {
+ return false;
+ }
+ if (str.length <= 2) {
+ return false;
+ }
+ return !str.split(";").some(str => Wikidata.ExtractKey(str) === undefined)
+ }
+
+ public reformat(str) {
+ if (str === undefined) {
+ return undefined;
+ }
+ let out = str.split(";").map(str => Wikidata.ExtractKey(str)).join("; ")
+ if (str.endsWith(";")) {
+ out = out + ";"
+ }
+ return out;
+ }
+
+ public inputHelper(currentValue, inputHelperOptions) {
+ const args = inputHelperOptions.args ?? []
+ const searchKey = args[0] ?? "name"
+
+ let searchFor = inputHelperOptions.feature?.properties[searchKey]?.toLowerCase()
+
+ const options = args[1]
+ if (searchFor !== undefined && options !== undefined) {
+ const prefixes = options["removePrefixes"]
+ const postfixes = options["removePostfixes"]
+ for (const postfix of postfixes ?? []) {
+ if (searchFor.endsWith(postfix)) {
+ searchFor = searchFor.substring(0, searchFor.length - postfix.length)
+ break;
+ }
+ }
+
+ for (const prefix of prefixes ?? []) {
+ if (searchFor.startsWith(prefix)) {
+ searchFor = searchFor.substring(prefix.length)
+ break;
+ }
+ }
+
+ }
+
+ return new WikidataSearchBox({
+ value: currentValue,
+ searchText: new UIEventSource(searchFor)
+ })
+ }
+}
+
+class OpeningHoursTextField implements TextFieldDef {
+ name = "opening_hours"
+ explanation =
+ new Combine([
+ "Has extra elements to easily input when a POI is opened.",
+ new Title("Helper arguments"),
+ new Table(["name", "doc"],
+ [
+ ["options", new Combine([
+ "A JSON-object of type `{ prefix: string, postfix: string }`. ",
+ new Table(["subarg", "doc"],
+ [
+ ["prefix", "Piece of text that will always be added to the front of the generated opening hours. If the OSM-data does not start with this, it will fail to parse"],
+ ["postfix", "Piece of text that will always be added to the end of the generated opening hours"],
+ ])
+
+ ])
+ ]
+ ]),
+ new Title("Example usage"),
+ "To add a conditional (based on time) access restriction:\n\n```" + `
+ "freeform": {
+ "key": "access:conditional",
+ "type": "opening_hours",
+ "helperArgs": [
+ {
+ "prefix":"no @ (",
+ "postfix":")"
+ }
+ ]
+ },` + "```\n\n*Don't forget to pass these in the rendering as well*: `{opening_hours_table(opening_hours,yes @ &LPARENS, &RPARENS )`"]).AsMarkdown()
+
+
+ isValid() {
+ return true
+ }
+
+ reformat(str) {
+ return str
+ }
+
+ inputHelper(value: UIEventSource, inputHelperOptions: {
+ location: [number, number],
+ mapBackgroundLayer?: UIEventSource,
+ args: (string | number | boolean | any)[]
+ feature?: any
+ }) {
+ const args = (inputHelperOptions.args ?? [])[0]
+ const prefix = args?.prefix ?? ""
+ const postfix = args?.postfix ?? ""
+
+ return new OpeningHoursInput(value, prefix, postfix)
+ }
+}
+
export default class ValidatedTextField {
public static tpList: TextFieldDef[] = [
@@ -147,60 +297,7 @@ export default class ValidatedTextField {
},
"decimal"
),
- ValidatedTextField.tp(
- "wikidata",
- "A wikidata identifier, e.g. Q42. Input helper arguments: [ key: the value of this tag will initialize search (default: name), options: { removePrefixes: string[], removePostfixes: string[] } these prefixes and postfixes will be removed from the initial search value]",
- (str) => {
- if (str === undefined) {
- return false;
- }
- if(str.length <= 2){
- return false;
- }
- return !str.split(";").some(str => Wikidata.ExtractKey(str) === undefined)
- },
- (str) => {
- if (str === undefined) {
- return undefined;
- }
- let out = str.split(";").map(str => Wikidata.ExtractKey(str)).join("; ")
- if(str.endsWith(";")){
- out = out + ";"
- }
- return out;
- },
- (currentValue, inputHelperOptions) => {
- const args = inputHelperOptions.args ?? []
- const searchKey = args[0] ?? "name"
-
- let searchFor = inputHelperOptions.feature?.properties[searchKey]?.toLowerCase()
-
- const options = args[1]
- if (searchFor !== undefined && options !== undefined) {
- const prefixes = options["removePrefixes"]
- const postfixes = options["removePostfixes"]
- for (const postfix of postfixes ?? []) {
- if (searchFor.endsWith(postfix)) {
- searchFor = searchFor.substring(0, searchFor.length - postfix.length)
- break;
- }
- }
-
- for (const prefix of prefixes ?? []) {
- if (searchFor.startsWith(prefix)) {
- searchFor = searchFor.substring(prefix.length)
- break;
- }
- }
-
- }
-
- return new WikidataSearchBox({
- value: currentValue,
- searchText: new UIEventSource(searchFor)
- })
- }
- ),
+ new WikidataTextField(),
ValidatedTextField.tp(
"int",
@@ -300,15 +397,7 @@ export default class ValidatedTextField {
undefined,
"tel"
),
- ValidatedTextField.tp(
- "opening_hours",
- "Has extra elements to easily input when a POI is opened",
- () => true,
- str => str,
- (value) => {
- return new OpeningHoursInput(value);
- }
- ),
+ new OpeningHoursTextField(),
ValidatedTextField.tp(
"color",
"Shows a color picker",
diff --git a/UI/OpeningHours/OpeningHoursInput.ts b/UI/OpeningHours/OpeningHoursInput.ts
index d8d5ce16b..88b061a3d 100644
--- a/UI/OpeningHours/OpeningHoursInput.ts
+++ b/UI/OpeningHours/OpeningHoursInput.ts
@@ -23,11 +23,39 @@ export default class OpeningHoursInput extends InputElement {
private readonly _value: UIEventSource;
private readonly _element: BaseUIElement;
- constructor(value: UIEventSource = new UIEventSource("")) {
+ constructor(value: UIEventSource = new UIEventSource(""), prefix = "", postfix = "") {
super();
this._value = value;
+ let valueWithoutPrefix = value
+ if (prefix !== "" && postfix !== "") {
+
+ valueWithoutPrefix = value.map(str => {
+ if (str === undefined) {
+ return undefined;
+ }
+ if(str === ""){
+ return ""
+ }
+ if (str.startsWith(prefix) && str.endsWith(postfix)) {
+ return str.substring(prefix.length, str.length - postfix.length)
+ }
+ return str
+ }, [], noPrefix => {
+ if (noPrefix === undefined) {
+ return undefined;
+ }
+ if(noPrefix === ""){
+ return ""
+ }
+ if (noPrefix.startsWith(prefix) && noPrefix.endsWith(postfix)) {
+ return noPrefix
+ }
- const leftoverRules = value.map(str => {
+ return prefix + noPrefix + postfix
+ })
+ }
+
+ const leftoverRules = valueWithoutPrefix.map(str => {
if (str === undefined) {
return []
}
@@ -45,9 +73,9 @@ export default class OpeningHoursInput extends InputElement {
return leftOvers;
})
// Note: MUST be bound AFTER the leftover rules!
- const rulesFromOhPicker = value.map(OH.Parse);
+ const rulesFromOhPicker = valueWithoutPrefix.map(OH.Parse);
- const ph = value.map(str => {
+ const ph = valueWithoutPrefix.map(str => {
if (str === undefined) {
return ""
}
@@ -68,7 +96,7 @@ export default class OpeningHoursInput extends InputElement {
...leftoverRules.data,
ph.data
]
- value.setData(Utils.NoEmpty(rules).join(";"));
+ valueWithoutPrefix.setData(Utils.NoEmpty(rules).join(";"));
}
rulesFromOhPicker.addCallback(update);
diff --git a/UI/OpeningHours/OpeningHoursVisualization.ts b/UI/OpeningHours/OpeningHoursVisualization.ts
index 785d046a9..fb8ae05d6 100644
--- a/UI/OpeningHours/OpeningHoursVisualization.ts
+++ b/UI/OpeningHours/OpeningHoursVisualization.ts
@@ -23,10 +23,16 @@ export default class OpeningHoursVisualization extends Toggle {
Translations.t.general.weekdays.abbreviations.sunday,
]
- constructor(tags: UIEventSource, key: string) {
+ constructor(tags: UIEventSource, key: string, prefix = "", postfix = "") {
const tagsDirect = tags.data;
const ohTable = new VariableUiElement(tags
- .map(tags => tags[key]) // This mapping will absorb all other changes to tags in order to prevent regeneration
+ .map(tags => {
+ const value : string = tags[key];
+ if(value.startsWith(prefix) && value.endsWith(postfix)){
+ return value.substring(prefix.length, value.length - postfix.length)
+ }
+ return value;
+ }) // This mapping will absorb all other changes to tags in order to prevent regeneration
.map(ohtext => {
try {
// noinspection JSPotentiallyInvalidConstructorUsage
diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts
index 1563cf3f5..8d52fbf78 100644
--- a/UI/SpecialVisualizations.ts
+++ b/UI/SpecialVisualizations.ts
@@ -302,9 +302,18 @@ export default class SpecialVisualizations {
name: "key",
defaultValue: "opening_hours",
doc: "The tagkey from which the table is constructed."
+ },{
+ name: "prefix",
+ defaultValue: "",
+ doc:"Remove this string from the start of the value before parsing. __Note: use `&LPARENs` to indicate `(` if needed__"
+ },{
+ name: "postfix",
+ defaultValue: "",
+ doc:"Remove this string from the end of the value before parsing. __Note: use `&RPARENs` to indicate `)` if needed__"
}],
+ example: "A normal opening hours table can be invoked with `{opening_hours_table()}`. A table for e.g. conditional access with opening hours can be `{opening_hours_table(access:conditional, no @ &LPARENS, &RPARENS)}`",
constr: (state: State, tagSource: UIEventSource, args) => {
- return new OpeningHoursVisualization(tagSource, args[0])
+ return new OpeningHoursVisualization(tagSource, args[0], args[1], args[2])
}
},
{
diff --git a/UI/SubstitutedTranslation.ts b/UI/SubstitutedTranslation.ts
index 361540e65..ff2d3a447 100644
--- a/UI/SubstitutedTranslation.ts
+++ b/UI/SubstitutedTranslation.ts
@@ -85,7 +85,9 @@ export class SubstitutedTranslation extends VariableUiElement {
const partAfter = SubstitutedTranslation.ExtractSpecialComponents(matched[4], extraMappings);
const args = knownSpecial.args.map(arg => arg.defaultValue ?? "");
if (argument.length > 0) {
- const realArgs = argument.split(",").map(str => str.trim());
+ const realArgs = argument.split(",").map(str => str.trim()
+ .replace(/&LPARENS/g, '(')
+ .replace(/&RPARENS/g, ')'));
for (let i = 0; i < realArgs.length; i++) {
if (args.length <= i) {
args.push(realArgs[i]);
diff --git a/assets/layers/bike_shop/bike_shop.json b/assets/layers/bike_shop/bike_shop.json
index 436c4604b..991843e5f 100644
--- a/assets/layers/bike_shop/bike_shop.json
+++ b/assets/layers/bike_shop/bike_shop.json
@@ -723,11 +723,13 @@
"iconOverlays": [
{
"if": "opening_hours~*",
- "then": "isOpen"
+ "then": "isOpen",
+ "badge": true
},
{
"if": "service:bicycle:pump=yes",
- "then": "circle:#e2783d;./assets/layers/bike_repair_station/pump.svg"
+ "then": "circle:#e2783d;./assets/layers/bike_repair_station/pump.svg",
+ "badge": true
},
{
"if": {
@@ -737,7 +739,8 @@
},
"then": {
"render": "./assets/layers/bike_cleaning/bike_cleaning_icon.svg"
- }
+ },
+ "badge": true
}
],
"width": {
diff --git a/css/openinghourstable.css b/css/openinghourstable.css
index b477b4862..877355426 100644
--- a/css/openinghourstable.css
+++ b/css/openinghourstable.css
@@ -16,6 +16,8 @@
border-collapse: collapse;
background-clip: padding-box;
border-right: 1px solid #ccc;
+ /* Somthing sets linehight to 1.5, but the leaflet-popup-content sets it to 1.4, throwing of the calculations... */
+ line-height: 1.5 !important;
}
.oh-timecell {