forked from MapComplete/MapComplete
Extract statistics panel, add statistics panel as special rendering
This commit is contained in:
parent
bc2f421108
commit
2412828a69
8 changed files with 204 additions and 132 deletions
|
@ -23,8 +23,11 @@ import TileFreshnessCalculator from "./TileFreshnessCalculator";
|
||||||
import FullNodeDatabaseSource from "./TiledFeatureSource/FullNodeDatabaseSource";
|
import FullNodeDatabaseSource from "./TiledFeatureSource/FullNodeDatabaseSource";
|
||||||
import MapState from "../State/MapState";
|
import MapState from "../State/MapState";
|
||||||
import {ElementStorage} from "../ElementStorage";
|
import {ElementStorage} from "../ElementStorage";
|
||||||
import {Feature, Geometry} from "@turf/turf";
|
|
||||||
import {OsmFeature} from "../../Models/OsmFeature";
|
import {OsmFeature} from "../../Models/OsmFeature";
|
||||||
|
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
|
||||||
|
import {FilterState} from "../../Models/FilteredLayer";
|
||||||
|
import {GeoOperations} from "../GeoOperations";
|
||||||
|
import {Utils} from "../../Utils";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -514,6 +517,62 @@ export default class FeaturePipeline {
|
||||||
return updater;
|
return updater;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds upon 'GetAllFeaturesAndMetaWithin', but does stricter BBOX-checking and applies the filters
|
||||||
|
*/
|
||||||
|
public getAllVisibleElementsWithmeta(bbox: BBox): { center: [number, number], element: OsmFeature, layer: LayerConfig }[] {
|
||||||
|
if (bbox === undefined) {
|
||||||
|
console.warn("No bbox")
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const layers = Utils.toIdRecord(this.state.layoutToUse.layers)
|
||||||
|
const elementsWithMeta: { features: OsmFeature[], layer: string }[] = this.GetAllFeaturesAndMetaWithin(bbox)
|
||||||
|
|
||||||
|
let elements: {center: [number, number], element: OsmFeature, layer: LayerConfig }[] = []
|
||||||
|
let seenElements = new Set<string>()
|
||||||
|
for (const elementsWithMetaElement of elementsWithMeta) {
|
||||||
|
const layer = layers[elementsWithMetaElement.layer]
|
||||||
|
if(layer.title === undefined){
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const filtered = this.state.filteredLayers.data.find(fl => fl.layerDef == layer);
|
||||||
|
for (let i = 0; i < elementsWithMetaElement.features.length; i++) {
|
||||||
|
const element = elementsWithMetaElement.features[i];
|
||||||
|
if (!filtered.isDisplayed.data) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (seenElements.has(element.properties.id)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seenElements.add(element.properties.id)
|
||||||
|
if (!bbox.overlapsWith(BBox.get(element))) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (layer?.isShown !== undefined && !layer.isShown.matchesProperties(element)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const activeFilters: FilterState[] = Array.from(filtered.appliedFilters.data.values());
|
||||||
|
if (!activeFilters.every(filter => filter?.currentFilter === undefined || filter?.currentFilter?.matchesProperties(element.properties))) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const center = GeoOperations.centerpointCoordinates(element);
|
||||||
|
elements.push({
|
||||||
|
element,
|
||||||
|
center,
|
||||||
|
layer: layers[elementsWithMetaElement.layer],
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inject a new point
|
* Inject a new point
|
||||||
*/
|
*/
|
||||||
|
|
51
UI/BigComponents/StatisticsPanel.ts
Normal file
51
UI/BigComponents/StatisticsPanel.ts
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||||
|
import Loading from "../Base/Loading";
|
||||||
|
import Title from "../Base/Title";
|
||||||
|
import TagRenderingChart from "./TagRenderingChart";
|
||||||
|
import Combine from "../Base/Combine";
|
||||||
|
import Locale from "../i18n/Locale";
|
||||||
|
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
|
import {OsmFeature} from "../../Models/OsmFeature";
|
||||||
|
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
|
||||||
|
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
|
||||||
|
|
||||||
|
export default class StatisticsPanel extends VariableUiElement {
|
||||||
|
constructor(elementsInview: UIEventSource<{ element: OsmFeature, layer: LayerConfig }[]>, state: {
|
||||||
|
layoutToUse: LayoutConfig
|
||||||
|
}) {
|
||||||
|
super(elementsInview.stabilized(1000).map(features => {
|
||||||
|
if (features === undefined) {
|
||||||
|
return new Loading("Loading data")
|
||||||
|
}
|
||||||
|
if (features.length === 0) {
|
||||||
|
return "No elements in view"
|
||||||
|
}
|
||||||
|
const els = []
|
||||||
|
for (const layer of state.layoutToUse.layers) {
|
||||||
|
if(layer.name === undefined){
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const featuresForLayer = features.filter(f => f.layer === layer).map(f => f.element)
|
||||||
|
if(featuresForLayer.length === 0){
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
els.push(new Title(layer.name.Clone(), 1).SetClass("mt-8"))
|
||||||
|
|
||||||
|
const layerStats = []
|
||||||
|
for (const tagRendering of (layer?.tagRenderings ?? [])) {
|
||||||
|
const chart = new TagRenderingChart(featuresForLayer, tagRendering, {
|
||||||
|
chartclasses: "w-full",
|
||||||
|
chartstyle: "height: 60rem",
|
||||||
|
includeTitle: false
|
||||||
|
})
|
||||||
|
const title = new Title(tagRendering.question?.Clone() ?? tagRendering.id, 4).SetClass("mt-8")
|
||||||
|
if(!chart.HasClass("hidden")){
|
||||||
|
layerStats.push(new Combine([title, chart]).SetClass("flex flex-col w-full lg:w-1/3"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
els.push(new Combine(layerStats).SetClass("flex flex-wrap"))
|
||||||
|
}
|
||||||
|
return new Combine(els)
|
||||||
|
}, [Locale.language]));
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,7 +12,6 @@ import {MinimapObj} from "./Base/Minimap";
|
||||||
import BaseUIElement from "./BaseUIElement";
|
import BaseUIElement from "./BaseUIElement";
|
||||||
import {VariableUiElement} from "./Base/VariableUIElement";
|
import {VariableUiElement} from "./Base/VariableUIElement";
|
||||||
import {GeoOperations} from "../Logic/GeoOperations";
|
import {GeoOperations} from "../Logic/GeoOperations";
|
||||||
import {BBox} from "../Logic/BBox";
|
|
||||||
import {OsmFeature} from "../Models/OsmFeature";
|
import {OsmFeature} from "../Models/OsmFeature";
|
||||||
import SearchAndGo from "./BigComponents/SearchAndGo";
|
import SearchAndGo from "./BigComponents/SearchAndGo";
|
||||||
import FeatureInfoBox from "./Popup/FeatureInfoBox";
|
import FeatureInfoBox from "./Popup/FeatureInfoBox";
|
||||||
|
@ -22,14 +21,11 @@ import Lazy from "./Base/Lazy";
|
||||||
import TagRenderingAnswer from "./Popup/TagRenderingAnswer";
|
import TagRenderingAnswer from "./Popup/TagRenderingAnswer";
|
||||||
import Hash from "../Logic/Web/Hash";
|
import Hash from "../Logic/Web/Hash";
|
||||||
import FilterView from "./BigComponents/FilterView";
|
import FilterView from "./BigComponents/FilterView";
|
||||||
import {FilterState} from "../Models/FilteredLayer";
|
|
||||||
import Translations from "./i18n/Translations";
|
import Translations from "./i18n/Translations";
|
||||||
import Constants from "../Models/Constants";
|
import Constants from "../Models/Constants";
|
||||||
import SimpleAddUI from "./BigComponents/SimpleAddUI";
|
import SimpleAddUI from "./BigComponents/SimpleAddUI";
|
||||||
import TagRenderingChart from "./BigComponents/TagRenderingChart";
|
|
||||||
import Loading from "./Base/Loading";
|
|
||||||
import BackToIndex from "./BigComponents/BackToIndex";
|
import BackToIndex from "./BigComponents/BackToIndex";
|
||||||
import Locale from "./i18n/Locale";
|
import StatisticsPanel from "./BigComponents/StatisticsPanel";
|
||||||
|
|
||||||
|
|
||||||
export default class DashboardGui {
|
export default class DashboardGui {
|
||||||
|
@ -94,63 +90,7 @@ export default class DashboardGui {
|
||||||
return new Combine(elements.map(e => self.singleElementView(e.element, e.layer, e.distance)))
|
return new Combine(elements.map(e => self.singleElementView(e.element, e.layer, e.distance)))
|
||||||
}
|
}
|
||||||
|
|
||||||
private visibleElements(map: MinimapObj & BaseUIElement, layers: Record<string, LayerConfig>): { distance: number, center: [number, number], element: OsmFeature, layer: LayerConfig }[] {
|
private documentationButtonFor(layerConfig: LayerConfig): BaseUIElement {
|
||||||
const bbox = map.bounds.data
|
|
||||||
if (bbox === undefined) {
|
|
||||||
console.warn("No bbox")
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
const location = map.location.data;
|
|
||||||
const loc: [number, number] = [location.lon, location.lat]
|
|
||||||
|
|
||||||
const elementsWithMeta: { features: OsmFeature[], layer: string }[] = this.state.featurePipeline.GetAllFeaturesAndMetaWithin(bbox)
|
|
||||||
|
|
||||||
let elements: { distance: number, center: [number, number], element: OsmFeature, layer: LayerConfig }[] = []
|
|
||||||
let seenElements = new Set<string>()
|
|
||||||
for (const elementsWithMetaElement of elementsWithMeta) {
|
|
||||||
const layer = layers[elementsWithMetaElement.layer]
|
|
||||||
if(layer.title === undefined){
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
const filtered = this.state.filteredLayers.data.find(fl => fl.layerDef == layer);
|
|
||||||
for (let i = 0; i < elementsWithMetaElement.features.length; i++) {
|
|
||||||
const element = elementsWithMetaElement.features[i];
|
|
||||||
if (!filtered.isDisplayed.data) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (seenElements.has(element.properties.id)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
seenElements.add(element.properties.id)
|
|
||||||
if (!bbox.overlapsWith(BBox.get(element))) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (layer?.isShown !== undefined && !layer.isShown.matchesProperties(element)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
const activeFilters: FilterState[] = Array.from(filtered.appliedFilters.data.values());
|
|
||||||
if (!activeFilters.every(filter => filter?.currentFilter === undefined || filter?.currentFilter?.matchesProperties(element.properties))) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
const center = GeoOperations.centerpointCoordinates(element);
|
|
||||||
elements.push({
|
|
||||||
element,
|
|
||||||
center,
|
|
||||||
layer: layers[elementsWithMetaElement.layer],
|
|
||||||
distance: GeoOperations.distanceBetween(loc, center)
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
elements.sort((e0, e1) => e0.distance - e1.distance)
|
|
||||||
|
|
||||||
|
|
||||||
return elements;
|
|
||||||
}
|
|
||||||
|
|
||||||
private documentationButtonFor(layerConfig: LayerConfig): BaseUIElement {
|
|
||||||
return this.viewSelector(Translations.W(layerConfig.name?.Clone() ?? layerConfig.id), new Combine(["Documentation about ", layerConfig.name?.Clone() ?? layerConfig.id]),
|
return this.viewSelector(Translations.W(layerConfig.name?.Clone() ?? layerConfig.id), new Combine(["Documentation about ", layerConfig.name?.Clone() ?? layerConfig.id]),
|
||||||
layerConfig.GenerateDocumentation([]),
|
layerConfig.GenerateDocumentation([]),
|
||||||
"documentation-" + layerConfig.id)
|
"documentation-" + layerConfig.id)
|
||||||
|
@ -166,6 +106,7 @@ export default class DashboardGui {
|
||||||
return this.viewSelector(new FixedUiElement("Documentation"), "Documentation",
|
return this.viewSelector(new FixedUiElement("Documentation"), "Documentation",
|
||||||
new Combine(layers.map(l => this.documentationButtonFor(l).SetClass("flex flex-col"))))
|
new Combine(layers.map(l => this.documentationButtonFor(l).SetClass("flex flex-col"))))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public setup(): void {
|
public setup(): void {
|
||||||
|
|
||||||
|
@ -191,7 +132,14 @@ export default class DashboardGui {
|
||||||
const elementsInview = new UIEventSource<{ distance: number, center: [number, number], element: OsmFeature, layer: LayerConfig }[]>([]);
|
const elementsInview = new UIEventSource<{ distance: number, center: [number, number], element: OsmFeature, layer: LayerConfig }[]>([]);
|
||||||
|
|
||||||
function update() {
|
function update() {
|
||||||
elementsInview.setData(self.visibleElements(map, layers))
|
const mapCenter = <[number,number]> [self.state.locationControl.data.lon, self.state.locationControl.data.lon]
|
||||||
|
const elements = self.state.featurePipeline.getAllVisibleElementsWithmeta(self.state.currentBounds.data).map(el => {
|
||||||
|
const distance = GeoOperations.distanceBetween(el.center, mapCenter)
|
||||||
|
return {...el, distance }
|
||||||
|
})
|
||||||
|
elements.sort((e0, e1) => e0.distance - e1.distance)
|
||||||
|
elementsInview.setData(elements)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
map.bounds.addCallbackAndRun(update)
|
map.bounds.addCallbackAndRun(update)
|
||||||
|
@ -235,60 +183,6 @@ export default class DashboardGui {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const statistics =
|
|
||||||
new VariableUiElement(elementsInview.stabilized(1000).map(features => {
|
|
||||||
if (features === undefined) {
|
|
||||||
return new Loading("Loading data")
|
|
||||||
}
|
|
||||||
if (features.length === 0) {
|
|
||||||
return "No elements in view"
|
|
||||||
}
|
|
||||||
const els = []
|
|
||||||
for (const layer of state.layoutToUse.layers) {
|
|
||||||
if(layer.name === undefined){
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
const featuresForLayer = features.filter(f => f.layer === layer).map(f => f.element)
|
|
||||||
if(featuresForLayer.length === 0){
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
els.push(new Title(layer.name))
|
|
||||||
|
|
||||||
const layerStats = []
|
|
||||||
for (const tagRendering of (layer?.tagRenderings ?? [])) {
|
|
||||||
const chart = new TagRenderingChart(featuresForLayer, tagRendering, {
|
|
||||||
chartclasses: "w-full",
|
|
||||||
chartstyle: "height: 60rem",
|
|
||||||
includeTitle: false
|
|
||||||
})
|
|
||||||
const full = new Lazy(() =>
|
|
||||||
new TagRenderingChart(featuresForLayer, tagRendering, {
|
|
||||||
chartstyle: "max-height: calc(100vh - 10rem)",
|
|
||||||
groupToOtherCutoff: 0
|
|
||||||
})
|
|
||||||
)
|
|
||||||
const title = new Title(tagRendering.question?.Clone() ?? tagRendering.id)
|
|
||||||
title.onClick(() => {
|
|
||||||
const current = self.currentView.data
|
|
||||||
full.onClick(() => {
|
|
||||||
self.currentView.setData(current)
|
|
||||||
})
|
|
||||||
self.currentView.setData(
|
|
||||||
{
|
|
||||||
title: new Title(tagRendering.question.Clone() ?? tagRendering.id),
|
|
||||||
contents: full
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if(!chart.HasClass("hidden")){
|
|
||||||
layerStats.push(new Combine([title, chart]).SetClass("flex flex-col w-full lg:w-1/3"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
els.push(new Combine(layerStats).SetClass("flex flex-wrap"))
|
|
||||||
}
|
|
||||||
return new Combine(els)
|
|
||||||
}, [Locale.language]))
|
|
||||||
|
|
||||||
|
|
||||||
new Combine([
|
new Combine([
|
||||||
new Combine([
|
new Combine([
|
||||||
|
@ -298,7 +192,7 @@ export default class DashboardGui {
|
||||||
this.viewSelector(new Title(
|
this.viewSelector(new Title(
|
||||||
new VariableUiElement(elementsInview.map(elements => "There are " + elements?.length + " elements in view"))),
|
new VariableUiElement(elementsInview.map(elements => "There are " + elements?.length + " elements in view"))),
|
||||||
"Statistics",
|
"Statistics",
|
||||||
statistics, "statistics"),
|
new StatisticsPanel(elementsInview, this.state), "statistics"),
|
||||||
|
|
||||||
this.viewSelector(new FixedUiElement("Filter"),
|
this.viewSelector(new FixedUiElement("Filter"),
|
||||||
"Filters", filterView, "filters"),
|
"Filters", filterView, "filters"),
|
||||||
|
|
|
@ -16,6 +16,7 @@ export default class LanguagePicker extends Toggle {
|
||||||
|
|
||||||
|
|
||||||
if (languages === undefined || languages.length <= 1) {
|
if (languages === undefined || languages.length <= 1) {
|
||||||
|
super(undefined,undefined,undefined)
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,8 @@ import {SaveButton} from "./Popup/SaveButton";
|
||||||
import {MapillaryLink} from "./BigComponents/MapillaryLink";
|
import {MapillaryLink} from "./BigComponents/MapillaryLink";
|
||||||
import {CheckBox} from "./Input/Checkboxes";
|
import {CheckBox} from "./Input/Checkboxes";
|
||||||
import Slider from "./Input/Slider";
|
import Slider from "./Input/Slider";
|
||||||
import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig";
|
import {OsmFeature} from "../Models/OsmFeature";
|
||||||
|
import StatisticsPanel from "./BigComponents/StatisticsPanel";
|
||||||
|
|
||||||
export interface SpecialVisualization {
|
export interface SpecialVisualization {
|
||||||
funcName: string,
|
funcName: string,
|
||||||
|
@ -1098,7 +1099,36 @@ export default class SpecialVisualizations {
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
new NearbyImageVis(),
|
new NearbyImageVis(),
|
||||||
new MapillaryLinkVis()
|
new MapillaryLinkVis(),
|
||||||
|
{
|
||||||
|
funcName: "statistics",
|
||||||
|
docs: "Show general statistics about the elements currently in view. Intended to use on the `current_view`-layer",
|
||||||
|
args: [],
|
||||||
|
constr : (state, tagsSource, args, guiState) => {
|
||||||
|
const elementsInview = new UIEventSource<{ distance: number, center: [number, number], element: OsmFeature, layer: LayerConfig }[]>([]);
|
||||||
|
function update() {
|
||||||
|
const mapCenter = <[number,number]> [state.locationControl.data.lon, state.locationControl.data.lon]
|
||||||
|
const bbox = state.currentBounds.data
|
||||||
|
const elements = state.featurePipeline.getAllVisibleElementsWithmeta(bbox).map(el => {
|
||||||
|
const distance = GeoOperations.distanceBetween(el.center, mapCenter)
|
||||||
|
return {...el, distance }
|
||||||
|
})
|
||||||
|
elements.sort((e0, e1) => e0.distance - e1.distance)
|
||||||
|
elementsInview.setData(elements)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
state.currentBounds.addCallbackAndRun(update)
|
||||||
|
state.featurePipeline.newDataLoadedSignal.addCallback(update);
|
||||||
|
state.filteredLayers.addCallbackAndRun(fls => {
|
||||||
|
for (const fl of fls) {
|
||||||
|
fl.isDisplayed.addCallback(update)
|
||||||
|
fl.appliedFilters.addCallback(update)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return new StatisticsPanel(elementsInview, state)
|
||||||
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
specialVisualizations.push(new AutoApplyButton(specialVisualizations))
|
specialVisualizations.push(new AutoApplyButton(specialVisualizations))
|
||||||
|
|
8
Utils.ts
8
Utils.ts
|
@ -1031,5 +1031,13 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
private static colorDiff(c0: { r: number, g: number, b: number }, c1: { r: number, g: number, b: number }) {
|
private static colorDiff(c0: { r: number, g: number, b: number }, c1: { r: number, g: number, b: number }) {
|
||||||
return Math.abs(c0.r - c1.r) + Math.abs(c0.g - c1.g) + Math.abs(c0.b - c1.b);
|
return Math.abs(c0.r - c1.r) + Math.abs(c0.g - c1.g) + Math.abs(c0.b - c1.b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static toIdRecord<T extends {id: string}>(ts: T[]): Record<string, T> {
|
||||||
|
const result : Record<string, T> = {}
|
||||||
|
for (const t of ts) {
|
||||||
|
result[t.id] = t
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -176,6 +176,30 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"builtin": "current_view",
|
||||||
|
"override": {
|
||||||
|
"+mapRendering": [
|
||||||
|
{
|
||||||
|
"location": [
|
||||||
|
"point", "centroid"
|
||||||
|
],
|
||||||
|
"icon": "statistics"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"=title": {
|
||||||
|
"render": {
|
||||||
|
"en": "Statistics"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tagRenderings": [
|
||||||
|
{
|
||||||
|
"id": "stats",
|
||||||
|
"render": "{statistics()}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"overrideAll": {
|
"overrideAll": {
|
||||||
|
@ -190,13 +214,14 @@
|
||||||
"condition": {
|
"condition": {
|
||||||
"and": [
|
"and": [
|
||||||
"entrance=",
|
"entrance=",
|
||||||
"kerb="
|
"kerb=",
|
||||||
|
"current_view!=yes"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"render": {
|
"render": {
|
||||||
"en": "<a href='#{_poi_entrance:id}'>This door has a width of {canonical(_poi_entrance:width)} meters</a>",
|
"en": "<a href='#{_poi_entrance:id}'>This door has a width of {canonical(_poi_entrance:width)} meters</a>",
|
||||||
"nl": "<a href='#{_poi_entrance:id}'>Deze deur heeft een breedte van {canonical(_poi_entrance:width)} meter</a>",
|
"nl": "<a href='#{_poi_entrance:id}'>Deze deur heeft een breedte van {canonical(_poi_entrance:width)} meter</a>",
|
||||||
"de": "<a href='#{_poi_entrance:id}'>Diese Tür hat eine Durchgangsbreite von {canonical(_poi_entrance:width)} Meter</a>",
|
"de": "<a href='#{_poi_entrance:id}'>Diese Tür hat eine Drchgangsbreite von {canonical(_poi_entrance:width)} Meter</a>",
|
||||||
"es": "<a href='#{_poi_entrance:id}'>Esta puerta tiene una ancho de {canonical(_poi_entrance:width)} metros</a>"
|
"es": "<a href='#{_poi_entrance:id}'>Esta puerta tiene una ancho de {canonical(_poi_entrance:width)} metros</a>"
|
||||||
},
|
},
|
||||||
"freeform": {
|
"freeform": {
|
||||||
|
|
|
@ -866,6 +866,14 @@ video {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mt-8 {
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-4 {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.mt-2 {
|
.mt-2 {
|
||||||
margin-top: 0.5rem;
|
margin-top: 0.5rem;
|
||||||
}
|
}
|
||||||
|
@ -878,10 +886,6 @@ video {
|
||||||
margin-right: 2rem;
|
margin-right: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mt-4 {
|
|
||||||
margin-top: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mt-6 {
|
.mt-6 {
|
||||||
margin-top: 1.5rem;
|
margin-top: 1.5rem;
|
||||||
}
|
}
|
||||||
|
@ -1515,11 +1519,6 @@ video {
|
||||||
background-color: rgba(224, 231, 255, var(--tw-bg-opacity));
|
background-color: rgba(224, 231, 255, var(--tw-bg-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-red-500 {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgba(239, 68, 68, var(--tw-bg-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-black {
|
.bg-black {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgba(0, 0, 0, var(--tw-bg-opacity));
|
background-color: rgba(0, 0, 0, var(--tw-bg-opacity));
|
||||||
|
@ -1540,6 +1539,11 @@ video {
|
||||||
background-color: rgba(209, 213, 219, var(--tw-bg-opacity));
|
background-color: rgba(209, 213, 219, var(--tw-bg-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bg-red-500 {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgba(239, 68, 68, var(--tw-bg-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
.bg-red-200 {
|
.bg-red-200 {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgba(254, 202, 202, var(--tw-bg-opacity));
|
background-color: rgba(254, 202, 202, var(--tw-bg-opacity));
|
||||||
|
|
Loading…
Reference in a new issue