Add question box as special rendering

This commit is contained in:
Pieter Vander Vennet 2023-03-31 03:28:11 +02:00
parent 15664df63f
commit d47fd7e746
42 changed files with 956 additions and 311 deletions

View file

@ -5,27 +5,27 @@ import { Utils } from "../../Utils"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import { Feature } from "geojson"
import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore"
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
import SvelteUIElement from "../../UI/Base/SvelteUIElement"
import TagRenderingAnswer from "../../UI/Popup/TagRenderingAnswer.svelte"
import TagRenderingAnswer from "../../UI/Popup/TagRendering/TagRenderingAnswer.svelte"
import { SpecialVisualizationState } from "../../UI/SpecialVisualization"
export default class TitleHandler {
constructor(
selectedElement: Store<Feature>,
selectedLayer: Store<LayerConfig>,
allElements: FeaturePropertiesStore,
layout: LayoutConfig
state: SpecialVisualizationState
) {
const currentTitle: Store<string> = selectedElement.map(
(selected) => {
const defaultTitle = layout?.title?.txt ?? "MapComplete"
const defaultTitle = state.layout?.title?.txt ?? "MapComplete"
if (selected === undefined) {
return defaultTitle
}
const tags = selected.properties
for (const layer of layout?.layers ?? []) {
for (const layer of state.layout?.layers ?? []) {
if (layer.title === undefined) {
continue
}
@ -33,7 +33,12 @@ export default class TitleHandler {
const tagsSource =
allElements.getStore(tags.id) ??
new UIEventSource<Record<string, string>>(tags)
const title = new SvelteUIElement(TagRenderingAnswer, { tags: tagsSource })
const title = new SvelteUIElement(TagRenderingAnswer, {
tags: tagsSource,
state,
selectedElement: selectedElement.data,
layer: selectedLayer.data,
})
return (
new Combine([defaultTitle, " | ", title]).ConstructElement()
?.textContent ?? defaultTitle

View file

@ -39,7 +39,7 @@ export class And extends TagsFilter {
return new And(ands)
}
matchesProperties(tags: any): boolean {
matchesProperties(tags: Record<string, string>): boolean {
for (const tagsFilter of this.and) {
if (!tagsFilter.matchesProperties(tags)) {
return false
@ -147,7 +147,7 @@ export class And extends TagsFilter {
return [].concat(...this.and.map((subkeys) => subkeys.usedTags()))
}
asChange(properties: any): { k: string; v: string }[] {
asChange(properties: Record<string, string>): { k: string; v: string }[] {
const result = []
for (const tagsFilter of this.and) {
result.push(...tagsFilter.asChange(properties))
@ -375,7 +375,7 @@ export class And extends TagsFilter {
return !this.and.some((t) => !t.isNegative())
}
visit(f: (TagsFilter: any) => void) {
visit(f: (tagsFilter: TagsFilter) => void) {
f(this)
this.and.forEach((sub) => sub.visit(f))
}

View file

@ -15,11 +15,11 @@ export default class ComparingTag implements TagsFilter {
this._representation = representation
}
asChange(properties: any): { k: string; v: string }[] {
asChange(properties: Record<string, string>): { k: string; v: string }[] {
throw "A comparable tag can not be used to be uploaded to OSM"
}
asHumanString(linkToWiki: boolean, shorten: boolean, properties: any) {
asHumanString(linkToWiki: boolean, shorten: boolean, properties: Record<string, string>) {
return this._key + this._representation
}
@ -44,7 +44,7 @@ export default class ComparingTag implements TagsFilter {
* t.matchesProperties({key: 0}) // => true
* t.matchesProperties({differentKey: 42}) // => false
*/
matchesProperties(properties: any): boolean {
matchesProperties(properties: Record<string, string>): boolean {
return this._predicate(properties[this._key])
}

View file

@ -17,7 +17,7 @@ export class Or extends TagsFilter {
return new Or(or)
}
matchesProperties(properties: any): boolean {
matchesProperties(properties: Record<string, string>): boolean {
for (const tagsFilter of this.or) {
if (tagsFilter.matchesProperties(properties)) {
return true
@ -82,7 +82,7 @@ export class Or extends TagsFilter {
return [].concat(...this.or.map((subkeys) => subkeys.usedTags()))
}
asChange(properties: any): { k: string; v: string }[] {
asChange(properties: Record<string, string>): { k: string; v: string }[] {
const result = []
for (const tagsFilter of this.or) {
result.push(...tagsFilter.asChange(properties))
@ -260,7 +260,7 @@ export class Or extends TagsFilter {
return this.or.some((t) => t.isNegative())
}
visit(f: (TagsFilter: any) => void) {
visit(f: (tagsFilter: TagsFilter) => void) {
f(this)
this.or.forEach((t) => t.visit(f))
}

View file

@ -122,7 +122,7 @@ export class RegexTag extends TagsFilter {
* new RegexTag("key","value").matchesProperties({"otherkey":"value"}) // => false
* new RegexTag("key","value",true).matchesProperties({"otherkey":"something"}) // => true
*/
matchesProperties(tags: any): boolean {
matchesProperties(tags: Record<string, string>): boolean {
if (typeof this.key === "string") {
const value = tags[this.key] ?? ""
return RegexTag.doesMatch(value, this.value) != this.invert
@ -244,7 +244,7 @@ export class RegexTag extends TagsFilter {
return []
}
asChange(properties: any): { k: string; v: string }[] {
asChange(properties: Record<string, string>): { k: string; v: string }[] {
if (this.invert) {
return []
}

View file

@ -22,7 +22,7 @@ export default class SubstitutingTag implements TagsFilter {
this._invert = invert
}
private static substituteString(template: string, dict: any): string {
private static substituteString(template: string, dict: Record<string, string>): string {
for (const k in dict) {
template = template.replace(new RegExp("\\{" + k + "\\}", "g"), dict[k])
}
@ -72,7 +72,7 @@ export default class SubstitutingTag implements TagsFilter {
* assign.matchesProperties({"_date:now": "2021-03-29"}) // => false
* assign.matchesProperties({"some_key": "2021-03-29"}) // => false
*/
matchesProperties(properties: any): boolean {
matchesProperties(properties: Record<string, string>): boolean {
const value = properties[this._key]
if (value === undefined || value === "") {
return false
@ -89,7 +89,7 @@ export default class SubstitutingTag implements TagsFilter {
return []
}
asChange(properties: any): { k: string; v: string }[] {
asChange(properties: Record<string, string>): { k: string; v: string }[] {
if (this._invert) {
throw "An inverted substituting tag can not be used to create a change"
}
@ -108,7 +108,7 @@ export default class SubstitutingTag implements TagsFilter {
return false
}
visit(f: (TagsFilter: any) => void) {
visit(f: (tagsFilter: TagsFilter) => void) {
f(this)
}
}

View file

@ -37,7 +37,7 @@ export class Tag extends TagsFilter {
* isEmpty.matchesProperties({"key": undefined}) // => true
*
*/
matchesProperties(properties: any): boolean {
matchesProperties(properties: Record<string, string>): boolean {
const foundValue = properties[this.key]
if (foundValue === undefined && (this.value === "" || this.value === undefined)) {
// The tag was not found
@ -62,7 +62,11 @@ export class Tag extends TagsFilter {
t.asHumanString() // => "key=value"
t.asHumanString(true) // => "<a href='https://wiki.openstreetmap.org/wiki/Key:key' target='_blank'>key</a>=<a href='https://wiki.openstreetmap.org/wiki/Tag:key%3Dvalue' target='_blank'>value</a>"
*/
asHumanString(linkToWiki?: boolean, shorten?: boolean, currentProperties?: any) {
asHumanString(
linkToWiki?: boolean,
shorten?: boolean,
currentProperties?: Record<string, string>
) {
let v = this.value
if (shorten) {
v = Utils.EllipsesAfter(v, 25)
@ -134,7 +138,7 @@ export class Tag extends TagsFilter {
return false
}
visit(f: (TagsFilter) => void) {
visit(f: (tagsFilter: TagsFilter) => void) {
f(this)
}
}

View file

@ -9,9 +9,13 @@ export abstract class TagsFilter {
*/
abstract shadows(other: TagsFilter): boolean
abstract matchesProperties(properties: any): boolean
abstract matchesProperties(properties: Record<string, string>): boolean
abstract asHumanString(linkToWiki: boolean, shorten: boolean, properties: any): string
abstract asHumanString(
linkToWiki: boolean,
shorten: boolean,
properties: Record<string, string>
): string
abstract usedKeys(): string[]
@ -27,7 +31,7 @@ export abstract class TagsFilter {
*
* Note: properties are the already existing tags-object. It is only used in the substituting tag
*/
abstract asChange(properties: any): { k: string; v: string }[]
abstract asChange(properties: Record<string, string>): { k: string; v: string }[]
/**
* Returns an optimized version (or self) of this tagsFilter
@ -54,5 +58,5 @@ export abstract class TagsFilter {
/**
* Walks the entire tree, every tagsFilter will be passed into the function once
*/
abstract visit(f: (TagsFilter) => void)
abstract visit(f: (tagsFilter: TagsFilter) => void)
}

View file

@ -35,6 +35,14 @@ export class QueryParameters {
return source
}
public static SetDefaultFor(key: string, value: string) {
if (QueryParameters.defaults[key] === value) {
return
}
QueryParameters.defaults[key] = value
QueryParameters.Serialize()
}
public static GetBooleanQueryParameter(
key: string,
deflt: boolean,
@ -80,6 +88,11 @@ export class QueryParameters {
}
}
/**
* Set the query parameters of the page location
* @constructor
* @private
*/
private static Serialize() {
const parts = []
for (const key of QueryParameters.order) {

View file

@ -23,6 +23,8 @@ import predifined_filters from "../../../assets/layers/filters/filters.json"
import { TagConfigJson } from "../Json/TagConfigJson"
import PointRenderingConfigJson from "../Json/PointRenderingConfigJson"
import LineRenderingConfigJson from "../Json/LineRenderingConfigJson"
import ValidationUtils from "./ValidationUtils"
import { RenderingSpecification } from "../../../UI/SpecialVisualization"
class ExpandFilter extends DesugaringStep<LayerConfigJson> {
private static readonly predefinedFilters = ExpandFilter.load_filters()
@ -218,7 +220,7 @@ class ExpandTagRendering extends Conversion<
matchingTrs = layerTrs
} else if (id.startsWith("*")) {
const id_ = id.substring(1)
matchingTrs = layerTrs.filter((tr) => tr.group === id_ || tr.labels?.indexOf(id_) >= 0)
matchingTrs = layerTrs.filter((tr) => tr.labels?.indexOf(id_) >= 0)
} else {
matchingTrs = layerTrs.filter((tr) => tr.id === id || tr.labels?.indexOf(id) >= 0)
}
@ -255,13 +257,6 @@ class ExpandTagRendering extends Conversion<
ctx: string
): TagRenderingConfigJson[] {
const state = this._state
if (tr === "questions") {
return [
{
id: "questions",
},
]
}
if (typeof tr === "string") {
const lookup = this.lookup(tr)
@ -415,6 +410,111 @@ class ExpandTagRendering extends Conversion<
}
}
export class AddQuestionBox extends DesugaringStep<LayerConfigJson> {
constructor() {
super(
"Adds a 'questions'-object if no question element is added yet",
["tagRenderings"],
"AddQuestionBox"
)
}
convert(
json: LayerConfigJson,
context: string
): { result: LayerConfigJson; errors?: string[]; warnings?: string[]; information?: string[] } {
if (json.tagRenderings === undefined) {
return { result: json }
}
json = JSON.parse(JSON.stringify(json))
const allSpecials: Exclude<RenderingSpecification, string>[] = []
.concat(
...json.tagRenderings.map((tr) =>
ValidationUtils.getSpecialVisualsationsWithArgs(<TagRenderingConfigJson>tr)
)
)
.filter((spec) => typeof spec !== "string")
const questionSpecials = allSpecials.filter((sp) => sp.func.funcName === "questions")
const noLabels = questionSpecials.filter(
(sp) => sp.args.length === 0 || sp.args[0].trim() === ""
)
const errors: string[] = []
const warnings: string[] = []
if (noLabels.length > 1) {
console.log(json.tagRenderings)
errors.push(
"At " +
context +
": multiple 'questions'-visualisations found which would show _all_ questions. Don't do this"
)
}
// ALl labels that are used in this layer
const allLabels = new Set(
[].concat(...json.tagRenderings.map((tr) => (<TagRenderingConfigJson>tr).labels ?? []))
)
const seen = new Set()
for (const questionSpecial of questionSpecials) {
if (typeof questionSpecial === "string") {
continue
}
const used = questionSpecial.args[0]
?.split(";")
?.map((a) => a.trim())
?.filter((s) => s != "")
const blacklisted = questionSpecial.args[1]
?.split(";")
?.map((a) => a.trim())
?.filter((s) => s != "")
if (blacklisted?.length > 0 && used?.length > 0) {
errors.push(
"At " +
context +
": the {questions()}-special rendering only supports either a blacklist OR a whitelist, but not both." +
"\n Whitelisted: " +
used.join(", ") +
"\n Blacklisted: " +
blacklisted.join(", ")
)
}
for (const usedLabel of used) {
if (!allLabels.has(usedLabel)) {
errors.push(
"At " +
context +
": this layers specifies a special question element for label `" +
usedLabel +
"`, but this label doesn't exist.\n" +
" Available labels are " +
Array.from(allLabels).join(", ")
)
}
seen.add(usedLabel)
}
}
if (noLabels.length == 0) {
/* At this point, we know which question labels are not yet handled and which already are handled, and we
* know there is no previous catch-all questions
*/
const question: TagRenderingConfigJson = {
id: "leftover-questions",
render: {
"*": `{questions( ,${Array.from(seen).join(";")})}`,
},
}
json.tagRenderings.push(question)
}
return {
result: json,
errors,
warnings,
}
}
}
export class ExpandRewrite<T> extends Conversion<T | RewritableConfigJson<T>, T[]> {
constructor() {
super("Applies a rewrite", [], "ExpandRewrite")

View file

@ -10,7 +10,7 @@ import {
SetDefault,
} from "./Conversion"
import { LayoutConfigJson } from "../Json/LayoutConfigJson"
import { PrepareLayer } from "./PrepareLayer"
import { AddQuestionBox, PrepareLayer } from "./PrepareLayer"
import { LayerConfigJson } from "../Json/LayerConfigJson"
import { Utils } from "../../../Utils"
import Constants from "../../Constants"
@ -336,7 +336,6 @@ export class AddMiniMap extends DesugaringStep<LayerConfigJson> {
if (!hasMinimap) {
layerConfig = { ...layerConfig }
layerConfig.tagRenderings = [...layerConfig.tagRenderings]
layerConfig.tagRenderings.push(state.tagRenderings.get("questions"))
layerConfig.tagRenderings.push(state.tagRenderings.get("minimap"))
}
@ -662,6 +661,7 @@ export class PrepareTheme extends Fuse<LayoutConfigJson> {
: new AddDefaultLayers(state),
new AddDependencyLayersToTheme(state),
new AddImportLayers(),
new On("layers", new Each(new AddQuestionBox())),
new On("layers", new Each(new AddMiniMap(state)))
)
}

View file

@ -619,12 +619,12 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> {
': detected `special` on the top level. Did you mean `{"render":{ "special": ... }}`'
)
}
if (json.group) {
if (json["group"]) {
errors.push(
"At " +
context +
': groups are deprecated, use `"label": ["' +
json.group +
json["group"] +
'"]` instead'
)
}

View file

@ -1,7 +1,7 @@
import { TagRenderingConfigJson } from "../Json/TagRenderingConfigJson"
import { Utils } from "../../../Utils"
import SpecialVisualizations from "../../../UI/SpecialVisualizations"
import { SpecialVisualization } from "../../../UI/SpecialVisualization"
import { RenderingSpecification, SpecialVisualization } from "../../../UI/SpecialVisualization"
export default class ValidationUtils {
/**
@ -11,11 +11,18 @@ export default class ValidationUtils {
public static getSpecialVisualisations(
renderingConfig: TagRenderingConfigJson
): SpecialVisualization[] {
return ValidationUtils.getSpecialVisualsationsWithArgs(renderingConfig).map(
(spec) => spec["func"]
)
}
public static getSpecialVisualsationsWithArgs(
renderingConfig: TagRenderingConfigJson
): RenderingSpecification[] {
const translations: any[] = Utils.NoNull([
renderingConfig.render,
...(renderingConfig.mappings ?? []).map((m) => m.then),
])
const all: SpecialVisualization[] = []
const all: RenderingSpecification[] = []
for (let translation of translations) {
if (typeof translation == "string") {
translation = { "*": translation }
@ -28,9 +35,7 @@ export default class ValidationUtils {
const template = translation[key]
const parts = SpecialVisualizations.constructSpecification(template)
const specials = parts
.filter((p) => typeof p !== "string")
.map((special) => special["func"])
const specials = parts.filter((p) => typeof p !== "string")
all.push(...specials)
}
}

View file

@ -244,8 +244,8 @@ export default class PointRenderingConfig extends WithContextLoader {
iconAndBadges.SetClass("w-full h-full")
}
const css = this.cssDef?.GetRenderValue(tags, undefined)?.txt
const cssClasses = this.cssClasses?.GetRenderValue(tags, undefined)?.txt
const css = this.cssDef?.GetRenderValue(tags)?.txt
const cssClasses = this.cssClasses?.GetRenderValue(tags)?.txt
let label = this.GetLabel(tags)
let htmlEl: BaseUIElement

View file

@ -203,19 +203,6 @@ export default class TagRenderingConfig {
throw `${context}: A question is defined, but no mappings nor freeform (key) are. The question is ${this.question.txt} at ${context}`
}
if (this.id === "questions" && this.render !== undefined) {
for (const ln in this.render.translations) {
const txt: string = this.render.translations[ln]
if (txt.indexOf("{questions}") >= 0) {
continue
}
throw `${context}: The rendering for language ${ln} does not contain {questions}. This is a bug, as this rendering should include exactly this to trigger those questions to be shown!`
}
if (this.freeform?.key !== undefined && this.freeform?.key !== "questions") {
throw `${context}: If the ID is questions to trigger a question box, the only valid freeform value is 'questions' as well. Set freeform to questions or remove the freeform all together`
}
}
if (this.freeform) {
if (this.render === undefined) {
throw `${context}: Detected a freeform key without rendering... Key: ${this.freeform.key} in ${context}`
@ -509,15 +496,11 @@ export default class TagRenderingConfig {
})
}
}
return applicableMappings
}
public GetRenderValue(
tags: any,
defltValue: any = undefined
): TypedTranslation<any> | undefined {
return this.GetRenderValueWithImage(tags, defltValue)?.then
public GetRenderValue(tags: Record<string, string>): TypedTranslation<any> | undefined {
return this.GetRenderValueWithImage(tags)?.then
}
/**
@ -526,8 +509,7 @@ export default class TagRenderingConfig {
* @constructor
*/
public GetRenderValueWithImage(
tags: any,
defltValue: any = undefined
tags: Record<string, string>
): { then: TypedTranslation<any>; icon?: string } | undefined {
if (this.condition !== undefined) {
if (!this.condition.matchesProperties(tags)) {
@ -554,7 +536,7 @@ export default class TagRenderingConfig {
return { then: this.render }
}
return { then: defltValue }
return undefined
}
/**
@ -625,6 +607,76 @@ export default class TagRenderingConfig {
}
}
/**
* Given a value for the freeform key and an overview of the selected mappings, construct the correct tagsFilter to apply
*
* @param freeformValue The freeform value which will be applied as 'freeform.key'. Ignored if 'freeform.key' is not set
*
* @param singleSelectedMapping (Only used if multiAnswer == false): the single mapping to apply. Use (mappings.length) for the freeform
* @param multiSelectedMapping (Only used if multiAnswer == true): all the mappings that must be applied. Set multiSelectedMapping[mappings.length] to use the freeform as well
*/
public constructChangeSpecification(
freeformValue: string | undefined,
singleSelectedMapping: number,
multiSelectedMapping: boolean[] | undefined
): UploadableTag {
if (
freeformValue === undefined &&
singleSelectedMapping === undefined &&
multiSelectedMapping === undefined
) {
return undefined
}
if (this.mappings === undefined && freeformValue === undefined) {
return undefined
}
if (
this.freeform !== undefined &&
(this.mappings === undefined ||
this.mappings.length == 0 ||
(singleSelectedMapping === this.mappings.length && !this.multiAnswer))
) {
// Either no mappings, or this is a radio-button selected freeform value
return new And([
new Tag(this.freeform.key, freeformValue),
...(this.freeform.addExtraTags ?? []),
])
}
if (this.multiAnswer) {
let selectedMappings: UploadableTag[] = this.mappings
.filter((_, i) => multiSelectedMapping[i])
.map((m) => new And([m.if, ...(m.addExtraTags ?? [])]))
let unselectedMappings: UploadableTag[] = this.mappings
.filter((_, i) => !multiSelectedMapping[i])
.map((m) => m.ifnot)
if (multiSelectedMapping.at(-1)) {
// The freeform value was selected as well
selectedMappings.push(
new And([
new Tag(this.freeform.key, freeformValue),
...(this.freeform.addExtraTags ?? []),
])
)
}
return TagUtils.FlattenMultiAnswer([...selectedMappings, ...unselectedMappings])
} else {
if (singleSelectedMapping === this.mappings.length) {
return new And([
new Tag(this.freeform.key, freeformValue),
...(this.freeform.addExtraTags ?? []),
])
} else {
return new And([
this.mappings[singleSelectedMapping].if,
...(this.mappings[singleSelectedMapping].addExtraTags ?? []),
])
}
}
}
GenerateDocumentation(): BaseUIElement {
let withRender: (BaseUIElement | string)[] = []
if (this.freeform?.key !== undefined) {

View file

@ -13,7 +13,7 @@
// Text for the current language
let txt: string | undefined;
onDestroy(Locale.language.addCallbackAndRunD(l => {
$: onDestroy(Locale.language.addCallbackAndRunD(l => {
const translation = t?.textFor(l)
if(translation === undefined){
return

View file

@ -3,52 +3,32 @@
import { UIEventSource } from "../../Logic/UIEventSource";
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
import type { SpecialVisualizationState } from "../SpecialVisualization";
import TagRenderingAnswer from "../Popup/TagRenderingAnswer.svelte";
import TagRenderingQuestion from "../Popup/TagRenderingQuestion.svelte";
import TagRenderingAnswer from "../Popup/TagRendering/TagRenderingAnswer.svelte";
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte";
import { onDestroy } from "svelte";
export let selectedElement: Feature;
export let layer: LayerConfig;
export let tags: UIEventSource<Record<string, string>>;
let _tags: Record<string, string>;
onDestroy(tags.addCallbackAndRun(tags => {
_tags = tags;
}));
export let state: SpecialVisualizationState;
/**
* const title = new TagRenderingAnswer(
* tags,
* layerConfig.title ?? new TagRenderingConfig("POI"),
* state
* ).SetClass("break-words font-bold sm:p-0.5 md:p-1 sm:p-1.5 md:p-2 text-2xl")
* const titleIcons = new Combine(
* layerConfig.titleIcons.map((icon) => {
* return new TagRenderingAnswer(
* tags,
* icon,
* state,
* "block h-8 max-h-8 align-baseline box-content sm:p-0.5 titleicon"
* )
* })
* ).SetClass("flex flex-row flex-wrap pt-0.5 sm:pt-1 items-center mr-2")
*
* return new Combine([
* new Combine([title, titleIcons]).SetClass(
* "flex flex-col sm:flex-row flex-grow justify-between"
* ),
* ])
*/
</script>
<div>
<div class="flex flex-col sm:flex-row flex-grow justify-between">
<!-- Title element-->
<h3>
<TagRenderingAnswer config={layer.title} {selectedElement} {tags}></TagRenderingAnswer>
<TagRenderingAnswer config={layer.title} {selectedElement} {state} {tags} {layer}></TagRenderingAnswer>
</h3>
<div class="flex flex-row flex-wrap pt-0.5 sm:pt-1 items-center mr-2">
{#each layer.titleIcons as titleIconConfig (titleIconConfig.id)}
<div class="w-8 h-8">
<TagRenderingAnswer config={titleIconConfig} {tags} {selectedElement}></TagRenderingAnswer>
<TagRenderingAnswer config={titleIconConfig} {tags} {selectedElement} {state} {layer}></TagRenderingAnswer>
</div>
{/each}
</div>
@ -58,10 +38,10 @@
<div class="flex flex-col">
{#each layer.tagRenderings as config (config.id)}
{#if config.IsKnown($tags)}
<TagRenderingAnswer {tags} {config} {state}></TagRenderingAnswer>
{:else}
<TagRenderingQuestion {config} {tags} {state}></TagRenderingQuestion>
{#if config.condition === undefined || config.condition.matchesProperties(_tags)}
{#if config.IsKnown(_tags)}
<TagRenderingEditable {tags} {config} {state} {selectedElement} {layer}></TagRenderingEditable>
{/if}
{/if}
{/each}
</div>

View file

@ -1,41 +1,53 @@
<script lang="ts">
import { Store, UIEventSource } from "../../Logic/UIEventSource";
import { UIEventSource } from "../../Logic/UIEventSource";
import type { ValidatorType } from "./Validators";
import Validators from "./Validators";
import { ExclamationIcon } from "@rgossiaux/svelte-heroicons/solid";
import { Translation } from "../i18n/Translation";
import { createEventDispatcher } from "svelte";
export let value: UIEventSource<string>;
// Internal state, only copied to 'value' so that no invalid values leak outside
let _value = new UIEventSource(value.data ?? "")
let _value = new UIEventSource(value.data ?? "");
export let type: ValidatorType;
let validator = Validators.get(type);
export let feedback: UIEventSource<Translation> | undefined = undefined
export let feedback: UIEventSource<Translation> | undefined = undefined;
_value.addCallbackAndRun(v => {
if(validator.isValid(v)){
feedback?.setData(undefined)
value.setData(v)
return
if (validator.isValid(v)) {
feedback?.setData(undefined);
value.setData(v);
return;
}
value.setData(undefined)
value.setData(undefined);
feedback?.setData(validator.getFeedback(v));
})
});
if (validator === undefined) {
throw "Not a valid type for a validator:" + type;
}
const isValid = _value.map(v => validator.isValid(v));
let htmlElem: HTMLInputElement;
let dispatch = createEventDispatcher<{ selected }>();
$: {
console.log(htmlElem)
if (htmlElem !== undefined) {
htmlElem.onfocus = () => {
console.log("Dispatching selected event")
return dispatch("selected");
};
}
}
</script>
{#if validator.textArea}
<textarea bind:value={$_value} inputmode={validator.inputmode ?? "text"}></textarea>
{:else }
<div class="flex">
<input bind:value={$_value} inputmode={validator.inputmode ?? "text"}>
<input bind:this={htmlElem} bind:value={$_value} inputmode={validator.inputmode ?? "text"}>
{#if !$isValid}
<ExclamationIcon class="h-6 w-6 -ml-6"></ExclamationIcon>
{/if}

51
UI/Popup/QuestionViz.ts Normal file
View file

@ -0,0 +1,51 @@
import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization"
import { Feature } from "geojson"
import BaseUIElement from "../BaseUIElement"
import { UIEventSource } from "../../Logic/UIEventSource"
import SvelteUIElement from "../Base/SvelteUIElement"
import Questionbox from "./TagRendering/Questionbox.svelte"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
/**
* Thin wrapper around QuestionBox.svelte to include it into the special Visualisations
*/
export default class QuestionViz implements SpecialVisualization {
funcName = "questions"
docs =
"The special element which shows the questions which are unkown. Added by default if not yet there"
args = [
{
name: "labels",
doc: "One or more ';'-separated labels. If these are given, only questions with these labels will be given. Use `unlabeled` for all questions that don't have an explicit label. If none given, all questions will be shown",
},
{
name: "blacklisted-labels",
doc: "One or more ';'-separated labels of questions which should _not_ be included",
},
]
constr(
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>,
args: string[],
feature: Feature,
layer: LayerConfig
): BaseUIElement {
const labels = args[0]
?.split(";")
?.map((s) => s.trim())
?.filter((s) => s !== "")
const blacklist = args[1]
?.split(";")
?.map((s) => s.trim())
?.filter((s) => s !== "")
return new SvelteUIElement(Questionbox, {
layer,
tags,
selectedElement: feature,
state,
onlyForLabels: labels,
notForLabels: blacklist,
})
}
}

View file

@ -0,0 +1,26 @@
<script lang="ts">
import { UIEventSource } from "../../../Logic/UIEventSource";
import { Translation } from "../../i18n/Translation";
import ValidatedInput from "../../InputElement/ValidatedInput.svelte";
import Tr from "../../Base/Tr.svelte";
import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig";
import Inline from "./Inline.svelte";
import { createEventDispatcher } from "svelte";
export let value: UIEventSource<string>;
export let config: TagRenderingConfig;
export let tags: UIEventSource<Record<string, string>>;
let feedback: UIEventSource<Translation> = new UIEventSource<Translation>(undefined);
let dispatch = createEventDispatcher<{ "selected" }>();
</script>
<Inline key={config.freeform.key} {tags} template={config.render}>
<ValidatedInput {feedback} type={config.freeform.type}
{value} on:selected={() => dispatch("selected")}></ValidatedInput>
</Inline>
{#if $feedback !== undefined}
<div class="alert">
<Tr t={$feedback} />
</div>
{/if}

View file

@ -0,0 +1,26 @@
<script lang="ts">
import { Utils } from "../../../Utils.js";
import { UIEventSource } from "../../../Logic/UIEventSource";
import { onDestroy } from "svelte";
import { Translation } from "../../i18n/Translation";
import Locale from "../../i18n/Locale";
export let template: Translation;
let _template: string
onDestroy(Locale.language.addCallbackAndRunD(l => {
_template = template.textFor(l)
}))
export let key: string;
export let tags: UIEventSource<Record<string, string>>;
let _tags = tags.data;
onDestroy(tags.addCallbackAndRunD(tags => {
_tags = tags;
}));
let [before, after] = _template.split("{" + key + "}");
</script>
<span>
{Utils.SubstituteKeys(before, _tags)}
<slot />
{Utils.SubstituteKeys(after, _tags)}
</span>

View file

@ -0,0 +1,108 @@
<script lang="ts">
/**
* Shows all questions for which the answers are unknown.
* The questions can either be shown all at once or one at a time (in which case they can be skipped)
*/
import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig";
import { UIEventSource } from "../../../Logic/UIEventSource";
import type { Feature } from "geojson";
import type { SpecialVisualizationState } from "../../SpecialVisualization";
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
import If from "../../Base/If.svelte";
import { onDestroy } from "svelte";
import TagRenderingQuestion from "./TagRenderingQuestion.svelte";
import Tr from "../../Base/Tr.svelte";
import Translations from "../../i18n/Translations.js";
export let layer: LayerConfig;
export let tags: UIEventSource<Record<string, string>>;
export let selectedElement: Feature;
export let state: SpecialVisualizationState;
/**
* If set, only questions for these labels will be shown
*/
export let onlyForLabels: string[] | undefined = undefined;
const _onlyForLabels = new Set(onlyForLabels);
/**
* If set, only questions _not_ having these labels will be shown
*/
export let notForLabels: string[] | undefined = undefined;
const _notForLabels = new Set(notForLabels);
function allowed(labels: string[]) {
if (onlyForLabels?.length > 0 && !labels.some(l => _onlyForLabels.has(l))) {
return false;
}
if (notForLabels?.length > 0 && labels.some(l => _notForLabels.has(l))) {
return false;
}
return true;
}
console.log("Got layer", layer, onlyForLabels, notForLabels);
const baseQuestions = (layer.tagRenderings ?? [])?.filter(tr => allowed(tr.labels) && tr.question !== undefined);
console.log("BaseQuestions are", baseQuestions);
let skippedQuestions = new UIEventSource<Set<string>>(new Set<string>());
let answered : number = 0
let questionsToAsk = tags.map(tags => {
const questionsToAsk: TagRenderingConfig[] = [];
for (const baseQuestion of baseQuestions) {
if (skippedQuestions.data.has(baseQuestion.id) > 0) {
continue;
}
if (baseQuestion.condition !== undefined && !baseQuestion.condition.matchesProperties(tags)) {
continue;
}
questionsToAsk.push(baseQuestion);
}
return questionsToAsk;
}, [skippedQuestions]);
let _questionsToAsk: TagRenderingConfig[];
let _firstQuestion: TagRenderingConfig
onDestroy(questionsToAsk.subscribe(qta => {
_questionsToAsk = qta;
_firstQuestion = qta[0]
}));
function skip(question: TagRenderingConfig, didAnswer: boolean = false) {
skippedQuestions.data.add(question.id);
skippedQuestions.ping();
if(didAnswer ){
answered ++
}
}
</script>
{#if _questionsToAsk.length === 0}
All done! You answered {answered} questions and skipped {$skippedQuestions.size} questions.
{#if $skippedQuestions.size > 0 }
<button on:click={() => skippedQuestions.setData(new Set())}>Re-activate skipped questions</button>
{/if}
{:else }
<div>
<If condition={state.userRelatedState.showAllQuestionsAtOnce}>
<div>
{#each _questionsToAsk as question (question.id)}
<TagRenderingQuestion config={question} {tags} {selectedElement} {state} {layer}></TagRenderingQuestion>
{/each}
</div>
<div slot="else">
<TagRenderingQuestion
config={_firstQuestion} {layer} {selectedElement} {state} {tags}
on:saved={() => {skip(_firstQuestion, true)}}>
<button on:click={() => {skip(_firstQuestion)} }
slot="cancel">
<Tr t={Translations.t.general.skip}></Tr>
</button>
</TagRenderingQuestion>
</div>
</If>
</div>
{/if}

View file

@ -1,14 +1,15 @@
<script lang="ts">
import { Translation } from "../i18n/Translation";
import SpecialVisualizations from "../SpecialVisualizations";
import { Translation } from "../../i18n/Translation";
import SpecialVisualizations from "../../SpecialVisualizations";
import { onDestroy } from "svelte";
import Locale from "../i18n/Locale";
import type { RenderingSpecification, SpecialVisualizationState } from "../SpecialVisualization";
import { Utils } from "../../Utils.js";
import Locale from "../../i18n/Locale";
import type { RenderingSpecification, SpecialVisualizationState } from "../../SpecialVisualization";
import { Utils } from "../../../Utils.js";
import type { Feature } from "geojson";
import { UIEventSource } from "../../Logic/UIEventSource.js";
import ToSvelte from "../Base/ToSvelte.svelte";
import FromHtml from "../Base/FromHtml.svelte";
import { UIEventSource } from "../../../Logic/UIEventSource.js";
import ToSvelte from "../../Base/ToSvelte.svelte";
import FromHtml from "../../Base/FromHtml.svelte";
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
/**
* The 'specialTranslation' renders a `Translation`-object, but interprets the special values as well
@ -17,18 +18,18 @@
export let state: SpecialVisualizationState;
export let tags: UIEventSource<Record<string, string>>;
export let feature: Feature;
export let layer: LayerConfig
let txt: string;
onDestroy(Locale.language.addCallbackAndRunD(l => {
txt = t.textFor(l);
}));
let specs: RenderingSpecification[];
specs = SpecialVisualizations.constructSpecification(txt);
let specs: RenderingSpecification[] = SpecialVisualizations.constructSpecification(txt);
</script>
{#each specs as specpart}
{#if typeof specpart === "string"}
<FromHtml src= {Utils.SubstituteKeys(specpart, $tags)}></FromHtml>
{:else if $tags !== undefined }
<ToSvelte construct={specpart.func.constr(state, tags, specpart.args, feature)}></ToSvelte>
<ToSvelte construct={specpart.func.constr(state, tags, specpart.args, feature, layer)}></ToSvelte>
{/if}
{/each}

View file

@ -0,0 +1,38 @@
<script lang="ts">
import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig";
import { Utils } from "../../../Utils";
import { Translation } from "../../i18n/Translation";
import TagRenderingMapping from "./TagRenderingMapping.svelte";
import type { SpecialVisualizationState } from "../../SpecialVisualization";
import type { Feature } from "geojson";
import { UIEventSource } from "../../../Logic/UIEventSource";
import { onDestroy } from "svelte";
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
export let tags: UIEventSource<Record<string, string> | undefined>;
let _tags: Record<string, string>;
onDestroy(tags.addCallbackAndRun(tags => {
_tags = tags;
}));
export let state: SpecialVisualizationState;
export let selectedElement: Feature;
export let config: TagRenderingConfig;
export let layer: LayerConfig
let trs: { then: Translation; icon?: string; iconClass?: string }[];
$: trs = Utils.NoNull(config?.GetRenderValues(_tags));
</script>
{#if config !== undefined && (config?.condition === undefined || config.condition.matchesProperties(_tags))}
{#if trs.length === 1}
<TagRenderingMapping mapping={trs[0]} {tags} {state} {selectedElement} {layer}></TagRenderingMapping>
{/if}
{#if trs.length > 1}
<ul>
{#each trs as mapping}
<li>
<TagRenderingMapping {mapping} {tags} {state} {selectedElement} {layer}></TagRenderingMapping>
</li>
{/each}
</ul>
{/if}
{/if}

View file

@ -0,0 +1,46 @@
<script lang="ts">
import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig";
import { UIEventSource } from "../../../Logic/UIEventSource";
import type { Feature } from "geojson";
import type { SpecialVisualizationState } from "../../SpecialVisualization";
import TagRenderingAnswer from "./TagRenderingAnswer.svelte";
import { PencilAltIcon } from "@rgossiaux/svelte-heroicons/solid";
import TagRenderingQuestion from "./TagRenderingQuestion.svelte";
import { onDestroy } from "svelte";
import Tr from "../../Base/Tr.svelte";
import Translations from "../../i18n/Translations.js";
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
export let config: TagRenderingConfig;
export let tags: UIEventSource<Record<string, string>>;
export let selectedElement: Feature;
export let state: SpecialVisualizationState;
export let layer: LayerConfig
export let showQuestionIfUnknown : boolean= false
let editMode = false
onDestroy(tags.addCallbackAndRunD(tags => {
editMode = showQuestionIfUnknown && !config.IsKnown(tags)
}))
</script>
{#if config.question}
{#if editMode}
<TagRenderingQuestion {config} {tags} {selectedElement} {state} {layer} >
<button slot="cancel" on:click={() => {editMode = false}}>
<Tr t={Translations.t.general.cancel}/>
</button>
</TagRenderingQuestion>
{:else}
<div class="flex justify-between">
<TagRenderingAnswer {config} {tags} {selectedElement} {state} {layer} />
<button on:click={() => {editMode = true}} class="w-6 h-6 rounded-full subtle-background p-1">
<PencilAltIcon></PencilAltIcon>
</button>
</div>
{/if}
{:else }
<TagRenderingAnswer {config} {tags} {selectedElement} {state} {layer} />
{/if}

View file

@ -1,13 +1,15 @@
<script lang="ts">
import { Translation } from "../i18n/Translation";
import { Translation } from "../../i18n/Translation";
import SpecialTranslation from "./SpecialTranslation.svelte";
import type { SpecialVisualizationState } from "../SpecialVisualization";
import type { SpecialVisualizationState } from "../../SpecialVisualization";
import type { Feature } from "geojson";
import { UIEventSource } from "../../Logic/UIEventSource";
import { UIEventSource } from "../../../Logic/UIEventSource";
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
export let selectedElement: Feature
export let tags: UIEventSource<Record<string, string>>;
export let state: SpecialVisualizationState
export let layer: LayerConfig
export let mapping: {
then: Translation; icon?: string; iconClass?: | "small"
| "medium"
@ -22,11 +24,11 @@
</script>
{#if mapping.icon !== undefined}
<div class="flex">
<div class="inline-flex">
<img class={iconclass+" mr-1"} src={mapping.icon}>
<SpecialTranslation t={mapping.then} {tags} {state} feature={selectedElement}></SpecialTranslation>
<SpecialTranslation t={mapping.then} {tags} {state} {layer} feature={selectedElement}></SpecialTranslation>
</div>
{:else if mapping.then !== undefined}
<SpecialTranslation t={mapping.then} {tags} {state} feature={selectedElement}></SpecialTranslation>
<SpecialTranslation t={mapping.then} {tags} {state} {layer} feature={selectedElement}></SpecialTranslation>
{/if}

View file

@ -0,0 +1,147 @@
<script lang="ts">
import { UIEventSource } from "../../../Logic/UIEventSource";
import type { SpecialVisualizationState } from "../../SpecialVisualization";
import Tr from "../../Base/Tr.svelte";
import If from "../../Base/If.svelte";
import TagRenderingMapping from "./TagRenderingMapping.svelte";
import type { Feature } from "geojson";
import type { Mapping } from "../../../Models/ThemeConfig/TagRenderingConfig";
import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig";
import { TagsFilter } from "../../../Logic/Tags/TagsFilter";
import FreeformInput from "./FreeformInput.svelte";
import Translations from "../../i18n/Translations.js";
import FromHtml from "../../Base/FromHtml.svelte";
import ChangeTagAction from "../../../Logic/Osm/Actions/ChangeTagAction";
import { createEventDispatcher } from "svelte";
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
export let config: TagRenderingConfig;
export let tags: UIEventSource<Record<string, string>>;
export let selectedElement: Feature;
export let state: SpecialVisualizationState;
export let layer: LayerConfig;
// Will be bound if a freeform is available
let freeformInput = new UIEventSource<string>(undefined);
let selectedMapping: number = 0;
let checkedMappings: boolean[];
if (config.mappings?.length > 0) {
checkedMappings = [...config.mappings.map(_ => false), false /*One element extra in case a freeform value is added*/];
}
let selectedTags: TagsFilter = undefined;
$:selectedTags = config?.constructChangeSpecification($freeformInput, selectedMapping, checkedMappings);
function mappingIsHidden(mapping: Mapping): boolean {
if (mapping.hideInAnswer === undefined || mapping.hideInAnswer === false) {
return false;
}
if (mapping.hideInAnswer === true) {
return true;
}
return (<TagsFilter>mapping.hideInAnswer).matchesProperties(tags.data);
}
let dispatch = createEventDispatcher<{
"saved": {
config: TagRenderingConfig,
applied: TagsFilter
}
}>();
function onSave() {
dispatch("saved", { config, applied: selectedTags });
const change = new ChangeTagAction(
tags.data.id,
selectedTags,
tags.data,
{
theme: state.layout.id,
changeType: "answer"
}
);
change.CreateChangeDescriptions().then(changes =>
state.changes.applyChanges(changes)
).catch(console.error);
}
</script>
{#if config.question !== undefined}
<div class="border border-black subtle-background flex flex-col">
<If condition={state.featureSwitchIsTesting}>
<div class="flex justify-between">
<Tr t={config.question}></Tr>
<span class="alert">{config.id}</span>
</div>
<Tr slot="else" t={config.question}></Tr>
</If>
{#if config.questionhint}
<div class="subtle">
<Tr t={config.questionHint}></Tr>
</div>
{/if}
{#if config.freeform?.key && !(config.mappings?.length > 0)}
<!-- There are no options to choose from, simply show the input element: fill out the text field -->
<FreeformInput {config} {tags} value={freeformInput} />
{/if}
{#if config.mappings !== undefined && !config.multiAnswer}
<!-- Simple radiobuttons as mapping -->
<div class="flex flex-col">
{#each config.mappings as mapping, i (mapping.then)}
{#if !mappingIsHidden(mapping) }
<label>
<input type="radio" bind:group={selectedMapping} name={"mappings-radio-"+config.id} value={i}>
<TagRenderingMapping {mapping} {tags} {state} {selectedElement} {layer}></TagRenderingMapping>
</label>
{/if}
{/each}
{#if config.freeform?.key}
<label>
<input type="radio" bind:group={selectedMapping} name={"mappings-radio-"+config.id}
value={config.mappings.length}>
<FreeformInput {config} {tags} value={freeformInput}
on:selected={() => selectedMapping = config.mappings.length } />
</label>
{/if}
</div>
{/if}
{#if config.mappings !== undefined && config.multiAnswer}
<!-- Multiple answers can be chosen: checkboxes -->
<div class="flex flex-col">
{#each config.mappings as mapping, i (mapping.then)}
{#if !mappingIsHidden(mapping) }
<label>
<input type="checkbox" name={"mappings-checkbox-"+config.id+"-"+i} bind:checked={checkedMappings[i]}>
<TagRenderingMapping {mapping} {tags} {state} {selectedElement}></TagRenderingMapping>
</label>
{/if}
{/each}
{#if config.freeform?.key}
<label>
<input type="checkbox" name={"mappings-checkbox-"+config.id+"-"+config.mappings.length}
bind:checked={checkedMappings[config.mappings.length]}>
<FreeformInput {config} {tags} value={freeformInput}
on:selected={() => checkedMappings[config.mappings.length] = true} />
</label>
{/if}
</div>
{/if}
<FromHtml src={selectedTags?.asHumanString(true, true, {})} />
<div>
<!-- TagRenderingQuestion-buttons -->
<slot name="cancel"></slot>
<button on:click={onSave}>
<Tr t={Translations.t.general.save}></Tr>
</button>
</div>
</div>
{/if}

View file

@ -1,34 +0,0 @@
<script lang="ts">
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig";
import { Utils } from "../../Utils";
import { Translation } from "../i18n/Translation";
import TagRenderingMapping from "./TagRenderingMapping.svelte";
import type { SpecialVisualizationState } from "../SpecialVisualization";
import type { Feature } from "geojson";
import { UIEventSource } from "../../Logic/UIEventSource";
import { onDestroy } from "svelte";
export let tags: UIEventSource<Record<string, string> | undefined>;
let _tags : Record<string, string>
onDestroy(tags.addCallbackAndRun(tags => {
_tags = tags
}))
export let state: SpecialVisualizationState
export let selectedElement: Feature
export let config: TagRenderingConfig;
let trs: { then: Translation; icon?: string; iconClass?: string }[];
$: trs = Utils.NoNull(config?.GetRenderValues(_tags));
</script>
{#if config !== undefined && (config?.condition === undefined || config.condition.matchesProperties(tags))}
<div>
{#if trs.length === 1}
<TagRenderingMapping mapping={trs[0]} {tags} {state} feature={selectedElement}></TagRenderingMapping>
{/if}
{#if trs.length > 1}
{#each trs as mapping}
<TagRenderingMapping mapping={trs} {tags} {state} feature=""{selectedElement}></TagRenderingMapping>
{/each}
{/if}
</div>
{/if}

View file

@ -1,53 +0,0 @@
<script lang="ts">
import { UIEventSource } from "../../Logic/UIEventSource";
import type { SpecialVisualizationState } from "../SpecialVisualization";
import Tr from "../Base/Tr.svelte";
import If from "../Base/If.svelte";
import ValidatedInput from "../InputElement/ValidatedInput.svelte";
import TagRenderingMapping from "./TagRenderingMapping.svelte";
import type { Feature } from "geojson";
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig";
export let config: TagRenderingConfig;
export let tags: UIEventSource<Record<string, string>>;
export let selectedElement: Feature;
export let state: SpecialVisualizationState;
state.featureSwitchIsTesting;
let freeformInput = new UIEventSource<string>(undefined);
</script>
{#if config.question !== undefined}
<div class="border border-black subtle-background">
<If condition={state.featureSwitchIsTesting}>
<div class="flex justify-between">
<Tr t={config.question}></Tr>
{config.id}
</div>
<Tr slot="else" t={config.question}></Tr>
</If>
{#if config.questionhint}
<div class="subtle">
<Tr t={config.question}></Tr>
</div>
{/if}
{#if config.freeform?.key && !(config.mappings?.length > 0)}
<!-- There are no options to choose from, simply show the input element: fill out the text field -->
<ValidatedInput type={config.freeform.type} value={freeformInput}></ValidatedInput>
{/if}
{#if config.mappings !== undefined}
<div class="flex flex-col">
{#each config.mappings as mapping}
{#if mapping.hideInAnswer === true || !(mapping.hideInAnswer) || (console.log(tags) || true) || !(mapping.hideInAnswer?.matchesProperties($tags)) }
<TagRenderingMapping {mapping} {tags} {state} {selectedElement}></TagRenderingMapping>
{/if}
{/each}
</div>
{/if}
</div>
{/if}

View file

@ -11,6 +11,7 @@ import { Feature, Geometry } from "geojson"
import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource"
import { MangroveIdentity } from "../Logic/Web/MangroveReviews"
import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore"
import LayerConfig from "../Models/ThemeConfig/LayerConfig"
/**
* The state needed to render a special Visualisation.
@ -47,7 +48,10 @@ export interface SpecialVisualizationState {
readonly fullNodeDatabase?: FullNodeDatabaseSource
readonly perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer>
readonly userRelatedState: { readonly mangroveIdentity: MangroveIdentity }
readonly userRelatedState: {
readonly mangroveIdentity: MangroveIdentity
readonly showAllQuestionsAtOnce: UIEventSource<boolean>
}
}
export interface SpecialVisualization {
@ -73,7 +77,8 @@ export interface SpecialVisualization {
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature
feature: Feature,
layer: LayerConfig
): BaseUIElement
}

View file

@ -55,6 +55,7 @@ import FeatureReviews from "../Logic/Web/MangroveReviews"
import Maproulette from "../Logic/Maproulette"
import SvelteUIElement from "./Base/SvelteUIElement"
import { BBoxFeatureSourceForLayer } from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource"
import QuestionViz from "./Popup/QuestionViz"
export default class SpecialVisualizations {
public static specialVisualizations: SpecialVisualization[] = SpecialVisualizations.initList()
@ -81,6 +82,10 @@ export default class SpecialVisualizations {
return []
}
if (template["type"] !== undefined) {
console.trace("Got a non-expanded template while constructing the specification")
throw "Got a non-expanded template while constructing the specification"
}
const allKnownSpecials = extraMappings.concat(SpecialVisualizations.specialVisualizations)
for (const knownSpecial of allKnownSpecials) {
// Note: the '.*?' in the regex reads as 'any character, but in a non-greedy way'
@ -226,6 +231,7 @@ export default class SpecialVisualizations {
private static initList(): SpecialVisualization[] {
const specialVisualizations: SpecialVisualization[] = [
new QuestionViz(),
new HistogramViz(),
new StealViz(),
new MinimapViz(),
@ -956,7 +962,8 @@ export default class SpecialVisualizations {
state,
new UIEventSource<Record<string, string>>(e.feature.properties),
e.args,
e.feature
e.feature,
undefined
)
})
return new Combine([new Title(s.funcName), s.docs, ...examples])

View file

@ -67,7 +67,7 @@
<ToSvelte construct={Svg.plus_ui}></ToSvelte>
</MapControlButton>
<MapControlButton on:click={() => mapproperties.zoom.update(z => z-1)}>
<ToSvelte class="w-7 h-7 block" construct={Svg.min_ui}></ToSvelte>
<ToSvelte construct={Svg.min_ui}></ToSvelte>
</MapControlButton>
<If condition={featureSwitches.featureSwitchGeolocation}>
<MapControlButton>
@ -79,9 +79,7 @@
<div class="absolute top-0 right-0 mt-4 mr-4">
<If condition={state.featureSwitches.featureSwitchSearch}>
<Geosearch bounds={state.mapProperties.bounds} layout={state.layout} location={state.mapProperties.location}
{selectedElement} {selectedLayer}
></Geosearch>
<Geosearch bounds={state.mapProperties.bounds} {selectedElement} {selectedLayer}></Geosearch>
</If>
</div>
@ -94,20 +92,20 @@
<TabGroup>
<TabList>
<Tab class={({selected}) => selected ? "tab-selected" : "tab-unselected"}>
<Tr t={layout.title}/>
<Tr t={layout.title} />
</Tab>
<Tab class={({selected}) => selected ? "tab-selected" : "tab-unselected"}>
<Tr t={Translations.t.general.menu.filter}/>
<Tr t={Translations.t.general.menu.filter} />
</Tab>
<Tab class={({selected}) => selected ? "tab-selected" : "tab-unselected"}>Tab 3</Tab>
</TabList>
<TabPanels>
<TabPanel class="flex flex-col">
<Tr t={layout.description}></Tr>
<Tr t={Translations.t.general.welcomeExplanation.general}/>
<Tr t={Translations.t.general.welcomeExplanation.general} />
{#if layout.layers.some((l) => l.presets?.length > 0)}
<If condition={state.featureSwitches.featureSwitchAddNew}>
<Tr t={Translations.t.general.welcomeExplanation.addNew}/>
<Tr t={Translations.t.general.welcomeExplanation.addNew} />
</If>
{/if}
@ -168,12 +166,12 @@
</If>
{#if $selectedElement !== undefined && $selectedLayer !== undefined}
<div class="absolute top-0 right-0 w-screen h-screen" style="background-color: #00000088">
<div class="absolute top-0 right-0 w-screen h-screen overflow-auto" style="background-color: #00000088">
<div class="w-full m-8 normal-background rounded overflow-auto">
<SelectedElementView layer={$selectedLayer} selectedElement={$selectedElement}
tags={$selectedElementTags} state={state}></SelectedElementView>
<div class="flex flex-col m-4 sm:m-6 md:m-8 p-4 sm:p-6 md:m-8 normal-background rounded normal-background">
<SelectedElementView layer={$selectedLayer} selectedElement={$selectedElement}
tags={$selectedElementTags} state={state}></SelectedElementView>
</div>
</div>

View file

@ -1591,7 +1591,9 @@
},
{
"id": "voltage-0",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What voltage do the plugs with <div style='display: inline-block'><b><b>Schuko wall plug</b> without ground pin (CEE7/4 type F)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/CEE7_4F.svg'/></div> offer?",
"nl": "Welke spanning levert de stekker van type <div style='display: inline-block'><b><b>Schuko stekker</b> zonder aardingspin (CEE7/4 type F)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/CEE7_4F.svg'/></div>",
@ -1629,7 +1631,9 @@
},
{
"id": "current-0",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What current do the plugs with <div style='display: inline-block'><b><b>Schuko wall plug</b> without ground pin (CEE7/4 type F)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/CEE7_4F.svg'/></div> offer?",
"nl": "Welke stroom levert de stekker van type <div style='display: inline-block'><b><b>Schuko stekker</b> zonder aardingspin (CEE7/4 type F)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/CEE7_4F.svg'/></div>?",
@ -1670,7 +1674,9 @@
},
{
"id": "power-output-0",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What power output does a single plug of type <div style='display: inline-block'><b><b>Schuko wall plug</b> without ground pin (CEE7/4 type F)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/CEE7_4F.svg'/></div> offer?",
"nl": "Welk vermogen levert een enkele stekker van type <div style='display: inline-block'><b><b>Schuko stekker</b> zonder aardingspin (CEE7/4 type F)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/CEE7_4F.svg'/></div>?",
@ -1711,7 +1717,9 @@
},
{
"id": "voltage-1",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What voltage do the plugs with <div style='display: inline-block'><b><b>European wall plug</b> with ground pin (CEE7/4 type E)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/TypeE.svg'/></div> offer?",
"nl": "Welke spanning levert de stekker van type <div style='display: inline-block'><b><b>Europese stekker</b> met aardingspin (CEE7/4 type E)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/TypeE.svg'/></div>",
@ -1749,7 +1757,9 @@
},
{
"id": "current-1",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What current do the plugs with <div style='display: inline-block'><b><b>European wall plug</b> with ground pin (CEE7/4 type E)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/TypeE.svg'/></div> offer?",
"nl": "Welke stroom levert de stekker van type <div style='display: inline-block'><b><b>Europese stekker</b> met aardingspin (CEE7/4 type E)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/TypeE.svg'/></div>?",
@ -1790,7 +1800,9 @@
},
{
"id": "power-output-1",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What power output does a single plug of type <div style='display: inline-block'><b><b>European wall plug</b> with ground pin (CEE7/4 type E)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/TypeE.svg'/></div> offer?",
"nl": "Welk vermogen levert een enkele stekker van type <div style='display: inline-block'><b><b>Europese stekker</b> met aardingspin (CEE7/4 type E)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/TypeE.svg'/></div>?",
@ -1843,7 +1855,9 @@
},
{
"id": "voltage-2",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What voltage do the plugs with <div style='display: inline-block'><b><b>Chademo</b></b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Chademo_type4.svg'/></div> offer?",
"nl": "Welke spanning levert de stekker van type <div style='display: inline-block'><b><b>Chademo</b></b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Chademo_type4.svg'/></div>",
@ -1883,7 +1897,9 @@
},
{
"id": "current-2",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What current do the plugs with <div style='display: inline-block'><b><b>Chademo</b></b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Chademo_type4.svg'/></div> offer?",
"nl": "Welke stroom levert de stekker van type <div style='display: inline-block'><b><b>Chademo</b></b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Chademo_type4.svg'/></div>?",
@ -1923,7 +1939,9 @@
},
{
"id": "power-output-2",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What power output does a single plug of type <div style='display: inline-block'><b><b>Chademo</b></b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Chademo_type4.svg'/></div> offer?",
"nl": "Welk vermogen levert een enkele stekker van type <div style='display: inline-block'><b><b>Chademo</b></b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Chademo_type4.svg'/></div>?",
@ -1961,7 +1979,9 @@
},
{
"id": "voltage-3",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What voltage do the plugs with <div style='display: inline-block'><b><b>Type 1 with cable</b> (J1772)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type1_J1772.svg'/></div> offer?",
"nl": "Welke spanning levert de stekker van type <div style='display: inline-block'><b><b>Type 1 met kabel</b> (J1772)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type1_J1772.svg'/></div>",
@ -2011,7 +2031,9 @@
},
{
"id": "current-3",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What current do the plugs with <div style='display: inline-block'><b><b>Type 1 with cable</b> (J1772)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type1_J1772.svg'/></div> offer?",
"nl": "Welke stroom levert de stekker van type <div style='display: inline-block'><b><b>Type 1 met kabel</b> (J1772)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type1_J1772.svg'/></div>?",
@ -2052,7 +2074,9 @@
},
{
"id": "power-output-3",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What power output does a single plug of type <div style='display: inline-block'><b><b>Type 1 with cable</b> (J1772)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type1_J1772.svg'/></div> offer?",
"nl": "Welk vermogen levert een enkele stekker van type <div style='display: inline-block'><b><b>Type 1 met kabel</b> (J1772)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type1_J1772.svg'/></div>?",
@ -2102,7 +2126,9 @@
},
{
"id": "voltage-4",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What voltage do the plugs with <div style='display: inline-block'><b><b>Type 1 <i>without</i> cable</b> (J1772)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type1_J1772.svg'/></div> offer?",
"nl": "Welke spanning levert de stekker van type <div style='display: inline-block'><b><b>Type 1 <i>zonder</i> kabel</b> (J1772)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type1_J1772.svg'/></div>",
@ -2152,7 +2178,9 @@
},
{
"id": "current-4",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What current do the plugs with <div style='display: inline-block'><b><b>Type 1 <i>without</i> cable</b> (J1772)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type1_J1772.svg'/></div> offer?",
"nl": "Welke stroom levert de stekker van type <div style='display: inline-block'><b><b>Type 1 <i>zonder</i> kabel</b> (J1772)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type1_J1772.svg'/></div>?",
@ -2193,7 +2221,9 @@
},
{
"id": "power-output-4",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What power output does a single plug of type <div style='display: inline-block'><b><b>Type 1 <i>without</i> cable</b> (J1772)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type1_J1772.svg'/></div> offer?",
"nl": "Welk vermogen levert een enkele stekker van type <div style='display: inline-block'><b><b>Type 1 <i>zonder</i> kabel</b> (J1772)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type1_J1772.svg'/></div>?",
@ -2267,7 +2297,9 @@
},
{
"id": "voltage-5",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What voltage do the plugs with <div style='display: inline-block'><b><b>Type 1 CCS</b> (aka Type 1 Combo)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type1-ccs.svg'/></div> offer?",
"nl": "Welke spanning levert de stekker van type <div style='display: inline-block'><b><b>Type 1 CCS</b> (ook gekend als Type 1 Combo)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type1-ccs.svg'/></div>",
@ -2317,7 +2349,9 @@
},
{
"id": "current-5",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What current do the plugs with <div style='display: inline-block'><b><b>Type 1 CCS</b> (aka Type 1 Combo)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type1-ccs.svg'/></div> offer?",
"nl": "Welke stroom levert de stekker van type <div style='display: inline-block'><b><b>Type 1 CCS</b> (ook gekend als Type 1 Combo)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type1-ccs.svg'/></div>?",
@ -2371,7 +2405,9 @@
},
{
"id": "power-output-5",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What power output does a single plug of type <div style='display: inline-block'><b><b>Type 1 CCS</b> (aka Type 1 Combo)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type1-ccs.svg'/></div> offer?",
"nl": "Welk vermogen levert een enkele stekker van type <div style='display: inline-block'><b><b>Type 1 CCS</b> (ook gekend als Type 1 Combo)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type1-ccs.svg'/></div>?",
@ -2445,7 +2481,9 @@
},
{
"id": "voltage-6",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What voltage do the plugs with <div style='display: inline-block'><b><b>Tesla Supercharger</b></b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Tesla-hpwc-model-s.svg'/></div> offer?",
"nl": "Welke spanning levert de stekker van type <div style='display: inline-block'><b><b>Tesla Supercharger</b></b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Tesla-hpwc-model-s.svg'/></div>",
@ -2483,7 +2521,9 @@
},
{
"id": "current-6",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What current do the plugs with <div style='display: inline-block'><b><b>Tesla Supercharger</b></b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Tesla-hpwc-model-s.svg'/></div> offer?",
"nl": "Welke stroom levert de stekker van type <div style='display: inline-block'><b><b>Tesla Supercharger</b></b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Tesla-hpwc-model-s.svg'/></div>?",
@ -2537,7 +2577,9 @@
},
{
"id": "power-output-6",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What power output does a single plug of type <div style='display: inline-block'><b><b>Tesla Supercharger</b></b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Tesla-hpwc-model-s.svg'/></div> offer?",
"nl": "Welk vermogen levert een enkele stekker van type <div style='display: inline-block'><b><b>Tesla Supercharger</b></b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Tesla-hpwc-model-s.svg'/></div>?",
@ -2599,7 +2641,9 @@
},
{
"id": "voltage-7",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What voltage do the plugs with <div style='display: inline-block'><b><b>Type 2</b> (mennekes)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type2_socket.svg'/></div> offer?",
"nl": "Welke spanning levert de stekker van type <div style='display: inline-block'><b><b>Type 2</b> (mennekes)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type2_socket.svg'/></div>",
@ -2649,7 +2693,9 @@
},
{
"id": "current-7",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What current do the plugs with <div style='display: inline-block'><b><b>Type 2</b> (mennekes)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type2_socket.svg'/></div> offer?",
"nl": "Welke stroom levert de stekker van type <div style='display: inline-block'><b><b>Type 2</b> (mennekes)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type2_socket.svg'/></div>?",
@ -2703,7 +2749,9 @@
},
{
"id": "power-output-7",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What power output does a single plug of type <div style='display: inline-block'><b><b>Type 2</b> (mennekes)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type2_socket.svg'/></div> offer?",
"nl": "Welk vermogen levert een enkele stekker van type <div style='display: inline-block'><b><b>Type 2</b> (mennekes)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type2_socket.svg'/></div>?",
@ -2753,7 +2801,9 @@
},
{
"id": "voltage-8",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What voltage do the plugs with <div style='display: inline-block'><b><b>Type 2 CCS</b> (mennekes)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type2_CCS.svg'/></div> offer?",
"nl": "Welke spanning levert de stekker van type <div style='display: inline-block'><b><b>Type 2 CCS</b> (mennekes)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type2_CCS.svg'/></div>",
@ -2803,7 +2853,9 @@
},
{
"id": "current-8",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What current do the plugs with <div style='display: inline-block'><b><b>Type 2 CCS</b> (mennekes)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type2_CCS.svg'/></div> offer?",
"nl": "Welke stroom levert de stekker van type <div style='display: inline-block'><b><b>Type 2 CCS</b> (mennekes)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type2_CCS.svg'/></div>?",
@ -2857,7 +2909,9 @@
},
{
"id": "power-output-8",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What power output does a single plug of type <div style='display: inline-block'><b><b>Type 2 CCS</b> (mennekes)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type2_CCS.svg'/></div> offer?",
"nl": "Welk vermogen levert een enkele stekker van type <div style='display: inline-block'><b><b>Type 2 CCS</b> (mennekes)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type2_CCS.svg'/></div>?",
@ -2895,7 +2949,9 @@
},
{
"id": "voltage-9",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What voltage do the plugs with <div style='display: inline-block'><b><b>Type 2 with cable</b> (mennekes)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type2_tethered.svg'/></div> offer?",
"nl": "Welke spanning levert de stekker van type <div style='display: inline-block'><b><b>Type 2 met kabel</b> (J1772)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type2_tethered.svg'/></div>",
@ -2945,7 +3001,9 @@
},
{
"id": "current-9",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What current do the plugs with <div style='display: inline-block'><b><b>Type 2 with cable</b> (mennekes)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type2_tethered.svg'/></div> offer?",
"nl": "Welke stroom levert de stekker van type <div style='display: inline-block'><b><b>Type 2 met kabel</b> (J1772)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type2_tethered.svg'/></div>?",
@ -2998,7 +3056,9 @@
},
{
"id": "power-output-9",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What power output does a single plug of type <div style='display: inline-block'><b><b>Type 2 with cable</b> (mennekes)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type2_tethered.svg'/></div> offer?",
"nl": "Welk vermogen levert een enkele stekker van type <div style='display: inline-block'><b><b>Type 2 met kabel</b> (J1772)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type2_tethered.svg'/></div>?",
@ -3048,7 +3108,9 @@
},
{
"id": "voltage-10",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What voltage do the plugs with <div style='display: inline-block'><b><b>Tesla Supercharger CCS</b> (a branded Type 2 CSS)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type2_CCS.svg'/></div> offer?",
"nl": "Welke spanning levert de stekker van type <div style='display: inline-block'><b><b>Tesla Supercharger CCS</b> (een type2 CCS met Tesla-logo)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type2_CCS.svg'/></div>",
@ -3098,7 +3160,9 @@
},
{
"id": "current-10",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What current do the plugs with <div style='display: inline-block'><b><b>Tesla Supercharger CCS</b> (a branded type2_css)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type2_CCS.svg'/></div> offer?",
"nl": "Welke stroom levert de stekker van type <div style='display: inline-block'><b><b>Tesla Supercharger CCS</b> (een type2 CCS met Tesla-logo)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type2_CCS.svg'/></div>?",
@ -3152,7 +3216,9 @@
},
{
"id": "power-output-10",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What power output does a single plug of type <div style='display: inline-block'><b><b>Tesla Supercharger CCS</b> (a branded Type 2 CSS)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type2_CCS.svg'/></div> offer?",
"nl": "Welk vermogen levert een enkele stekker van type <div style='display: inline-block'><b><b>Tesla Supercharger CCS</b> (een type2 CCS met Tesla-logo)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type2_CCS.svg'/></div>?",
@ -3190,7 +3256,9 @@
},
{
"id": "voltage-11",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What voltage do the plugs with <div style='display: inline-block'><b><b>Tesla Supercharger (Destination)</b></b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Tesla-hpwc-model-s.svg'/></div> offer?",
"nl": "Welke spanning levert de stekker van type <div style='display: inline-block'><b><b>Tesla Supercharger (Destination)</b></b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Tesla-hpwc-model-s.svg'/></div>",
@ -3228,7 +3296,9 @@
},
{
"id": "current-11",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What current do the plugs with <div style='display: inline-block'><b><b>Tesla Supercharger (Destination)</b></b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Tesla-hpwc-model-s.svg'/></div> offer?",
"nl": "Welke stroom levert de stekker van type <div style='display: inline-block'><b><b>Tesla Supercharger (Destination)</b></b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Tesla-hpwc-model-s.svg'/></div>?",
@ -3284,7 +3354,9 @@
},
{
"id": "power-output-11",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What power output does a single plug of type <div style='display: inline-block'><b><b>Tesla Supercharger (Destination)</b></b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Tesla-hpwc-model-s.svg'/></div> offer?",
"nl": "Welk vermogen levert een enkele stekker van type <div style='display: inline-block'><b><b>Tesla Supercharger (destination)</b></b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Tesla-hpwc-model-s.svg'/></div>?",
@ -3346,7 +3418,9 @@
},
{
"id": "voltage-12",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What voltage do the plugs with <div style='display: inline-block'><b><b>Tesla Supercharger (Destination)</b> (A Type 2 with cable branded as Tesla)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type2_tethered.svg'/></div> offer?",
"nl": "Welke spanning levert de stekker van type <div style='display: inline-block'><b><b>Tesla supercharger (destination).</b> (Een Type 2 met kabel en Tesla-logo)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type2_tethered.svg'/></div>?",
@ -3396,7 +3470,9 @@
},
{
"id": "current-12",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What current do the plugs with <div style='display: inline-block'><b><b>Tesla Supercharger (Destination)</b> (A Type 2 with cable branded as Tesla)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type2_tethered.svg'/></div> offer?",
"nl": "Welke stroom levert de stekker van type <div style='display: inline-block'><b><b>Tesla Supercharger (Destination)</b> (Een Type 2 met kabel en Tesla-logo)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type2_tethered.svg'/></div>?",
@ -3449,7 +3525,9 @@
},
{
"id": "power-output-12",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What power output does a single plug of type <div style='display: inline-block'><b><b>Tesla Supercharger (Destination)</b> (A Type 2 with cable branded as Tesla)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type2_tethered.svg'/></div> offer?",
"nl": "Welk vermogen levert een enkele stekker van type <div style='display: inline-block'><b><b>Tesla supercharger (destination)</b> (Een Type 2 met kabel en Tesla-logo)</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/Type2_tethered.svg'/></div>?",
@ -3499,7 +3577,9 @@
},
{
"id": "voltage-13",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What voltage do the plugs with <div style='display: inline-block'><b><b>USB</b> to charge phones and small electronics</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/usb_port.svg'/></div> offer?",
"nl": "Welke spanning levert de stekker van type <div style='display: inline-block'><b><b>USB</b> om GSMs en kleine electronica op te laden</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/usb_port.svg'/></div>",
@ -3537,7 +3617,9 @@
},
{
"id": "current-13",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What current do the plugs with <div style='display: inline-block'><b><b>USB</b> to charge phones and small electronics</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/usb_port.svg'/></div> offer?",
"nl": "Welke stroom levert de stekker van type <div style='display: inline-block'><b><b>USB</b> om GSMs en kleine electronica op te laden</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/usb_port.svg'/></div>?",
@ -3595,7 +3677,9 @@
},
{
"id": "power-output-13",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What power output does a single plug of type <div style='display: inline-block'><b><b>USB</b> to charge phones and small electronics</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/usb_port.svg'/></div> offer?",
"nl": "Welk vermogen levert een enkele stekker van type <div style='display: inline-block'><b><b>USB</b> om GSMs en kleine electronica op te laden</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/usb_port.svg'/></div>?",
@ -3645,7 +3729,9 @@
},
{
"id": "voltage-14",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What voltage do the plugs with <div style='display: inline-block'><b><b>Bosch Active Connect with 3 pins</b> and cable</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/bosch-3pin.svg'/></div> offer?",
"nl": "Welke spanning levert de stekker van type <div style='display: inline-block'><b><b>Bosch Active Connect met 3 pinnen</b> aan een kabel</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/bosch-3pin.svg'/></div>",
@ -3670,7 +3756,9 @@
},
{
"id": "current-14",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What current do the plugs with <div style='display: inline-block'><b><b>Bosch Active Connect with 3 pins</b> and cable</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/bosch-3pin.svg'/></div> offer?",
"nl": "Welke stroom levert de stekker van type <div style='display: inline-block'><b><b>Bosch Active Connect met 3 pinnen</b> aan een kabel</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/bosch-3pin.svg'/></div>?",
@ -3697,7 +3785,9 @@
},
{
"id": "power-output-14",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What power output does a single plug of type <div style='display: inline-block'><b><b>Bosch Active Connect with 3 pins</b> and cable</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/bosch-3pin.svg'/></div> offer?",
"nl": "Welk vermogen levert een enkele stekker van type <div style='display: inline-block'><b><b>Bosch Active Connect met 3 pinnen</b> aan een kabel</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/bosch-3pin.svg'/></div>?",
@ -3722,7 +3812,9 @@
},
{
"id": "voltage-15",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What voltage do the plugs with <div style='display: inline-block'><b><b>Bosch Active Connect with 5 pins</b> and cable</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/bosch-5pin.svg'/></div> offer?",
"nl": "Welke spanning levert de stekker van type <div style='display: inline-block'><b><b>Bosch Active Connect met 5 pinnen</b> aan een kabel</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/bosch-5pin.svg'/></div>",
@ -3747,7 +3839,9 @@
},
{
"id": "current-15",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What current do the plugs with <div style='display: inline-block'><b><b>Bosch Active Connect with 5 pins</b> and cable</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/bosch-5pin.svg'/></div> offer?",
"nl": "Welke stroom levert de stekker van type <div style='display: inline-block'><b><b>Bosch Active Connect met 5 pinnen</b> aan een kabel</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/bosch-5pin.svg'/></div>?",
@ -3774,7 +3868,9 @@
},
{
"id": "power-output-15",
"label": ["technical"],
"labels": [
"technical"
],
"question": {
"en": "What power output does a single plug of type <div style='display: inline-block'><b><b>Bosch Active Connect with 5 pins</b> and cable</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/bosch-5pin.svg'/></div> offer?",
"nl": "Welk vermogen levert een enkele stekker van type <div style='display: inline-block'><b><b>Bosch Active Connect met 5 pinnen</b> aan een kabel</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/bosch-5pin.svg'/></div>?",
@ -4471,16 +4567,13 @@
]
}
},
"questions",
{
"id": "questions"
},
{
"id": "questions",
"label": ["technical"],
"id": "questions_technical",
"render": {
"en": "<h3>Technical questions</h3>The questions below are very technical. Feel free to ignore them<br/>{questions}",
"nl": "<h3>Technische vragen</h3>De vragen hieronder zijn erg technisch - sla deze over indien je hier geen tijd voor hebt<br/>{questions}",
"de": "<h3>Technische Fragen</h3>Die folgenden Fragen sind sehr technisch. Sie können sie gerne ignorieren<br/>{questions}"
"en": "<h3>Technical questions</h3>The questions below are very technical. Feel free to ignore them<br/>{questions(technical)}",
"nl": "<h3>Technische vragen</h3>De vragen hieronder zijn erg technisch - sla deze over indien je hier geen tijd voor hebt<br/>{questions(technical)}",
"de": "<h3>Technische Fragen</h3>Die folgenden Fragen sind sehr technisch. Sie können sie gerne ignorieren<br/>{questions(technical)}"
}
}
],
@ -5065,4 +5158,4 @@
},
"neededChangesets": 10
}
}
}

View file

@ -722,10 +722,9 @@
},
{
"id": "questions",
"label": ["technical"],
"render": {
"en": "<h3>Technical questions</h3>The questions below are very technical. Feel free to ignore them<br/>{questions}",
"nl": "<h3>Technische vragen</h3>De vragen hieronder zijn erg technisch - sla deze over indien je hier geen tijd voor hebt<br/>{questions}"
"en": "<h3>Technical questions</h3>The questions below are very technical. Feel free to ignore them<br/>{questions(technical)}",
"nl": "<h3>Technische vragen</h3>De vragen hieronder zijn erg technisch - sla deze over indien je hier geen tijd voor hebt<br/>{questions(technical)}"
}
}
],

View file

@ -161,7 +161,7 @@ function run(file, protojson) {
technicalQuestions.push({
"id": "voltage-" + i,
group: "technical",
labels: ["technical"],
question: {
en: `What voltage do the plugs with ${descrWithImage_en} offer?`,
nl: `Welke spanning levert de stekker van type ${descrWithImage_nl}`
@ -195,7 +195,7 @@ function run(file, protojson) {
technicalQuestions.push({
"id": "current-" + i,
group:"technical",
labels:["technical"],
question: {
en: `What current do the plugs with ${descrWithImage_en} offer?`,
nl: `Welke stroom levert de stekker van type ${descrWithImage_nl}?`,
@ -229,7 +229,7 @@ function run(file, protojson) {
technicalQuestions.push({
"id": "power-output-" + i,
group:"technical",
labels:["technical"],
question: {
en: `What power output does a single plug of type ${descrWithImage_en} offer?`,
nl: `Welk vermogen levert een enkele stekker van type ${descrWithImage_nl}?`,

View file

@ -1694,4 +1694,4 @@
"fr": "Toutes les infrastructures sur lesquelles quelqu'un peut rouler, accompagnées de questions sur cette infrastructure",
"ca": "Totes les infraestructures per les quals algú pot ciclar, acompanyades de preguntes sobre aquesta infraestructura"
}
}
}

View file

@ -90,11 +90,15 @@
},
{
"id": "translations-title",
"label": ["translations"],
"label": [
"translations"
],
"render": "<h3>Translating MapComplete</h3>"
},
{
"label": ["translations"],
"label": [
"translations"
],
"id": "translation-mode",
"question": {
"en": "Do you want to help translating MapComplete?",
@ -127,7 +131,9 @@
]
},
{
"label": ["translations"],
"label": [
"translations"
],
"id": "translation-help",
"mappings": [
{
@ -153,7 +159,9 @@
]
},
{
"label": ["translations"],
"label": [
"translations"
],
"id": "translation-completeness",
"render": {
"ca": "Les traduccions de {_theme} en {_language} tenen un {_translation_percentage}%: {_translation_translated_count} cadenes de {_translation_total} estan traduïdes",
@ -188,7 +196,9 @@
},
{
"id": "translation-links",
"label": ["translations"],
"label": [
"translations"
],
"condition": {
"and": [
"_translation_links~*",
@ -318,4 +328,4 @@
}
],
"mapRendering": null
}
}

View file

@ -2,7 +2,7 @@
"id": "shared_questions",
"questions": {
"description": "Show the images block at this location",
"id": "questions"
"render": "{questions()}"
},
"images": {
"description": "This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata`",
@ -1984,4 +1984,4 @@
"pl": "Nazwa sieci to <b>{internet_access:ssid}</b>"
}
}
}
}

View file

@ -699,7 +699,7 @@
"_applicable=feat.get('_overlapping')?.filter(p => (p._imported_osm_object_found === 'true' || p._intersects_with_other_features === ''))?.map(p => p.id)",
"_applicable_count=feat.get('_applicable')?.length"
],
"tagRenderings": [
"tagRenderings+": [
{
"id": "hw",
"render": "There are {_applicable_count} applicable elements in view",

View file

@ -551,7 +551,7 @@
"builtin": "current_view",
"override": {
"title": "Statistics on changesets in the current view",
"tagRenderings": [
"tagRenderings+": [
{
"id": "link_to_more",
"render": {

View file

@ -249,7 +249,7 @@
"builtin": "current_view",
"override": {
"title": "Statistics on changesets in the current view",
"tagRenderings": [
"tagRenderings+": [
{
"id": "link_to_more",
"render": {

View file

@ -397,7 +397,7 @@
"es": "Estadísticas"
}
},
"tagRenderings": [
"tagRenderings+": [
{
"id": "stats",
"render": "{statistics()}"
@ -495,4 +495,4 @@
]
},
"enableDownload": true
}
}