forked from MapComplete/MapComplete
		
	Add metatagging, fritures
This commit is contained in:
		
							parent
							
								
									1e0a1fdf97
								
							
						
					
					
						commit
						99225957cc
					
				
					 15 changed files with 406 additions and 38 deletions
				
			
		|  | @ -13,6 +13,7 @@ import * as nature from "../assets/themes/nature/nature.json" | |||
| import * as maps from "../assets/themes/maps/maps.json" | ||||
| import * as shops from "../assets/themes/shops/shops.json" | ||||
| import * as bike_monitoring_stations from "../assets/themes/bike_monitoring_station/bike_monitoring_stations.json" | ||||
| import * as fritures from "../assets/themes/fritures/fritures.json" | ||||
| import {PersonalLayout} from "../Logic/PersonalLayout"; | ||||
| import {StreetWidth} from "./StreetWidth/StreetWidth"; | ||||
| 
 | ||||
|  | @ -66,6 +67,7 @@ export class AllKnownLayouts { | |||
|         FromJSON.LayoutFromJSON(nature), | ||||
|         FromJSON.LayoutFromJSON(cyclestreets), | ||||
|         FromJSON.LayoutFromJSON(maps), | ||||
|         FromJSON.LayoutFromJSON(fritures), | ||||
|         AllKnownLayouts.GenerateBuurtNatuur(), | ||||
|         AllKnownLayouts.GenerateBikeMonitoringStations(), | ||||
| 
 | ||||
|  |  | |||
|  | @ -15,7 +15,7 @@ import {DropDown} from "./UI/Input/DropDown"; | |||
| import {LayerSelection} from "./UI/LayerSelection"; | ||||
| import {Preset} from "./Customizations/LayerDefinition"; | ||||
| import {VariableUiElement} from "./UI/Base/VariableUIElement"; | ||||
| import {LayerUpdater} from "./Logic/LayerUpdater"; | ||||
| import {UpdateFromOverpass} from "./Logic/UpdateFromOverpass"; | ||||
| import {UIEventSource} from "./Logic/UIEventSource"; | ||||
| import {QueryParameters} from "./Logic/Web/QueryParameters"; | ||||
| import {PersonalLayout} from "./Logic/PersonalLayout"; | ||||
|  | @ -451,7 +451,7 @@ export class InitUiElements { | |||
|             ) | ||||
|         ); | ||||
|         State.state.bm = bm; | ||||
|         State.state.layerUpdater = new LayerUpdater(State.state); | ||||
|         State.state.layerUpdater = new UpdateFromOverpass(State.state); | ||||
| 
 | ||||
|         State.state.availableBackgroundLayers = new AvailableBaseLayers(State.state).availableEditorLayers; | ||||
|         const queryParam = QueryParameters.GetQueryParameter("background", State.state.layoutToUse.data.defaultBackground); | ||||
|  |  | |||
|  | @ -110,31 +110,6 @@ export class FilteredLayer { | |||
|             const tags = TagUtils.proprtiesToKV(feature.properties); | ||||
|              | ||||
|             if (this.filters.matches(tags)) { | ||||
|                 const centerPoint = GeoOperations.centerpoint(feature); | ||||
|                 const sqMeters = GeoOperations.surfaceAreaInSqMeters(feature); | ||||
|                 feature.properties["_surface"] = "" + sqMeters; | ||||
|                 feature.properties["_surface:ha"] = "" + Math.floor(sqMeters / 1000)/10; | ||||
| 
 | ||||
|                 const lat = centerPoint.geometry.coordinates[1]; | ||||
|                 const lon = centerPoint.geometry.coordinates[0] | ||||
|                 feature.properties["_lon"] = "" + lat; // We expect a string here for lat/lon
 | ||||
|                 feature.properties["_lat"] = "" + lon; | ||||
|                 // But the codegrid SHOULD be a number!
 | ||||
|                 CodeGrid.getCode(lat, lon, (error, code) => { | ||||
|                     if (error === null) { | ||||
|                         feature.properties["_country"] = code; | ||||
|                     } else { | ||||
|                         console.warn("Could not determine country for", feature.properties.id, error); | ||||
|                     } | ||||
|                 }) | ||||
| 
 | ||||
|                 if (feature.geometry.type !== "Point") { | ||||
|                     if (this._wayHandling === LayerDefinition.WAYHANDLING_CENTER_AND_WAY) { | ||||
|                         selfFeatures.push(centerPoint); | ||||
|                     } else if (this._wayHandling === LayerDefinition.WAYHANDLING_CENTER_ONLY) { | ||||
|                         feature = centerPoint; | ||||
|                     } | ||||
|                 } | ||||
|                 selfFeatures.push(feature); | ||||
|             } else { | ||||
|                 leftoverFeatures.push(feature); | ||||
|  |  | |||
							
								
								
									
										124
									
								
								Logic/MetaTagging.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								Logic/MetaTagging.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,124 @@ | |||
| import {GeoOperations} from "./GeoOperations"; | ||||
| import CodeGrid from "./Web/CodeGrid"; | ||||
| import State from "../State"; | ||||
| import opening_hours from "opening_hours"; | ||||
| 
 | ||||
| 
 | ||||
| class SimpleMetaTagger { | ||||
|     private _f: (feature: any) => void; | ||||
|     public readonly keys: string[]; | ||||
|     public readonly doc: string; | ||||
| 
 | ||||
| 
 | ||||
|     constructor(keys: string[], doc: string, f: ((feature: any) => void)) { | ||||
|         this.keys = keys; | ||||
|         this.doc = doc; | ||||
|         this._f = f; | ||||
|         for (const key of keys) { | ||||
|             if (!key.startsWith('_')) { | ||||
|                 throw `Incorrect metakey ${key}: it should start with underscore (_)` | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     addMetaTags(features: any[]) { | ||||
|         for (const feature of features) { | ||||
|             this._f(feature); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * Metatagging adds various tags to the elements, e.g. lat, lon, surface area, ... | ||||
|  * | ||||
|  * All metatags start with an underscore | ||||
|  */ | ||||
| export default class MetaTagging { | ||||
| 
 | ||||
| 
 | ||||
|     public static metatags = [ | ||||
|         new SimpleMetaTagger(["_lat", "_lon"], "The latitude and longitude of the point (or centerpoint in the case of a way/area)", | ||||
|             (feature => { | ||||
|                 const centerPoint = GeoOperations.centerpoint(feature); | ||||
|                 const lat = centerPoint.geometry.coordinates[1]; | ||||
|                 const lon = centerPoint.geometry.coordinates[0]; | ||||
|                 feature.properties["_lat"] = "" + lat; | ||||
|                 feature.properties["_lon"] = "" + lon; | ||||
|             }) | ||||
|         ), | ||||
|         new SimpleMetaTagger( | ||||
|             ["_surface", "_surface:ha"], "The surface area of the feature, in square meters and in hectare. Not set on points and ways", | ||||
|             (feature => { | ||||
|                 const sqMeters = GeoOperations.surfaceAreaInSqMeters(feature); | ||||
|                 feature.properties["_surface"] = "" + sqMeters; | ||||
|                 feature.properties["_surface:ha"] = "" + Math.floor(sqMeters / 1000) / 10; | ||||
| 
 | ||||
|             }) | ||||
|         ), | ||||
| 
 | ||||
| 
 | ||||
|         new SimpleMetaTagger( | ||||
|             ["_country"], "The country code of the point", | ||||
|             (feature => { | ||||
|                 const centerPoint = GeoOperations.centerpoint(feature); | ||||
|                 const lat = centerPoint.geometry.coordinates[1]; | ||||
|                 const lon = centerPoint.geometry.coordinates[0] | ||||
|                 // But the codegrid SHOULD be a number!
 | ||||
|                 CodeGrid.getCode(lat, lon, (error, code) => { | ||||
|                     if (error === null) { | ||||
|                         feature.properties["_country"] = code; | ||||
|                         State.state.allElements.addOrGetElement(feature).ping(); | ||||
|                     } else { | ||||
|                         console.warn("Could not determine country for", feature.properties.id, error); | ||||
|                     } | ||||
|                 }); | ||||
|             }) | ||||
|         ), | ||||
|         new SimpleMetaTagger( | ||||
|             ["_isOpen", "_isOpen:description"], "If 'opening_hours' is present, it will add the current state of the feature (being 'yes' or 'no", | ||||
|             (feature => { | ||||
|                 const tagsSource = State.state.allElements.addOrGetElement(feature); | ||||
|                 tagsSource.addCallback(tags => { | ||||
| 
 | ||||
|                     if (tags["opening_hours"] !== undefined && tags["_country"] !== undefined) { | ||||
| 
 | ||||
|                         const oh = new opening_hours(tags["opening_hours"], { | ||||
|                             lat: tags._lat, | ||||
|                             lon: tags._lon, | ||||
|                             address: { | ||||
|                                 country_code: tags._country | ||||
|                             } | ||||
|                         }, {tag_key: "opening_hours"}); | ||||
| 
 | ||||
|                         const updateTags = () => { | ||||
|                             tags["_isOpen"] = oh.getState() ? "yes" : "no"; | ||||
|                             const comment = oh.getComment(); | ||||
|                             if (comment) { | ||||
|                                 tags["_isOpen:description"] = comment; | ||||
|                             } | ||||
|                             const nextChange = oh.getNextChange() as Date; | ||||
|                             window.setTimeout( | ||||
|                                 updateTags, | ||||
|                                 (nextChange.getTime() - (new Date()).getTime()) | ||||
|                             ) | ||||
|                         } | ||||
|                         updateTags(); | ||||
|                     } | ||||
| 
 | ||||
|                 }) | ||||
| 
 | ||||
|             }) | ||||
|         ) | ||||
|     ]; | ||||
| 
 | ||||
|     static addMetatags(features: any[]) { | ||||
| 
 | ||||
|         for (const metatag of MetaTagging.metatags) { | ||||
|             metatag.addMetaTags(features); | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| } | ||||
|  | @ -5,8 +5,9 @@ import {Bounds} from "./Bounds"; | |||
| import {Overpass} from "./Osm/Overpass"; | ||||
| import State from "../State"; | ||||
| import {LayerDefinition} from "../Customizations/LayerDefinition"; | ||||
| import MetaTagging from "./MetaTagging"; | ||||
| 
 | ||||
| export class LayerUpdater { | ||||
| export class UpdateFromOverpass { | ||||
| 
 | ||||
|     public readonly sufficentlyZoomed: UIEventSource<boolean>; | ||||
|     public readonly runningQuery: UIEventSource<boolean> = new UIEventSource<boolean>(false); | ||||
|  | @ -100,6 +101,8 @@ export class LayerUpdater { | |||
|             } | ||||
|         } | ||||
|          | ||||
|         MetaTagging.addMetatags(geojson.features); | ||||
|          | ||||
|         function renderLayers(layers: FilteredLayer[]) { | ||||
|             if (layers.length === 0) { | ||||
|                 self.runningQuery.setData(false); | ||||
|  | @ -8,6 +8,7 @@ export default class CodeGrid { | |||
|         CodeGrid.grid.getCode(lat, lon, handle); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     private static InitGrid(): any { | ||||
|         const grid = codegrid.CodeGrid("./tiles/"); | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										4
									
								
								State.ts
									
										
									
									
									
								
							
							
						
						
									
										4
									
								
								State.ts
									
										
									
									
									
								
							|  | @ -8,7 +8,7 @@ import {OsmConnection} from "./Logic/Osm/OsmConnection"; | |||
| import Locale from "./UI/i18n/Locale"; | ||||
| import Translations from "./UI/i18n/Translations"; | ||||
| import {FilteredLayer} from "./Logic/FilteredLayer"; | ||||
| import {LayerUpdater} from "./Logic/LayerUpdater"; | ||||
| import {UpdateFromOverpass} from "./Logic/UpdateFromOverpass"; | ||||
| import {UIEventSource} from "./Logic/UIEventSource"; | ||||
| import {LocalStorageSource} from "./Logic/Web/LocalStorageSource"; | ||||
| import {QueryParameters} from "./Logic/Web/QueryParameters"; | ||||
|  | @ -66,7 +66,7 @@ export default class State { | |||
| 
 | ||||
|     public favouriteLayers: UIEventSource<string[]>; | ||||
| 
 | ||||
|     public layerUpdater: LayerUpdater; | ||||
|     public layerUpdater: UpdateFromOverpass; | ||||
| 
 | ||||
| 
 | ||||
|     public filteredLayers: UIEventSource<FilteredLayer[]> = new UIEventSource<FilteredLayer[]>([]) | ||||
|  |  | |||
|  | @ -33,7 +33,6 @@ export class TabbedComponent extends UIElement { | |||
|         headerBar = "<div class='tabs-header-bar'>" + headerBar + "</div>" | ||||
| 
 | ||||
|         const content = this.content[this._source.data]; | ||||
|         console.log("Rendering tab", this._source.data); | ||||
|         return headerBar + "<div class='tab-content'>" + (content?.Render() ?? "") + "</div>"; | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -87,7 +87,7 @@ export class ImageUploadFlow extends UIElement { | |||
|         } | ||||
| 
 | ||||
|         const extraInfo = new Combine([ | ||||
|             Translations.t.image.respectPrivacy, | ||||
|             Translations.t.image.respectPrivacy.SetStyle("font-size:small;"), | ||||
|             "<br/>", | ||||
|             this._licensePicker, | ||||
|             "<br/>", | ||||
|  |  | |||
|  | @ -115,6 +115,12 @@ export default class PublicHolidayInput extends InputElement<string> { | |||
|             } | ||||
|         } | ||||
|          | ||||
|         if(str === "PH open"){ | ||||
|             return { | ||||
|                 mode: "open" | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (!str.startsWith("PH ")) { | ||||
|             return null; | ||||
|         } | ||||
|  |  | |||
|  | @ -17,10 +17,6 @@ export class MoreScreen extends UIElement { | |||
|         super(State.state.locationControl); | ||||
|         this.ListenTo(State.state.osmConnection.userDetails); | ||||
|         this.ListenTo(State.state.installedThemes); | ||||
|          | ||||
|         State.state.installedThemes.addCallback(themes => { | ||||
|             console.log("INSTALLED THEMES COUNT:", themes.length) | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     private createLinkButton(layout: Layout, customThemeDefinition: string = undefined) { | ||||
|  | @ -72,7 +68,6 @@ export class MoreScreen extends UIElement { | |||
| 
 | ||||
|     InnerRender(): string { | ||||
| 
 | ||||
|         console.log("Inner rendering MORE") | ||||
|         const tr = Translations.t.general.morescreen; | ||||
| 
 | ||||
|         const els: UIElement[] = [] | ||||
|  |  | |||
|  | @ -33,7 +33,7 @@ export default class OpeningHoursVisualization extends UIElement { | |||
| 
 | ||||
|         const values = [[], [], [], [], [], [], []]; | ||||
| 
 | ||||
|        const start  = new Date(from); | ||||
|         const start = new Date(from); | ||||
|         // We go one day more into the past, in order to force rendering of holidays in the start of the period
 | ||||
|         start.setDate(from.getDate() - 1); | ||||
|          | ||||
|  |  | |||
|  | @ -166,6 +166,21 @@ export default class SpecialVisualizations { | |||
|                     const source = LiveQueryHandler.FetchLiveData(url, shorthands.split(";")); | ||||
|                     return new VariableUiElement(source.map(data => data[neededValue] ?? "Loading...")); | ||||
|                 } | ||||
|             }, | ||||
|              | ||||
|             { | ||||
|                 funcName: "all_tags", | ||||
|                 docs: "Prints all key-value pairs of the object - used for debugging", | ||||
|                 args:[], | ||||
|                 constr: ((tags: UIEventSource<any>) => { | ||||
|                     return new VariableUiElement(tags.map(tags => { | ||||
|                         const parts = []; | ||||
|                         for (const key in tags) { | ||||
|                             parts.push(key+"="+tags[key]); | ||||
|                         } | ||||
|                         return parts.join("<br/>") | ||||
|                     })).SetStyle("border: 1px solid black; border-radius: 1em;padding:1em;display:block;") | ||||
|                 }) | ||||
|             } | ||||
| 
 | ||||
|         ] | ||||
|  |  | |||
|  | @ -327,6 +327,15 @@ | |||
|       "freeform": { | ||||
|         "key": "description:0" | ||||
|       } | ||||
|     }, | ||||
|     {"#": "Surface are", | ||||
|     "render": { | ||||
|       "en": "Surface area: {_surface:ha}Ha", | ||||
|       "mappings": { | ||||
|         "if": "_surface:ha=0", | ||||
|         "then": "" | ||||
|       } | ||||
|     } | ||||
|     } | ||||
|   ], | ||||
|   "hideUnderlayingFeaturesMinPercentage": 10, | ||||
|  |  | |||
							
								
								
									
										239
									
								
								assets/themes/fritures/fritures.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										239
									
								
								assets/themes/fritures/fritures.json
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,239 @@ | |||
| { | ||||
|   "id": "id", | ||||
|   "title": { | ||||
|     "nl": "Friturenkaart", | ||||
|     "fr": "Carte des fritures" | ||||
|   }, | ||||
|   "description": { | ||||
|     "nl": "Op deze kaart vind je je favoriete frituur!" | ||||
|   }, | ||||
|   "language": [ | ||||
|     "nl", | ||||
|     "fr" | ||||
|   ], | ||||
|   "maintainer": "", | ||||
|   "icon": "https://upload.wikimedia.org/wikipedia/commons/5/55/French_fries_juliane_kr_r.svg", | ||||
|   "version": "0", | ||||
|   "startLat": 0, | ||||
|   "startLon": 0, | ||||
|   "startZoom": 1, | ||||
|   "widenFactor": 0.05, | ||||
|   "socialImage": "", | ||||
|   "layers": [ | ||||
|     { | ||||
|       "id": "fritures", | ||||
|       "name": { | ||||
|         "nl": "Frituren", | ||||
|         "fr": "Fritures" | ||||
|       }, | ||||
|       "minzoom": 8, | ||||
|       "overpassTags": { | ||||
|         "and": [ | ||||
|           "cuisine~.*friture.*" | ||||
|         ] | ||||
|       }, | ||||
|       "title": { | ||||
|         "render": { | ||||
|           "nl": "Frituur", | ||||
|           "fr": "Friture" | ||||
|         }, | ||||
|         "mappings": [ | ||||
|           { | ||||
|             "if": { | ||||
|               "and": [ | ||||
|                 "name~*" | ||||
|               ] | ||||
|             }, | ||||
|             "then": { | ||||
|               "nl": " <i>{name}</i>", | ||||
|               "fr": " <i>{name}</i>" | ||||
|             } | ||||
|           } | ||||
|         ] | ||||
|       }, | ||||
|       "description": {}, | ||||
|       "tagRenderings": [ | ||||
|         { | ||||
|           "render": { | ||||
|             "nl": "<a href='tel:{phone}'>{phone}</a>", | ||||
|             "fr": "<a href='tel:{phone}'>{phone}</a>" | ||||
|           }, | ||||
|           "question": { | ||||
|             "en": "What is the phone number?", | ||||
|             "nl": "Wat is het telefoonnummer van deze frituur?", | ||||
|             "fr": "Quel est le numéro de téléphone de cette friture?" | ||||
|           }, | ||||
|           "freeform": { | ||||
|             "key": "phone", | ||||
|             "type": "phone" | ||||
|           } | ||||
|         }, | ||||
|         { | ||||
|           "render": { | ||||
|             "en": "<a href='{website}'>{website}</a>" | ||||
|           }, | ||||
|           "question": { | ||||
|             "en": "What is the website of this shop?", | ||||
|             "nl": "Wat is de website van deze frituur?", | ||||
|             "fr": "Quel est le site web de cette  friture?" | ||||
|           }, | ||||
|           "freeform": { | ||||
|             "key": "website", | ||||
|             "type": "url" | ||||
|           } | ||||
|         }, | ||||
|         { | ||||
|           "render": { | ||||
|             "nl": "<h3>Openingsuren</h3>{opening_hours_table(opening_hours)}", | ||||
|             "fr": "<h3>Horaires</h3>{opening_hours_table(opening_hours)}" | ||||
|           }, | ||||
|           "question": { | ||||
|             "nl": "Wat zijn de openinguren van deze frituur?", | ||||
|             "fr": "Quand est ce-que ce friture ouvert?" | ||||
|           }, | ||||
|           "freeform": { | ||||
|             "key": "opening_hours", | ||||
|             "type": "opening_hours" | ||||
|           } | ||||
|         }, | ||||
|         { | ||||
|           "render": { | ||||
|             "nl": "" | ||||
|           }, | ||||
|           "question": { | ||||
|             "nl": "Heeft deze frituur vegetarische snacks?", | ||||
|             "fr": "Cette friture est-elle équipée de snacks végétariens ?" | ||||
|           }, | ||||
|           "mappings": [ | ||||
|             { | ||||
|               "if": { | ||||
|                 "and": [ | ||||
|                   "diet:vegetarian=yes" | ||||
|                 ] | ||||
|               }, | ||||
|               "then": { | ||||
|                 "nl": "Er zijn vegetarische snacks aanwezig", | ||||
|                 "fr": "Des collations végétariennes sont disponibles" | ||||
|               } | ||||
|             }, | ||||
|             { | ||||
|               "if": { | ||||
|                 "and": [ | ||||
|                   "diet:vegetarian=limited" | ||||
|                 ] | ||||
|               }, | ||||
|               "then": { | ||||
|                 "nl": "Slechts enkele vegetarische snacks", | ||||
|                 "fr": "Quelques snacks végétariens seulement" | ||||
|               } | ||||
|             }, | ||||
|             { | ||||
|               "if": { | ||||
|                 "and": [ | ||||
|                   "diet:vegetarian=no" | ||||
|                 ] | ||||
|               }, | ||||
|               "then": { | ||||
|                 "nl": "Geen vegetarische snacks beschikbaar", | ||||
|                 "fr": "Pas d'en-cas végétariens disponibles" | ||||
|               } | ||||
|             } | ||||
|           ] | ||||
|         }, | ||||
|         { | ||||
|           "render": { | ||||
|             "nl": "" | ||||
|           }, | ||||
|           "question": { | ||||
|             "nl": "Heeft deze frituur veganistische snacks?", | ||||
|             "fr": "Cette friture est-elle équipée de snacks végétaliens ?" | ||||
|           }, | ||||
|           "mappings": [ | ||||
|             { | ||||
|               "if": { | ||||
|                 "and": [ | ||||
|                   "diet:vegan=yes" | ||||
|                 ] | ||||
|               }, | ||||
|               "then": { | ||||
|                 "nl": "Er zijn veganistische snacks aanwezig", | ||||
|                 "fr": "Des collations végétaliens sont disponibles" | ||||
|               } | ||||
|             }, | ||||
|             { | ||||
|               "if": { | ||||
|                 "and": [ | ||||
|                   "diet:vegan=limited" | ||||
|                 ] | ||||
|               }, | ||||
|               "then": { | ||||
|                 "nl": "Slechts enkele veganistische snacks", | ||||
|                 "fr": "Quelques snacks végétaliens seulement" | ||||
|               } | ||||
|             }, | ||||
|             { | ||||
|               "if": { | ||||
|                 "and": [ | ||||
|                   "diet:vegetarian=no" | ||||
|                 ] | ||||
|               }, | ||||
|               "then": { | ||||
|                 "nl": "Geen veganistische snacks beschikbaar", | ||||
|                 "fr": "Pas d'en-cas végétaliens disponibles" | ||||
|               } | ||||
|             } | ||||
|           ] | ||||
|         }, | ||||
|         { | ||||
|           "question": { | ||||
|             "nl": "Bakt deze frituur in dierlijk  vetof plantaardig olie?", | ||||
|             "fr": "Cette friteuse fonctionne-t-elle avec de la graisse animale ou végétale ?" | ||||
|           }, | ||||
|           "mappings": [ | ||||
|             { | ||||
|               "if": { | ||||
|                 "and": [ | ||||
|                   "friture:oil=vegetable" | ||||
|                 ] | ||||
|               }, | ||||
|               "then": { | ||||
|                 "nl": "Plantaardige olie", | ||||
|                 "fr": "Huile végétale" | ||||
|               } | ||||
|             }, | ||||
|             { | ||||
|               "if": { | ||||
|                 "and": [ | ||||
|                   "friture:oil=animal" | ||||
|                 ] | ||||
|               }, | ||||
|               "then": { | ||||
|                 "nl": "Dierlijk vet", | ||||
|                 "fr": "Graisse animale" | ||||
|               } | ||||
|             } | ||||
|           ], | ||||
|           "freeform": { | ||||
|             "key": "" | ||||
|           } | ||||
|         } | ||||
|       ], | ||||
|       "icon": { | ||||
|         "render": "https://upload.wikimedia.org/wikipedia/commons/5/55/French_fries_juliane_kr_r.svg" | ||||
|       }, | ||||
|       "width": { | ||||
|         "render": "8" | ||||
|       }, | ||||
|       "iconSize": { | ||||
|         "render": "40,40,center" | ||||
|       }, | ||||
|       "color": { | ||||
|         "render": "#00f" | ||||
|       }, | ||||
|       "presets": [], | ||||
|       "wayHandling": 1 | ||||
|     } | ||||
|   ], | ||||
|   "roamingRenderings": [], | ||||
|   "shortDescription": {} | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue