forked from MapComplete/MapComplete
refactoring: more fixes, first attempt at tagRenderingAnswer
This commit is contained in:
parent
aaaaf1948d
commit
29372c465e
24 changed files with 278 additions and 113 deletions
|
@ -9,7 +9,7 @@
|
|||
import FromHtml from "./FromHtml.svelte";
|
||||
|
||||
export let t: Translation;
|
||||
export let tags: Record<string, string> | undefined;
|
||||
export let tags: Record<string, string> | undefined = undefined;
|
||||
// Text for the current language
|
||||
let txt: string | undefined;
|
||||
|
||||
|
|
|
@ -78,6 +78,7 @@
|
|||
</div>
|
||||
{:else }
|
||||
<input
|
||||
type="search"
|
||||
bind:this={inputElement}
|
||||
on:keypress={keypr => keypr.key === "Enter" ? performSearch() : undefined}
|
||||
|
||||
|
|
|
@ -83,9 +83,6 @@ export default class LeftControls extends Combine {
|
|||
"filters",
|
||||
guiState.filterViewIsOpened
|
||||
)
|
||||
const toggledFilter = new MapControlButton(Svg.layers_svg()).onClick(() =>
|
||||
guiState.filterViewIsOpened.setData(true)
|
||||
)
|
||||
state.featureSwitchFilter.addCallbackAndRun((f) => {
|
||||
Hotkeys.RegisterHotkey(
|
||||
{ nomod: "B" },
|
||||
|
@ -96,8 +93,6 @@ export default class LeftControls extends Combine {
|
|||
)
|
||||
})
|
||||
|
||||
const filterButton = new Toggle(toggledFilter, undefined, state.featureSwitchFilter)
|
||||
|
||||
const mapSwitch = new Toggle(
|
||||
new BackgroundMapSwitch(state, state.backgroundLayer, { enableHotkeys: true }),
|
||||
undefined,
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
|
||||
import type { SpecialVisualizationState } from "../SpecialVisualization";
|
||||
import TagRenderingAnswer from "../Popup/TagRenderingAnswer.svelte";
|
||||
import TagRenderingQuestion from "../Popup/TagRenderingQuestion.svelte";
|
||||
|
||||
export let selectedElement: Feature;
|
||||
export let layer: LayerConfig;
|
||||
|
@ -41,7 +42,7 @@
|
|||
<div class="flex flex-col sm:flex-row flex-grow justify-between">
|
||||
<!-- Title element-->
|
||||
<h3>
|
||||
<TagRenderingAnswer config={layer.title} {tags} {selectedElement}></TagRenderingAnswer>
|
||||
<TagRenderingAnswer config={layer.title} {selectedElement} {tags}></TagRenderingAnswer>
|
||||
</h3>
|
||||
|
||||
<div class="flex flex-row flex-wrap pt-0.5 sm:pt-1 items-center mr-2">
|
||||
|
@ -57,7 +58,11 @@
|
|||
|
||||
<div class="flex flex-col">
|
||||
{#each layer.tagRenderings as config (config.id)}
|
||||
<TagRenderingAnswer {tags} {config} {state}></TagRenderingAnswer>
|
||||
{#if config.IsKnown($tags)}
|
||||
<TagRenderingAnswer {tags} {config} {state}></TagRenderingAnswer>
|
||||
{:else}
|
||||
<TagRenderingQuestion {config} {tags} {state}></TagRenderingQuestion>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
|
|
43
UI/InputElement/ValidatedInput.svelte
Normal file
43
UI/InputElement/ValidatedInput.svelte
Normal file
|
@ -0,0 +1,43 @@
|
|||
<script lang="ts">
|
||||
|
||||
import { Store, 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";
|
||||
|
||||
export let value: UIEventSource<string>;
|
||||
// Internal state, only copied to 'value' so that no invalid values leak outside
|
||||
let _value = new UIEventSource(value.data ?? "")
|
||||
export let type: ValidatorType;
|
||||
let validator = Validators.get(type);
|
||||
export let feedback: UIEventSource<Translation> | undefined = undefined
|
||||
_value.addCallbackAndRun(v => {
|
||||
if(validator.isValid(v)){
|
||||
feedback?.setData(undefined)
|
||||
value.setData(v)
|
||||
return
|
||||
}
|
||||
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));
|
||||
|
||||
|
||||
</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"}>
|
||||
{#if !$isValid}
|
||||
<ExclamationIcon class="h-6 w-6 -ml-6"></ExclamationIcon>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
|
@ -17,10 +17,17 @@ export abstract class Validator {
|
|||
* What HTML-inputmode to use
|
||||
*/
|
||||
public readonly inputmode?: string
|
||||
public readonly textArea: boolean
|
||||
|
||||
constructor(name: string, explanation: string | BaseUIElement, inputmode?: string) {
|
||||
constructor(
|
||||
name: string,
|
||||
explanation: string | BaseUIElement,
|
||||
inputmode?: string,
|
||||
textArea?: false | boolean
|
||||
) {
|
||||
this.name = name
|
||||
this.inputmode = inputmode
|
||||
this.textArea = textArea ?? false
|
||||
if (this.name.endsWith("textfield")) {
|
||||
this.name = this.name.substr(0, this.name.length - "TextField".length)
|
||||
}
|
||||
|
@ -46,7 +53,7 @@ export abstract class Validator {
|
|||
}
|
||||
}
|
||||
|
||||
public isValid(string: string, requestCountry: () => string): boolean {
|
||||
public isValid(string: string, requestCountry?: () => string): boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
|
@ -19,8 +19,29 @@ import BaseUIElement from "../BaseUIElement"
|
|||
import Combine from "../Base/Combine"
|
||||
import Title from "../Base/Title"
|
||||
|
||||
export type ValidatorType = typeof Validators.availableTypes[number]
|
||||
|
||||
export default class Validators {
|
||||
private static readonly AllValidators: ReadonlyArray<Validator> = [
|
||||
public static readonly availableTypes = [
|
||||
"string",
|
||||
"text",
|
||||
"date",
|
||||
"nat",
|
||||
"int",
|
||||
"distance",
|
||||
"direction",
|
||||
"wikidata",
|
||||
"pnat",
|
||||
"float",
|
||||
"pfloat",
|
||||
"email",
|
||||
"url",
|
||||
"phone",
|
||||
"opening_hours",
|
||||
"color",
|
||||
] as const
|
||||
|
||||
public static readonly AllValidators: ReadonlyArray<Validator> = [
|
||||
new StringValidator(),
|
||||
new TextValidator(),
|
||||
new DateValidator(),
|
||||
|
@ -38,8 +59,16 @@ export default class Validators {
|
|||
new OpeningHoursValidator(),
|
||||
new ColorValidator(),
|
||||
]
|
||||
public static allTypes: Map<string, Validator> = Validators.allTypesDict()
|
||||
|
||||
private static _byType = Validators._byTypeConstructor()
|
||||
|
||||
private static _byTypeConstructor(): Map<ValidatorType, Validator> {
|
||||
const map = new Map<ValidatorType, Validator>()
|
||||
for (const validator of Validators.AllValidators) {
|
||||
map.set(<ValidatorType>validator.name, validator)
|
||||
}
|
||||
return map
|
||||
}
|
||||
public static HelpText(): BaseUIElement {
|
||||
const explanations: BaseUIElement[] = Validators.AllValidators.map((type) =>
|
||||
new Combine([new Title(type.name, 3), type.explanation]).SetClass("flex flex-col")
|
||||
|
@ -51,15 +80,7 @@ export default class Validators {
|
|||
]).SetClass("flex flex-col")
|
||||
}
|
||||
|
||||
public static AvailableTypes(): string[] {
|
||||
return Validators.AllValidators.map((tp) => tp.name)
|
||||
}
|
||||
|
||||
private static allTypesDict(): Map<string, Validator> {
|
||||
const types = new Map<string, Validator>()
|
||||
for (const tp of Validators.AllValidators) {
|
||||
types.set(tp.name, tp)
|
||||
}
|
||||
return types
|
||||
static get(type: ValidatorType): Validator {
|
||||
return Validators._byType.get(type)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,17 @@ export default class DirectionValidator extends IntValidator {
|
|||
)
|
||||
}
|
||||
|
||||
isValid(str): boolean {
|
||||
if (str.endsWith("°")) {
|
||||
str = str.substring(0, str.length - 1)
|
||||
}
|
||||
return super.isValid(str)
|
||||
}
|
||||
|
||||
reformat(str): string {
|
||||
if (str.endsWith("°")) {
|
||||
str = str.substring(0, str.length - 1)
|
||||
}
|
||||
const n = Number(str) % 360
|
||||
return "" + n
|
||||
}
|
||||
|
|
|
@ -2,6 +2,6 @@ import { Validator } from "../Validator"
|
|||
|
||||
export default class TextValidator extends Validator {
|
||||
constructor() {
|
||||
super("text", "A longer piece of text. Uses an textArea instead of a textField", "text")
|
||||
super("text", "A longer piece of text. Uses an textArea instead of a textField", "text", true)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -245,8 +245,17 @@ class LineRenderingLayer {
|
|||
})
|
||||
|
||||
this._visibility?.addCallbackAndRunD((visible) => {
|
||||
map.setLayoutProperty(linelayer, "visibility", visible ? "visible" : "none")
|
||||
map.setLayoutProperty(polylayer, "visibility", visible ? "visible" : "none")
|
||||
try {
|
||||
map.setLayoutProperty(linelayer, "visibility", visible ? "visible" : "none")
|
||||
map.setLayoutProperty(polylayer, "visibility", visible ? "visible" : "none")
|
||||
} catch (e) {
|
||||
console.warn(
|
||||
"Error while setting visiblity of layers ",
|
||||
linelayer,
|
||||
polylayer,
|
||||
e
|
||||
)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
src.setData({
|
||||
|
@ -323,6 +332,7 @@ export default class ShowDataLayer {
|
|||
})
|
||||
})
|
||||
}
|
||||
|
||||
public static showRange(
|
||||
map: Store<MlMap>,
|
||||
features: FeatureSource,
|
||||
|
|
14
UI/Popup/TagExplanation.svelte
Normal file
14
UI/Popup/TagExplanation.svelte
Normal file
|
@ -0,0 +1,14 @@
|
|||
<script lang="ts">/**
|
||||
* Shows an explanation about the given TagsFilter.
|
||||
*/
|
||||
import { TagsFilter } from "../../Logic/Tags/TagsFilter";
|
||||
import FromHtml from "../Base/FromHtml.svelte";
|
||||
|
||||
export let tagsFilter: TagsFilter;
|
||||
export let properties: Record<string, string> | undefined = {};
|
||||
export let linkToWiki: boolean = false;
|
||||
</script>
|
||||
|
||||
{#if tagsFilter !== undefined}
|
||||
<FromHtml src={tagsFilter.asHumanString(linkToWiki, false, properties)} />
|
||||
{/if}
|
53
UI/Popup/TagRenderingQuestion.svelte
Normal file
53
UI/Popup/TagRenderingQuestion.svelte
Normal file
|
@ -0,0 +1,53 @@
|
|||
<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}
|
|
@ -41,7 +41,7 @@
|
|||
|
||||
|
||||
<div class="h-screen w-screen absolute top-0 left-0 flex">
|
||||
<MaplibreMap class="w-full h-full border border-black" map={maplibremap}></MaplibreMap>
|
||||
<MaplibreMap map={maplibremap}></MaplibreMap>
|
||||
</div>
|
||||
|
||||
<div class="absolute top-0 left-0 mt-2 ml-2">
|
||||
|
@ -64,7 +64,7 @@
|
|||
|
||||
<div class="absolute bottom-0 right-0 mb-4 mr-4">
|
||||
<MapControlButton on:click={() => mapproperties.zoom.update(z => z+1)}>
|
||||
<ToSvelte class="w-7 h-7 block" construct={Svg.plus_ui}></ToSvelte>
|
||||
<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>
|
||||
|
@ -81,7 +81,7 @@
|
|||
<If condition={state.featureSwitches.featureSwitchSearch}>
|
||||
<Geosearch bounds={state.mapProperties.bounds} layout={state.layout} location={state.mapProperties.location}
|
||||
{selectedElement} {selectedLayer}
|
||||
zoom={state.mapProperties.zoom}></Geosearch>
|
||||
></Geosearch>
|
||||
</If>
|
||||
</div>
|
||||
|
||||
|
@ -168,11 +168,14 @@
|
|||
</If>
|
||||
|
||||
{#if $selectedElement !== undefined && $selectedLayer !== undefined}
|
||||
<div class="absolute top-0 right-0 normal-background">
|
||||
<div class="absolute top-0 right-0 w-screen h-screen" 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>
|
||||
</div>
|
||||
{/if}
|
||||
<style>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue