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

@ -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>