forked from MapComplete/MapComplete
Chore: formatting
This commit is contained in:
parent
6c3c67af56
commit
286578bfc7
58 changed files with 2199 additions and 1915 deletions
|
@ -24,7 +24,8 @@
|
|||
unknown: t.loginFailedUnreachableMode,
|
||||
readonly: t.loginFailedReadonlyMode,
|
||||
}
|
||||
const apiState = state?.osmConnection?.apiIsOnline ?? new ImmutableStore<OsmServiceState>("online")
|
||||
const apiState =
|
||||
state?.osmConnection?.apiIsOnline ?? new ImmutableStore<OsmServiceState>("online")
|
||||
</script>
|
||||
|
||||
{#if $badge}
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
<slot name="image" slot="image" />
|
||||
<div class="flex w-full items-center justify-between" slot="message">
|
||||
<slot />
|
||||
<ChevronRightIcon class={clss?.indexOf("small") >= 0? "h-4 w-4 shrink-0": "h-12 w-12 shrink-0" }/>
|
||||
<ChevronRightIcon
|
||||
class={clss?.indexOf("small") >= 0 ? "h-4 w-4 shrink-0" : "h-12 w-12 shrink-0"}
|
||||
/>
|
||||
</div>
|
||||
</SubtleButton>
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
* Thin wrapper around 'TabGroup' which binds the state
|
||||
*/
|
||||
|
||||
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@rgossiaux/svelte-headlessui";
|
||||
import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource";
|
||||
import { twJoin } from "tailwind-merge";
|
||||
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@rgossiaux/svelte-headlessui"
|
||||
import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { twJoin } from "tailwind-merge"
|
||||
|
||||
/**
|
||||
* If a condition is given for a certain tab, it will only be shown if this condition is true.
|
||||
|
@ -43,49 +43,63 @@
|
|||
<div class="interactive sticky top-0 flex items-center justify-between">
|
||||
<TabList class="flex flex-wrap">
|
||||
{#if $$slots.title0}
|
||||
<Tab class={({ selected }) => twJoin("tab", selected && "primary", !$condition0 && "hidden")}>
|
||||
<Tab
|
||||
class={({ selected }) => twJoin("tab", selected && "primary", !$condition0 && "hidden")}
|
||||
>
|
||||
<div bind:this={tabElements[0]} class="flex">
|
||||
<slot name="title0">Tab 0</slot>
|
||||
</div>
|
||||
</Tab>
|
||||
{/if}
|
||||
{#if $$slots.title1}
|
||||
<Tab class={({ selected }) => twJoin("tab", selected && "primary", !$condition1 && "hidden")}>
|
||||
<Tab
|
||||
class={({ selected }) => twJoin("tab", selected && "primary", !$condition1 && "hidden")}
|
||||
>
|
||||
<div bind:this={tabElements[1]} class="flex">
|
||||
<slot name="title1" />
|
||||
</div>
|
||||
</Tab>
|
||||
{/if}
|
||||
{#if $$slots.title2}
|
||||
<Tab class={({ selected }) => twJoin("tab", selected && "primary", !$condition2 && "hidden")}>
|
||||
<Tab
|
||||
class={({ selected }) => twJoin("tab", selected && "primary", !$condition2 && "hidden")}
|
||||
>
|
||||
<div bind:this={tabElements[2]} class="flex">
|
||||
<slot name="title2" />
|
||||
</div>
|
||||
</Tab>
|
||||
{/if}
|
||||
{#if $$slots.title3}
|
||||
<Tab class={({ selected }) => twJoin("tab", selected && "primary", !$condition3 && "hidden")}>
|
||||
<Tab
|
||||
class={({ selected }) => twJoin("tab", selected && "primary", !$condition3 && "hidden")}
|
||||
>
|
||||
<div bind:this={tabElements[3]} class="flex">
|
||||
<slot name="title3" />
|
||||
</div>
|
||||
</Tab>
|
||||
{/if}
|
||||
{#if $$slots.title4}
|
||||
<Tab class={({ selected }) => twJoin("tab", selected && "primary", !$condition4 && "hidden")}>
|
||||
<Tab
|
||||
class={({ selected }) => twJoin("tab", selected && "primary", !$condition4 && "hidden")}
|
||||
>
|
||||
<div bind:this={tabElements[4]} class="flex">
|
||||
<slot name="title4" />
|
||||
</div>
|
||||
</Tab>
|
||||
{/if}
|
||||
{#if $$slots.title5}
|
||||
<Tab class={({ selected }) => twJoin("tab", selected && "primary", !$condition5 && "hidden")}>
|
||||
<Tab
|
||||
class={({ selected }) => twJoin("tab", selected && "primary", !$condition5 && "hidden")}
|
||||
>
|
||||
<div bind:this={tabElements[5]} class="flex">
|
||||
<slot name="title5" />
|
||||
</div>
|
||||
</Tab>
|
||||
{/if}
|
||||
{#if $$slots.title6}
|
||||
<Tab class={({ selected }) => twJoin("tab", selected && "primary", !$condition6 && "hidden")}>
|
||||
<Tab
|
||||
class={({ selected }) => twJoin("tab", selected && "primary", !$condition6 && "hidden")}
|
||||
>
|
||||
<div bind:this={tabElements[6]} class="flex">
|
||||
<slot name="title6" />
|
||||
</div>
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
<Tr t={Translations.t.general.returnToTheMap} />
|
||||
</button>
|
||||
{:else}
|
||||
<div class="flex flex-col gap-y-2 overflow-y-auto p-1 px-2 h-full">
|
||||
<div class="flex h-full flex-col gap-y-2 overflow-y-auto p-1 px-2">
|
||||
{#each layer.tagRenderings as config (config.id)}
|
||||
{#if (config.condition?.matchesProperties($tags) ?? true) && config.metacondition?.matchesProperties({ ...$tags, ..._metatags } ?? true)}
|
||||
{#if config.IsKnown($tags)}
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
mapExtent: state.mapProperties.bounds.data,
|
||||
width: maindiv.offsetWidth,
|
||||
height: maindiv.offsetHeight,
|
||||
noSelfIntersectingLines: true
|
||||
noSelfIntersectingLines: true,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
@ -84,7 +84,7 @@
|
|||
helperText={t.downloadAsSvgLinesOnlyHelper}
|
||||
construct={() => offerSvg(true)}
|
||||
/>
|
||||
|
||||
|
||||
<DownloadButton
|
||||
{state}
|
||||
{metaIsIncluded}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
*/
|
||||
|
||||
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
||||
import { ImmutableStore, Store } from "../../Logic/UIEventSource";
|
||||
import { ImmutableStore, Store } from "../../Logic/UIEventSource"
|
||||
import type { OsmTags } from "../../Models/OsmFeature"
|
||||
import LoginToggle from "../Base/LoginToggle.svelte"
|
||||
import Translations from "../i18n/Translations"
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<script lang="ts">
|
||||
import {UIEventSource} from "../../../Logic/UIEventSource";
|
||||
import { UIEventSource } from "../../../Logic/UIEventSource"
|
||||
|
||||
/**
|
||||
* Simply shows the image
|
||||
*/
|
||||
export let value: UIEventSource<undefined | string>
|
||||
/**
|
||||
* Simply shows the image
|
||||
*/
|
||||
export let value: UIEventSource<undefined | string>
|
||||
</script>
|
||||
|
||||
<img src={$value}/>
|
||||
<img src={$value} />
|
||||
|
|
|
@ -1,31 +1,39 @@
|
|||
<script lang="ts">/**
|
||||
* Input helper to create a tag. The tag is JSON-encoded
|
||||
*/
|
||||
import { UIEventSource } from "../../../Logic/UIEventSource";
|
||||
import BasicTagInput from "../../Studio/TagInput/BasicTagInput.svelte";
|
||||
import { TagUtils } from "../../../Logic/Tags/TagUtils";
|
||||
import nmd from "nano-markdown"
|
||||
import FromHtml from "../../Base/FromHtml.svelte";
|
||||
export let value: UIEventSource<undefined | string>;
|
||||
export let args: string[] = [];
|
||||
let uploadableOnly: boolean = args[0] === "uploadableOnly";
|
||||
export let overpassSupportNeeded: boolean;
|
||||
<script lang="ts">
|
||||
/**
|
||||
* Input helper to create a tag. The tag is JSON-encoded
|
||||
*/
|
||||
import { UIEventSource } from "../../../Logic/UIEventSource"
|
||||
import BasicTagInput from "../../Studio/TagInput/BasicTagInput.svelte"
|
||||
import { TagUtils } from "../../../Logic/Tags/TagUtils"
|
||||
import nmd from "nano-markdown"
|
||||
import FromHtml from "../../Base/FromHtml.svelte"
|
||||
export let value: UIEventSource<undefined | string>
|
||||
export let args: string[] = []
|
||||
let uploadableOnly: boolean = args[0] === "uploadableOnly"
|
||||
export let overpassSupportNeeded: boolean
|
||||
|
||||
/**
|
||||
* Only show the taginfo-statistics if they are suspicious (thus: less then 250 entries)
|
||||
*/
|
||||
export let silent: boolean = false;
|
||||
let mode: string = "=";
|
||||
let dropdownFocussed = new UIEventSource(false);
|
||||
let documentation = TagUtils.modeDocumentation[mode];
|
||||
$: documentation = TagUtils.modeDocumentation[mode];
|
||||
/**
|
||||
* Only show the taginfo-statistics if they are suspicious (thus: less then 250 entries)
|
||||
*/
|
||||
export let silent: boolean = false
|
||||
let mode: string = "="
|
||||
let dropdownFocussed = new UIEventSource(false)
|
||||
let documentation = TagUtils.modeDocumentation[mode]
|
||||
$: documentation = TagUtils.modeDocumentation[mode]
|
||||
</script>
|
||||
|
||||
|
||||
<BasicTagInput bind:mode={mode} {dropdownFocussed} {overpassSupportNeeded} {silent} tag={value} {uploadableOnly} on:submit />
|
||||
<BasicTagInput
|
||||
bind:mode
|
||||
{dropdownFocussed}
|
||||
{overpassSupportNeeded}
|
||||
{silent}
|
||||
tag={value}
|
||||
{uploadableOnly}
|
||||
on:submit
|
||||
/>
|
||||
{#if $dropdownFocussed}
|
||||
<div class="border border-dashed border-black p-2 m-2">
|
||||
<b>{documentation.name}</b>
|
||||
<FromHtml src={nmd(documentation.docs)}/>
|
||||
<div class="m-2 border border-dashed border-black p-2">
|
||||
<b>{documentation.name}</b>
|
||||
<FromHtml src={nmd(documentation.docs)} />
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
@ -1,22 +1,21 @@
|
|||
<script lang="ts">/**
|
||||
* Input helper to create a tag. The tag is JSON-encoded
|
||||
*/
|
||||
import { UIEventSource } from "../../../Logic/UIEventSource";
|
||||
import type { TagConfigJson } from "../../../Models/ThemeConfig/Json/TagConfigJson";
|
||||
import FullTagInput from "../../Studio/TagInput/FullTagInput.svelte";
|
||||
<script lang="ts">
|
||||
/**
|
||||
* Input helper to create a tag. The tag is JSON-encoded
|
||||
*/
|
||||
import { UIEventSource } from "../../../Logic/UIEventSource"
|
||||
import type { TagConfigJson } from "../../../Models/ThemeConfig/Json/TagConfigJson"
|
||||
import FullTagInput from "../../Studio/TagInput/FullTagInput.svelte"
|
||||
|
||||
export let value: UIEventSource<TagConfigJson>;
|
||||
export let uploadableOnly: boolean;
|
||||
export let overpassSupportNeeded: boolean;
|
||||
export let value: UIEventSource<TagConfigJson>
|
||||
export let uploadableOnly: boolean
|
||||
export let overpassSupportNeeded: boolean
|
||||
|
||||
/**
|
||||
* Only show the taginfo-statistics if they are suspicious (thus: less then 250 entries)
|
||||
*/
|
||||
export let silent: boolean = false;
|
||||
|
||||
let tag: UIEventSource<string | TagConfigJson> = value
|
||||
/**
|
||||
* Only show the taginfo-statistics if they are suspicious (thus: less then 250 entries)
|
||||
*/
|
||||
export let silent: boolean = false
|
||||
|
||||
let tag: UIEventSource<string | TagConfigJson> = value
|
||||
</script>
|
||||
|
||||
|
||||
<FullTagInput {overpassSupportNeeded} {silent} {tag} {uploadableOnly} on:submit/>
|
||||
<FullTagInput {overpassSupportNeeded} {silent} {tag} {uploadableOnly} on:submit />
|
||||
|
|
|
@ -1,55 +1,59 @@
|
|||
<script lang="ts">
|
||||
import { UIEventSource } from "../../../Logic/UIEventSource"
|
||||
import LanguageUtils from "../../../Utils/LanguageUtils"
|
||||
import { createEventDispatcher, onDestroy } from "svelte"
|
||||
import ValidatedInput from "../ValidatedInput.svelte"
|
||||
|
||||
import { UIEventSource } from "../../../Logic/UIEventSource";
|
||||
import LanguageUtils from "../../../Utils/LanguageUtils";
|
||||
import { createEventDispatcher, onDestroy } from "svelte";
|
||||
import ValidatedInput from "../ValidatedInput.svelte";
|
||||
export let value: UIEventSource<Record<string, string>> = new UIEventSource<
|
||||
Record<string, string>
|
||||
>({})
|
||||
|
||||
export let value: UIEventSource<Record<string, string>> = new UIEventSource<Record<string, string>>({});
|
||||
|
||||
export let args: string[] = []
|
||||
|
||||
|
||||
let prefix = args[0] ?? ""
|
||||
let postfix = args[1] ?? ""
|
||||
|
||||
let translations: UIEventSource<Record<string, string>> = value
|
||||
|
||||
const allLanguages: string[] = LanguageUtils.usedLanguagesSorted;
|
||||
let currentLang = new UIEventSource("en");
|
||||
const currentVal = new UIEventSource<string>("");
|
||||
const allLanguages: string[] = LanguageUtils.usedLanguagesSorted
|
||||
let currentLang = new UIEventSource("en")
|
||||
const currentVal = new UIEventSource<string>("")
|
||||
let dispatch = createEventDispatcher<{ submit }>()
|
||||
|
||||
function update() {
|
||||
const v = currentVal.data;
|
||||
const l = currentLang.data;
|
||||
if(translations.data === "" || translations.data === undefined){
|
||||
const v = currentVal.data
|
||||
const l = currentLang.data
|
||||
if (translations.data === "" || translations.data === undefined) {
|
||||
translations.data = {}
|
||||
}
|
||||
if (translations.data[l] === v) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
translations.data[l] = v;
|
||||
translations.ping();
|
||||
translations.data[l] = v
|
||||
translations.ping()
|
||||
}
|
||||
|
||||
onDestroy(currentLang.addCallbackAndRunD(currentLang => {
|
||||
console.log("Applying current lang:", currentLang);
|
||||
if(!translations.data){
|
||||
translations.data = {}
|
||||
}
|
||||
translations.data[currentLang] = translations.data[currentLang] ?? "";
|
||||
currentVal.setData(translations.data[currentLang]);
|
||||
}));
|
||||
|
||||
|
||||
onDestroy(currentVal.addCallbackAndRunD(v => {
|
||||
update();
|
||||
}));
|
||||
onDestroy(
|
||||
currentLang.addCallbackAndRunD((currentLang) => {
|
||||
console.log("Applying current lang:", currentLang)
|
||||
if (!translations.data) {
|
||||
translations.data = {}
|
||||
}
|
||||
translations.data[currentLang] = translations.data[currentLang] ?? ""
|
||||
currentVal.setData(translations.data[currentLang])
|
||||
})
|
||||
)
|
||||
|
||||
onDestroy(
|
||||
currentVal.addCallbackAndRunD((v) => {
|
||||
update()
|
||||
})
|
||||
)
|
||||
</script>
|
||||
<div class="flex font-bold space-x-1 m-1 mt-2 interactive">
|
||||
|
||||
<div class="interactive m-1 mt-2 flex space-x-1 font-bold">
|
||||
<span>
|
||||
{prefix}
|
||||
{prefix}
|
||||
</span>
|
||||
<select bind:value={$currentLang}>
|
||||
{#each allLanguages as language}
|
||||
|
@ -58,8 +62,13 @@
|
|||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
<ValidatedInput type="string" cls="w-full" value={currentVal} on:submit={() => dispatch("submit")} />
|
||||
<ValidatedInput
|
||||
type="string"
|
||||
cls="w-full"
|
||||
value={currentVal}
|
||||
on:submit={() => dispatch("submit")}
|
||||
/>
|
||||
<span>
|
||||
{postfix}
|
||||
{postfix}
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
@ -4,50 +4,49 @@
|
|||
* Note that all values are stringified
|
||||
*/
|
||||
|
||||
import { UIEventSource } from "../../Logic/UIEventSource";
|
||||
import type { ValidatorType } from "./Validators";
|
||||
import InputHelpers from "./InputHelpers";
|
||||
import ToSvelte from "../Base/ToSvelte.svelte";
|
||||
import type { Feature } from "geojson";
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import ImageHelper from "./Helpers/ImageHelper.svelte";
|
||||
import TranslationInput from "./Helpers/TranslationInput.svelte";
|
||||
import TagInput from "./Helpers/TagInput.svelte";
|
||||
import SimpleTagInput from "./Helpers/SimpleTagInput.svelte";
|
||||
import DirectionInput from "./Helpers/DirectionInput.svelte";
|
||||
import DateInput from "./Helpers/DateInput.svelte";
|
||||
import ColorInput from "./Helpers/ColorInput.svelte";
|
||||
import OpeningHoursInput from "./Helpers/OpeningHoursInput.svelte";
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import type { ValidatorType } from "./Validators"
|
||||
import InputHelpers from "./InputHelpers"
|
||||
import ToSvelte from "../Base/ToSvelte.svelte"
|
||||
import type { Feature } from "geojson"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import ImageHelper from "./Helpers/ImageHelper.svelte"
|
||||
import TranslationInput from "./Helpers/TranslationInput.svelte"
|
||||
import TagInput from "./Helpers/TagInput.svelte"
|
||||
import SimpleTagInput from "./Helpers/SimpleTagInput.svelte"
|
||||
import DirectionInput from "./Helpers/DirectionInput.svelte"
|
||||
import DateInput from "./Helpers/DateInput.svelte"
|
||||
import ColorInput from "./Helpers/ColorInput.svelte"
|
||||
import OpeningHoursInput from "./Helpers/OpeningHoursInput.svelte"
|
||||
|
||||
export let type: ValidatorType;
|
||||
export let value: UIEventSource<string | object>;
|
||||
export let type: ValidatorType
|
||||
export let value: UIEventSource<string | object>
|
||||
|
||||
export let feature: Feature;
|
||||
export let args: (string | number | boolean)[] = undefined;
|
||||
export let feature: Feature
|
||||
export let args: (string | number | boolean)[] = undefined
|
||||
|
||||
let properties = { feature, args: args ?? [] };
|
||||
let properties = { feature, args: args ?? [] }
|
||||
let dispatch = createEventDispatcher<{
|
||||
selected
|
||||
}>();
|
||||
|
||||
}>()
|
||||
</script>
|
||||
|
||||
{#if type === "translation" }
|
||||
{#if type === "translation"}
|
||||
<TranslationInput {value} on:submit {args} />
|
||||
{:else if type === "direction"}
|
||||
<DirectionInput {value} mapProperties={InputHelpers.constructMapProperties(properties)} />
|
||||
{:else if type === "date"}
|
||||
<DateInput { value } />
|
||||
<DateInput {value} />
|
||||
{:else if type === "color"}
|
||||
<ColorInput { value } />
|
||||
<ColorInput {value} />
|
||||
{:else if type === "image"}
|
||||
<ImageHelper { value } />
|
||||
<ImageHelper {value} />
|
||||
{:else if type === "tag"}
|
||||
<TagInput { value } on:submit />
|
||||
<TagInput {value} on:submit />
|
||||
{:else if type === "simple_tag"}
|
||||
<SimpleTagInput { value } {args} on:submit />
|
||||
<SimpleTagInput {value} {args} on:submit />
|
||||
{:else if type === "opening_hours"}
|
||||
<OpeningHoursInput { value } />
|
||||
<OpeningHoursInput {value} />
|
||||
{:else if type === "wikidata"}
|
||||
<ToSvelte construct={() => InputHelpers.constructWikidataHelper(value, properties)} />
|
||||
{/if}
|
||||
|
|
|
@ -1,145 +1,156 @@
|
|||
<script lang="ts">
|
||||
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, onDestroy} from "svelte"
|
||||
import {Validator} from "./Validator"
|
||||
import {Unit} from "../../Models/Unit"
|
||||
import UnitInput from "../Popup/UnitInput.svelte"
|
||||
import {Utils} from "../../Utils";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
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, onDestroy } from "svelte"
|
||||
import { Validator } from "./Validator"
|
||||
import { Unit } from "../../Models/Unit"
|
||||
import UnitInput from "../Popup/UnitInput.svelte"
|
||||
import { Utils } from "../../Utils"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export let type: ValidatorType
|
||||
export let feedback: UIEventSource<Translation> | undefined = undefined
|
||||
export let cls : string = undefined
|
||||
export let cls: string = undefined
|
||||
export let getCountry: () => string | undefined
|
||||
export let placeholder: string | Translation | undefined
|
||||
export let unit: Unit = undefined
|
||||
export let value: UIEventSource<string>
|
||||
/**
|
||||
* Internal state bound to the input element.
|
||||
*
|
||||
* This is only copied to 'value' when appropriate so that no invalid values leak outside;
|
||||
* Additionally, the unit is added when copying
|
||||
*/
|
||||
let _value = new UIEventSource(value.data ?? "")
|
||||
export let value: UIEventSource<string>
|
||||
/**
|
||||
* Internal state bound to the input element.
|
||||
*
|
||||
* This is only copied to 'value' when appropriate so that no invalid values leak outside;
|
||||
* Additionally, the unit is added when copying
|
||||
*/
|
||||
let _value = new UIEventSource(value.data ?? "")
|
||||
|
||||
let validator: Validator = Validators.get(type ?? "string")
|
||||
if(validator === undefined){
|
||||
console.warn("Didn't find a validator for type", type)
|
||||
}
|
||||
let selectedUnit: UIEventSource<string> = new UIEventSource<string>(undefined)
|
||||
let _placeholder = placeholder ?? validator?.getPlaceholder() ?? type
|
||||
let validator: Validator = Validators.get(type ?? "string")
|
||||
if (validator === undefined) {
|
||||
console.warn("Didn't find a validator for type", type)
|
||||
}
|
||||
let selectedUnit: UIEventSource<string> = new UIEventSource<string>(undefined)
|
||||
let _placeholder = placeholder ?? validator?.getPlaceholder() ?? type
|
||||
|
||||
function initValueAndDenom() {
|
||||
if (unit && value.data) {
|
||||
const [v, denom] = unit?.findDenomination(value.data, getCountry)
|
||||
if (denom) {
|
||||
_value.setData(v)
|
||||
selectedUnit.setData(denom.canonical)
|
||||
} else {
|
||||
_value.setData(value.data ?? "")
|
||||
}
|
||||
} else {
|
||||
_value.setData(value.data ?? "")
|
||||
}
|
||||
function initValueAndDenom() {
|
||||
if (unit && value.data) {
|
||||
const [v, denom] = unit?.findDenomination(value.data, getCountry)
|
||||
if (denom) {
|
||||
_value.setData(v)
|
||||
selectedUnit.setData(denom.canonical)
|
||||
} else {
|
||||
_value.setData(value.data ?? "")
|
||||
}
|
||||
} else {
|
||||
_value.setData(value.data ?? "")
|
||||
}
|
||||
}
|
||||
|
||||
initValueAndDenom()
|
||||
|
||||
$: {
|
||||
// The type changed -> reset some values
|
||||
validator = Validators.get(type ?? "string")
|
||||
|
||||
_placeholder = placeholder ?? validator?.getPlaceholder() ?? type
|
||||
feedback?.setData(validator?.getFeedback(_value.data, getCountry))
|
||||
|
||||
initValueAndDenom()
|
||||
}
|
||||
|
||||
$: {
|
||||
// The type changed -> reset some values
|
||||
validator = Validators.get(type ?? "string")
|
||||
|
||||
_placeholder = placeholder ?? validator?.getPlaceholder() ?? type
|
||||
feedback?.setData(validator?.getFeedback(_value.data, getCountry))
|
||||
|
||||
initValueAndDenom()
|
||||
function setValues() {
|
||||
// Update the value stores
|
||||
const v = _value.data
|
||||
if (!validator?.isValid(v, getCountry) || v === "") {
|
||||
feedback?.setData(validator?.getFeedback(v, getCountry))
|
||||
value.setData("")
|
||||
return
|
||||
}
|
||||
|
||||
function setValues() {
|
||||
// Update the value stores
|
||||
const v = _value.data
|
||||
if (!validator?.isValid(v, getCountry) || v === "") {
|
||||
feedback?.setData(validator?.getFeedback(v, getCountry))
|
||||
value.setData("")
|
||||
return
|
||||
}
|
||||
|
||||
if (unit !== undefined && isNaN(Number(v))) {
|
||||
value.setData(undefined)
|
||||
return
|
||||
}
|
||||
|
||||
feedback?.setData(undefined)
|
||||
if(selectedUnit.data){
|
||||
value.setData(v + selectedUnit.data)
|
||||
|
||||
}else{
|
||||
value.setData(v)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onDestroy(_value.addCallbackAndRun((_) => setValues()))
|
||||
onDestroy(value.addCallbackAndRunD(fromUpstream => {
|
||||
if(_value.data !== fromUpstream && fromUpstream !== ""){
|
||||
_value.setData(fromUpstream)
|
||||
}
|
||||
}))
|
||||
onDestroy(selectedUnit.addCallback((_) => setValues()))
|
||||
if (validator === undefined) {
|
||||
throw "Not a valid type (no validator found) for type '" + type+"'; did you perhaps mean one of: "+Utils.sortedByLevenshteinDistance(type, Validators.AllValidators.map(v => v.name), v => v).slice(0, 5).join(", ")
|
||||
if (unit !== undefined && isNaN(Number(v))) {
|
||||
value.setData(undefined)
|
||||
return
|
||||
}
|
||||
|
||||
const isValid = _value.map((v) => validator?.isValid(v, getCountry) ?? true)
|
||||
|
||||
let htmlElem: HTMLInputElement
|
||||
|
||||
let dispatch = createEventDispatcher<{ selected, submit }>()
|
||||
$: {
|
||||
if (htmlElem !== undefined) {
|
||||
htmlElem.onfocus = () => dispatch("selected")
|
||||
}
|
||||
feedback?.setData(undefined)
|
||||
if (selectedUnit.data) {
|
||||
value.setData(v + selectedUnit.data)
|
||||
} else {
|
||||
value.setData(v)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches the submit, but only if the value is valid
|
||||
*/
|
||||
function sendSubmit(){
|
||||
if(feedback?.data){
|
||||
console.log("Not sending a submit as there is feedback")
|
||||
}
|
||||
dispatch("submit")
|
||||
onDestroy(_value.addCallbackAndRun((_) => setValues()))
|
||||
onDestroy(
|
||||
value.addCallbackAndRunD((fromUpstream) => {
|
||||
if (_value.data !== fromUpstream && fromUpstream !== "") {
|
||||
_value.setData(fromUpstream)
|
||||
}
|
||||
})
|
||||
)
|
||||
onDestroy(selectedUnit.addCallback((_) => setValues()))
|
||||
if (validator === undefined) {
|
||||
throw (
|
||||
"Not a valid type (no validator found) for type '" +
|
||||
type +
|
||||
"'; did you perhaps mean one of: " +
|
||||
Utils.sortedByLevenshteinDistance(
|
||||
type,
|
||||
Validators.AllValidators.map((v) => v.name),
|
||||
(v) => v
|
||||
)
|
||||
.slice(0, 5)
|
||||
.join(", ")
|
||||
)
|
||||
}
|
||||
|
||||
const isValid = _value.map((v) => validator?.isValid(v, getCountry) ?? true)
|
||||
|
||||
let htmlElem: HTMLInputElement
|
||||
|
||||
let dispatch = createEventDispatcher<{ selected; submit }>()
|
||||
$: {
|
||||
if (htmlElem !== undefined) {
|
||||
htmlElem.onfocus = () => dispatch("selected")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches the submit, but only if the value is valid
|
||||
*/
|
||||
function sendSubmit() {
|
||||
if (feedback?.data) {
|
||||
console.log("Not sending a submit as there is feedback")
|
||||
}
|
||||
dispatch("submit")
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if validator?.textArea}
|
||||
<form on:submit|preventDefault={() => sendSubmit()}>
|
||||
|
||||
<textarea
|
||||
class="w-full"
|
||||
bind:value={$_value}
|
||||
inputmode={validator?.inputmode ?? "text"}
|
||||
placeholder={_placeholder}></textarea>
|
||||
</form>
|
||||
<form on:submit|preventDefault={() => sendSubmit()}>
|
||||
<textarea
|
||||
class="w-full"
|
||||
bind:value={$_value}
|
||||
inputmode={validator?.inputmode ?? "text"}
|
||||
placeholder={_placeholder}
|
||||
/>
|
||||
</form>
|
||||
{:else}
|
||||
<form class={twMerge("inline-flex",cls )} on:submit|preventDefault={() => sendSubmit()}>
|
||||
<input
|
||||
bind:this={htmlElem}
|
||||
bind:value={$_value}
|
||||
class="w-full"
|
||||
inputmode={validator?.inputmode ?? "text"}
|
||||
placeholder={_placeholder}
|
||||
/>
|
||||
{#if !$isValid}
|
||||
<ExclamationIcon class="-ml-6 h-6 w-6"/>
|
||||
{/if}
|
||||
<form class={twMerge("inline-flex", cls)} on:submit|preventDefault={() => sendSubmit()}>
|
||||
<input
|
||||
bind:this={htmlElem}
|
||||
bind:value={$_value}
|
||||
class="w-full"
|
||||
inputmode={validator?.inputmode ?? "text"}
|
||||
placeholder={_placeholder}
|
||||
/>
|
||||
{#if !$isValid}
|
||||
<ExclamationIcon class="-ml-6 h-6 w-6" />
|
||||
{/if}
|
||||
|
||||
{#if unit !== undefined}
|
||||
<UnitInput {unit} {selectedUnit} textValue={_value} upstreamValue={value}/>
|
||||
{/if}
|
||||
</form>
|
||||
{#if unit !== undefined}
|
||||
<UnitInput {unit} {selectedUnit} textValue={_value} upstreamValue={value} />
|
||||
{/if}
|
||||
</form>
|
||||
{/if}
|
||||
|
|
|
@ -3,7 +3,6 @@ import { Translation } from "../../i18n/Translation"
|
|||
import Translations from "../../i18n/Translations"
|
||||
|
||||
export default class TagKeyValidator extends Validator {
|
||||
|
||||
public readonly isMeta = true
|
||||
constructor() {
|
||||
super("key", "Validates a key, mostly that no weird characters are used")
|
||||
|
|
|
@ -8,7 +8,6 @@ import SimpleTagValidator from "./SimpleTagValidator"
|
|||
* Checks that the input conforms a JSON-encoded tag expression or a simpleTag`key=value`,
|
||||
*/
|
||||
export default class TagValidator extends Validator {
|
||||
|
||||
public readonly isMeta = true
|
||||
constructor() {
|
||||
super("tag", "A simple tag of the format `key=value` OR a tagExpression")
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { Validator } from "../Validator"
|
||||
|
||||
export default class TranslationValidator extends Validator {
|
||||
|
||||
public readonly isMeta = true
|
||||
constructor() {
|
||||
super("translation", "Makes sure the the string is of format `Record<string, string>` ")
|
||||
|
|
|
@ -3,8 +3,9 @@ import { Validator } from "../Validator"
|
|||
export default class UrlValidator extends Validator {
|
||||
constructor(name?: string, explanation?: string) {
|
||||
super(
|
||||
name ??"url",
|
||||
explanation?? "The validatedTextField will format URLs to always be valid and have a https://-header (even though the 'https'-part will be hidden from the user. Furthermore, some tracking parameters will be removed",
|
||||
name ?? "url",
|
||||
explanation ??
|
||||
"The validatedTextField will format URLs to always be valid and have a https://-header (even though the 'https'-part will be hidden from the user. Furthermore, some tracking parameters will be removed",
|
||||
"url"
|
||||
)
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
</td>
|
||||
<td>
|
||||
<b>{contributor.nrOfImages}</b>
|
||||
total images
|
||||
total images
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
|
|
|
@ -1,41 +1,40 @@
|
|||
<script lang="ts">
|
||||
import { IconConfig } from "../../Models/ThemeConfig/PointRenderingConfig";
|
||||
import { Store } from "../../Logic/UIEventSource";
|
||||
import Pin from "../../assets/svg/Pin.svelte";
|
||||
import Square from "../../assets/svg/Square.svelte";
|
||||
import Circle from "../../assets/svg/Circle.svelte";
|
||||
import Checkmark from "../../assets/svg/Checkmark.svelte";
|
||||
import Clock from "../../assets/svg/Clock.svelte";
|
||||
import Close from "../../assets/svg/Close.svelte";
|
||||
import Crosshair from "../../assets/svg/Crosshair.svelte";
|
||||
import Help from "../../assets/svg/Help.svelte";
|
||||
import Home from "../../assets/svg/Home.svelte";
|
||||
import Invalid from "../../assets/svg/Invalid.svelte";
|
||||
import Location from "../../assets/svg/Location.svelte";
|
||||
import Location_empty from "../../assets/svg/Location_empty.svelte";
|
||||
import Location_locked from "../../assets/svg/Location_locked.svelte";
|
||||
import Note from "../../assets/svg/Note.svelte";
|
||||
import Resolved from "../../assets/svg/Resolved.svelte";
|
||||
import Ring from "../../assets/svg/Ring.svelte";
|
||||
import Scissors from "../../assets/svg/Scissors.svelte";
|
||||
import Teardrop from "../../assets/svg/Teardrop.svelte";
|
||||
import Teardrop_with_hole_green from "../../assets/svg/Teardrop_with_hole_green.svelte";
|
||||
import Triangle from "../../assets/svg/Triangle.svelte";
|
||||
import Icon from "./Icon.svelte";
|
||||
import { IconConfig } from "../../Models/ThemeConfig/PointRenderingConfig"
|
||||
import { Store } from "../../Logic/UIEventSource"
|
||||
import Pin from "../../assets/svg/Pin.svelte"
|
||||
import Square from "../../assets/svg/Square.svelte"
|
||||
import Circle from "../../assets/svg/Circle.svelte"
|
||||
import Checkmark from "../../assets/svg/Checkmark.svelte"
|
||||
import Clock from "../../assets/svg/Clock.svelte"
|
||||
import Close from "../../assets/svg/Close.svelte"
|
||||
import Crosshair from "../../assets/svg/Crosshair.svelte"
|
||||
import Help from "../../assets/svg/Help.svelte"
|
||||
import Home from "../../assets/svg/Home.svelte"
|
||||
import Invalid from "../../assets/svg/Invalid.svelte"
|
||||
import Location from "../../assets/svg/Location.svelte"
|
||||
import Location_empty from "../../assets/svg/Location_empty.svelte"
|
||||
import Location_locked from "../../assets/svg/Location_locked.svelte"
|
||||
import Note from "../../assets/svg/Note.svelte"
|
||||
import Resolved from "../../assets/svg/Resolved.svelte"
|
||||
import Ring from "../../assets/svg/Ring.svelte"
|
||||
import Scissors from "../../assets/svg/Scissors.svelte"
|
||||
import Teardrop from "../../assets/svg/Teardrop.svelte"
|
||||
import Teardrop_with_hole_green from "../../assets/svg/Teardrop_with_hole_green.svelte"
|
||||
import Triangle from "../../assets/svg/Triangle.svelte"
|
||||
import Icon from "./Icon.svelte"
|
||||
|
||||
/**
|
||||
* Renders a single icon.
|
||||
*
|
||||
* Icons -placed on top of each other- form a 'Marker' together
|
||||
*/
|
||||
export let icon: IconConfig;
|
||||
export let tags: Store<Record<string, string>>;
|
||||
|
||||
let iconItem = icon.icon?.GetRenderValue(tags)?.txt;
|
||||
$: iconItem = icon.icon?.GetRenderValue($tags)?.txt;
|
||||
let color = icon.color?.GetRenderValue(tags)?.txt ?? "#000000";
|
||||
$: color = icon.color?.GetRenderValue($tags)?.txt ?? "#000000";
|
||||
export let icon: IconConfig
|
||||
export let tags: Store<Record<string, string>>
|
||||
|
||||
let iconItem = icon.icon?.GetRenderValue(tags)?.txt
|
||||
$: iconItem = icon.icon?.GetRenderValue($tags)?.txt
|
||||
let color = icon.color?.GetRenderValue(tags)?.txt ?? "#000000"
|
||||
$: color = icon.color?.GetRenderValue($tags)?.txt ?? "#000000"
|
||||
</script>
|
||||
|
||||
<Icon icon={iconItem} {color}/>
|
||||
<Icon icon={iconItem} {color} />
|
||||
|
|
|
@ -1,19 +1,18 @@
|
|||
<script lang="ts">
|
||||
|
||||
import PointRenderingConfig, { IconConfig } from "../../Models/ThemeConfig/PointRenderingConfig";
|
||||
import { Store } from "../../Logic/UIEventSource";
|
||||
import DynamicIcon from "./DynamicIcon.svelte";
|
||||
import PointRenderingConfig, { IconConfig } from "../../Models/ThemeConfig/PointRenderingConfig"
|
||||
import { Store } from "../../Logic/UIEventSource"
|
||||
import DynamicIcon from "./DynamicIcon.svelte"
|
||||
|
||||
/**
|
||||
* Renders a 'marker', which consists of multiple 'icons'
|
||||
*/
|
||||
export let config: PointRenderingConfig;
|
||||
let icons: IconConfig[] = config.marker;
|
||||
export let tags: Store<Record<string, string>>;
|
||||
|
||||
export let config: PointRenderingConfig
|
||||
let icons: IconConfig[] = config.marker
|
||||
export let tags: Store<Record<string, string>>
|
||||
</script>
|
||||
|
||||
{#if config !== undefined}
|
||||
<div class="relative w-full h-full">
|
||||
<div class="relative h-full w-full">
|
||||
{#each icons as icon}
|
||||
<DynamicIcon {icon} {tags} />
|
||||
{/each}
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
<script lang="ts">
|
||||
import Pin from "../../assets/svg/Pin.svelte";
|
||||
import Square from "../../assets/svg/Square.svelte";
|
||||
import Circle from "../../assets/svg/Circle.svelte";
|
||||
import Checkmark from "../../assets/svg/Checkmark.svelte";
|
||||
import Clock from "../../assets/svg/Clock.svelte";
|
||||
import Close from "../../assets/svg/Close.svelte";
|
||||
import Crosshair from "../../assets/svg/Crosshair.svelte";
|
||||
import Help from "../../assets/svg/Help.svelte";
|
||||
import Home from "../../assets/svg/Home.svelte";
|
||||
import Invalid from "../../assets/svg/Invalid.svelte";
|
||||
import Location from "../../assets/svg/Location.svelte";
|
||||
import Location_empty from "../../assets/svg/Location_empty.svelte";
|
||||
import Location_locked from "../../assets/svg/Location_locked.svelte";
|
||||
import Note from "../../assets/svg/Note.svelte";
|
||||
import Resolved from "../../assets/svg/Resolved.svelte";
|
||||
import Ring from "../../assets/svg/Ring.svelte";
|
||||
import Scissors from "../../assets/svg/Scissors.svelte";
|
||||
import Teardrop from "../../assets/svg/Teardrop.svelte";
|
||||
import Teardrop_with_hole_green from "../../assets/svg/Teardrop_with_hole_green.svelte";
|
||||
import Triangle from "../../assets/svg/Triangle.svelte";
|
||||
import Pin from "../../assets/svg/Pin.svelte"
|
||||
import Square from "../../assets/svg/Square.svelte"
|
||||
import Circle from "../../assets/svg/Circle.svelte"
|
||||
import Checkmark from "../../assets/svg/Checkmark.svelte"
|
||||
import Clock from "../../assets/svg/Clock.svelte"
|
||||
import Close from "../../assets/svg/Close.svelte"
|
||||
import Crosshair from "../../assets/svg/Crosshair.svelte"
|
||||
import Help from "../../assets/svg/Help.svelte"
|
||||
import Home from "../../assets/svg/Home.svelte"
|
||||
import Invalid from "../../assets/svg/Invalid.svelte"
|
||||
import Location from "../../assets/svg/Location.svelte"
|
||||
import Location_empty from "../../assets/svg/Location_empty.svelte"
|
||||
import Location_locked from "../../assets/svg/Location_locked.svelte"
|
||||
import Note from "../../assets/svg/Note.svelte"
|
||||
import Resolved from "../../assets/svg/Resolved.svelte"
|
||||
import Ring from "../../assets/svg/Ring.svelte"
|
||||
import Scissors from "../../assets/svg/Scissors.svelte"
|
||||
import Teardrop from "../../assets/svg/Teardrop.svelte"
|
||||
import Teardrop_with_hole_green from "../../assets/svg/Teardrop_with_hole_green.svelte"
|
||||
import Triangle from "../../assets/svg/Triangle.svelte"
|
||||
|
||||
/**
|
||||
* Renders a single icon.
|
||||
|
@ -26,13 +26,12 @@
|
|||
* Icons -placed on top of each other- form a 'Marker' together
|
||||
*/
|
||||
|
||||
export let icon: string | undefined;
|
||||
export let color: string | undefined;
|
||||
|
||||
export let icon: string | undefined
|
||||
export let color: string | undefined
|
||||
</script>
|
||||
|
||||
{#if icon}
|
||||
<div class="absolute top-0 left-0 w-full h-full">
|
||||
<div class="absolute top-0 left-0 h-full w-full">
|
||||
{#if icon === "pin"}
|
||||
<Pin {color} />
|
||||
{:else if icon === "square"}
|
||||
|
@ -74,7 +73,7 @@
|
|||
{:else if icon === "triangle"}
|
||||
<Triangle {color} />
|
||||
{:else}
|
||||
<img class="w-full h-full" src={icon} />
|
||||
<img class="h-full w-full" src={icon} />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
<script lang="ts">
|
||||
|
||||
import Icon from "./Icon.svelte";
|
||||
import Icon from "./Icon.svelte"
|
||||
|
||||
/**
|
||||
* Renders a 'marker', which consists of multiple 'icons'
|
||||
*/
|
||||
export let icons: { icon: string, color: string }[]
|
||||
|
||||
export let icons: { icon: string; color: string }[]
|
||||
</script>
|
||||
|
||||
{#if icons !== undefined && icons.length > 0}
|
||||
<div class="relative w-full h-full">
|
||||
<div class="relative h-full w-full">
|
||||
{#each icons as icon}
|
||||
<Icon icon={icon.icon} color={icon.color} />
|
||||
{/each}
|
||||
|
|
|
@ -423,10 +423,7 @@ class LineRenderingLayer {
|
|||
}
|
||||
|
||||
export default class ShowDataLayer {
|
||||
private static rangeLayer = new LayerConfig(
|
||||
<any>range_layer,
|
||||
"ShowDataLayer.ts:range.json"
|
||||
)
|
||||
private static rangeLayer = new LayerConfig(<any>range_layer, "ShowDataLayer.ts:range.json")
|
||||
private readonly _options: ShowDataLayerOptions & {
|
||||
layer: LayerConfig
|
||||
drawMarkers?: true | boolean
|
||||
|
|
|
@ -3,109 +3,109 @@
|
|||
* This component ties together all the steps that are needed to create a new point.
|
||||
* There are many subcomponents which help with that
|
||||
*/
|
||||
import type { SpecialVisualizationState } from "../../SpecialVisualization";
|
||||
import PresetList from "./PresetList.svelte";
|
||||
import type PresetConfig from "../../../Models/ThemeConfig/PresetConfig";
|
||||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
|
||||
import Tr from "../../Base/Tr.svelte";
|
||||
import SubtleButton from "../../Base/SubtleButton.svelte";
|
||||
import FromHtml from "../../Base/FromHtml.svelte";
|
||||
import Translations from "../../i18n/Translations.js";
|
||||
import TagHint from "../TagHint.svelte";
|
||||
import { And } from "../../../Logic/Tags/And.js";
|
||||
import LoginToggle from "../../Base/LoginToggle.svelte";
|
||||
import Constants from "../../../Models/Constants.js";
|
||||
import FilteredLayer from "../../../Models/FilteredLayer";
|
||||
import { Store, UIEventSource } from "../../../Logic/UIEventSource";
|
||||
import { EyeIcon, EyeOffIcon } from "@rgossiaux/svelte-heroicons/solid";
|
||||
import LoginButton from "../../Base/LoginButton.svelte";
|
||||
import NewPointLocationInput from "../../BigComponents/NewPointLocationInput.svelte";
|
||||
import CreateNewNodeAction from "../../../Logic/Osm/Actions/CreateNewNodeAction";
|
||||
import { OsmWay } from "../../../Logic/Osm/OsmObject";
|
||||
import { Tag } from "../../../Logic/Tags/Tag";
|
||||
import type { WayId } from "../../../Models/OsmFeature";
|
||||
import Loading from "../../Base/Loading.svelte";
|
||||
import type { GlobalFilter } from "../../../Models/GlobalFilter";
|
||||
import { onDestroy } from "svelte";
|
||||
import NextButton from "../../Base/NextButton.svelte";
|
||||
import BackButton from "../../Base/BackButton.svelte";
|
||||
import ToSvelte from "../../Base/ToSvelte.svelte";
|
||||
import Svg from "../../../Svg";
|
||||
import OpenBackgroundSelectorButton from "../../BigComponents/OpenBackgroundSelectorButton.svelte";
|
||||
import { twJoin } from "tailwind-merge";
|
||||
import type { SpecialVisualizationState } from "../../SpecialVisualization"
|
||||
import PresetList from "./PresetList.svelte"
|
||||
import type PresetConfig from "../../../Models/ThemeConfig/PresetConfig"
|
||||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
|
||||
import Tr from "../../Base/Tr.svelte"
|
||||
import SubtleButton from "../../Base/SubtleButton.svelte"
|
||||
import FromHtml from "../../Base/FromHtml.svelte"
|
||||
import Translations from "../../i18n/Translations.js"
|
||||
import TagHint from "../TagHint.svelte"
|
||||
import { And } from "../../../Logic/Tags/And.js"
|
||||
import LoginToggle from "../../Base/LoginToggle.svelte"
|
||||
import Constants from "../../../Models/Constants.js"
|
||||
import FilteredLayer from "../../../Models/FilteredLayer"
|
||||
import { Store, UIEventSource } from "../../../Logic/UIEventSource"
|
||||
import { EyeIcon, EyeOffIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
import LoginButton from "../../Base/LoginButton.svelte"
|
||||
import NewPointLocationInput from "../../BigComponents/NewPointLocationInput.svelte"
|
||||
import CreateNewNodeAction from "../../../Logic/Osm/Actions/CreateNewNodeAction"
|
||||
import { OsmWay } from "../../../Logic/Osm/OsmObject"
|
||||
import { Tag } from "../../../Logic/Tags/Tag"
|
||||
import type { WayId } from "../../../Models/OsmFeature"
|
||||
import Loading from "../../Base/Loading.svelte"
|
||||
import type { GlobalFilter } from "../../../Models/GlobalFilter"
|
||||
import { onDestroy } from "svelte"
|
||||
import NextButton from "../../Base/NextButton.svelte"
|
||||
import BackButton from "../../Base/BackButton.svelte"
|
||||
import ToSvelte from "../../Base/ToSvelte.svelte"
|
||||
import Svg from "../../../Svg"
|
||||
import OpenBackgroundSelectorButton from "../../BigComponents/OpenBackgroundSelectorButton.svelte"
|
||||
import { twJoin } from "tailwind-merge"
|
||||
|
||||
export let coordinate: { lon: number; lat: number };
|
||||
export let state: SpecialVisualizationState;
|
||||
export let coordinate: { lon: number; lat: number }
|
||||
export let state: SpecialVisualizationState
|
||||
|
||||
let selectedPreset: {
|
||||
preset: PresetConfig
|
||||
layer: LayerConfig
|
||||
icon: string
|
||||
tags: Record<string, string>
|
||||
} = undefined;
|
||||
let checkedOfGlobalFilters: number = 0;
|
||||
let confirmedCategory = false;
|
||||
} = undefined
|
||||
let checkedOfGlobalFilters: number = 0
|
||||
let confirmedCategory = false
|
||||
$: if (selectedPreset === undefined) {
|
||||
confirmedCategory = false;
|
||||
creating = false;
|
||||
checkedOfGlobalFilters = 0;
|
||||
confirmedCategory = false
|
||||
creating = false
|
||||
checkedOfGlobalFilters = 0
|
||||
}
|
||||
|
||||
let flayer: FilteredLayer = undefined;
|
||||
let layerIsDisplayed: UIEventSource<boolean> | undefined = undefined;
|
||||
let layerHasFilters: Store<boolean> | undefined = undefined;
|
||||
let globalFilter: UIEventSource<GlobalFilter[]> = state.layerState.globalFilters;
|
||||
let _globalFilter: GlobalFilter[] = [];
|
||||
let flayer: FilteredLayer = undefined
|
||||
let layerIsDisplayed: UIEventSource<boolean> | undefined = undefined
|
||||
let layerHasFilters: Store<boolean> | undefined = undefined
|
||||
let globalFilter: UIEventSource<GlobalFilter[]> = state.layerState.globalFilters
|
||||
let _globalFilter: GlobalFilter[] = []
|
||||
onDestroy(
|
||||
globalFilter.addCallbackAndRun((globalFilter) => {
|
||||
console.log("Global filters are", globalFilter);
|
||||
_globalFilter = globalFilter ?? [];
|
||||
console.log("Global filters are", globalFilter)
|
||||
_globalFilter = globalFilter ?? []
|
||||
})
|
||||
);
|
||||
)
|
||||
$: {
|
||||
flayer = state.layerState.filteredLayers.get(selectedPreset?.layer?.id);
|
||||
layerIsDisplayed = flayer?.isDisplayed;
|
||||
layerHasFilters = flayer?.hasFilter;
|
||||
flayer = state.layerState.filteredLayers.get(selectedPreset?.layer?.id)
|
||||
layerIsDisplayed = flayer?.isDisplayed
|
||||
layerHasFilters = flayer?.hasFilter
|
||||
}
|
||||
const t = Translations.t.general.add;
|
||||
const t = Translations.t.general.add
|
||||
|
||||
const zoom = state.mapProperties.zoom;
|
||||
const zoom = state.mapProperties.zoom
|
||||
|
||||
const isLoading = state.dataIsLoading;
|
||||
let preciseCoordinate: UIEventSource<{ lon: number; lat: number }> = new UIEventSource(undefined);
|
||||
let snappedToObject: UIEventSource<string> = new UIEventSource<string>(undefined);
|
||||
const isLoading = state.dataIsLoading
|
||||
let preciseCoordinate: UIEventSource<{ lon: number; lat: number }> = new UIEventSource(undefined)
|
||||
let snappedToObject: UIEventSource<string> = new UIEventSource<string>(undefined)
|
||||
|
||||
// Small helper variable: if the map is tapped, we should let the 'Next'-button grab some attention as users have to click _that_ to continue, not the map
|
||||
let preciseInputIsTapped = false;
|
||||
let preciseInputIsTapped = false
|
||||
|
||||
let creating = false;
|
||||
let creating = false
|
||||
|
||||
/**
|
||||
* Call when the user should restart the flow by clicking on the map, e.g. because they disabled filters.
|
||||
* Will delete the lastclick-location
|
||||
*/
|
||||
function abort() {
|
||||
state.selectedElement.setData(undefined);
|
||||
state.selectedElement.setData(undefined)
|
||||
// When aborted, we force the contributors to place the pin _again_
|
||||
// This is because there might be a nearby object that was disabled; this forces them to re-evaluate the map
|
||||
state.lastClickObject.features.setData([]);
|
||||
preciseInputIsTapped = false;
|
||||
state.lastClickObject.features.setData([])
|
||||
preciseInputIsTapped = false
|
||||
}
|
||||
|
||||
async function confirm() {
|
||||
creating = true;
|
||||
const location: { lon: number; lat: number } = preciseCoordinate.data;
|
||||
const snapTo: WayId | undefined = <WayId>snappedToObject.data;
|
||||
creating = true
|
||||
const location: { lon: number; lat: number } = preciseCoordinate.data
|
||||
const snapTo: WayId | undefined = <WayId>snappedToObject.data
|
||||
const tags: Tag[] = selectedPreset.preset.tags.concat(
|
||||
..._globalFilter.map((f) => f?.onNewPoint?.tags ?? [])
|
||||
);
|
||||
console.log("Creating new point at", location, "snapped to", snapTo, "with tags", tags);
|
||||
)
|
||||
console.log("Creating new point at", location, "snapped to", snapTo, "with tags", tags)
|
||||
|
||||
let snapToWay: undefined | OsmWay = undefined;
|
||||
let snapToWay: undefined | OsmWay = undefined
|
||||
if (snapTo !== undefined && snapTo !== null) {
|
||||
const downloaded = await state.osmObjectDownloader.DownloadObjectAsync(snapTo, 0);
|
||||
const downloaded = await state.osmObjectDownloader.DownloadObjectAsync(snapTo, 0)
|
||||
if (downloaded !== "deleted") {
|
||||
snapToWay = downloaded;
|
||||
snapToWay = downloaded
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -113,44 +113,44 @@
|
|||
theme: state.layout?.id ?? "unkown",
|
||||
changeType: "create",
|
||||
snapOnto: snapToWay,
|
||||
reusePointWithinMeters: 1
|
||||
});
|
||||
await state.changes.applyAction(newElementAction);
|
||||
state.newFeatures.features.ping();
|
||||
reusePointWithinMeters: 1,
|
||||
})
|
||||
await state.changes.applyAction(newElementAction)
|
||||
state.newFeatures.features.ping()
|
||||
// The 'changes' should have created a new point, which added this into the 'featureProperties'
|
||||
const newId = newElementAction.newElementId;
|
||||
console.log("Applied pending changes, fetching store for", newId);
|
||||
const tagsStore = state.featureProperties.getStore(newId);
|
||||
const newId = newElementAction.newElementId
|
||||
console.log("Applied pending changes, fetching store for", newId)
|
||||
const tagsStore = state.featureProperties.getStore(newId)
|
||||
if (!tagsStore) {
|
||||
console.error("Bug: no tagsStore found for", newId);
|
||||
console.error("Bug: no tagsStore found for", newId)
|
||||
}
|
||||
{
|
||||
// Set some metainfo
|
||||
const properties = tagsStore.data;
|
||||
const properties = tagsStore.data
|
||||
if (snapTo) {
|
||||
// metatags (starting with underscore) are not uploaded, so we can safely mark this
|
||||
delete properties["_referencing_ways"];
|
||||
properties["_referencing_ways"] = `["${snapTo}"]`;
|
||||
delete properties["_referencing_ways"]
|
||||
properties["_referencing_ways"] = `["${snapTo}"]`
|
||||
}
|
||||
properties["_backend"] = state.osmConnection.Backend();
|
||||
properties["_last_edit:timestamp"] = new Date().toISOString();
|
||||
const userdetails = state.osmConnection.userDetails.data;
|
||||
properties["_last_edit:contributor"] = userdetails.name;
|
||||
properties["_last_edit:uid"] = "" + userdetails.uid;
|
||||
tagsStore.ping();
|
||||
properties["_backend"] = state.osmConnection.Backend()
|
||||
properties["_last_edit:timestamp"] = new Date().toISOString()
|
||||
const userdetails = state.osmConnection.userDetails.data
|
||||
properties["_last_edit:contributor"] = userdetails.name
|
||||
properties["_last_edit:uid"] = "" + userdetails.uid
|
||||
tagsStore.ping()
|
||||
}
|
||||
const feature = state.indexedFeatures.featuresById.data.get(newId);
|
||||
console.log("Selecting feature", feature, "and opening their popup");
|
||||
abort();
|
||||
state.selectedLayer.setData(selectedPreset.layer);
|
||||
state.selectedElement.setData(feature);
|
||||
tagsStore.ping();
|
||||
const feature = state.indexedFeatures.featuresById.data.get(newId)
|
||||
console.log("Selecting feature", feature, "and opening their popup")
|
||||
abort()
|
||||
state.selectedLayer.setData(selectedPreset.layer)
|
||||
state.selectedElement.setData(feature)
|
||||
tagsStore.ping()
|
||||
}
|
||||
|
||||
function confirmSync() {
|
||||
confirm()
|
||||
.then((_) => console.debug("New point successfully handled"))
|
||||
.catch((e) => console.error("Handling the new point went wrong due to", e));
|
||||
.catch((e) => console.error("Handling the new point went wrong due to", e))
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -163,7 +163,6 @@
|
|||
<Tr slot="message" t={Translations.t.general.add.pleaseLogin} />
|
||||
</LoginButton>
|
||||
<div class="h-full w-full">
|
||||
|
||||
{#if $zoom < Constants.minZoomLevelToAddNewPoint}
|
||||
<div class="alert">
|
||||
<Tr t={Translations.t.general.add.zoomInFurther} />
|
||||
|
@ -179,8 +178,8 @@
|
|||
<PresetList
|
||||
{state}
|
||||
on:select={(event) => {
|
||||
selectedPreset = event.detail
|
||||
}}
|
||||
selectedPreset = event.detail
|
||||
}}
|
||||
/>
|
||||
{:else if !$layerIsDisplayed}
|
||||
<!-- Check that the layer is enabled, so that we don't add a duplicate -->
|
||||
|
@ -195,9 +194,9 @@
|
|||
<button
|
||||
class="flex w-full gap-x-1"
|
||||
on:click={() => {
|
||||
abort()
|
||||
state.guistate.openFilterView(selectedPreset.layer)
|
||||
}}
|
||||
abort()
|
||||
state.guistate.openFilterView(selectedPreset.layer)
|
||||
}}
|
||||
>
|
||||
<ToSvelte construct={Svg.layers_svg().SetClass("w-12")} />
|
||||
<Tr t={Translations.t.general.add.openLayerControl} />
|
||||
|
@ -206,12 +205,14 @@
|
|||
<button
|
||||
class="primary flex w-full gap-x-1"
|
||||
on:click={() => {
|
||||
layerIsDisplayed.setData(true)
|
||||
abort()
|
||||
}}
|
||||
layerIsDisplayed.setData(true)
|
||||
abort()
|
||||
}}
|
||||
>
|
||||
<EyeIcon class="w-12" />
|
||||
<Tr t={Translations.t.general.add.enableLayer.Subs({ name: selectedPreset.layer.name })} />
|
||||
<Tr
|
||||
t={Translations.t.general.add.enableLayer.Subs({ name: selectedPreset.layer.name })}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
{:else if $layerHasFilters}
|
||||
|
@ -224,9 +225,9 @@
|
|||
<button
|
||||
class="primary flex w-full gap-x-1"
|
||||
on:click={() => {
|
||||
abort()
|
||||
state.layerState.filteredLayers.get(selectedPreset.layer.id).disableAllFilters()
|
||||
}}
|
||||
abort()
|
||||
state.layerState.filteredLayers.get(selectedPreset.layer.id).disableAllFilters()
|
||||
}}
|
||||
>
|
||||
<EyeOffIcon class="w-12" />
|
||||
<Tr t={Translations.t.general.add.disableFilters} />
|
||||
|
@ -234,9 +235,9 @@
|
|||
<button
|
||||
class="flex w-full gap-x-1"
|
||||
on:click={() => {
|
||||
abort()
|
||||
state.guistate.openFilterView(selectedPreset.layer)
|
||||
}}
|
||||
abort()
|
||||
state.guistate.openFilterView(selectedPreset.layer)
|
||||
}}
|
||||
>
|
||||
<ToSvelte construct={Svg.layers_svg().SetClass("w-12")} />
|
||||
<Tr t={Translations.t.general.add.openLayerControl} />
|
||||
|
@ -265,10 +266,10 @@
|
|||
{/if}
|
||||
</h3>
|
||||
<span class="flex flex-wrap items-stretch">
|
||||
{#each selectedPreset.preset.exampleImages as src}
|
||||
<img {src} class="m-1 h-64 w-auto rounded-lg" />
|
||||
{/each}
|
||||
</span>
|
||||
{#each selectedPreset.preset.exampleImages as src}
|
||||
<img {src} class="m-1 h-64 w-auto rounded-lg" />
|
||||
{/each}
|
||||
</span>
|
||||
{/if}
|
||||
<TagHint
|
||||
embedIn={(tags) => t.presetInfo.Subs({ tags })}
|
||||
|
@ -295,8 +296,8 @@
|
|||
<Tr t={_globalFilter[checkedOfGlobalFilters].onNewPoint?.safetyCheck} cls="mx-12" />
|
||||
<SubtleButton
|
||||
on:click={() => {
|
||||
checkedOfGlobalFilters = checkedOfGlobalFilters + 1
|
||||
}}
|
||||
checkedOfGlobalFilters = checkedOfGlobalFilters + 1
|
||||
}}
|
||||
>
|
||||
<img
|
||||
slot="image"
|
||||
|
@ -306,27 +307,27 @@
|
|||
<Tr
|
||||
slot="message"
|
||||
t={_globalFilter[checkedOfGlobalFilters].onNewPoint?.confirmAddNew.Subs({
|
||||
preset: selectedPreset.preset,
|
||||
})}
|
||||
preset: selectedPreset.preset,
|
||||
})}
|
||||
/>
|
||||
</SubtleButton>
|
||||
<SubtleButton
|
||||
on:click={() => {
|
||||
globalFilter.setData([])
|
||||
abort()
|
||||
}}
|
||||
globalFilter.setData([])
|
||||
abort()
|
||||
}}
|
||||
>
|
||||
<img slot="image" src="./assets/svg/close.svg" class="h-8 w-8" />
|
||||
<Tr slot="message" t={Translations.t.general.cancel} />
|
||||
</SubtleButton>
|
||||
{:else if !creating}
|
||||
<div class="flex flex-col h-full">
|
||||
<div class="relative min-h-20 h-full w-full p-1 ">
|
||||
<div class="flex h-full flex-col">
|
||||
<div class="min-h-20 relative h-full w-full p-1">
|
||||
<div class="h-full w-full overflow-hidden rounded-xl">
|
||||
<NewPointLocationInput
|
||||
on:click={() => {
|
||||
preciseInputIsTapped = true
|
||||
}}
|
||||
preciseInputIsTapped = true
|
||||
}}
|
||||
value={preciseCoordinate}
|
||||
snappedTo={snappedToObject}
|
||||
{state}
|
||||
|
@ -338,9 +339,9 @@
|
|||
|
||||
<div
|
||||
class={twJoin(
|
||||
!preciseInputIsTapped && "hidden",
|
||||
"absolute top-0 flex w-full justify-center p-12"
|
||||
)}
|
||||
!preciseInputIsTapped && "hidden",
|
||||
"absolute top-0 flex w-full justify-center p-12"
|
||||
)}
|
||||
>
|
||||
<!-- This is an _extra_ button that appears when the map is tapped - see usertest 2023-01-07 -->
|
||||
<NextButton on:click={confirmSync} clss="primary w-fit">
|
||||
|
|
|
@ -11,9 +11,9 @@
|
|||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
|
||||
import FromHtml from "../../Base/FromHtml.svelte"
|
||||
import NextButton from "../../Base/NextButton.svelte"
|
||||
import { UIElement } from "../../UIElement";
|
||||
import ToSvelte from "../../Base/ToSvelte.svelte";
|
||||
import BaseUIElement from "../../BaseUIElement";
|
||||
import { UIElement } from "../../UIElement"
|
||||
import ToSvelte from "../../Base/ToSvelte.svelte"
|
||||
import BaseUIElement from "../../BaseUIElement"
|
||||
|
||||
/**
|
||||
* This component lists all the presets and allows the user to select one
|
||||
|
@ -86,7 +86,7 @@
|
|||
|
||||
{#each presets as preset}
|
||||
<NextButton on:click={() => dispatch("select", preset)}>
|
||||
<ToSvelte slot="image" construct={() => preset.icon} />
|
||||
<ToSvelte slot="image" construct={() => preset.icon} />
|
||||
<div class="flex flex-col">
|
||||
<b class="w-fit">
|
||||
<Tr t={preset.text} />
|
||||
|
|
|
@ -8,11 +8,11 @@
|
|||
import Toggle from "../Input/Toggle"
|
||||
import Lazy from "../Base/Lazy"
|
||||
import BaseUIElement from "../BaseUIElement"
|
||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
|
||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
|
||||
|
||||
//Svelte props
|
||||
export let tags: UIEventSource<any>
|
||||
export let state: {layoutToUse: LayoutConfig} = undefined
|
||||
export let state: { layoutToUse: LayoutConfig } = undefined
|
||||
|
||||
const calculatedTags = [].concat(
|
||||
...(state?.layoutToUse?.layers?.map((l) => l.calculatedTags?.map((c) => c[0]) ?? []) ?? [])
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import InputHelper from "../../InputElement/InputHelper.svelte"
|
||||
import type { Feature } from "geojson"
|
||||
import { Unit } from "../../../Models/Unit"
|
||||
import InputHelpers from "../../InputElement/InputHelpers";
|
||||
import InputHelpers from "../../InputElement/InputHelpers"
|
||||
|
||||
export let value: UIEventSource<string>
|
||||
export let config: TagRenderingConfig
|
||||
|
@ -25,8 +25,8 @@
|
|||
inline = config.freeform?.inline
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher<{selected}>()
|
||||
export let feedback: UIEventSource<Translation>
|
||||
const dispatch = createEventDispatcher<{ selected }>()
|
||||
export let feedback: UIEventSource<Translation>
|
||||
onDestroy(
|
||||
value.addCallbackD(() => {
|
||||
dispatch("selected")
|
||||
|
@ -65,5 +65,11 @@
|
|||
/>
|
||||
{/if}
|
||||
|
||||
<InputHelper args={config.freeform.helperArgs} {feature} type={config.freeform.type} {value} on:submit />
|
||||
<InputHelper
|
||||
args={config.freeform.helperArgs}
|
||||
{feature}
|
||||
type={config.freeform.type}
|
||||
{value}
|
||||
on:submit
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -95,7 +95,11 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div bind:this={questionboxElem} class="marker-questionbox-root" class:hidden={_questionsToAsk.length === 0 && skipped === 0 && answered === 0}>
|
||||
<div
|
||||
bind:this={questionboxElem}
|
||||
class="marker-questionbox-root"
|
||||
class:hidden={_questionsToAsk.length === 0 && skipped === 0 && answered === 0}
|
||||
>
|
||||
{#if _questionsToAsk.length === 0}
|
||||
{#if skipped + answered > 0}
|
||||
<div class="thanks">
|
||||
|
|
|
@ -1,48 +1,51 @@
|
|||
<script lang="ts">
|
||||
import { Translation } from "../../i18n/Translation";
|
||||
import SpecialVisualizations from "../../SpecialVisualizations";
|
||||
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 LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
|
||||
import WeblateLink from "../../Base/WeblateLink.svelte";
|
||||
import FromHtml from "../../Base/FromHtml.svelte";
|
||||
import BaseUIElement from "../../BaseUIElement";
|
||||
import { Translation } from "../../i18n/Translation"
|
||||
import SpecialVisualizations from "../../SpecialVisualizations"
|
||||
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 LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
|
||||
import WeblateLink from "../../Base/WeblateLink.svelte"
|
||||
import FromHtml from "../../Base/FromHtml.svelte"
|
||||
import BaseUIElement from "../../BaseUIElement"
|
||||
|
||||
/**
|
||||
* The 'specialTranslation' renders a `Translation`-object, but interprets the special values as well
|
||||
*/
|
||||
export let t: Translation;
|
||||
export let state: SpecialVisualizationState;
|
||||
export let tags: UIEventSource<Record<string, string>>;
|
||||
export let feature: Feature;
|
||||
export let layer: LayerConfig | undefined;
|
||||
export let t: Translation
|
||||
export let state: SpecialVisualizationState
|
||||
export let tags: UIEventSource<Record<string, string>>
|
||||
export let feature: Feature
|
||||
export let layer: LayerConfig | undefined
|
||||
|
||||
let language = Locale.language;
|
||||
let txt: string = t.textFor($language);
|
||||
let specs: RenderingSpecification[] = [];
|
||||
let language = Locale.language
|
||||
let txt: string = t.textFor($language)
|
||||
let specs: RenderingSpecification[] = []
|
||||
$: {
|
||||
try {
|
||||
if (txt !== undefined) {
|
||||
const key = "cached_special_spec_" + $language;
|
||||
specs = t[key];
|
||||
const key = "cached_special_spec_" + $language
|
||||
specs = t[key]
|
||||
if (specs === undefined) {
|
||||
specs = SpecialVisualizations.constructSpecification(txt);
|
||||
t[key] = specs;
|
||||
specs = SpecialVisualizations.constructSpecification(txt)
|
||||
t[key] = specs
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Could not construct a specification and with arguments", txt, "due to", e);
|
||||
console.error("Could not construct a specification and with arguments", txt, "due to", e)
|
||||
}
|
||||
}
|
||||
|
||||
function createVisualisation(specpart: Exclude<RenderingSpecification, string>): BaseUIElement {
|
||||
{
|
||||
try {
|
||||
return specpart.func.constr(state, tags, specpart.args, feature, layer);
|
||||
return specpart.func.constr(state, tags, specpart.args, feature, layer)
|
||||
} catch (e) {
|
||||
console.error(
|
||||
"Could not construct a special visualisation with specification",
|
||||
|
@ -51,7 +54,7 @@
|
|||
tags,
|
||||
"due to",
|
||||
e
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,27 +1,27 @@
|
|||
<script lang="ts">
|
||||
import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig";
|
||||
import { Store, UIEventSource } from "../../../Logic/UIEventSource";
|
||||
import type { Feature } from "geojson";
|
||||
import type { SpecialVisualizationState } from "../../SpecialVisualization";
|
||||
import TagRenderingAnswer from "./TagRenderingAnswer.svelte";
|
||||
import { PencilAltIcon, XCircleIcon } 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";
|
||||
import { Utils } from "../../../Utils";
|
||||
import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig"
|
||||
import { Store, UIEventSource } from "../../../Logic/UIEventSource"
|
||||
import type { Feature } from "geojson"
|
||||
import type { SpecialVisualizationState } from "../../SpecialVisualization"
|
||||
import TagRenderingAnswer from "./TagRenderingAnswer.svelte"
|
||||
import { PencilAltIcon, XCircleIcon } 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"
|
||||
import { Utils } from "../../../Utils"
|
||||
|
||||
export let config: TagRenderingConfig;
|
||||
export let tags: UIEventSource<Record<string, string>>;
|
||||
export let selectedElement: Feature | undefined;
|
||||
export let state: SpecialVisualizationState;
|
||||
export let layer: LayerConfig = undefined;
|
||||
export let config: TagRenderingConfig
|
||||
export let tags: UIEventSource<Record<string, string>>
|
||||
export let selectedElement: Feature | undefined
|
||||
export let state: SpecialVisualizationState
|
||||
export let layer: LayerConfig = undefined
|
||||
|
||||
export let editingEnabled: Store<boolean> | undefined = state?.featureSwitchUserbadge;
|
||||
|
||||
export let highlightedRendering: UIEventSource<string> = undefined;
|
||||
export let showQuestionIfUnknown: boolean = false;
|
||||
export let editingEnabled: Store<boolean> | undefined = state?.featureSwitchUserbadge
|
||||
|
||||
export let highlightedRendering: UIEventSource<string> = undefined
|
||||
export let showQuestionIfUnknown: boolean = false
|
||||
/**
|
||||
* Indicates if this tagRendering currently shows the attribute or asks the question to _change_ the property
|
||||
*/
|
||||
|
@ -31,10 +31,10 @@
|
|||
tags.addCallbackD((tags) => {
|
||||
editMode = !config.IsKnown(tags)
|
||||
})
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
let htmlElem: HTMLDivElement;
|
||||
let htmlElem: HTMLDivElement
|
||||
$: {
|
||||
if (editMode && htmlElem !== undefined && config.IsKnown(tags)) {
|
||||
// EditMode switched to true yet the answer is already known, so the person wants to make a change
|
||||
|
@ -42,32 +42,32 @@
|
|||
|
||||
// Some delay is applied to give Svelte the time to render the _question_
|
||||
window.setTimeout(() => {
|
||||
Utils.scrollIntoView(<any>htmlElem);
|
||||
}, 50);
|
||||
Utils.scrollIntoView(<any>htmlElem)
|
||||
}, 50)
|
||||
}
|
||||
}
|
||||
|
||||
const _htmlElement = new UIEventSource<HTMLElement>(undefined);
|
||||
$: _htmlElement.setData(htmlElem);
|
||||
const _htmlElement = new UIEventSource<HTMLElement>(undefined)
|
||||
$: _htmlElement.setData(htmlElem)
|
||||
|
||||
function setHighlighting() {
|
||||
if (highlightedRendering === undefined) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
if (htmlElem === undefined) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
const highlighted = highlightedRendering.data;
|
||||
const highlighted = highlightedRendering.data
|
||||
if (config.id === highlighted) {
|
||||
htmlElem.classList.add("glowing-shadow");
|
||||
htmlElem.classList.add("glowing-shadow")
|
||||
} else {
|
||||
htmlElem.classList.remove("glowing-shadow");
|
||||
htmlElem.classList.remove("glowing-shadow")
|
||||
}
|
||||
}
|
||||
|
||||
if (highlightedRendering) {
|
||||
onDestroy(highlightedRendering?.addCallbackAndRun(() => setHighlighting()));
|
||||
onDestroy(_htmlElement.addCallbackAndRun(() => setHighlighting()));
|
||||
onDestroy(highlightedRendering?.addCallbackAndRun(() => setHighlighting()))
|
||||
onDestroy(_htmlElement.addCallbackAndRun(() => setHighlighting()))
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -106,7 +106,7 @@
|
|||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="overflow-hidden p-2 w-full">
|
||||
<div class="w-full overflow-hidden p-2">
|
||||
<TagRenderingAnswer {config} {tags} {selectedElement} {state} {layer} />
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
@ -1,43 +1,43 @@
|
|||
<script lang="ts">
|
||||
import { ImmutableStore, UIEventSource } from "../../../Logic/UIEventSource";
|
||||
import type { SpecialVisualizationState } from "../../SpecialVisualization";
|
||||
import Tr from "../../Base/Tr.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 ChangeTagAction from "../../../Logic/Osm/Actions/ChangeTagAction";
|
||||
import { createEventDispatcher, onDestroy } from "svelte";
|
||||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
|
||||
import SpecialTranslation from "./SpecialTranslation.svelte";
|
||||
import TagHint from "../TagHint.svelte";
|
||||
import LoginToggle from "../../Base/LoginToggle.svelte";
|
||||
import SubtleButton from "../../Base/SubtleButton.svelte";
|
||||
import Loading from "../../Base/Loading.svelte";
|
||||
import TagRenderingMappingInput from "./TagRenderingMappingInput.svelte";
|
||||
import { Translation } from "../../i18n/Translation";
|
||||
import Constants from "../../../Models/Constants";
|
||||
import { Unit } from "../../../Models/Unit";
|
||||
import UserRelatedState from "../../../Logic/State/UserRelatedState";
|
||||
import { twJoin } from "tailwind-merge";
|
||||
import { TagUtils } from "../../../Logic/Tags/TagUtils";
|
||||
import { ImmutableStore, UIEventSource } from "../../../Logic/UIEventSource"
|
||||
import type { SpecialVisualizationState } from "../../SpecialVisualization"
|
||||
import Tr from "../../Base/Tr.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 ChangeTagAction from "../../../Logic/Osm/Actions/ChangeTagAction"
|
||||
import { createEventDispatcher, onDestroy } from "svelte"
|
||||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
|
||||
import SpecialTranslation from "./SpecialTranslation.svelte"
|
||||
import TagHint from "../TagHint.svelte"
|
||||
import LoginToggle from "../../Base/LoginToggle.svelte"
|
||||
import SubtleButton from "../../Base/SubtleButton.svelte"
|
||||
import Loading from "../../Base/Loading.svelte"
|
||||
import TagRenderingMappingInput from "./TagRenderingMappingInput.svelte"
|
||||
import { Translation } from "../../i18n/Translation"
|
||||
import Constants from "../../../Models/Constants"
|
||||
import { Unit } from "../../../Models/Unit"
|
||||
import UserRelatedState from "../../../Logic/State/UserRelatedState"
|
||||
import { twJoin } from "tailwind-merge"
|
||||
import { TagUtils } from "../../../Logic/Tags/TagUtils"
|
||||
|
||||
export let config: TagRenderingConfig;
|
||||
export let tags: UIEventSource<Record<string, string>>;
|
||||
export let selectedElement: Feature;
|
||||
export let state: SpecialVisualizationState;
|
||||
export let layer: LayerConfig | undefined;
|
||||
export let config: TagRenderingConfig
|
||||
export let tags: UIEventSource<Record<string, string>>
|
||||
export let selectedElement: Feature
|
||||
export let state: SpecialVisualizationState
|
||||
export let layer: LayerConfig | undefined
|
||||
|
||||
let feedback: UIEventSource<Translation> = new UIEventSource<Translation>(undefined);
|
||||
let feedback: UIEventSource<Translation> = new UIEventSource<Translation>(undefined)
|
||||
|
||||
let unit: Unit = layer?.units?.find((unit) => unit.appliesToKeys.has(config.freeform?.key));
|
||||
let unit: Unit = layer?.units?.find((unit) => unit.appliesToKeys.has(config.freeform?.key))
|
||||
|
||||
// Will be bound if a freeform is available
|
||||
let freeformInput = new UIEventSource<string>(tags?.[config.freeform?.key]);
|
||||
let selectedMapping: number = undefined;
|
||||
let checkedMappings: boolean[];
|
||||
let freeformInput = new UIEventSource<string>(tags?.[config.freeform?.key])
|
||||
let selectedMapping: number = undefined
|
||||
let checkedMappings: boolean[]
|
||||
|
||||
/**
|
||||
* Prepares and fills the checkedMappings
|
||||
|
@ -45,12 +45,12 @@
|
|||
function initialize(tgs: Record<string, string>, confg: TagRenderingConfig) {
|
||||
mappings = confg.mappings?.filter((m) => {
|
||||
if (typeof m.hideInAnswer === "boolean") {
|
||||
return !m.hideInAnswer;
|
||||
return !m.hideInAnswer
|
||||
}
|
||||
return !m.hideInAnswer.matchesProperties(tgs);
|
||||
});
|
||||
return !m.hideInAnswer.matchesProperties(tgs)
|
||||
})
|
||||
// We received a new config -> reinit
|
||||
unit = layer?.units?.find((unit) => unit.appliesToKeys.has(config.freeform?.key));
|
||||
unit = layer?.units?.find((unit) => unit.appliesToKeys.has(config.freeform?.key))
|
||||
|
||||
if (
|
||||
confg.mappings?.length > 0 &&
|
||||
|
@ -58,56 +58,56 @@
|
|||
(checkedMappings === undefined ||
|
||||
checkedMappings?.length < confg.mappings.length + (confg.freeform ? 1 : 0))
|
||||
) {
|
||||
const seenFreeforms = [];
|
||||
TagUtils.FlattenMultiAnswer();
|
||||
const seenFreeforms = []
|
||||
TagUtils.FlattenMultiAnswer()
|
||||
checkedMappings = [
|
||||
...confg.mappings.map((mapping) => {
|
||||
const matches = TagUtils.MatchesMultiAnswer(mapping.if, tgs);
|
||||
const matches = TagUtils.MatchesMultiAnswer(mapping.if, tgs)
|
||||
if (matches && confg.freeform) {
|
||||
const newProps = TagUtils.changeAsProperties(mapping.if.asChange());
|
||||
seenFreeforms.push(newProps[confg.freeform.key]);
|
||||
const newProps = TagUtils.changeAsProperties(mapping.if.asChange())
|
||||
seenFreeforms.push(newProps[confg.freeform.key])
|
||||
}
|
||||
return matches;
|
||||
})
|
||||
];
|
||||
return matches
|
||||
}),
|
||||
]
|
||||
|
||||
if (tgs !== undefined && confg.freeform) {
|
||||
const unseenFreeformValues = tgs[confg.freeform.key]?.split(";") ?? [];
|
||||
const unseenFreeformValues = tgs[confg.freeform.key]?.split(";") ?? []
|
||||
for (const seenFreeform of seenFreeforms) {
|
||||
if (!seenFreeform) {
|
||||
continue;
|
||||
continue
|
||||
}
|
||||
const index = unseenFreeformValues.indexOf(seenFreeform);
|
||||
const index = unseenFreeformValues.indexOf(seenFreeform)
|
||||
if (index < 0) {
|
||||
continue;
|
||||
continue
|
||||
}
|
||||
unseenFreeformValues.splice(index, 1);
|
||||
unseenFreeformValues.splice(index, 1)
|
||||
}
|
||||
// TODO this has _to much_ values
|
||||
freeformInput.setData(unseenFreeformValues.join(";"));
|
||||
checkedMappings.push(unseenFreeformValues.length > 0);
|
||||
freeformInput.setData(unseenFreeformValues.join(";"))
|
||||
checkedMappings.push(unseenFreeformValues.length > 0)
|
||||
}
|
||||
}
|
||||
if (confg.freeform?.key) {
|
||||
if (!confg.multiAnswer) {
|
||||
// Somehow, setting multi-answer freeform values is broken if this is not set
|
||||
freeformInput.setData(tgs[confg.freeform.key]);
|
||||
freeformInput.setData(tgs[confg.freeform.key])
|
||||
}
|
||||
} else {
|
||||
freeformInput.setData(undefined);
|
||||
freeformInput.setData(undefined)
|
||||
}
|
||||
feedback.setData(undefined);
|
||||
feedback.setData(undefined)
|
||||
}
|
||||
|
||||
$: {
|
||||
// Even though 'config' is not declared as a store, Svelte uses it as one to update the component
|
||||
// We want to (re)-initialize whenever the 'tags' or 'config' change - but not when 'checkedConfig' changes
|
||||
initialize($tags, config);
|
||||
initialize($tags, config)
|
||||
}
|
||||
export let selectedTags: TagsFilter = undefined;
|
||||
export let selectedTags: TagsFilter = undefined
|
||||
|
||||
let mappings: Mapping[] = config?.mappings;
|
||||
let searchTerm: UIEventSource<string> = new UIEventSource("");
|
||||
let mappings: Mapping[] = config?.mappings
|
||||
let searchTerm: UIEventSource<string> = new UIEventSource("")
|
||||
|
||||
$: {
|
||||
try {
|
||||
|
@ -116,10 +116,10 @@
|
|||
selectedMapping,
|
||||
checkedMappings,
|
||||
tags.data
|
||||
);
|
||||
)
|
||||
} catch (e) {
|
||||
console.error("Could not calculate changeSpecification:", e);
|
||||
selectedTags = undefined;
|
||||
console.error("Could not calculate changeSpecification:", e)
|
||||
selectedTags = undefined
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -128,55 +128,56 @@
|
|||
config: TagRenderingConfig
|
||||
applied: TagsFilter
|
||||
}
|
||||
}>();
|
||||
}>()
|
||||
|
||||
function onSave() {
|
||||
if (selectedTags === undefined) {
|
||||
console.log("SelectedTags is undefined, ignoring 'onSave'-event")
|
||||
return;
|
||||
return
|
||||
}
|
||||
if (layer === undefined || layer?.source === null) {
|
||||
/**
|
||||
* This is a special, priviliged layer.
|
||||
* We simply apply the tags onto the records
|
||||
*/
|
||||
const kv = selectedTags.asChange(tags.data);
|
||||
const kv = selectedTags.asChange(tags.data)
|
||||
for (const { k, v } of kv) {
|
||||
if (v === undefined || v === "") {
|
||||
delete tags.data[k];
|
||||
delete tags.data[k]
|
||||
} else {
|
||||
tags.data[k] = v;
|
||||
tags.data[k] = v
|
||||
}
|
||||
}
|
||||
tags.ping();
|
||||
return;
|
||||
tags.ping()
|
||||
return
|
||||
}
|
||||
|
||||
dispatch("saved", { config, applied: selectedTags });
|
||||
dispatch("saved", { config, applied: selectedTags })
|
||||
const change = new ChangeTagAction(tags.data.id, selectedTags, tags.data, {
|
||||
theme: state.layout.id,
|
||||
changeType: "answer"
|
||||
});
|
||||
freeformInput.setData(undefined);
|
||||
selectedMapping = undefined;
|
||||
selectedTags = undefined;
|
||||
changeType: "answer",
|
||||
})
|
||||
freeformInput.setData(undefined)
|
||||
selectedMapping = undefined
|
||||
selectedTags = undefined
|
||||
|
||||
change
|
||||
.CreateChangeDescriptions()
|
||||
.then((changes) => state.changes.applyChanges(changes))
|
||||
.catch(console.error);
|
||||
.catch(console.error)
|
||||
}
|
||||
|
||||
let featureSwitchIsTesting = state?.featureSwitchIsTesting ?? new ImmutableStore(false);
|
||||
let featureSwitchIsDebugging = state?.featureSwitches?.featureSwitchIsDebugging ?? new ImmutableStore(false);
|
||||
let showTags = state?.userRelatedState?.showTags ?? new ImmutableStore(undefined);
|
||||
let numberOfCs = state?.osmConnection?.userDetails?.data?.csCount ?? 0;
|
||||
let featureSwitchIsTesting = state?.featureSwitchIsTesting ?? new ImmutableStore(false)
|
||||
let featureSwitchIsDebugging =
|
||||
state?.featureSwitches?.featureSwitchIsDebugging ?? new ImmutableStore(false)
|
||||
let showTags = state?.userRelatedState?.showTags ?? new ImmutableStore(undefined)
|
||||
let numberOfCs = state?.osmConnection?.userDetails?.data?.csCount ?? 0
|
||||
if (state?.osmConnection) {
|
||||
onDestroy(
|
||||
state.osmConnection?.userDetails?.addCallbackAndRun((ud) => {
|
||||
numberOfCs = ud.csCount;
|
||||
numberOfCs = ud.csCount
|
||||
})
|
||||
);
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -249,7 +250,9 @@
|
|||
bind:group={selectedMapping}
|
||||
name={"mappings-radio-" + config.id}
|
||||
value={i}
|
||||
on:keypress={e => {if(e.key === "Enter") onSave()}}
|
||||
on:keypress={(e) => {
|
||||
if (e.key === "Enter") onSave()
|
||||
}}
|
||||
/>
|
||||
</TagRenderingMappingInput>
|
||||
{/each}
|
||||
|
|
|
@ -1,44 +1,48 @@
|
|||
<script lang="ts">
|
||||
import type { ConfigMeta } from "./configMeta";
|
||||
import EditLayerState from "./EditLayerState";
|
||||
import type {
|
||||
QuestionableTagRenderingConfigJson
|
||||
} from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson";
|
||||
import { UIEventSource } from "../../Logic/UIEventSource";
|
||||
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte";
|
||||
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig";
|
||||
import type { ConfigMeta } from "./configMeta"
|
||||
import EditLayerState from "./EditLayerState"
|
||||
import type { QuestionableTagRenderingConfigJson } from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte"
|
||||
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
|
||||
|
||||
export let schema: ConfigMeta;
|
||||
export let state: EditLayerState;
|
||||
export let path: (string | number)[] = [];
|
||||
export let schema: ConfigMeta
|
||||
export let state: EditLayerState
|
||||
export let path: (string | number)[] = []
|
||||
|
||||
const configJson: QuestionableTagRenderingConfigJson = {
|
||||
mappings: schema.hints.suggestions,
|
||||
multiAnswer: true,
|
||||
id: "multi_anwser_"+path.join("_"),
|
||||
question: schema.hints.question
|
||||
id: "multi_anwser_" + path.join("_"),
|
||||
question: schema.hints.question,
|
||||
}
|
||||
const tags = new UIEventSource({})
|
||||
|
||||
{
|
||||
// Setting the initial value
|
||||
const v = <string[]> state.getCurrentValueFor(path)
|
||||
if(v && v.length > 0){
|
||||
tags.setData({value: v.join(";")})
|
||||
const v = <string[]>state.getCurrentValueFor(path)
|
||||
if (v && v.length > 0) {
|
||||
tags.setData({ value: v.join(";") })
|
||||
}
|
||||
}
|
||||
|
||||
tags.addCallbackD(tags => {
|
||||
|
||||
tags.addCallbackD((tags) => {
|
||||
const values = tags["value"]?.split(";")
|
||||
if(!values){
|
||||
if (!values) {
|
||||
return
|
||||
}
|
||||
state.setValueAt(path, values)
|
||||
})
|
||||
|
||||
|
||||
const config = new TagRenderingConfig(configJson)
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<TagRenderingEditable {config} selectedElement={undefined} showQuestionIfUnknown={true} {state} {tags} />
|
||||
<TagRenderingEditable
|
||||
{config}
|
||||
selectedElement={undefined}
|
||||
showQuestionIfUnknown={true}
|
||||
{state}
|
||||
{tags}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -1,18 +1,17 @@
|
|||
<script lang="ts">
|
||||
import { OsmConnection } from "../../Logic/Osm/OsmConnection";
|
||||
import EditItemButton from "./EditItemButton.svelte";
|
||||
|
||||
export let layerIds: { id: string, owner: number }[];
|
||||
export let category: "layers" | "themes" = "layers";
|
||||
export let osmConnection: OsmConnection;
|
||||
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
|
||||
import EditItemButton from "./EditItemButton.svelte"
|
||||
|
||||
export let layerIds: { id: string; owner: number }[]
|
||||
export let category: "layers" | "themes" = "layers"
|
||||
export let osmConnection: OsmConnection
|
||||
</script>
|
||||
|
||||
{#if layerIds.length > 0}
|
||||
<slot name="title" />
|
||||
<div class="flex flex-wrap">
|
||||
{#each Array.from(layerIds) as layer}
|
||||
<EditItemButton info={layer} {category} {osmConnection} on:layerSelected/>
|
||||
<EditItemButton info={layer} {category} {osmConnection} on:layerSelected />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
@ -1,35 +1,36 @@
|
|||
<script lang="ts">
|
||||
import { UIEventSource } from "../../Logic/UIEventSource";
|
||||
import { OsmConnection } from "../../Logic/Osm/OsmConnection";
|
||||
import Marker from "../Map/Marker.svelte";
|
||||
import NextButton from "../Base/NextButton.svelte";
|
||||
import { AllKnownLayouts } from "../../Customizations/AllKnownLayouts";
|
||||
import { AllSharedLayers } from "../../Customizations/AllSharedLayers";
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
|
||||
import Marker from "../Map/Marker.svelte"
|
||||
import NextButton from "../Base/NextButton.svelte"
|
||||
import { AllKnownLayouts } from "../../Customizations/AllKnownLayouts"
|
||||
import { AllSharedLayers } from "../../Customizations/AllSharedLayers"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
|
||||
export let info: { id: string, owner: number };
|
||||
export let category: "layers" | "themes";
|
||||
export let osmConnection: OsmConnection;
|
||||
export let info: { id: string; owner: number }
|
||||
export let category: "layers" | "themes"
|
||||
export let osmConnection: OsmConnection
|
||||
|
||||
let displayName = UIEventSource.FromPromise(osmConnection.getInformationAboutUser(info.owner)).mapD(response => response.display_name);
|
||||
let displayName = UIEventSource.FromPromise(
|
||||
osmConnection.getInformationAboutUser(info.owner)
|
||||
).mapD((response) => response.display_name)
|
||||
|
||||
let selfId = osmConnection.userDetails.mapD(ud => ud.uid)
|
||||
let selfId = osmConnection.userDetails.mapD((ud) => ud.uid)
|
||||
function fetchIconDescription(layerId): any {
|
||||
if (category === "themes") {
|
||||
return AllKnownLayouts.allKnownLayouts.get(layerId).icon;
|
||||
return AllKnownLayouts.allKnownLayouts.get(layerId).icon
|
||||
}
|
||||
return AllSharedLayers.getSharedLayersConfigs().get(layerId)?._layerIcon;
|
||||
return AllSharedLayers.getSharedLayersConfigs().get(layerId)?._layerIcon
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher<{ layerSelected: string }>();
|
||||
|
||||
const dispatch = createEventDispatcher<{ layerSelected: string }>()
|
||||
</script>
|
||||
|
||||
<NextButton clss="small" on:click={() => dispatch("layerSelected", info)}>
|
||||
<div class="w-4 h-4 mr-1">
|
||||
<div class="mr-1 h-4 w-4">
|
||||
<Marker icons={fetchIconDescription(info.id)} />
|
||||
</div>
|
||||
<b class="px-1"> {info.id}</b>
|
||||
<b class="px-1">{info.id}</b>
|
||||
{#if info.owner && info.owner !== $selfId}
|
||||
(made by {$displayName ?? info.owner})
|
||||
{/if}
|
||||
|
|
|
@ -1,81 +1,81 @@
|
|||
<script lang="ts">
|
||||
import type { HighlightedTagRendering } from "./EditLayerState";
|
||||
import EditLayerState from "./EditLayerState";
|
||||
import layerSchemaRaw from "../../assets/schemas/layerconfigmeta.json";
|
||||
import Region from "./Region.svelte";
|
||||
import TabbedGroup from "../Base/TabbedGroup.svelte";
|
||||
import { Store, UIEventSource } from "../../Logic/UIEventSource";
|
||||
import type { ConfigMeta } from "./configMeta";
|
||||
import { Utils } from "../../Utils";
|
||||
import type { ConversionMessage } from "../../Models/ThemeConfig/Conversion/Conversion";
|
||||
import ErrorIndicatorForRegion from "./ErrorIndicatorForRegion.svelte";
|
||||
import { ChevronRightIcon } from "@rgossiaux/svelte-heroicons/solid";
|
||||
import SchemaBasedInput from "./SchemaBasedInput.svelte";
|
||||
import FloatOver from "../Base/FloatOver.svelte";
|
||||
import TagRenderingInput from "./TagRenderingInput.svelte";
|
||||
import FromHtml from "../Base/FromHtml.svelte";
|
||||
import AllTagsPanel from "../Popup/AllTagsPanel.svelte";
|
||||
import QuestionPreview from "./QuestionPreview.svelte";
|
||||
import ShowConversionMessages from "./ShowConversionMessages.svelte";
|
||||
import type { HighlightedTagRendering } from "./EditLayerState"
|
||||
import EditLayerState from "./EditLayerState"
|
||||
import layerSchemaRaw from "../../assets/schemas/layerconfigmeta.json"
|
||||
import Region from "./Region.svelte"
|
||||
import TabbedGroup from "../Base/TabbedGroup.svelte"
|
||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import type { ConfigMeta } from "./configMeta"
|
||||
import { Utils } from "../../Utils"
|
||||
import type { ConversionMessage } from "../../Models/ThemeConfig/Conversion/Conversion"
|
||||
import ErrorIndicatorForRegion from "./ErrorIndicatorForRegion.svelte"
|
||||
import { ChevronRightIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
import SchemaBasedInput from "./SchemaBasedInput.svelte"
|
||||
import FloatOver from "../Base/FloatOver.svelte"
|
||||
import TagRenderingInput from "./TagRenderingInput.svelte"
|
||||
import FromHtml from "../Base/FromHtml.svelte"
|
||||
import AllTagsPanel from "../Popup/AllTagsPanel.svelte"
|
||||
import QuestionPreview from "./QuestionPreview.svelte"
|
||||
import ShowConversionMessages from "./ShowConversionMessages.svelte"
|
||||
|
||||
const layerSchema: ConfigMeta[] = <any>layerSchemaRaw;
|
||||
const layerSchema: ConfigMeta[] = <any>layerSchemaRaw
|
||||
|
||||
export let state: EditLayerState;
|
||||
let messages = state.messages;
|
||||
let hasErrors = messages.mapD((m: ConversionMessage[]) => m.filter(m => m.level === "error").length);
|
||||
const configuration = state.configuration;
|
||||
export let state: EditLayerState
|
||||
let messages = state.messages
|
||||
let hasErrors = messages.mapD(
|
||||
(m: ConversionMessage[]) => m.filter((m) => m.level === "error").length
|
||||
)
|
||||
const configuration = state.configuration
|
||||
|
||||
const allNames = Utils.Dedup(layerSchema.map(meta => meta.hints.group));
|
||||
const allNames = Utils.Dedup(layerSchema.map((meta) => meta.hints.group))
|
||||
|
||||
const perRegion: Record<string, ConfigMeta[]> = {};
|
||||
const perRegion: Record<string, ConfigMeta[]> = {}
|
||||
for (const region of allNames) {
|
||||
perRegion[region] = layerSchema.filter(meta => meta.hints.group === region);
|
||||
perRegion[region] = layerSchema.filter((meta) => meta.hints.group === region)
|
||||
}
|
||||
|
||||
|
||||
let title: Store<string> = state.getStoreFor(["id"]);
|
||||
const wl = window.location;
|
||||
const baseUrl = wl.protocol + "//" + wl.host + "/theme.html?userlayout=";
|
||||
let title: Store<string> = state.getStoreFor(["id"])
|
||||
const wl = window.location
|
||||
const baseUrl = wl.protocol + "//" + wl.host + "/theme.html?userlayout="
|
||||
|
||||
function firstPathsFor(...regionNames: string[]): Set<string> {
|
||||
const pathNames = new Set<string>();
|
||||
const pathNames = new Set<string>()
|
||||
for (const regionName of regionNames) {
|
||||
const region: ConfigMeta[] = perRegion[regionName];
|
||||
const region: ConfigMeta[] = perRegion[regionName]
|
||||
for (const configMeta of region) {
|
||||
pathNames.add(configMeta.path[0]);
|
||||
pathNames.add(configMeta.path[0])
|
||||
}
|
||||
}
|
||||
return pathNames;
|
||||
|
||||
return pathNames
|
||||
}
|
||||
|
||||
function configForRequiredField(id: string): ConfigMeta {
|
||||
let config = layerSchema.find(config => config.path.length === 1 && config.path[0] === id);
|
||||
config = Utils.Clone(config);
|
||||
config.required = true;
|
||||
config.hints.ifunset = undefined;
|
||||
return config;
|
||||
let config = layerSchema.find((config) => config.path.length === 1 && config.path[0] === id)
|
||||
config = Utils.Clone(config)
|
||||
config.required = true
|
||||
config.hints.ifunset = undefined
|
||||
return config
|
||||
}
|
||||
|
||||
let requiredFields = ["id", "name", "description", "source"];
|
||||
let currentlyMissing = state.configuration.map(config => {
|
||||
let requiredFields = ["id", "name", "description", "source"]
|
||||
let currentlyMissing = state.configuration.map((config) => {
|
||||
if (!config) {
|
||||
return [];
|
||||
return []
|
||||
}
|
||||
const missing = [];
|
||||
const missing = []
|
||||
for (const requiredField of requiredFields) {
|
||||
if (!config[requiredField]) {
|
||||
missing.push(requiredField);
|
||||
missing.push(requiredField)
|
||||
}
|
||||
}
|
||||
return missing;
|
||||
});
|
||||
return missing
|
||||
})
|
||||
|
||||
let highlightedItem: UIEventSource<HighlightedTagRendering> = state.highlightedItem;
|
||||
let highlightedItem: UIEventSource<HighlightedTagRendering> = state.highlightedItem
|
||||
</script>
|
||||
<div class="h-screen flex flex-col">
|
||||
|
||||
<div class="w-full flex justify-between my-2">
|
||||
<div class="flex h-screen flex-col">
|
||||
<div class="my-2 flex w-full justify-between">
|
||||
<slot />
|
||||
{#if $title === undefined}
|
||||
<h3>Creating a new layer</h3>
|
||||
|
@ -83,11 +83,17 @@
|
|||
<h3>Editing layer {$title}</h3>
|
||||
{/if}
|
||||
{#if $currentlyMissing.length > 0}
|
||||
<div class="w-16"/> <!-- Empty div, simply hide this -->
|
||||
<div class="w-16" />
|
||||
<!-- Empty div, simply hide this -->
|
||||
{:else if $hasErrors > 0}
|
||||
<div class="alert">{$hasErrors} errors detected</div>
|
||||
{:else}
|
||||
<a class="primary button" href={baseUrl+state.server.layerUrl(title.data)} target="_blank" rel="noopener">
|
||||
<a
|
||||
class="primary button"
|
||||
href={baseUrl + state.server.layerUrl(title.data)}
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
Try it out
|
||||
<ChevronRightIcon class="h-6 w-6 shrink-0" />
|
||||
</a>
|
||||
|
@ -95,29 +101,29 @@
|
|||
</div>
|
||||
|
||||
{#if $currentlyMissing.length > 0}
|
||||
|
||||
{#each requiredFields as required}
|
||||
<SchemaBasedInput {state}
|
||||
schema={configForRequiredField(required)}
|
||||
path={[required]} />
|
||||
<SchemaBasedInput {state} schema={configForRequiredField(required)} path={[required]} />
|
||||
{/each}
|
||||
{:else}
|
||||
<div class="m4 h-full overflow-y-auto">
|
||||
<TabbedGroup>
|
||||
<div slot="title0" class="flex">General properties
|
||||
<div slot="title0" class="flex">
|
||||
General properties
|
||||
<ErrorIndicatorForRegion firstPaths={firstPathsFor("Basic")} {state} />
|
||||
</div>
|
||||
<div class="flex flex-col" slot="content0">
|
||||
<Region {state} configs={perRegion["Basic"]} />
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div slot="title1" class="flex">Information panel (questions and answers)
|
||||
<ErrorIndicatorForRegion firstPaths={firstPathsFor("title","tagrenderings","editing")} {state} />
|
||||
<div slot="title1" class="flex">
|
||||
Information panel (questions and answers)
|
||||
<ErrorIndicatorForRegion
|
||||
firstPaths={firstPathsFor("title", "tagrenderings", "editing")}
|
||||
{state}
|
||||
/>
|
||||
</div>
|
||||
<div slot="content1">
|
||||
<QuestionPreview path={["title"]} {state} schema={perRegion["title"][0]}></QuestionPreview>
|
||||
<QuestionPreview path={["title"]} {state} schema={perRegion["title"][0]} />
|
||||
<Region configs={perRegion["tagrenderings"]} {state} title="Popup contents" />
|
||||
<Region configs={perRegion["editing"]} {state} title="Other editing elements" />
|
||||
</div>
|
||||
|
@ -131,16 +137,21 @@
|
|||
<Region {state} configs={perRegion["presets"]} />
|
||||
</div>
|
||||
|
||||
<div slot="title3" class="flex">Rendering on the map
|
||||
<ErrorIndicatorForRegion firstPaths={firstPathsFor("linerendering","pointrendering")} {state} />
|
||||
<div slot="title3" class="flex">
|
||||
Rendering on the map
|
||||
<ErrorIndicatorForRegion
|
||||
firstPaths={firstPathsFor("linerendering", "pointrendering")}
|
||||
{state}
|
||||
/>
|
||||
</div>
|
||||
<div slot="content3">
|
||||
<Region configs={perRegion["linerendering"]} {state} />
|
||||
<Region configs={perRegion["pointrendering"]} {state} />
|
||||
</div>
|
||||
|
||||
<div slot="title4" class="flex">Advanced functionality
|
||||
<ErrorIndicatorForRegion firstPaths={firstPathsFor("advanced","expert")} {state} />
|
||||
<div slot="title4" class="flex">
|
||||
Advanced functionality
|
||||
<ErrorIndicatorForRegion firstPaths={firstPathsFor("advanced", "expert")} {state} />
|
||||
</div>
|
||||
<div slot="content4">
|
||||
<Region configs={perRegion["advanced"]} {state} />
|
||||
|
@ -149,30 +160,33 @@
|
|||
<div slot="title5">Configuration file</div>
|
||||
<div slot="content5">
|
||||
<div>
|
||||
Below, you'll find the raw configuration file in `.json`-format.
|
||||
This is mostly for debugging purposes
|
||||
Below, you'll find the raw configuration file in `.json`-format. This is mostly for
|
||||
debugging purposes
|
||||
</div>
|
||||
<div class="literal-code">
|
||||
<FromHtml src={JSON.stringify($configuration, null, " ").replaceAll("\n","</br>")} />
|
||||
<FromHtml src={JSON.stringify($configuration, null, " ").replaceAll("\n", "</br>")} />
|
||||
</div>
|
||||
|
||||
<ShowConversionMessages messages={$messages} />
|
||||
<div>
|
||||
The testobject (which is used to render the questions in the 'information panel' item has the following
|
||||
tags:
|
||||
The testobject (which is used to render the questions in the 'information panel' item
|
||||
has the following tags:
|
||||
</div>
|
||||
|
||||
<AllTagsPanel tags={state.testTags}></AllTagsPanel>
|
||||
<AllTagsPanel tags={state.testTags} />
|
||||
</div>
|
||||
</TabbedGroup>
|
||||
</div>
|
||||
{#if $highlightedItem !== undefined}
|
||||
<FloatOver on:close={() => highlightedItem.setData(undefined)}>
|
||||
<div>
|
||||
<TagRenderingInput path={$highlightedItem.path} {state} schema={$highlightedItem.schema} />
|
||||
<TagRenderingInput
|
||||
path={$highlightedItem.path}
|
||||
{state}
|
||||
schema={$highlightedItem.schema}
|
||||
/>
|
||||
</div>
|
||||
</FloatOver>
|
||||
{/if}
|
||||
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
@ -1,37 +1,45 @@
|
|||
<script lang="ts">
|
||||
import { EditThemeState } from "./EditLayerState";
|
||||
import type { ConfigMeta } from "./configMeta";
|
||||
import { ChevronRightIcon } from "@rgossiaux/svelte-heroicons/solid";
|
||||
import type { ConversionMessage } from "../../Models/ThemeConfig/Conversion/Conversion";
|
||||
import TabbedGroup from "../Base/TabbedGroup.svelte";
|
||||
import ShowConversionMessages from "./ShowConversionMessages.svelte";
|
||||
import Region from "./Region.svelte";
|
||||
import { EditThemeState } from "./EditLayerState"
|
||||
import type { ConfigMeta } from "./configMeta"
|
||||
import { ChevronRightIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
import type { ConversionMessage } from "../../Models/ThemeConfig/Conversion/Conversion"
|
||||
import TabbedGroup from "../Base/TabbedGroup.svelte"
|
||||
import ShowConversionMessages from "./ShowConversionMessages.svelte"
|
||||
import Region from "./Region.svelte"
|
||||
|
||||
export let state: EditThemeState;
|
||||
let schema: ConfigMeta[] = state.schema.filter(schema => schema.path.length > 0);
|
||||
let config = state.configuration;
|
||||
let messages = state.messages;
|
||||
let hasErrors = messages.map((m: ConversionMessage[]) => m.filter(m => m.level === "error").length);
|
||||
let title = state.getStoreFor(["id"]);
|
||||
const wl = window.location;
|
||||
const baseUrl = wl.protocol + "//" + wl.host + "/theme.html?userlayout=";
|
||||
export let state: EditThemeState
|
||||
let schema: ConfigMeta[] = state.schema.filter((schema) => schema.path.length > 0)
|
||||
let config = state.configuration
|
||||
let messages = state.messages
|
||||
let hasErrors = messages.map(
|
||||
(m: ConversionMessage[]) => m.filter((m) => m.level === "error").length
|
||||
)
|
||||
let title = state.getStoreFor(["id"])
|
||||
const wl = window.location
|
||||
const baseUrl = wl.protocol + "//" + wl.host + "/theme.html?userlayout="
|
||||
|
||||
const perRegion: Record<string, ConfigMeta[]> = {};
|
||||
const perRegion: Record<string, ConfigMeta[]> = {}
|
||||
for (const schemaElement of schema) {
|
||||
const key = schemaElement.hints.group ?? "no-group";
|
||||
const list = perRegion[key] ?? (perRegion[key] = []);
|
||||
list.push(schemaElement);
|
||||
const key = schemaElement.hints.group ?? "no-group"
|
||||
const list = perRegion[key] ?? (perRegion[key] = [])
|
||||
list.push(schemaElement)
|
||||
}
|
||||
console.log({perRegion, schema})
|
||||
console.log({ perRegion, schema })
|
||||
</script>
|
||||
<div class="flex flex-col h-screen">
|
||||
<div class="w-full flex justify-between my-2">
|
||||
|
||||
<div class="flex h-screen flex-col">
|
||||
<div class="my-2 flex w-full justify-between">
|
||||
<slot />
|
||||
<h3>Editing theme {$title}</h3>
|
||||
{#if $hasErrors > 0}
|
||||
<div class="alert">{$hasErrors} errors detected</div>
|
||||
{:else}
|
||||
<a class="primary button" href={baseUrl+state.server.urlFor($title, "themes")} target="_blank" rel="noopener">
|
||||
<a
|
||||
class="primary button"
|
||||
href={baseUrl + state.server.urlFor($title, "themes")}
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
Try it out
|
||||
<ChevronRightIcon class="h-6 w-6 shrink-0" />
|
||||
</a>
|
||||
|
@ -43,36 +51,32 @@ console.log({perRegion, schema})
|
|||
<TabbedGroup>
|
||||
<div slot="title0">Basic properties</div>
|
||||
<div slot="content0">
|
||||
<Region configs={perRegion["basic"]} path={[]} {state} title="Basic properties"/>
|
||||
<Region configs={perRegion["start_location"]} path={[]} {state} title="Start location"/>
|
||||
|
||||
<Region configs={perRegion["basic"]} path={[]} {state} title="Basic properties" />
|
||||
<Region configs={perRegion["start_location"]} path={[]} {state} title="Start location" />
|
||||
</div>
|
||||
|
||||
|
||||
<div slot="title1">Layers</div>
|
||||
<div slot="content1">
|
||||
<Region configs={perRegion["layers"]} path={[]} {state} />
|
||||
|
||||
</div>
|
||||
<div slot="title2">Feature switches</div>
|
||||
<div slot="content2">
|
||||
<Region configs={perRegion["feature_switches"]} path={[]} {state}></Region>
|
||||
<Region configs={perRegion["feature_switches"]} path={[]} {state} />
|
||||
</div>
|
||||
|
||||
<div slot="title3">Advanced options</div>
|
||||
<div slot="content3">
|
||||
<Region configs={perRegion["advanced"]} path={[]} {state}></Region>
|
||||
<Region configs={perRegion["advanced"]} path={[]} {state} />
|
||||
</div>
|
||||
|
||||
|
||||
<div slot="title4">Configuration file</div>
|
||||
<div slot="content4">
|
||||
<div class="literal-code">
|
||||
{JSON.stringify($config)}
|
||||
</div>
|
||||
|
||||
<ShowConversionMessages messages={$messages}></ShowConversionMessages>
|
||||
|
||||
<ShowConversionMessages messages={$messages} />
|
||||
</div>
|
||||
</TabbedGroup>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
<script lang="ts">
|
||||
import EditLayerState from "./EditLayerState";
|
||||
import { ExclamationIcon } from "@rgossiaux/svelte-heroicons/solid";
|
||||
|
||||
export let firstPaths: Set<string>;
|
||||
export let state: EditLayerState;
|
||||
let messagesCount = state.messages.map(msgs => msgs.filter(msg => {
|
||||
const pth = msg.context.path
|
||||
return firstPaths.has(pth[0]) || (pth.length > 1 && firstPaths.has(pth[1]));
|
||||
}).length);
|
||||
import EditLayerState from "./EditLayerState"
|
||||
import { ExclamationIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
|
||||
export let firstPaths: Set<string>
|
||||
export let state: EditLayerState
|
||||
let messagesCount = state.messages.map(
|
||||
(msgs) =>
|
||||
msgs.filter((msg) => {
|
||||
const pth = msg.context.path
|
||||
return firstPaths.has(pth[0]) || (pth.length > 1 && firstPaths.has(pth[1]))
|
||||
}).length
|
||||
)
|
||||
</script>
|
||||
|
||||
{#if $messagesCount > 0}
|
||||
<span class="alert flex w-min">
|
||||
<ExclamationIcon class="w-6 h-6" />
|
||||
<ExclamationIcon class="h-6 w-6" />
|
||||
{$messagesCount}
|
||||
</span>
|
||||
{/if}
|
||||
|
|
|
@ -1,56 +1,62 @@
|
|||
<script lang="ts">
|
||||
import EditLayerState from "./EditLayerState"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import type { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson"
|
||||
import { TagUtils } from "../../Logic/Tags/TagUtils"
|
||||
import FromHtml from "../Base/FromHtml.svelte"
|
||||
import { PencilIcon } from "@rgossiaux/svelte-heroicons/outline"
|
||||
import Region from "./Region.svelte"
|
||||
import type { ConfigMeta } from "./configMeta"
|
||||
import configs from "../../assets/schemas/questionabletagrenderingconfigmeta.json"
|
||||
import { Utils } from "../../Utils"
|
||||
import ToSvelte from "../Base/ToSvelte.svelte"
|
||||
import { VariableUiElement } from "../Base/VariableUIElement"
|
||||
|
||||
import EditLayerState from "./EditLayerState";
|
||||
import { UIEventSource } from "../../Logic/UIEventSource";
|
||||
import type { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson";
|
||||
import { TagUtils } from "../../Logic/Tags/TagUtils";
|
||||
import FromHtml from "../Base/FromHtml.svelte";
|
||||
import { PencilIcon } from "@rgossiaux/svelte-heroicons/outline";
|
||||
import Region from "./Region.svelte";
|
||||
import type { ConfigMeta } from "./configMeta";
|
||||
import configs from "../../assets/schemas/questionabletagrenderingconfigmeta.json";
|
||||
import { Utils } from "../../Utils";
|
||||
import ToSvelte from "../Base/ToSvelte.svelte";
|
||||
import { VariableUiElement } from "../Base/VariableUIElement";
|
||||
|
||||
export let state: EditLayerState;
|
||||
export let path: (string | number)[];
|
||||
let tag: UIEventSource<TagConfigJson> = state.getStoreFor([...path, "if"]);
|
||||
let parsedTag = tag.map(t => t ? TagUtils.Tag(t) : undefined);
|
||||
let exampleTags = parsedTag.map(pt => {
|
||||
export let state: EditLayerState
|
||||
export let path: (string | number)[]
|
||||
let tag: UIEventSource<TagConfigJson> = state.getStoreFor([...path, "if"])
|
||||
let parsedTag = tag.map((t) => (t ? TagUtils.Tag(t) : undefined))
|
||||
let exampleTags = parsedTag.map((pt) => {
|
||||
if (!pt) {
|
||||
return {};
|
||||
return {}
|
||||
}
|
||||
const keys = pt.usedKeys();
|
||||
const o = {};
|
||||
const keys = pt.usedKeys()
|
||||
const o = {}
|
||||
for (const key of keys) {
|
||||
o[key] = "value";
|
||||
o[key] = "value"
|
||||
}
|
||||
return o;
|
||||
});
|
||||
let uploadableOnly: boolean = true;
|
||||
return o
|
||||
})
|
||||
let uploadableOnly: boolean = true
|
||||
|
||||
let thenText: UIEventSource<Record<string, string>> = state.getStoreFor([...path, "then"])
|
||||
let thenTextEn = thenText .mapD(translation => typeof translation === "string" ? translation : translation["en"] )
|
||||
let editMode = Object.keys($thenText ?? {})?.length === 0;
|
||||
let thenTextEn = thenText.mapD((translation) =>
|
||||
typeof translation === "string" ? translation : translation["en"]
|
||||
)
|
||||
let editMode = Object.keys($thenText ?? {})?.length === 0
|
||||
|
||||
let mappingConfigs: ConfigMeta[] = configs.filter(c => c.path[0] === "mappings")
|
||||
.map(c => <ConfigMeta>Utils.Clone(c))
|
||||
.map(c => {
|
||||
c.path.splice(0, 1);
|
||||
return c;
|
||||
let mappingConfigs: ConfigMeta[] = configs
|
||||
.filter((c) => c.path[0] === "mappings")
|
||||
.map((c) => <ConfigMeta>Utils.Clone(c))
|
||||
.map((c) => {
|
||||
c.path.splice(0, 1)
|
||||
return c
|
||||
})
|
||||
.filter(c => c.path.length == 1 && c.hints.group !== "hidden");
|
||||
.filter((c) => c.path.length == 1 && c.hints.group !== "hidden")
|
||||
</script>
|
||||
|
||||
<button on:click={() => {editMode = !editMode}}>
|
||||
<PencilIcon class="w-6 h-6" />
|
||||
<button
|
||||
on:click={() => {
|
||||
editMode = !editMode
|
||||
}}
|
||||
>
|
||||
<PencilIcon class="h-6 w-6" />
|
||||
</button>
|
||||
|
||||
{#if editMode}
|
||||
<div class="flex justify-between items-start w-full">
|
||||
<div class="flex flex-col w-full">
|
||||
<Region {state} configs={mappingConfigs} path={path} />
|
||||
<div class="flex w-full items-start justify-between">
|
||||
<div class="flex w-full flex-col">
|
||||
<Region {state} configs={mappingConfigs} {path} />
|
||||
</div>
|
||||
|
||||
<slot name="delete" />
|
||||
|
@ -64,7 +70,6 @@
|
|||
{:else}
|
||||
<i>No then is set</i>
|
||||
{/if}
|
||||
<FromHtml src={ $parsedTag?.asHumanString(false, false, $exampleTags)} />
|
||||
<FromHtml src={$parsedTag?.asHumanString(false, false, $exampleTags)} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
|
|
@ -1,86 +1,82 @@
|
|||
<script lang="ts">
|
||||
import type { ConfigMeta } from "./configMeta";
|
||||
import EditLayerState from "./EditLayerState";
|
||||
import * as questions from "../../assets/generated/layers/questions.json";
|
||||
import { ImmutableStore, Store } from "../../Logic/UIEventSource";
|
||||
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte";
|
||||
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig";
|
||||
import nmd from "nano-markdown";
|
||||
import type {
|
||||
QuestionableTagRenderingConfigJson
|
||||
} from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson.js";
|
||||
import type { TagRenderingConfigJson } from "../../Models/ThemeConfig/Json/TagRenderingConfigJson";
|
||||
import FromHtml from "../Base/FromHtml.svelte";
|
||||
import ShowConversionMessage from "./ShowConversionMessage.svelte";
|
||||
import NextButton from "../Base/NextButton.svelte";
|
||||
import type { ConfigMeta } from "./configMeta"
|
||||
import EditLayerState from "./EditLayerState"
|
||||
import * as questions from "../../assets/generated/layers/questions.json"
|
||||
import { ImmutableStore, Store } from "../../Logic/UIEventSource"
|
||||
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte"
|
||||
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
|
||||
import nmd from "nano-markdown"
|
||||
import type { QuestionableTagRenderingConfigJson } from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson.js"
|
||||
import type { TagRenderingConfigJson } from "../../Models/ThemeConfig/Json/TagRenderingConfigJson"
|
||||
import FromHtml from "../Base/FromHtml.svelte"
|
||||
import ShowConversionMessage from "./ShowConversionMessage.svelte"
|
||||
import NextButton from "../Base/NextButton.svelte"
|
||||
|
||||
export let state: EditLayerState;
|
||||
export let path: ReadonlyArray<string | number>;
|
||||
export let schema: ConfigMeta;
|
||||
let value = state.getStoreFor(path);
|
||||
export let state: EditLayerState
|
||||
export let path: ReadonlyArray<string | number>
|
||||
export let schema: ConfigMeta
|
||||
let value = state.getStoreFor(path)
|
||||
|
||||
let perId: Record<string, TagRenderingConfigJson[]> = {};
|
||||
let perId: Record<string, TagRenderingConfigJson[]> = {}
|
||||
for (let tagRendering of questions.tagRenderings) {
|
||||
if (tagRendering.labels) {
|
||||
for (let label of tagRendering.labels) {
|
||||
perId[label] = (perId[label] ?? []).concat(tagRendering);
|
||||
perId[label] = (perId[label] ?? []).concat(tagRendering)
|
||||
}
|
||||
}
|
||||
perId[tagRendering.id] = [tagRendering];
|
||||
perId[tagRendering.id] = [tagRendering]
|
||||
}
|
||||
|
||||
let configJson: Store<QuestionableTagRenderingConfigJson[]> = value.map(x => {
|
||||
let configJson: Store<QuestionableTagRenderingConfigJson[]> = value.map((x) => {
|
||||
if (typeof x === "string") {
|
||||
return perId[x];
|
||||
return perId[x]
|
||||
} else {
|
||||
return [x];
|
||||
return [x]
|
||||
}
|
||||
});
|
||||
let configs: Store<TagRenderingConfig[]> = configJson.map(configs => {
|
||||
})
|
||||
let configs: Store<TagRenderingConfig[]> = configJson.map((configs) => {
|
||||
if (!configs) {
|
||||
return [{ error: "No configuartions found" }];
|
||||
return [{ error: "No configuartions found" }]
|
||||
}
|
||||
console.log("Regenerating configs");
|
||||
return configs.map(config => {
|
||||
console.log("Regenerating configs")
|
||||
return configs.map((config) => {
|
||||
try {
|
||||
return new TagRenderingConfig(config);
|
||||
return new TagRenderingConfig(config)
|
||||
} catch (e) {
|
||||
return { error: e };
|
||||
return { error: e }
|
||||
}
|
||||
});
|
||||
});
|
||||
let id: Store<string> = value.mapD(c => {
|
||||
})
|
||||
})
|
||||
let id: Store<string> = value.mapD((c) => {
|
||||
if (c?.id) {
|
||||
return c.id;
|
||||
return c.id
|
||||
}
|
||||
if (typeof c === "string") {
|
||||
return c;
|
||||
return c
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
return undefined
|
||||
})
|
||||
|
||||
let tags = state.testTags;
|
||||
let tags = state.testTags
|
||||
|
||||
let messages = state.messagesFor(path);
|
||||
let messages = state.messagesFor(path)
|
||||
|
||||
let description = schema.description;
|
||||
let description = schema.description
|
||||
if (description) {
|
||||
try {
|
||||
description = nmd(description);
|
||||
description = nmd(description)
|
||||
} catch (e) {
|
||||
console.error("Could not convert description to markdown", { description });
|
||||
console.error("Could not convert description to markdown", { description })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex">
|
||||
|
||||
<div class="flex flex-col interactive border-interactive m-4 w-full">
|
||||
|
||||
<div class="interactive border-interactive m-4 flex w-full flex-col">
|
||||
{#if $id}
|
||||
TagRendering {$id}
|
||||
{/if}
|
||||
<NextButton clss="primary" on:click={() => state.highlightedItem.setData({path, schema})}>
|
||||
<NextButton clss="primary" on:click={() => state.highlightedItem.setData({ path, schema })}>
|
||||
{#if schema.hints.question}
|
||||
{schema.hints.question}
|
||||
{/if}
|
||||
|
@ -92,40 +88,40 @@
|
|||
<ShowConversionMessage {message} />
|
||||
{/each}
|
||||
|
||||
<slot class="self-end my-4"></slot>
|
||||
|
||||
|
||||
<slot class="my-4 self-end" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col w-full m-4">
|
||||
<div class="m-4 flex w-full flex-col">
|
||||
<h3>Preview of this question</h3>
|
||||
{#each $configs as config}
|
||||
{#if config.error !== undefined}
|
||||
<div class="alert">Could not create a preview of this tagRendering: {config.error}</div>
|
||||
{:else if config.condition && !config.condition.matchesProperties($tags)}
|
||||
This tagRendering is currently not shown. It will appear if the feature matches the
|
||||
condition
|
||||
<b>
|
||||
<FromHtml src={config.condition.asHumanString(true, false, {})} />
|
||||
</b>
|
||||
|
||||
Try to answer the relevant question above
|
||||
{:else if config.metacondition && !config.metacondition.matchesProperties($tags)}
|
||||
This tagRendering is currently not shown. It will appear if the feature matches the
|
||||
metacondition
|
||||
<b>
|
||||
<FromHtml src={config.metacondition.asHumanString(true, false, {})} />
|
||||
</b>
|
||||
For a breakdown of usable meta conditions, go to a mapcomplete theme > settings and enable debug-data.
|
||||
The meta-tags will appear at the bottom
|
||||
{:else}
|
||||
{#if config.condition && !config.condition.matchesProperties($tags)}
|
||||
This tagRendering is currently not shown. It will appear if the feature matches the condition
|
||||
<b>
|
||||
<FromHtml src={config.condition.asHumanString(true, false, {})} />
|
||||
</b>
|
||||
|
||||
Try to answer the relevant question above
|
||||
{:else if config.metacondition && !config.metacondition.matchesProperties($tags)}
|
||||
This tagRendering is currently not shown. It will appear if the feature matches the metacondition
|
||||
<b>
|
||||
<FromHtml src={config.metacondition.asHumanString(true, false, {})} />
|
||||
</b>
|
||||
For a breakdown of usable meta conditions, go to a mapcomplete theme > settings and enable debug-data. The meta-tags will appear at the bottom
|
||||
{:else}
|
||||
<TagRenderingEditable
|
||||
selectedElement={state.exampleFeature}
|
||||
config={config} editingEnabled={new ImmutableStore(true)} showQuestionIfUnknown={true}
|
||||
{state}
|
||||
{tags}></TagRenderingEditable>
|
||||
{/if}
|
||||
<TagRenderingEditable
|
||||
selectedElement={state.exampleFeature}
|
||||
{config}
|
||||
editingEnabled={new ImmutableStore(true)}
|
||||
showQuestionIfUnknown={true}
|
||||
{state}
|
||||
{tags}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
|
|
@ -1,30 +1,33 @@
|
|||
<script lang="ts">/***
|
||||
* A 'region' is a collection of properties that can be edited which are somewhat related.
|
||||
* They will typically be a subset of some properties
|
||||
*/
|
||||
import type { ConfigMeta } from "./configMeta";
|
||||
import EditLayerState from "./EditLayerState";
|
||||
import SchemaBasedInput from "./SchemaBasedInput.svelte";
|
||||
<script lang="ts">
|
||||
/***
|
||||
* A 'region' is a collection of properties that can be edited which are somewhat related.
|
||||
* They will typically be a subset of some properties
|
||||
*/
|
||||
import type { ConfigMeta } from "./configMeta"
|
||||
import EditLayerState from "./EditLayerState"
|
||||
import SchemaBasedInput from "./SchemaBasedInput.svelte"
|
||||
|
||||
export let state: EditLayerState;
|
||||
export let configs: ConfigMeta[];
|
||||
export let title: string | undefined = undefined;
|
||||
export let state: EditLayerState
|
||||
export let configs: ConfigMeta[]
|
||||
export let title: string | undefined = undefined
|
||||
|
||||
export let path: (string | number)[] = [];
|
||||
|
||||
let expertMode = state.expertMode
|
||||
let configsNoHidden = configs.filter(schema => schema.hints?.group !== "hidden")
|
||||
let configsFiltered = $expertMode ? configsNoHidden : configsNoHidden.filter(schema => schema.hints?.group !== "expert")
|
||||
export let path: (string | number)[] = []
|
||||
|
||||
let expertMode = state.expertMode
|
||||
let configsNoHidden = configs.filter((schema) => schema.hints?.group !== "hidden")
|
||||
let configsFiltered = $expertMode
|
||||
? configsNoHidden
|
||||
: configsNoHidden.filter((schema) => schema.hints?.group !== "expert")
|
||||
</script>
|
||||
|
||||
{#if configs === undefined}
|
||||
Bug: 'Region' received 'undefined'
|
||||
{:else if configs.length === 0}
|
||||
Bug: Region received empty list as configuration
|
||||
{:else if title}
|
||||
<div class="w-full flex flex-col">
|
||||
<div class="flex w-full flex-col">
|
||||
<h3>{title}</h3>
|
||||
<div class="pl-2 border border-black flex flex-col gap-y-1 w-full">
|
||||
<div class="flex w-full flex-col gap-y-1 border border-black pl-2">
|
||||
<slot name="description" />
|
||||
{#each configsFiltered as config}
|
||||
<SchemaBasedInput {state} path={config.path} schema={config} />
|
||||
|
@ -32,10 +35,9 @@ let configsFiltered = $expertMode ? configsNoHidden : configsNoHidden.filter(sch
|
|||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="pl-2 flex flex-col gap-y-1 w-full">
|
||||
<div class="flex w-full flex-col gap-y-1 pl-2">
|
||||
{#each configsFiltered as config}
|
||||
<SchemaBasedInput {state} path={path.concat(config.path)} schema={config} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
|
|
@ -1,66 +1,70 @@
|
|||
<script lang="ts">
|
||||
import EditLayerState from "./EditLayerState";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import type {TagConfigJson} from "../../Models/ThemeConfig/Json/TagConfigJson";
|
||||
import FullTagInput from "./TagInput/FullTagInput.svelte";
|
||||
import type {ConfigMeta} from "./configMeta";
|
||||
import {PencilAltIcon} from "@rgossiaux/svelte-heroicons/solid";
|
||||
import { onDestroy } from "svelte";
|
||||
import EditLayerState from "./EditLayerState"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import type { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson"
|
||||
import FullTagInput from "./TagInput/FullTagInput.svelte"
|
||||
import type { ConfigMeta } from "./configMeta"
|
||||
import { PencilAltIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
import { onDestroy } from "svelte"
|
||||
|
||||
/**
|
||||
* Thin wrapper around 'TagInput' which registers the output with the state
|
||||
*/
|
||||
export let path: (string | number)[]
|
||||
export let state: EditLayerState
|
||||
/**
|
||||
* Thin wrapper around 'TagInput' which registers the output with the state
|
||||
*/
|
||||
export let path: (string | number)[]
|
||||
export let state: EditLayerState
|
||||
|
||||
export let schema: ConfigMeta
|
||||
export let schema: ConfigMeta
|
||||
|
||||
const initialValue = state.getCurrentValueFor(path)
|
||||
let tag: UIEventSource<TagConfigJson> = new UIEventSource<TagConfigJson>(initialValue)
|
||||
const initialValue = state.getCurrentValueFor(path)
|
||||
let tag: UIEventSource<TagConfigJson> = new UIEventSource<TagConfigJson>(initialValue)
|
||||
|
||||
onDestroy(state.register(path, tag))
|
||||
onDestroy(state.register(path, tag))
|
||||
|
||||
let mode: "editing" | "set" = tag.data === undefined ? "editing" : "set"
|
||||
let mode: "editing" | "set" = tag.data === undefined ? "editing" : "set"
|
||||
|
||||
function simplify(tag: TagConfigJson): string {
|
||||
if (typeof tag === "string") {
|
||||
return tag
|
||||
}
|
||||
if (tag["and"]) {
|
||||
return "{ and: " + simplify(tag["and"].map(simplify).join(" ; ") + " }")
|
||||
}
|
||||
if (tag["or"]) {
|
||||
return "{ or: " + simplify(tag["or"].map(simplify).join(" ; ") + " }")
|
||||
}
|
||||
function simplify(tag: TagConfigJson): string {
|
||||
if (typeof tag === "string") {
|
||||
return tag
|
||||
}
|
||||
|
||||
if (tag["and"]) {
|
||||
return "{ and: " + simplify(tag["and"].map(simplify).join(" ; ") + " }")
|
||||
}
|
||||
if (tag["or"]) {
|
||||
return "{ or: " + simplify(tag["or"].map(simplify).join(" ; ") + " }")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if mode === "editing"}
|
||||
<div class="interactive border-interactive">
|
||||
<h3>{schema.hints.question ?? "What tags should be applied?"}</h3>
|
||||
{schema.description}
|
||||
<FullTagInput {tag}/>
|
||||
<div class="flex justify-end">
|
||||
|
||||
<button class="primary w-fit" on:click={() => {mode = "set"}}>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
<div class="subtle">RegisteredTagInput based on schema: {JSON.stringify(schema)}</div>
|
||||
<div class="interactive border-interactive">
|
||||
<h3>{schema.hints.question ?? "What tags should be applied?"}</h3>
|
||||
{schema.description}
|
||||
<FullTagInput {tag} />
|
||||
<div class="flex justify-end">
|
||||
<button
|
||||
class="primary w-fit"
|
||||
on:click={() => {
|
||||
mode = "set"
|
||||
}}
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
<div class="subtle">RegisteredTagInput based on schema: {JSON.stringify(schema)}</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="low-interaction flex justify-between">
|
||||
<div>
|
||||
|
||||
{schema.path.at(-1)}
|
||||
{simplify($tag)}
|
||||
</div>
|
||||
<button
|
||||
on:click={() => {mode = "editing"}}
|
||||
class="secondary h-8 w-8 shrink-0 self-start rounded-full p-1"
|
||||
>
|
||||
<PencilAltIcon/>
|
||||
</button>
|
||||
<div class="low-interaction flex justify-between">
|
||||
<div>
|
||||
{schema.path.at(-1)}
|
||||
{simplify($tag)}
|
||||
</div>
|
||||
<button
|
||||
on:click={() => {
|
||||
mode = "editing"
|
||||
}}
|
||||
class="secondary h-8 w-8 shrink-0 self-start rounded-full p-1"
|
||||
>
|
||||
<PencilAltIcon />
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
@ -1,117 +1,114 @@
|
|||
<script lang="ts">
|
||||
import EditLayerState from "./EditLayerState";
|
||||
import type { ConfigMeta } from "./configMeta";
|
||||
import { UIEventSource } from "../../Logic/UIEventSource";
|
||||
import SchemaBasedInput from "./SchemaBasedInput.svelte";
|
||||
import SchemaBasedField from "./SchemaBasedField.svelte";
|
||||
import { TrashIcon } from "@babeard/svelte-heroicons/mini";
|
||||
import QuestionPreview from "./QuestionPreview.svelte";
|
||||
import SchemaBasedMultiType from "./SchemaBasedMultiType.svelte";
|
||||
import ShowConversionMessage from "./ShowConversionMessage.svelte";
|
||||
import EditLayerState from "./EditLayerState"
|
||||
import type { ConfigMeta } from "./configMeta"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import SchemaBasedInput from "./SchemaBasedInput.svelte"
|
||||
import SchemaBasedField from "./SchemaBasedField.svelte"
|
||||
import { TrashIcon } from "@babeard/svelte-heroicons/mini"
|
||||
import QuestionPreview from "./QuestionPreview.svelte"
|
||||
import SchemaBasedMultiType from "./SchemaBasedMultiType.svelte"
|
||||
import ShowConversionMessage from "./ShowConversionMessage.svelte"
|
||||
|
||||
export let state: EditLayerState;
|
||||
export let schema: ConfigMeta;
|
||||
export let state: EditLayerState
|
||||
export let schema: ConfigMeta
|
||||
|
||||
|
||||
let title = schema.path.at(-1);
|
||||
let singular = title;
|
||||
let title = schema.path.at(-1)
|
||||
let singular = title
|
||||
if (title?.endsWith("s")) {
|
||||
singular = title.slice(0, title.length - 1);
|
||||
singular = title.slice(0, title.length - 1)
|
||||
}
|
||||
let article = "a";
|
||||
let article = "a"
|
||||
if (singular?.match(/^[aeoui]/)) {
|
||||
article = "an";
|
||||
article = "an"
|
||||
}
|
||||
export let path: (string | number)[] = [];
|
||||
|
||||
const isTagRenderingBlock = path.length === 1 && path[0] === "tagRenderings";
|
||||
export let path: (string | number)[] = []
|
||||
|
||||
const isTagRenderingBlock = path.length === 1 && path[0] === "tagRenderings"
|
||||
|
||||
if (isTagRenderingBlock) {
|
||||
schema = { ...schema };
|
||||
schema.description = undefined;
|
||||
schema = { ...schema }
|
||||
schema.description = undefined
|
||||
}
|
||||
|
||||
const subparts: ConfigMeta = state.getSchemaStartingWith(schema.path)
|
||||
.filter(part => part.path.length - 1 === schema.path.length);
|
||||
|
||||
const subparts: ConfigMeta = state
|
||||
.getSchemaStartingWith(schema.path)
|
||||
.filter((part) => part.path.length - 1 === schema.path.length)
|
||||
|
||||
let messages = state.messagesFor(path)
|
||||
|
||||
|
||||
const currentValue : UIEventSource<any[]> = state.getStoreFor(path);
|
||||
if(currentValue.data === undefined){
|
||||
|
||||
const currentValue: UIEventSource<any[]> = state.getStoreFor(path)
|
||||
if (currentValue.data === undefined) {
|
||||
currentValue.setData([])
|
||||
}
|
||||
|
||||
function createItem(valueToSet?: any) {
|
||||
if(currentValue.data === undefined){
|
||||
if (currentValue.data === undefined) {
|
||||
currentValue.setData([])
|
||||
}
|
||||
currentValue.data.push(valueToSet)
|
||||
currentValue.ping()
|
||||
|
||||
if(isTagRenderingBlock){
|
||||
state.highlightedItem.setData({path: [...path, currentValue.data.length - 1], schema})
|
||||
|
||||
if (isTagRenderingBlock) {
|
||||
state.highlightedItem.setData({ path: [...path, currentValue.data.length - 1], schema })
|
||||
}
|
||||
}
|
||||
|
||||
function fusePath(i: number, subpartPath: string[]): (string | number)[] {
|
||||
const newPath = [...path, i];
|
||||
const toAdd = [...subpartPath];
|
||||
const newPath = [...path, i]
|
||||
const toAdd = [...subpartPath]
|
||||
for (const part of path) {
|
||||
if (toAdd[0] === part) {
|
||||
toAdd.splice(0, 1);
|
||||
}else{
|
||||
toAdd.splice(0, 1)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
newPath.push(...toAdd);
|
||||
console.log({newPath})
|
||||
return newPath;
|
||||
newPath.push(...toAdd)
|
||||
console.log({ newPath })
|
||||
return newPath
|
||||
}
|
||||
|
||||
function schemaForMultitype() {
|
||||
const sch = {...schema}
|
||||
const sch = { ...schema }
|
||||
sch.hints.typehint = undefined
|
||||
return sch
|
||||
}
|
||||
|
||||
|
||||
function del(i: number){
|
||||
|
||||
function del(i: number) {
|
||||
currentValue.data.splice(i, 1)
|
||||
currentValue.ping()
|
||||
}
|
||||
|
||||
|
||||
function swap(i: number, j: number) {
|
||||
const x = currentValue.data[i]
|
||||
currentValue.data[i] = currentValue.data[j]
|
||||
currentValue.data[i] = currentValue.data[j]
|
||||
currentValue.data[j] = x
|
||||
currentValue.ping()
|
||||
}
|
||||
|
||||
function moveTo(source: number, target: number){
|
||||
|
||||
function moveTo(source: number, target: number) {
|
||||
const x = currentValue.data[source]
|
||||
currentValue.data.splice(source, 1)
|
||||
currentValue.data.splice(target, 0, x)
|
||||
currentValue.ping()
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<div class="pl-2">
|
||||
<h3>{schema.path.at(-1)}</h3>
|
||||
|
||||
{#if subparts.length > 0}
|
||||
<span class="subtle">
|
||||
{schema.description}
|
||||
</span>
|
||||
<span class="subtle">
|
||||
{schema.description}
|
||||
</span>
|
||||
{/if}
|
||||
{#if $currentValue === undefined}
|
||||
No array defined
|
||||
No array defined
|
||||
{:else if $currentValue.length === 0}
|
||||
No values are defined
|
||||
{#if $messages.length > 0}
|
||||
{#each $messages as message}
|
||||
<ShowConversionMessage {message}/>
|
||||
<ShowConversionMessage {message} />
|
||||
{/each}
|
||||
{/if}
|
||||
{:else if subparts.length === 0}
|
||||
|
@ -119,52 +116,79 @@
|
|||
{#each $currentValue as value, i}
|
||||
<div class="flex w-full">
|
||||
<SchemaBasedField {state} {schema} path={[...path, i]} />
|
||||
<button class="border-black border rounded-full p-1 w-fit h-fit"
|
||||
on:click={() => {del(i)}}>
|
||||
<TrashIcon class="w-4 h-4" />
|
||||
<button
|
||||
class="h-fit w-fit rounded-full border border-black p-1"
|
||||
on:click={() => {
|
||||
del(i)
|
||||
}}
|
||||
>
|
||||
<TrashIcon class="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
{:else}
|
||||
{#each $currentValue as value, i}
|
||||
{#if !isTagRenderingBlock}
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="m-0">{singular} {i}</h3>
|
||||
<button class="border-black border rounded-full p-1 w-fit h-fit"
|
||||
on:click={() => {del(i)}}>
|
||||
<TrashIcon class="w-4 h-4" />
|
||||
<button
|
||||
class="h-fit w-fit rounded-full border border-black p-1"
|
||||
on:click={() => {
|
||||
del(i)
|
||||
}}
|
||||
>
|
||||
<TrashIcon class="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="border border-black">
|
||||
{#if isTagRenderingBlock}
|
||||
<QuestionPreview {state} path={[...path, i]} {schema}>
|
||||
<button on:click={() => {del(i)}}>
|
||||
<TrashIcon class="w-4 h-4" />
|
||||
<button
|
||||
on:click={() => {
|
||||
del(i)
|
||||
}}
|
||||
>
|
||||
<TrashIcon class="h-4 w-4" />
|
||||
Delete this question
|
||||
</button>
|
||||
|
||||
{#if i > 0}
|
||||
<button on:click={() => {moveTo(i, 0)}}>
|
||||
<button
|
||||
on:click={() => {
|
||||
moveTo(i, 0)
|
||||
}}
|
||||
>
|
||||
Move to front
|
||||
</button>
|
||||
|
||||
<button on:click={() => {swap(i, i-1)}}>
|
||||
<button
|
||||
on:click={() => {
|
||||
swap(i, i - 1)
|
||||
}}
|
||||
>
|
||||
Move up
|
||||
</button>
|
||||
{/if}
|
||||
{#if i + 1 < $currentValue.length}
|
||||
<button on:click={() => {swap(i, i+1)}}>
|
||||
<button
|
||||
on:click={() => {
|
||||
swap(i, i + 1)
|
||||
}}
|
||||
>
|
||||
Move down
|
||||
</button>
|
||||
<button on:click={() => {moveTo(i, $currentValue.length-1)}}>
|
||||
<button
|
||||
on:click={() => {
|
||||
moveTo(i, $currentValue.length - 1)
|
||||
}}
|
||||
>
|
||||
Move to back
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
</QuestionPreview>
|
||||
{:else if schema.hints.types}
|
||||
<SchemaBasedMultiType {state} path={fusePath(i, [])} schema={schemaForMultitype()}/>
|
||||
{:else if schema.hints.types}
|
||||
<SchemaBasedMultiType {state} path={fusePath(i, [])} schema={schemaForMultitype()} />
|
||||
{:else}
|
||||
{#each subparts as subpart}
|
||||
<SchemaBasedInput {state} path={fusePath(i, subpart.path)} schema={subpart} />
|
||||
|
@ -176,7 +200,13 @@
|
|||
<div class="flex">
|
||||
<button on:click={() => createItem()}>Add {article} {singular}</button>
|
||||
{#if path.length === 1 && path[0] === "tagRenderings"}
|
||||
<button on:click={() => {createItem("images");}}>Add a builtin tagRendering</button>
|
||||
<button
|
||||
on:click={() => {
|
||||
createItem("images")
|
||||
}}
|
||||
>
|
||||
Add a builtin tagRendering
|
||||
</button>
|
||||
{/if}
|
||||
<slot name="extra-button" />
|
||||
</div>
|
||||
|
|
|
@ -1,50 +1,55 @@
|
|||
<script lang="ts">
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import type { ConfigMeta } from "./configMeta"
|
||||
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte"
|
||||
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
|
||||
import nmd from "nano-markdown"
|
||||
import type { QuestionableTagRenderingConfigJson } from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
|
||||
import EditLayerState from "./EditLayerState"
|
||||
import { onDestroy } from "svelte"
|
||||
import type { JsonSchemaType } from "./jsonSchema"
|
||||
import { ConfigMetaUtils } from "./configMeta.ts"
|
||||
import ShowConversionMessage from "./ShowConversionMessage.svelte"
|
||||
|
||||
import { UIEventSource } from "../../Logic/UIEventSource";
|
||||
import type { ConfigMeta } from "./configMeta";
|
||||
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte";
|
||||
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig";
|
||||
import nmd from "nano-markdown";
|
||||
import type {
|
||||
QuestionableTagRenderingConfigJson
|
||||
} from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson";
|
||||
import EditLayerState from "./EditLayerState";
|
||||
import { onDestroy } from "svelte";
|
||||
import type { JsonSchemaType } from "./jsonSchema";
|
||||
import { ConfigMetaUtils } from "./configMeta.ts";
|
||||
import ShowConversionMessage from "./ShowConversionMessage.svelte";
|
||||
export let state: EditLayerState
|
||||
export let path: (string | number)[] = []
|
||||
export let schema: ConfigMeta
|
||||
export let startInEditModeIfUnset: boolean = schema.hints && !schema.hints.ifunset
|
||||
let value = new UIEventSource<string | any>(undefined)
|
||||
|
||||
export let state: EditLayerState;
|
||||
export let path: (string | number)[] = [];
|
||||
export let schema: ConfigMeta;
|
||||
export let startInEditModeIfUnset: boolean = schema.hints && !schema.hints.ifunset;
|
||||
let value = new UIEventSource<string | any>(undefined);
|
||||
const isTranslation =
|
||||
schema.hints?.typehint === "translation" ||
|
||||
schema.hints?.typehint === "rendered" ||
|
||||
ConfigMetaUtils.isTranslation(schema)
|
||||
let type = schema.hints.typehint ?? "string"
|
||||
|
||||
const isTranslation = schema.hints?.typehint === "translation" || schema.hints?.typehint === "rendered" || ConfigMetaUtils.isTranslation(schema);
|
||||
let type = schema.hints.typehint ?? "string";
|
||||
|
||||
let rendervalue = (schema.hints.inline ?? schema.path.join(".")) + (isTranslation ? " <b>{translated(value)}</b>" : " <b>{value}</b>");
|
||||
let rendervalue =
|
||||
(schema.hints.inline ?? schema.path.join(".")) +
|
||||
(isTranslation ? " <b>{translated(value)}</b>" : " <b>{value}</b>")
|
||||
|
||||
if (schema.type === "boolean") {
|
||||
rendervalue = undefined;
|
||||
rendervalue = undefined
|
||||
}
|
||||
if (schema.hints.typehint === "tag" || schema.hints.typehint === "simple_tag") {
|
||||
rendervalue = "{tags()}";
|
||||
rendervalue = "{tags()}"
|
||||
}
|
||||
|
||||
let helperArgs = schema.hints.typehelper?.split(",");
|
||||
let inline = schema.hints.inline !== undefined;
|
||||
let helperArgs = schema.hints.typehelper?.split(",")
|
||||
let inline = schema.hints.inline !== undefined
|
||||
if (isTranslation) {
|
||||
type = "translation";
|
||||
type = "translation"
|
||||
if (schema.hints.inline) {
|
||||
const inlineValue = schema.hints.inline;
|
||||
rendervalue = inlineValue;
|
||||
inline = false;
|
||||
helperArgs = [inlineValue.substring(0, inlineValue.indexOf("{")), inlineValue.substring(inlineValue.indexOf("}") + 1)];
|
||||
const inlineValue = schema.hints.inline
|
||||
rendervalue = inlineValue
|
||||
inline = false
|
||||
helperArgs = [
|
||||
inlineValue.substring(0, inlineValue.indexOf("{")),
|
||||
inlineValue.substring(inlineValue.indexOf("}") + 1),
|
||||
]
|
||||
}
|
||||
}
|
||||
if (type.endsWith("[]")) {
|
||||
type = type.substring(0, type.length - 2);
|
||||
type = type.substring(0, type.length - 2)
|
||||
}
|
||||
|
||||
const configJson: QuestionableTagRenderingConfigJson = {
|
||||
|
@ -52,125 +57,154 @@
|
|||
render: rendervalue,
|
||||
question: schema.hints.question,
|
||||
questionHint: nmd(schema.description),
|
||||
freeform: schema.type === "boolean" ? undefined : {
|
||||
key: "value",
|
||||
type,
|
||||
inline,
|
||||
helperArgs
|
||||
}
|
||||
};
|
||||
freeform:
|
||||
schema.type === "boolean"
|
||||
? undefined
|
||||
: {
|
||||
key: "value",
|
||||
type,
|
||||
inline,
|
||||
helperArgs,
|
||||
},
|
||||
}
|
||||
|
||||
if (schema.hints.default) {
|
||||
configJson.mappings = [{
|
||||
if: "value=", // We leave this blank
|
||||
then: path.at(-1) + " is not set. The default value <b>" + schema.hints.default + "</b> will be used. " + (schema.hints.ifunset ?? "")
|
||||
}];
|
||||
configJson.mappings = [
|
||||
{
|
||||
if: "value=", // We leave this blank
|
||||
then:
|
||||
path.at(-1) +
|
||||
" is not set. The default value <b>" +
|
||||
schema.hints.default +
|
||||
"</b> will be used. " +
|
||||
(schema.hints.ifunset ?? ""),
|
||||
},
|
||||
]
|
||||
} else if (!schema.required) {
|
||||
configJson.mappings = [{
|
||||
if: "value=",
|
||||
then: path.at(-1) + " is not set. " + (schema.hints.ifunset ?? "")
|
||||
}];
|
||||
configJson.mappings = [
|
||||
{
|
||||
if: "value=",
|
||||
then: path.at(-1) + " is not set. " + (schema.hints.ifunset ?? ""),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
function mightBeBoolean(type: undefined | JsonSchemaType): boolean {
|
||||
if (type === undefined) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
if (type["type"]) {
|
||||
type = type["type"];
|
||||
type = type["type"]
|
||||
}
|
||||
if (type === "boolean") {
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
if (!Array.isArray(type)) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
|
||||
return type.some(t => mightBeBoolean(t));
|
||||
return type.some((t) => mightBeBoolean(t))
|
||||
}
|
||||
|
||||
if (mightBeBoolean(schema.type)) {
|
||||
configJson.mappings = configJson.mappings ?? [];
|
||||
configJson.mappings = configJson.mappings ?? []
|
||||
configJson.mappings.push(
|
||||
{
|
||||
if: "value=true",
|
||||
then: schema.hints?.iftrue ?? "Yes"
|
||||
then: schema.hints?.iftrue ?? "Yes",
|
||||
},
|
||||
{
|
||||
if: "value=false",
|
||||
then: schema.hints?.iffalse ?? "No"
|
||||
then: schema.hints?.iffalse ?? "No",
|
||||
}
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
if (schema.hints.suggestions) {
|
||||
if (!configJson.mappings) {
|
||||
configJson.mappings = [];
|
||||
configJson.mappings = []
|
||||
}
|
||||
configJson.mappings.push(...schema.hints.suggestions);
|
||||
configJson.mappings.push(...schema.hints.suggestions)
|
||||
}
|
||||
let config: TagRenderingConfig;
|
||||
let err: string = undefined;
|
||||
let messages = state.messagesFor(path);
|
||||
let config: TagRenderingConfig
|
||||
let err: string = undefined
|
||||
let messages = state.messagesFor(path)
|
||||
try {
|
||||
config = new TagRenderingConfig(configJson, "config based on " + schema.path.join("."));
|
||||
config = new TagRenderingConfig(configJson, "config based on " + schema.path.join("."))
|
||||
} catch (e) {
|
||||
console.error(e, config);
|
||||
err = path.join(".") + " " + e;
|
||||
console.error(e, config)
|
||||
err = path.join(".") + " " + e
|
||||
}
|
||||
let startValue = state.getCurrentValueFor(path);
|
||||
let startInEditMode = !startValue && startInEditModeIfUnset;
|
||||
const tags = new UIEventSource<Record<string, string>>({ value: startValue });
|
||||
let startValue = state.getCurrentValueFor(path)
|
||||
let startInEditMode = !startValue && startInEditModeIfUnset
|
||||
const tags = new UIEventSource<Record<string, string>>({ value: startValue })
|
||||
try {
|
||||
onDestroy(state.register(path, tags.map(tgs => {
|
||||
const v = tgs["value"];
|
||||
if (typeof v !== "string") {
|
||||
return { ...v };
|
||||
}
|
||||
if (schema.type === "boolan") {
|
||||
return v === "true" || v === "yes" || v === "1";
|
||||
}
|
||||
if (mightBeBoolean(schema.type)) {
|
||||
if (v === "true" || v === "yes" || v === "1") {
|
||||
return true;
|
||||
}
|
||||
if (v === "false" || v === "no" || v === "0") {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (schema.type === "number") {
|
||||
if (v === "") {
|
||||
return undefined;
|
||||
}
|
||||
return Number(v);
|
||||
}
|
||||
if (isTranslation && typeof v === "string") {
|
||||
if (v === "") {
|
||||
return {};
|
||||
}
|
||||
return JSON.parse(v);
|
||||
}
|
||||
return v;
|
||||
}), isTranslation));
|
||||
onDestroy(
|
||||
state.register(
|
||||
path,
|
||||
tags.map((tgs) => {
|
||||
const v = tgs["value"]
|
||||
if (typeof v !== "string") {
|
||||
return { ...v }
|
||||
}
|
||||
if (schema.type === "boolan") {
|
||||
return v === "true" || v === "yes" || v === "1"
|
||||
}
|
||||
if (mightBeBoolean(schema.type)) {
|
||||
if (v === "true" || v === "yes" || v === "1") {
|
||||
return true
|
||||
}
|
||||
if (v === "false" || v === "no" || v === "0") {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if (schema.type === "number") {
|
||||
if (v === "") {
|
||||
return undefined
|
||||
}
|
||||
return Number(v)
|
||||
}
|
||||
if (isTranslation && typeof v === "string") {
|
||||
if (v === "") {
|
||||
return {}
|
||||
}
|
||||
return JSON.parse(v)
|
||||
}
|
||||
return v
|
||||
}),
|
||||
isTranslation
|
||||
)
|
||||
)
|
||||
} catch (e) {
|
||||
console.error("Could not register", path, "due to", e);
|
||||
console.error("Could not register", path, "due to", e)
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if err !== undefined}
|
||||
<span class="alert">{err}</span>
|
||||
{:else}
|
||||
<div class="w-full flex flex-col">
|
||||
<TagRenderingEditable editMode={startInEditMode} {config} selectedElement={undefined} showQuestionIfUnknown={true}
|
||||
{state} {tags} />
|
||||
<div class="flex w-full flex-col">
|
||||
<TagRenderingEditable
|
||||
editMode={startInEditMode}
|
||||
{config}
|
||||
selectedElement={undefined}
|
||||
showQuestionIfUnknown={true}
|
||||
{state}
|
||||
{tags}
|
||||
/>
|
||||
{#if $messages.length > 0}
|
||||
{#each $messages as message}
|
||||
<ShowConversionMessage {message} />
|
||||
{/each}
|
||||
{/if}
|
||||
{#if window.location.hostname === "127.0.0.1"}
|
||||
<span class="subtle" on:click={() => console.log(schema)}>SchemaBasedField <b>{path.join(".")}</b> <span class="cursor-pointer"
|
||||
on:click={() => console.log(schema)}>{schema.hints.typehint}</span> Group: {schema.hints.group}</span>
|
||||
<span class="subtle" on:click={() => console.log(schema)}>
|
||||
SchemaBasedField <b>{path.join(".")}</b>
|
||||
<span class="cursor-pointer" on:click={() => console.log(schema)}>
|
||||
{schema.hints.typehint}
|
||||
</span>
|
||||
Group: {schema.hints.group}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
<script lang="ts">
|
||||
import type { ConfigMeta } from "./configMeta";
|
||||
import SchemaBasedField from "./SchemaBasedField.svelte";
|
||||
import EditLayerState from "./EditLayerState";
|
||||
import SchemaBasedArray from "./SchemaBasedArray.svelte";
|
||||
import SchemaBasedMultiType from "./SchemaBasedMultiType.svelte";
|
||||
import ArrayMultiAnswer from "./ArrayMultiAnswer.svelte";
|
||||
import type { ConfigMeta } from "./configMeta"
|
||||
import SchemaBasedField from "./SchemaBasedField.svelte"
|
||||
import EditLayerState from "./EditLayerState"
|
||||
import SchemaBasedArray from "./SchemaBasedArray.svelte"
|
||||
import SchemaBasedMultiType from "./SchemaBasedMultiType.svelte"
|
||||
import ArrayMultiAnswer from "./ArrayMultiAnswer.svelte"
|
||||
|
||||
export let schema: ConfigMeta;
|
||||
export let state: EditLayerState;
|
||||
export let path: (string | number)[] = [];
|
||||
let expertMode = state.expertMode;
|
||||
export let schema: ConfigMeta
|
||||
export let state: EditLayerState
|
||||
export let path: (string | number)[] = []
|
||||
let expertMode = state.expertMode
|
||||
</script>
|
||||
|
||||
{#if (schema.hints?.group !== "expert" || $expertMode) && schema.hints.group !== "hidden"}
|
||||
{#if schema.hints?.typehint?.endsWith("[]")}
|
||||
<!-- We cheat a bit here by matching this 'magical' type... -->
|
||||
|
@ -25,5 +26,7 @@
|
|||
<SchemaBasedField {path} {state} {schema} />
|
||||
{/if}
|
||||
{:else if window.location.hostname === "127.0.0.1"}
|
||||
<div class="subtle">Not showing SBI {schema.path.join(".")} due to group {schema.hints.group}</div>
|
||||
<div class="subtle">
|
||||
Not showing SBI {schema.path.join(".")} due to group {schema.hints.group}
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
@ -1,223 +1,236 @@
|
|||
<script lang="ts">
|
||||
|
||||
import EditLayerState from "./EditLayerState";
|
||||
import type { ConfigMeta } from "./configMeta";
|
||||
import { UIEventSource } from "../../Logic/UIEventSource";
|
||||
import type {
|
||||
QuestionableTagRenderingConfigJson
|
||||
} from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson";
|
||||
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte";
|
||||
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig";
|
||||
import { onDestroy } from "svelte";
|
||||
import SchemaBasedInput from "./SchemaBasedInput.svelte";
|
||||
import type { JsonSchemaType } from "./jsonSchema";
|
||||
import EditLayerState from "./EditLayerState"
|
||||
import type { ConfigMeta } from "./configMeta"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import type { QuestionableTagRenderingConfigJson } from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
|
||||
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte"
|
||||
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
|
||||
import { onDestroy } from "svelte"
|
||||
import SchemaBasedInput from "./SchemaBasedInput.svelte"
|
||||
import type { JsonSchemaType } from "./jsonSchema"
|
||||
// @ts-ignore
|
||||
import nmd from "nano-markdown";
|
||||
import ShowConversionMessage from "./ShowConversionMessage.svelte";
|
||||
import nmd from "nano-markdown"
|
||||
import ShowConversionMessage from "./ShowConversionMessage.svelte"
|
||||
|
||||
/**
|
||||
* If 'types' is defined: allow the user to pick one of the types to input.
|
||||
*/
|
||||
|
||||
export let state: EditLayerState;
|
||||
export let path: (string | number)[] = [];
|
||||
export let schema: ConfigMeta;
|
||||
let expertMode = state.expertMode;
|
||||
const defaultOption = schema.hints.typesdefault ? Number(schema.hints.typesdefault) : undefined;
|
||||
export let state: EditLayerState
|
||||
export let path: (string | number)[] = []
|
||||
export let schema: ConfigMeta
|
||||
let expertMode = state.expertMode
|
||||
const defaultOption = schema.hints.typesdefault ? Number(schema.hints.typesdefault) : undefined
|
||||
|
||||
const hasBooleanOption = (<JsonSchemaType[]>schema.type)?.findIndex(t => t["type"] === "boolean");
|
||||
const types = schema.hints.types.split(";");
|
||||
const hasBooleanOption = (<JsonSchemaType[]>schema.type)?.findIndex(
|
||||
(t) => t["type"] === "boolean"
|
||||
)
|
||||
const types = schema.hints.types.split(";")
|
||||
if (hasBooleanOption >= 0) {
|
||||
types.splice(hasBooleanOption);
|
||||
types.splice(hasBooleanOption)
|
||||
}
|
||||
|
||||
|
||||
let lastIsString = false;
|
||||
let lastIsString = false
|
||||
{
|
||||
const types: string | string[] = Array.isArray(schema.type) ? schema.type[schema.type.length - 1].type : [];
|
||||
lastIsString = types === "string" || (Array.isArray(types) && types.some(i => i === "string"));
|
||||
const types: string | string[] = Array.isArray(schema.type)
|
||||
? schema.type[schema.type.length - 1].type
|
||||
: []
|
||||
lastIsString = types === "string" || (Array.isArray(types) && types.some((i) => i === "string"))
|
||||
}
|
||||
|
||||
if (lastIsString) {
|
||||
types.splice(types.length - 1, 1);
|
||||
types.splice(types.length - 1, 1)
|
||||
}
|
||||
const configJson: QuestionableTagRenderingConfigJson = {
|
||||
id: "TYPE_OF:" + path.join("_"),
|
||||
question: "Which subcategory is needed for " + schema.path.at(-1) + "?",
|
||||
questionHint: nmd(schema.description),
|
||||
mappings: types.map(opt => opt.trim()).filter(opt => opt.length > 0).map((opt, i) => ({
|
||||
if: "chosen_type_index=" + i,
|
||||
addExtraTags: ["value="],
|
||||
then: opt + (i === defaultOption ? " (Default)" : "")
|
||||
})),
|
||||
render: !lastIsString ? undefined : (schema.hints.inline ?? "Use a hardcoded value: <b>{value}</b>"),
|
||||
freeform: !lastIsString ? undefined : {
|
||||
key: "value",
|
||||
inline: true,
|
||||
type: schema.hints.typehint,
|
||||
addExtraTags: ["chosen_type_index="]
|
||||
}
|
||||
};
|
||||
let tags = new UIEventSource<Record<string, string>>({});
|
||||
mappings: types
|
||||
.map((opt) => opt.trim())
|
||||
.filter((opt) => opt.length > 0)
|
||||
.map((opt, i) => ({
|
||||
if: "chosen_type_index=" + i,
|
||||
addExtraTags: ["value="],
|
||||
then: opt + (i === defaultOption ? " (Default)" : ""),
|
||||
})),
|
||||
render: !lastIsString
|
||||
? undefined
|
||||
: schema.hints.inline ?? "Use a hardcoded value: <b>{value}</b>",
|
||||
freeform: !lastIsString
|
||||
? undefined
|
||||
: {
|
||||
key: "value",
|
||||
inline: true,
|
||||
type: schema.hints.typehint,
|
||||
addExtraTags: ["chosen_type_index="],
|
||||
},
|
||||
}
|
||||
let tags = new UIEventSource<Record<string, string>>({})
|
||||
|
||||
if (schema.hints.ifunset) {
|
||||
configJson.mappings.push(
|
||||
{
|
||||
if: { and: ["value=", "chosen_type_index="] },
|
||||
then: schema.hints.ifunset
|
||||
}
|
||||
);
|
||||
configJson.mappings.push({
|
||||
if: { and: ["value=", "chosen_type_index="] },
|
||||
then: schema.hints.ifunset,
|
||||
})
|
||||
}
|
||||
if (schema.hints.suggestions) {
|
||||
configJson.mappings.push(...schema.hints.suggestions);
|
||||
configJson.mappings.push(...schema.hints.suggestions)
|
||||
}
|
||||
|
||||
if (hasBooleanOption >= 0) {
|
||||
configJson.mappings.unshift(
|
||||
{
|
||||
if: "value=true",
|
||||
then: (schema.hints.iftrue ?? "Yes"),
|
||||
addExtraTags: ["chosen_type_index="]
|
||||
then: schema.hints.iftrue ?? "Yes",
|
||||
addExtraTags: ["chosen_type_index="],
|
||||
},
|
||||
{
|
||||
if: "value=false",
|
||||
then: (schema.hints.iffalse ?? "No"),
|
||||
addExtraTags: ["chosen_type_index="]
|
||||
then: schema.hints.iffalse ?? "No",
|
||||
addExtraTags: ["chosen_type_index="],
|
||||
}
|
||||
);
|
||||
)
|
||||
}
|
||||
const config = new TagRenderingConfig(configJson, "config based on " + schema.path.join("."));
|
||||
let chosenOption: number = (defaultOption);
|
||||
const config = new TagRenderingConfig(configJson, "config based on " + schema.path.join("."))
|
||||
let chosenOption: number = defaultOption
|
||||
|
||||
|
||||
const existingValue = state.getCurrentValueFor(path);
|
||||
let hasOverride = existingValue?.override !== undefined;
|
||||
const existingValue = state.getCurrentValueFor(path)
|
||||
let hasOverride = existingValue?.override !== undefined
|
||||
if (hasBooleanOption >= 0 && (existingValue === true || existingValue === false)) {
|
||||
tags.setData({ value: "" + existingValue });
|
||||
tags.setData({ value: "" + existingValue })
|
||||
} else if (lastIsString && typeof existingValue === "string") {
|
||||
tags.setData({ value: existingValue });
|
||||
chosenOption = undefined;
|
||||
tags.setData({ value: existingValue })
|
||||
chosenOption = undefined
|
||||
} else if (existingValue) {
|
||||
// We found an existing value. Let's figure out what type it matches and select that one
|
||||
// We run over all possibilities and check what is required
|
||||
const possibleTypes: { index: number, matchingPropertiesCount: number, optionalMatches: number }[] = [];
|
||||
const possibleTypes: {
|
||||
index: number
|
||||
matchingPropertiesCount: number
|
||||
optionalMatches: number
|
||||
}[] = []
|
||||
outer: for (let i = 0; i < (<[]>schema.type).length; i++) {
|
||||
const type = schema.type[i];
|
||||
let optionalMatches = 0;
|
||||
const type = schema.type[i]
|
||||
let optionalMatches = 0
|
||||
for (const key of Object.keys(type.properties ?? {})) {
|
||||
if (!!existingValue[key]) {
|
||||
optionalMatches++;
|
||||
optionalMatches++
|
||||
}
|
||||
}
|
||||
if (type.required) {
|
||||
let numberOfMatches = 0;
|
||||
let numberOfMatches = 0
|
||||
|
||||
for (const requiredAttribute of type.required) {
|
||||
if (existingValue[requiredAttribute] === undefined) {
|
||||
// The 'existingValue' does _not_ have this required attribute, so it cannot be of this type
|
||||
continue outer;
|
||||
continue outer
|
||||
}
|
||||
numberOfMatches++;
|
||||
numberOfMatches++
|
||||
}
|
||||
possibleTypes.push({ index: i, matchingPropertiesCount: numberOfMatches, optionalMatches });
|
||||
possibleTypes.push({ index: i, matchingPropertiesCount: numberOfMatches, optionalMatches })
|
||||
} else {
|
||||
possibleTypes.push({ index: i, matchingPropertiesCount: 0, optionalMatches });
|
||||
possibleTypes.push({ index: i, matchingPropertiesCount: 0, optionalMatches })
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
possibleTypes.sort((a, b) => b.optionalMatches - a.optionalMatches);
|
||||
possibleTypes.sort((a, b) => b.matchingPropertiesCount - a.matchingPropertiesCount);
|
||||
possibleTypes.sort((a, b) => b.optionalMatches - a.optionalMatches)
|
||||
possibleTypes.sort((a, b) => b.matchingPropertiesCount - a.matchingPropertiesCount)
|
||||
if (possibleTypes.length > 0) {
|
||||
chosenOption = possibleTypes[0].index;
|
||||
tags.setData({ chosen_type_index: "" + chosenOption });
|
||||
|
||||
chosenOption = possibleTypes[0].index
|
||||
tags.setData({ chosen_type_index: "" + chosenOption })
|
||||
}
|
||||
} else if (defaultOption !== undefined) {
|
||||
tags.setData({ chosen_type_index: "" + defaultOption });
|
||||
tags.setData({ chosen_type_index: "" + defaultOption })
|
||||
} else {
|
||||
chosenOption = defaultOption;
|
||||
chosenOption = defaultOption
|
||||
}
|
||||
|
||||
if (hasBooleanOption >= 0 || lastIsString) {
|
||||
|
||||
const directValue = tags.mapD(tags => {
|
||||
const directValue = tags.mapD((tags) => {
|
||||
if (tags["chosen_type_index"]) {
|
||||
return "";
|
||||
return ""
|
||||
}
|
||||
if (lastIsString) {
|
||||
return tags["value"];
|
||||
return tags["value"]
|
||||
}
|
||||
return tags["value"] === "true";
|
||||
});
|
||||
onDestroy(state.register(path, directValue));
|
||||
return tags["value"] === "true"
|
||||
})
|
||||
onDestroy(state.register(path, directValue))
|
||||
}
|
||||
|
||||
let subSchemas: ConfigMeta[] = [];
|
||||
let subSchemas: ConfigMeta[] = []
|
||||
|
||||
let subpath = path;
|
||||
const store = state.getStoreFor(path);
|
||||
onDestroy(tags.addCallbackAndRun(tags => {
|
||||
if (tags["value"] !== undefined && tags["value"] !== "") {
|
||||
chosenOption = undefined;
|
||||
return;
|
||||
}
|
||||
const oldOption = chosenOption;
|
||||
chosenOption = tags["chosen_type_index"] ? Number(tags["chosen_type_index"]) : defaultOption;
|
||||
const type = schema.type[chosenOption];
|
||||
if (chosenOption !== oldOption) {
|
||||
// Reset the values beneath
|
||||
subSchemas = [];
|
||||
const o = state.getCurrentValueFor(path) ?? {};
|
||||
for (const key of type?.required ?? []) {
|
||||
o[key] ??= {};
|
||||
let subpath = path
|
||||
const store = state.getStoreFor(path)
|
||||
onDestroy(
|
||||
tags.addCallbackAndRun((tags) => {
|
||||
if (tags["value"] !== undefined && tags["value"] !== "") {
|
||||
chosenOption = undefined
|
||||
return
|
||||
}
|
||||
store.setData(o);
|
||||
}
|
||||
if (!type) {
|
||||
return;
|
||||
}
|
||||
subpath = path;
|
||||
const cleanPath = <string[]>path.filter(p => typeof p === "string");
|
||||
if (type["$ref"] === "#/definitions/Record<string,string>") {
|
||||
// The subtype is a translation object
|
||||
const schema = state.getTranslationAt(cleanPath);
|
||||
subSchemas.push(schema);
|
||||
subpath = path.slice(0, path.length - 2);
|
||||
return;
|
||||
}
|
||||
if (!type.properties) {
|
||||
return;
|
||||
}
|
||||
for (const crumble of Object.keys(type.properties)) {
|
||||
subSchemas.push(...(state.getSchema([...cleanPath, crumble])));
|
||||
}
|
||||
}));
|
||||
let messages = state.messagesFor(path);
|
||||
|
||||
const oldOption = chosenOption
|
||||
chosenOption = tags["chosen_type_index"] ? Number(tags["chosen_type_index"]) : defaultOption
|
||||
const type = schema.type[chosenOption]
|
||||
if (chosenOption !== oldOption) {
|
||||
// Reset the values beneath
|
||||
subSchemas = []
|
||||
const o = state.getCurrentValueFor(path) ?? {}
|
||||
for (const key of type?.required ?? []) {
|
||||
o[key] ??= {}
|
||||
}
|
||||
store.setData(o)
|
||||
}
|
||||
if (!type) {
|
||||
return
|
||||
}
|
||||
subpath = path
|
||||
const cleanPath = <string[]>path.filter((p) => typeof p === "string")
|
||||
if (type["$ref"] === "#/definitions/Record<string,string>") {
|
||||
// The subtype is a translation object
|
||||
const schema = state.getTranslationAt(cleanPath)
|
||||
subSchemas.push(schema)
|
||||
subpath = path.slice(0, path.length - 2)
|
||||
return
|
||||
}
|
||||
if (!type.properties) {
|
||||
return
|
||||
}
|
||||
for (const crumble of Object.keys(type.properties)) {
|
||||
subSchemas.push(...state.getSchema([...cleanPath, crumble]))
|
||||
}
|
||||
})
|
||||
)
|
||||
let messages = state.messagesFor(path)
|
||||
</script>
|
||||
|
||||
<div class="p-2 border-2 border-dashed border-gray-300 flex flex-col gap-y-2 m-1">
|
||||
<div class="m-1 flex flex-col gap-y-2 border-2 border-dashed border-gray-300 p-2">
|
||||
{#if schema.hints.title !== undefined}
|
||||
<h3>{schema.hints.title}</h3>
|
||||
<div> {schema.description} </div>
|
||||
<div>{schema.description}</div>
|
||||
{/if}
|
||||
{#if hasOverride}
|
||||
This object refers to {existingValue.builtin} and overrides some properties. This cannot be edited with MapComplete
|
||||
Studio
|
||||
This object refers to {existingValue.builtin} and overrides some properties. This cannot be edited
|
||||
with MapComplete Studio
|
||||
{:else}
|
||||
<div>
|
||||
<TagRenderingEditable {config} selectedElement={undefined} showQuestionIfUnknown={!schema.hints?.ifunset} {state} {tags} />
|
||||
<TagRenderingEditable
|
||||
{config}
|
||||
selectedElement={undefined}
|
||||
showQuestionIfUnknown={!schema.hints?.ifunset}
|
||||
{state}
|
||||
{tags}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if chosenOption !== undefined}
|
||||
{#each subSchemas as subschema}
|
||||
{#if $expertMode || subschema.hints?.group !== "expert"}
|
||||
<SchemaBasedInput {state} schema={subschema}
|
||||
path={[...subpath, (subschema?.path?.at(-1) ?? "???")]}></SchemaBasedInput>
|
||||
<SchemaBasedInput
|
||||
{state}
|
||||
schema={subschema}
|
||||
path={[...subpath, subschema?.path?.at(-1) ?? "???"]}
|
||||
/>
|
||||
{:else if window.location.hostname === "127.0.0.1"}
|
||||
<span class="subtle">Omitted expert question {subschema.path.join(".")}</span>
|
||||
|
||||
{/if}
|
||||
{/each}
|
||||
{:else if $messages.length > 0}
|
||||
|
@ -227,7 +240,11 @@
|
|||
{/if}
|
||||
{/if}
|
||||
{#if window.location.hostname === "127.0.0.1"}
|
||||
<span class="subtle">SchemaBasedMultiType <b>{path.join(".")}</b> <span class="cursor-pointer"
|
||||
on:click={() => console.log(schema)}>{schema.hints.typehint}</span></span>
|
||||
<span class="subtle">
|
||||
SchemaBasedMultiType <b>{path.join(".")}</b>
|
||||
<span class="cursor-pointer" on:click={() => console.log(schema)}>
|
||||
{schema.hints.typehint}
|
||||
</span>
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
@ -1,16 +1,19 @@
|
|||
<script lang="ts">
|
||||
import EditLayerState from "./EditLayerState";
|
||||
import type { ConfigMeta } from "./configMeta";
|
||||
import { UIEventSource } from "../../Logic/UIEventSource";
|
||||
import TranslationInput from "../InputElement/Helpers/TranslationInput.svelte";
|
||||
import EditLayerState from "./EditLayerState"
|
||||
import type { ConfigMeta } from "./configMeta"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import TranslationInput from "../InputElement/Helpers/TranslationInput.svelte"
|
||||
|
||||
export let state: EditLayerState;
|
||||
export let path: (string | number)[] = [];
|
||||
export let schema: ConfigMeta;
|
||||
export let state: EditLayerState
|
||||
export let path: (string | number)[] = []
|
||||
export let schema: ConfigMeta
|
||||
|
||||
let value = new UIEventSource<string>({});
|
||||
let value = new UIEventSource<string>({})
|
||||
console.log("Registering translation to path", path)
|
||||
state.register(path, value.mapD(v => JSON.parse(value.data )));
|
||||
state.register(
|
||||
path,
|
||||
value.mapD((v) => JSON.parse(value.data))
|
||||
)
|
||||
</script>
|
||||
|
||||
<TranslationInput {value} />
|
||||
|
|
|
@ -1,29 +1,29 @@
|
|||
<script lang="ts">
|
||||
import type { ConversionMessage } from "../../Models/ThemeConfig/Conversion/Conversion";
|
||||
import { ExclamationIcon, InformationCircleIcon } from "@rgossiaux/svelte-heroicons/solid";
|
||||
import type { ConversionMessage } from "../../Models/ThemeConfig/Conversion/Conversion"
|
||||
import { ExclamationIcon, InformationCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
|
||||
/**
|
||||
* Single conversion message, styled depending on the type
|
||||
*/
|
||||
export let message: ConversionMessage;
|
||||
export let message: ConversionMessage
|
||||
</script>
|
||||
|
||||
{#if message.level === "error"}
|
||||
<div class="alert flex justify-between items-center">
|
||||
<ExclamationIcon class="w-6 h-6 mx-1 shrink-0" />
|
||||
<div class="alert flex items-center justify-between">
|
||||
<ExclamationIcon class="mx-1 h-6 w-6 shrink-0" />
|
||||
{message.message}
|
||||
<div/>
|
||||
<div />
|
||||
</div>
|
||||
{:else if message.level === "warning"}
|
||||
<div class="warning flex justify-between items-center">
|
||||
<ExclamationIcon class="w-6 h-6 mx-1 shrink-0" />
|
||||
<div class="warning flex items-center justify-between">
|
||||
<ExclamationIcon class="mx-1 h-6 w-6 shrink-0" />
|
||||
{message.message}
|
||||
<div/>
|
||||
<div />
|
||||
</div>
|
||||
{:else if message.level === "information"}
|
||||
<div class="information flex justify-between items-center">
|
||||
<InformationCircleIcon class="w-6 h-6 mx-1 shrink-0" />
|
||||
<div class="information flex items-center justify-between">
|
||||
<InformationCircleIcon class="mx-1 h-6 w-6 shrink-0" />
|
||||
{message.message}
|
||||
<div/>
|
||||
<div />
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
<script lang="ts">
|
||||
import type { ConversionMessage } from "../../Models/ThemeConfig/Conversion/Conversion";
|
||||
import type { ConversionMessage } from "../../Models/ThemeConfig/Conversion/Conversion"
|
||||
|
||||
export let messages: ConversionMessage[];
|
||||
export let messages: ConversionMessage[]
|
||||
</script>
|
||||
|
||||
{#if messages.length === 0}
|
||||
<div class="thanks">
|
||||
No errors, warnings or messages
|
||||
</div>
|
||||
{/if}
|
||||
<div class="thanks">No errors, warnings or messages</div>
|
||||
{/if}
|
||||
|
||||
{#each messages as message}
|
||||
<li>
|
||||
|
@ -16,7 +14,7 @@
|
|||
<span class="literal-code">{message.context.path.join(".")}</span>
|
||||
{message.message}
|
||||
<span class="literal-code">
|
||||
{message.context.operation.join(".")}
|
||||
</span>
|
||||
{message.context.operation.join(".")}
|
||||
</span>
|
||||
</li>
|
||||
{/each}
|
||||
|
|
|
@ -1,134 +1,130 @@
|
|||
<script lang="ts">/**
|
||||
* Allows to create `and` and `or` expressions graphically
|
||||
*/
|
||||
<script lang="ts">
|
||||
/**
|
||||
* Allows to create `and` and `or` expressions graphically
|
||||
*/
|
||||
|
||||
import { UIEventSource } from "../../Logic/UIEventSource";
|
||||
import type { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson";
|
||||
import BasicTagInput from "./TagInput/BasicTagInput.svelte";
|
||||
import FullTagInput from "./TagInput/FullTagInput.svelte";
|
||||
import { TrashIcon } from "@babeard/svelte-heroicons/mini";
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import type { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson"
|
||||
import BasicTagInput from "./TagInput/BasicTagInput.svelte"
|
||||
import FullTagInput from "./TagInput/FullTagInput.svelte"
|
||||
import { TrashIcon } from "@babeard/svelte-heroicons/mini"
|
||||
|
||||
export let tag: UIEventSource<TagConfigJson>;
|
||||
let mode: "and" | "or" = "and";
|
||||
export let tag: UIEventSource<TagConfigJson>
|
||||
let mode: "and" | "or" = "and"
|
||||
|
||||
let basicTags: UIEventSource<UIEventSource<string>[]> = new UIEventSource([]);
|
||||
let basicTags: UIEventSource<UIEventSource<string>[]> = new UIEventSource([])
|
||||
|
||||
/**
|
||||
* Sub-expressions
|
||||
*/
|
||||
let expressions: UIEventSource<UIEventSource<TagConfigJson>[]> = new UIEventSource([]);
|
||||
/**
|
||||
* Sub-expressions
|
||||
*/
|
||||
let expressions: UIEventSource<UIEventSource<TagConfigJson>[]> = new UIEventSource([])
|
||||
|
||||
export let uploadableOnly: boolean;
|
||||
export let overpassSupportNeeded: boolean;
|
||||
export let uploadableOnly: boolean
|
||||
export let overpassSupportNeeded: boolean
|
||||
|
||||
export let silent: boolean;
|
||||
export let silent: boolean
|
||||
|
||||
function update(_) {
|
||||
let config: TagConfigJson = <any>{};
|
||||
if (!mode) {
|
||||
return;
|
||||
}
|
||||
const tags = [];
|
||||
|
||||
const subpartSources = (<UIEventSource<string | TagConfigJson>[]>basicTags.data).concat(expressions.data);
|
||||
for (const src of subpartSources) {
|
||||
const t = src.data;
|
||||
if (!t) {
|
||||
// We indicate upstream that this value is invalid
|
||||
tag.setData(undefined);
|
||||
return;
|
||||
function update(_) {
|
||||
let config: TagConfigJson = <any>{}
|
||||
if (!mode) {
|
||||
return
|
||||
}
|
||||
const tags = []
|
||||
|
||||
const subpartSources = (<UIEventSource<string | TagConfigJson>[]>basicTags.data).concat(
|
||||
expressions.data
|
||||
)
|
||||
for (const src of subpartSources) {
|
||||
const t = src.data
|
||||
if (!t) {
|
||||
// We indicate upstream that this value is invalid
|
||||
tag.setData(undefined)
|
||||
return
|
||||
}
|
||||
tags.push(t)
|
||||
}
|
||||
if (tags.length === 1) {
|
||||
tag.setData(tags[0])
|
||||
} else {
|
||||
config[mode] = tags
|
||||
tag.setData(config)
|
||||
}
|
||||
tags.push(t);
|
||||
}
|
||||
if (tags.length === 1) {
|
||||
tag.setData(tags[0]);
|
||||
|
||||
function addBasicTag(value?: string) {
|
||||
const src = new UIEventSource(value)
|
||||
basicTags.data.push(src)
|
||||
basicTags.ping()
|
||||
src.addCallbackAndRunD((_) => update(_))
|
||||
}
|
||||
|
||||
function removeTag(basicTag: UIEventSource<any>) {
|
||||
const index = basicTags.data.indexOf(basicTag)
|
||||
console.log("Removing", index, basicTag)
|
||||
if (index >= 0) {
|
||||
basicTag.setData(undefined)
|
||||
basicTags.data.splice(index, 1)
|
||||
basicTags.ping()
|
||||
}
|
||||
}
|
||||
|
||||
function removeExpression(expr: UIEventSource<any>) {
|
||||
const index = expressions.data.indexOf(expr)
|
||||
if (index >= 0) {
|
||||
expr.setData(undefined)
|
||||
expressions.data.splice(index, 1)
|
||||
expressions.ping()
|
||||
}
|
||||
}
|
||||
|
||||
function addExpression(expr?: TagConfigJson) {
|
||||
const src = new UIEventSource(expr)
|
||||
expressions.data.push(src)
|
||||
expressions.ping()
|
||||
src.addCallbackAndRunD((_) => update(_))
|
||||
}
|
||||
|
||||
$: update(mode)
|
||||
expressions.addCallback((_) => update(_))
|
||||
basicTags.addCallback((_) => update(_))
|
||||
|
||||
let initialTag: TagConfigJson = tag.data
|
||||
|
||||
function initWith(initialTag: TagConfigJson) {
|
||||
if (typeof initialTag === "string") {
|
||||
if (initialTag.startsWith("{")) {
|
||||
initialTag = JSON.parse(initialTag)
|
||||
} else {
|
||||
addBasicTag(initialTag)
|
||||
return
|
||||
}
|
||||
}
|
||||
mode = <"or" | "and">Object.keys(initialTag)[0]
|
||||
const subExprs = <TagConfigJson[]>initialTag[mode]
|
||||
if (!subExprs || subExprs.length == 0) {
|
||||
return
|
||||
}
|
||||
if (subExprs.length == 1) {
|
||||
initWith(subExprs[0])
|
||||
return
|
||||
}
|
||||
for (const subExpr of subExprs) {
|
||||
if (typeof subExpr === "string") {
|
||||
addBasicTag(subExpr)
|
||||
} else {
|
||||
addExpression(subExpr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!initialTag) {
|
||||
addBasicTag()
|
||||
} else {
|
||||
config[mode] = tags;
|
||||
tag.setData(config);
|
||||
initWith(initialTag)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function addBasicTag(value?: string) {
|
||||
const src = new UIEventSource(value);
|
||||
basicTags.data.push(src);
|
||||
basicTags.ping();
|
||||
src.addCallbackAndRunD(_ => update(_));
|
||||
}
|
||||
|
||||
function removeTag(basicTag: UIEventSource<any>) {
|
||||
const index = basicTags.data.indexOf(basicTag);
|
||||
console.log("Removing", index, basicTag);
|
||||
if (index >= 0) {
|
||||
basicTag.setData(undefined);
|
||||
basicTags.data.splice(index, 1);
|
||||
basicTags.ping();
|
||||
}
|
||||
}
|
||||
|
||||
function removeExpression(expr: UIEventSource<any>) {
|
||||
const index = expressions.data.indexOf(expr);
|
||||
if (index >= 0) {
|
||||
expr.setData(undefined);
|
||||
expressions.data.splice(index, 1);
|
||||
expressions.ping();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function addExpression(expr?: TagConfigJson) {
|
||||
const src = new UIEventSource(expr);
|
||||
expressions.data.push(src);
|
||||
expressions.ping();
|
||||
src.addCallbackAndRunD(_ => update(_));
|
||||
}
|
||||
|
||||
|
||||
$: update(mode);
|
||||
expressions.addCallback(_ => update(_));
|
||||
basicTags.addCallback(_ => update(_));
|
||||
|
||||
let initialTag: TagConfigJson = tag.data;
|
||||
|
||||
function initWith(initialTag: TagConfigJson) {
|
||||
if (typeof initialTag === "string") {
|
||||
if (initialTag.startsWith("{")) {
|
||||
initialTag = JSON.parse(initialTag);
|
||||
} else {
|
||||
addBasicTag(initialTag);
|
||||
return;
|
||||
}
|
||||
}
|
||||
mode = <"or" | "and">Object.keys(initialTag)[0];
|
||||
const subExprs = (<TagConfigJson[]>initialTag[mode]);
|
||||
if (!subExprs || subExprs.length == 0) {
|
||||
return;
|
||||
}
|
||||
if (subExprs.length == 1) {
|
||||
initWith(subExprs[0]);
|
||||
return;
|
||||
}
|
||||
for (const subExpr of subExprs) {
|
||||
if (typeof subExpr === "string") {
|
||||
addBasicTag(subExpr);
|
||||
} else {
|
||||
addExpression(subExpr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!initialTag) {
|
||||
addBasicTag();
|
||||
} else {
|
||||
initWith(initialTag);
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<div class="flex items-center">
|
||||
|
||||
{#if !uploadableOnly}
|
||||
<select bind:value={mode}>
|
||||
<option value="and">and</option>
|
||||
|
@ -136,14 +132,16 @@ if (!initialTag) {
|
|||
</select>
|
||||
{/if}
|
||||
|
||||
<div class="border-l-4 border-black flex flex-col ml-1 pl-1">
|
||||
<div class="ml-1 flex flex-col border-l-4 border-black pl-1">
|
||||
{#each $basicTags as basicTag (basicTag)}
|
||||
<div class="flex">
|
||||
<BasicTagInput {silent} {overpassSupportNeeded} {uploadableOnly} tag={basicTag} on:submit />
|
||||
{#if $basicTags.length + $expressions.length > 1}
|
||||
<button class="border border-black rounded-full w-fit h-fit p-0"
|
||||
on:click={() => removeTag(basicTag)}>
|
||||
<TrashIcon class="w-4 h-4 p-1" />
|
||||
<button
|
||||
class="h-fit w-fit rounded-full border border-black p-0"
|
||||
on:click={() => removeTag(basicTag)}
|
||||
>
|
||||
<TrashIcon class="h-4 w-4 p-1" />
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -151,23 +149,18 @@ if (!initialTag) {
|
|||
{#each $expressions as expression}
|
||||
<FullTagInput {silent} {overpassSupportNeeded} {uploadableOnly} tag={expression}>
|
||||
<button class="small" slot="delete" on:click={() => removeExpression(expression)}>
|
||||
<TrashIcon class="w-3 h-3 p-0" />
|
||||
<TrashIcon class="h-3 w-3 p-0" />
|
||||
Delete subexpression
|
||||
</button>
|
||||
</FullTagInput>
|
||||
{/each}
|
||||
<div class="flex">
|
||||
<button class="w-fit small" on:click={() => addBasicTag()}>
|
||||
Add a tag
|
||||
</button>
|
||||
<button class="small w-fit" on:click={() => addBasicTag()}>Add a tag</button>
|
||||
{#if !uploadableOnly}
|
||||
<!-- Do not allow to add an expression, as everything is 'and' anyway -->
|
||||
<button class="w-fit small" on:click={() => addExpression()}>
|
||||
Add an expression
|
||||
</button>
|
||||
<button class="small w-fit" on:click={() => addExpression()}>Add an expression</button>
|
||||
{/if}
|
||||
<slot name="delete" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
@ -1,63 +1,67 @@
|
|||
<script lang="ts">/**
|
||||
* A small component showing statistics from tagInfo.
|
||||
* Will show this in an 'alert' if very little (<250) tags are known
|
||||
*/
|
||||
import { TagUtils } from "../../Logic/Tags/TagUtils";
|
||||
import { Store, UIEventSource } from "../../Logic/UIEventSource";
|
||||
import type { TagInfoStats } from "../../Logic/Web/TagInfo";
|
||||
import TagInfo from "../../Logic/Web/TagInfo";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import Loading from "../Base/Loading.svelte";
|
||||
<script lang="ts">
|
||||
/**
|
||||
* A small component showing statistics from tagInfo.
|
||||
* Will show this in an 'alert' if very little (<250) tags are known
|
||||
*/
|
||||
import { TagUtils } from "../../Logic/Tags/TagUtils"
|
||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import type { TagInfoStats } from "../../Logic/Web/TagInfo"
|
||||
import TagInfo from "../../Logic/Web/TagInfo"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
import Loading from "../Base/Loading.svelte"
|
||||
|
||||
export let silent = false;
|
||||
export let tag: UIEventSource<string>;
|
||||
const tagStabilized = tag.stabilized(500);
|
||||
const tagInfoStats: Store<TagInfoStats> = tagStabilized.bind(tag => {
|
||||
export let silent = false
|
||||
export let tag: UIEventSource<string>
|
||||
const tagStabilized = tag.stabilized(500)
|
||||
const tagInfoStats: Store<TagInfoStats> = tagStabilized.bind((tag) => {
|
||||
if (!tag) {
|
||||
return undefined;
|
||||
return undefined
|
||||
}
|
||||
try {
|
||||
|
||||
const t = TagUtils.Tag(tag);
|
||||
const k = t["key"];
|
||||
let v = t["value"];
|
||||
if (typeof v !== "string") {
|
||||
v = undefined;
|
||||
}
|
||||
if (!k) {
|
||||
return undefined;
|
||||
}
|
||||
return UIEventSource.FromPromise(TagInfo.global.getStats(k, v));
|
||||
const t = TagUtils.Tag(tag)
|
||||
const k = t["key"]
|
||||
let v = t["value"]
|
||||
if (typeof v !== "string") {
|
||||
v = undefined
|
||||
}
|
||||
if (!k) {
|
||||
return undefined
|
||||
}
|
||||
return UIEventSource.FromPromise(TagInfo.global.getStats(k, v))
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
return undefined
|
||||
}
|
||||
});
|
||||
const tagInfoUrl: Store<string> = tagStabilized.mapD(tag => {
|
||||
})
|
||||
const tagInfoUrl: Store<string> = tagStabilized.mapD((tag) => {
|
||||
try {
|
||||
|
||||
const t = TagUtils.Tag(tag);
|
||||
const k = t["key"];
|
||||
let v = t["value"];
|
||||
if (typeof v !== "string") {
|
||||
v = undefined;
|
||||
}
|
||||
if (!k) {
|
||||
return undefined;
|
||||
}
|
||||
return TagInfo.global.webUrl(k, v);
|
||||
const t = TagUtils.Tag(tag)
|
||||
const k = t["key"]
|
||||
let v = t["value"]
|
||||
if (typeof v !== "string") {
|
||||
v = undefined
|
||||
}
|
||||
if (!k) {
|
||||
return undefined
|
||||
}
|
||||
return TagInfo.global.webUrl(k, v)
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
return undefined
|
||||
}
|
||||
});
|
||||
const total = tagInfoStats.mapD(data => data.data.find(i => i.type === "all").count);
|
||||
})
|
||||
const total = tagInfoStats.mapD((data) => data.data.find((i) => i.type === "all").count)
|
||||
</script>
|
||||
|
||||
{#if $tagStabilized !== $tag}
|
||||
{#if !silent}
|
||||
<Loading />
|
||||
{/if}
|
||||
{:else if $tagInfoStats && (!silent || $total < 250) }
|
||||
<a href={$tagInfoUrl} target="_blank" class={twMerge(($total < 250) ? "alert" : "thanks", "w-fit link-underline")}>
|
||||
{$total} features have <span class="literal-code">{$tag}</span>
|
||||
</a>
|
||||
{#if !silent}
|
||||
<Loading />
|
||||
{/if}
|
||||
{:else if $tagInfoStats && (!silent || $total < 250)}
|
||||
<a
|
||||
href={$tagInfoUrl}
|
||||
target="_blank"
|
||||
class={twMerge($total < 250 ? "alert" : "thanks", "link-underline w-fit")}
|
||||
>
|
||||
{$total} features have
|
||||
<span class="literal-code">{$tag}</span>
|
||||
</a>
|
||||
{/if}
|
||||
|
|
|
@ -1,134 +1,138 @@
|
|||
<script lang="ts">
|
||||
import ValidatedInput from "../../InputElement/ValidatedInput.svelte"
|
||||
import { UIEventSource } from "../../../Logic/UIEventSource"
|
||||
import { onDestroy } from "svelte"
|
||||
import Tr from "../../Base/Tr.svelte"
|
||||
import { TagUtils } from "../../../Logic/Tags/TagUtils"
|
||||
import TagInfoStats from "../TagInfoStats.svelte"
|
||||
import { Translation } from "../../i18n/Translation"
|
||||
|
||||
import ValidatedInput from "../../InputElement/ValidatedInput.svelte";
|
||||
import {UIEventSource} from "../../../Logic/UIEventSource";
|
||||
import {onDestroy} from "svelte";
|
||||
import Tr from "../../Base/Tr.svelte";
|
||||
import {TagUtils} from "../../../Logic/Tags/TagUtils";
|
||||
import TagInfoStats from "../TagInfoStats.svelte";
|
||||
import { Translation } from "../../i18n/Translation";
|
||||
export let tag: UIEventSource<string> = new UIEventSource<string>(undefined)
|
||||
export let uploadableOnly: boolean
|
||||
export let overpassSupportNeeded: boolean
|
||||
|
||||
export let tag: UIEventSource<string> = new UIEventSource<string>(undefined)
|
||||
export let uploadableOnly: boolean
|
||||
export let overpassSupportNeeded: boolean
|
||||
|
||||
export let dropdownFocussed = new UIEventSource(false)
|
||||
export let dropdownFocussed = new UIEventSource(false)
|
||||
|
||||
/**
|
||||
* If set, do not show tagInfo if there are many features matching
|
||||
*/
|
||||
export let silent : boolean = false
|
||||
|
||||
export let selected: UIEventSource<boolean> = new UIEventSource<boolean>(false)
|
||||
|
||||
let feedbackGlobal = tag.map(tag => {
|
||||
if (!tag) {
|
||||
return undefined
|
||||
}
|
||||
try {
|
||||
TagUtils.Tag(tag)
|
||||
return undefined
|
||||
} catch (e) {
|
||||
return e
|
||||
}
|
||||
/**
|
||||
* If set, do not show tagInfo if there are many features matching
|
||||
*/
|
||||
export let silent: boolean = false
|
||||
|
||||
})
|
||||
export let selected: UIEventSource<boolean> = new UIEventSource<boolean>(false)
|
||||
|
||||
let feedbackKey = new UIEventSource<Translation>(undefined)
|
||||
let keyValue = new UIEventSource<string>(undefined)
|
||||
|
||||
|
||||
let feedbackValue = new UIEventSource<Translation>(undefined)
|
||||
/**
|
||||
* The value of the tag. The name is a bit confusing
|
||||
*/
|
||||
let valueValue = new UIEventSource<string>(undefined)
|
||||
|
||||
|
||||
export let mode: string = "="
|
||||
let modes: string[] = []
|
||||
|
||||
for (const k in TagUtils.modeDocumentation) {
|
||||
const docs = TagUtils.modeDocumentation[k]
|
||||
if (overpassSupportNeeded && !docs.overpassSupport) {
|
||||
continue
|
||||
}
|
||||
if (uploadableOnly && !docs.uploadable) {
|
||||
continue
|
||||
}
|
||||
modes.push(k)
|
||||
let feedbackGlobal = tag.map((tag) => {
|
||||
if (!tag) {
|
||||
return undefined
|
||||
}
|
||||
if (!uploadableOnly && !overpassSupportNeeded) {
|
||||
modes.push(...TagUtils.comparators.map(c => c[0]))
|
||||
try {
|
||||
TagUtils.Tag(tag)
|
||||
return undefined
|
||||
} catch (e) {
|
||||
return e
|
||||
}
|
||||
})
|
||||
|
||||
let feedbackKey = new UIEventSource<Translation>(undefined)
|
||||
let keyValue = new UIEventSource<string>(undefined)
|
||||
|
||||
if (tag.data) {
|
||||
const sortedModes = [...modes]
|
||||
sortedModes.sort((a, b) => b.length - a.length)
|
||||
const t = tag.data
|
||||
console.log(t)
|
||||
for (const m of sortedModes) {
|
||||
if (t.indexOf(m) >= 0) {
|
||||
const [k, v] = t.split(m)
|
||||
keyValue.setData(k)
|
||||
valueValue.setData(v)
|
||||
mode = m
|
||||
break
|
||||
}
|
||||
}
|
||||
let feedbackValue = new UIEventSource<Translation>(undefined)
|
||||
/**
|
||||
* The value of the tag. The name is a bit confusing
|
||||
*/
|
||||
let valueValue = new UIEventSource<string>(undefined)
|
||||
|
||||
export let mode: string = "="
|
||||
let modes: string[] = []
|
||||
|
||||
for (const k in TagUtils.modeDocumentation) {
|
||||
const docs = TagUtils.modeDocumentation[k]
|
||||
if (overpassSupportNeeded && !docs.overpassSupport) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
onDestroy(valueValue.addCallbackAndRun(setTag))
|
||||
onDestroy(keyValue.addCallbackAndRun(setTag))
|
||||
|
||||
$: {
|
||||
setTag(mode)
|
||||
if (uploadableOnly && !docs.uploadable) {
|
||||
continue
|
||||
}
|
||||
modes.push(k)
|
||||
}
|
||||
if (!uploadableOnly && !overpassSupportNeeded) {
|
||||
modes.push(...TagUtils.comparators.map((c) => c[0]))
|
||||
}
|
||||
|
||||
function setTag(_) {
|
||||
const k = keyValue.data
|
||||
const v = valueValue.data ?? ""
|
||||
if(k === undefined || k === ""){
|
||||
tag.setData(undefined)
|
||||
return
|
||||
}
|
||||
const t = k + mode + v
|
||||
try {
|
||||
TagUtils.Tag(t)
|
||||
tag.setData(t)
|
||||
} catch (e) {
|
||||
tag.setData(undefined)
|
||||
}
|
||||
if (tag.data) {
|
||||
const sortedModes = [...modes]
|
||||
sortedModes.sort((a, b) => b.length - a.length)
|
||||
const t = tag.data
|
||||
console.log(t)
|
||||
for (const m of sortedModes) {
|
||||
if (t.indexOf(m) >= 0) {
|
||||
const [k, v] = t.split(m)
|
||||
keyValue.setData(k)
|
||||
valueValue.setData(v)
|
||||
mode = m
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onDestroy(valueValue.addCallbackAndRun(setTag))
|
||||
onDestroy(keyValue.addCallbackAndRun(setTag))
|
||||
|
||||
$: {
|
||||
setTag(mode)
|
||||
}
|
||||
|
||||
function setTag(_) {
|
||||
const k = keyValue.data
|
||||
const v = valueValue.data ?? ""
|
||||
if (k === undefined || k === "") {
|
||||
tag.setData(undefined)
|
||||
return
|
||||
}
|
||||
const t = k + mode + v
|
||||
try {
|
||||
TagUtils.Tag(t)
|
||||
tag.setData(t)
|
||||
} catch (e) {
|
||||
tag.setData(undefined)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<div class="flex items-center">
|
||||
<div class="flex h-fit">
|
||||
<ValidatedInput
|
||||
feedback={feedbackKey}
|
||||
placeholder="The key of the tag"
|
||||
type="key"
|
||||
value={keyValue}
|
||||
on:submit
|
||||
/>
|
||||
<select
|
||||
bind:value={mode}
|
||||
on:focusin={() => dropdownFocussed.setData(true)}
|
||||
on:focusout={() => dropdownFocussed.setData(false)}
|
||||
>
|
||||
{#each modes as option}
|
||||
<option value={option}>
|
||||
{option}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
<ValidatedInput
|
||||
feedback={feedbackValue}
|
||||
placeholder="The value of the tag"
|
||||
type="string"
|
||||
value={valueValue}
|
||||
on:submit
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex h-fit ">
|
||||
|
||||
<ValidatedInput feedback={feedbackKey} placeholder="The key of the tag" type="key"
|
||||
value={keyValue} on:submit></ValidatedInput>
|
||||
<select bind:value={mode} on:focusin={() => dropdownFocussed.setData(true)} on:focusout={() => dropdownFocussed.setData(false)}>
|
||||
{#each modes as option}
|
||||
<option value={option}>
|
||||
{option}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
<ValidatedInput feedback={feedbackValue} placeholder="The value of the tag" type="string"
|
||||
value={valueValue} on:submit></ValidatedInput>
|
||||
</div>
|
||||
|
||||
{#if $feedbackKey}
|
||||
<Tr cls="alert" t={$feedbackKey}/>
|
||||
{:else if $feedbackValue}
|
||||
<Tr cls="alert" t={$feedbackValue}/>
|
||||
{:else if $feedbackGlobal}
|
||||
<Tr cls="alert" t={$feedbackGlobal}/>
|
||||
{/if}
|
||||
<TagInfoStats {silent} {tag}/>
|
||||
{#if $feedbackKey}
|
||||
<Tr cls="alert" t={$feedbackKey} />
|
||||
{:else if $feedbackValue}
|
||||
<Tr cls="alert" t={$feedbackValue} />
|
||||
{:else if $feedbackGlobal}
|
||||
<Tr cls="alert" t={$feedbackGlobal} />
|
||||
{/if}
|
||||
<TagInfoStats {silent} {tag} />
|
||||
</div>
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
<script lang="ts">/**
|
||||
* An element input a tag; has `and`, `or`, `regex`, ...
|
||||
*/
|
||||
import type { TagConfigJson } from "../../../Models/ThemeConfig/Json/TagConfigJson";
|
||||
import { UIEventSource } from "../../../Logic/UIEventSource";
|
||||
import TagExpression from "../TagExpression.svelte";
|
||||
<script lang="ts">
|
||||
/**
|
||||
* An element input a tag; has `and`, `or`, `regex`, ...
|
||||
*/
|
||||
import type { TagConfigJson } from "../../../Models/ThemeConfig/Json/TagConfigJson"
|
||||
import { UIEventSource } from "../../../Logic/UIEventSource"
|
||||
import TagExpression from "../TagExpression.svelte"
|
||||
|
||||
|
||||
export let tag: UIEventSource<string | TagConfigJson>
|
||||
export let uploadableOnly: boolean
|
||||
export let overpassSupportNeeded: boolean
|
||||
export let silent: boolean
|
||||
export let tag: UIEventSource<string | TagConfigJson>
|
||||
export let uploadableOnly: boolean
|
||||
export let overpassSupportNeeded: boolean
|
||||
export let silent: boolean
|
||||
</script>
|
||||
|
||||
<div class="m-2">
|
||||
<TagExpression {silent} {overpassSupportNeeded} {tag} {uploadableOnly} on:submit>
|
||||
<slot name="delete" slot="delete"/>
|
||||
</TagExpression>
|
||||
<TagExpression {silent} {overpassSupportNeeded} {tag} {uploadableOnly} on:submit>
|
||||
<slot name="delete" slot="delete" />
|
||||
</TagExpression>
|
||||
</div>
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
<script lang="ts">
|
||||
|
||||
import SchemaBasedInput from "./SchemaBasedInput.svelte";
|
||||
import EditLayerState from "./EditLayerState";
|
||||
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig";
|
||||
import SchemaBasedInput from "./SchemaBasedInput.svelte"
|
||||
import EditLayerState from "./EditLayerState"
|
||||
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
|
||||
|
||||
export let state: EditLayerState
|
||||
export let path : (number | string)[]
|
||||
|
||||
let schema : TagRenderingConfig
|
||||
export let state: EditLayerState
|
||||
export let path: (number | string)[]
|
||||
|
||||
let schema: TagRenderingConfig
|
||||
</script>
|
||||
|
||||
XYZ
|
||||
|
|
|
@ -1,173 +1,227 @@
|
|||
<script lang="ts">/**
|
||||
* Little helper class to deal with choosing a builtin tagRendering or defining one yourself.
|
||||
* Breaks the ideology that everything should be schema based
|
||||
*/
|
||||
import EditLayerState from "./EditLayerState";
|
||||
import type { ConfigMeta } from "./configMeta";
|
||||
import type {
|
||||
MappingConfigJson,
|
||||
QuestionableTagRenderingConfigJson
|
||||
} from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson";
|
||||
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig";
|
||||
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte";
|
||||
import { Store, UIEventSource } from "../../Logic/UIEventSource";
|
||||
import * as questions from "../../assets/generated/layers/questions.json";
|
||||
import MappingInput from "./MappingInput.svelte";
|
||||
import { TrashIcon } from "@rgossiaux/svelte-heroicons/outline";
|
||||
import questionableTagRenderingSchemaRaw from "../../assets/schemas/questionabletagrenderingconfigmeta.json";
|
||||
import SchemaBasedField from "./SchemaBasedField.svelte";
|
||||
import Region from "./Region.svelte";
|
||||
import NextButton from "../Base/NextButton.svelte";
|
||||
import { QuestionMarkCircleIcon } from "@rgossiaux/svelte-heroicons/solid";
|
||||
import { LocalStorageSource } from "../../Logic/Web/LocalStorageSource";
|
||||
import { onMount } from "svelte";
|
||||
<script lang="ts">
|
||||
/**
|
||||
* Little helper class to deal with choosing a builtin tagRendering or defining one yourself.
|
||||
* Breaks the ideology that everything should be schema based
|
||||
*/
|
||||
import EditLayerState from "./EditLayerState"
|
||||
import type { ConfigMeta } from "./configMeta"
|
||||
import type {
|
||||
MappingConfigJson,
|
||||
QuestionableTagRenderingConfigJson,
|
||||
} from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
|
||||
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
|
||||
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte"
|
||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import * as questions from "../../assets/generated/layers/questions.json"
|
||||
import MappingInput from "./MappingInput.svelte"
|
||||
import { TrashIcon } from "@rgossiaux/svelte-heroicons/outline"
|
||||
import questionableTagRenderingSchemaRaw from "../../assets/schemas/questionabletagrenderingconfigmeta.json"
|
||||
import SchemaBasedField from "./SchemaBasedField.svelte"
|
||||
import Region from "./Region.svelte"
|
||||
import NextButton from "../Base/NextButton.svelte"
|
||||
import { QuestionMarkCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
import { LocalStorageSource } from "../../Logic/Web/LocalStorageSource"
|
||||
import { onMount } from "svelte"
|
||||
|
||||
export let state: EditLayerState;
|
||||
export let schema: ConfigMeta;
|
||||
export let path: (string | number)[];
|
||||
let expertMode = state.expertMode;
|
||||
const store = state.getStoreFor(path);
|
||||
let value = store.data;
|
||||
let hasSeenIntro = UIEventSource.asBoolean(LocalStorageSource.Get("studio-seen-tagrendering-tutorial", "false"))
|
||||
onMount(() => {
|
||||
if(!hasSeenIntro.data){
|
||||
state.showIntro.setData("tagrenderings")
|
||||
hasSeenIntro.setData(true)
|
||||
}
|
||||
})
|
||||
/**
|
||||
* Allows the theme builder to create 'writable' themes.
|
||||
* Should only be enabled for 'tagrenderings' in the theme, if the source is OSM
|
||||
*/
|
||||
let allowQuestions: Store<boolean> = (state.configuration.mapD(config => path.at(0) === "tagRenderings" && config.source?.geoJson === undefined));
|
||||
export let state: EditLayerState
|
||||
export let schema: ConfigMeta
|
||||
export let path: (string | number)[]
|
||||
let expertMode = state.expertMode
|
||||
const store = state.getStoreFor(path)
|
||||
let value = store.data
|
||||
let hasSeenIntro = UIEventSource.asBoolean(
|
||||
LocalStorageSource.Get("studio-seen-tagrendering-tutorial", "false")
|
||||
)
|
||||
onMount(() => {
|
||||
if (!hasSeenIntro.data) {
|
||||
state.showIntro.setData("tagrenderings")
|
||||
hasSeenIntro.setData(true)
|
||||
}
|
||||
})
|
||||
/**
|
||||
* Allows the theme builder to create 'writable' themes.
|
||||
* Should only be enabled for 'tagrenderings' in the theme, if the source is OSM
|
||||
*/
|
||||
let allowQuestions: Store<boolean> = state.configuration.mapD(
|
||||
(config) => path.at(0) === "tagRenderings" && config.source?.geoJson === undefined
|
||||
)
|
||||
|
||||
let mappingsBuiltin: MappingConfigJson[] = []
|
||||
let perLabel: Record<string, MappingConfigJson> = {}
|
||||
for (const tr of questions.tagRenderings) {
|
||||
let description = tr["description"] ?? tr["question"] ?? "No description available"
|
||||
description = description["en"] ?? description
|
||||
if (tr["labels"]) {
|
||||
const labels: string[] = tr["labels"]
|
||||
for (const label of labels) {
|
||||
let labelMapping: MappingConfigJson = perLabel[label]
|
||||
|
||||
let mappingsBuiltin: MappingConfigJson[] = [];
|
||||
let perLabel: Record<string, MappingConfigJson> = {};
|
||||
for (const tr of questions.tagRenderings) {
|
||||
let description = tr["description"] ?? tr["question"] ?? "No description available";
|
||||
description = description["en"] ?? description;
|
||||
if (tr["labels"]) {
|
||||
const labels: string[] = tr["labels"];
|
||||
for (const label of labels) {
|
||||
let labelMapping: MappingConfigJson = perLabel[label];
|
||||
|
||||
if (!labelMapping) {
|
||||
labelMapping = {
|
||||
if: "value=" + label,
|
||||
then: {
|
||||
en: "Builtin collection <b>" + label + "</b>:"
|
||||
if (!labelMapping) {
|
||||
labelMapping = {
|
||||
if: "value=" + label,
|
||||
then: {
|
||||
en: "Builtin collection <b>" + label + "</b>:",
|
||||
},
|
||||
}
|
||||
};
|
||||
perLabel[label] = labelMapping;
|
||||
mappingsBuiltin.push(labelMapping);
|
||||
perLabel[label] = labelMapping
|
||||
mappingsBuiltin.push(labelMapping)
|
||||
}
|
||||
labelMapping.then.en = labelMapping.then.en + "<div>" + description + "</div>"
|
||||
}
|
||||
labelMapping.then.en = labelMapping.then.en + "<div>" + description + "</div>";
|
||||
}
|
||||
|
||||
mappingsBuiltin.push({
|
||||
if: "value=" + tr["id"],
|
||||
then: {
|
||||
en: "Builtin <b>" + tr["id"] + "</b> <div class='subtle'>" + description + "</div>",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const configBuiltin = new TagRenderingConfig(<QuestionableTagRenderingConfigJson>{
|
||||
question: "Which builtin element should be shown?",
|
||||
mappings: mappingsBuiltin,
|
||||
})
|
||||
|
||||
const tags = new UIEventSource({ value })
|
||||
|
||||
tags.addCallbackAndRunD((tgs) => {
|
||||
store.setData(tgs["value"])
|
||||
})
|
||||
|
||||
let mappings: UIEventSource<MappingConfigJson[]> = state.getStoreFor([...path, "mappings"])
|
||||
|
||||
const topLevelItems: Record<string, ConfigMeta> = {}
|
||||
for (const item of questionableTagRenderingSchemaRaw) {
|
||||
if (item.path.length === 1) {
|
||||
topLevelItems[item.path[0]] = <ConfigMeta>item
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
mappingsBuiltin.push({
|
||||
if: "value=" + tr["id"],
|
||||
then: {
|
||||
"en": "Builtin <b>" + tr["id"] + "</b> <div class='subtle'>" + description + "</div>"
|
||||
function initMappings() {
|
||||
if (mappings.data === undefined) {
|
||||
mappings.setData([])
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const configBuiltin = new TagRenderingConfig(<QuestionableTagRenderingConfigJson>{
|
||||
question: "Which builtin element should be shown?",
|
||||
mappings: mappingsBuiltin
|
||||
});
|
||||
|
||||
|
||||
const tags = new UIEventSource({ value });
|
||||
|
||||
tags.addCallbackAndRunD(tgs => {
|
||||
store.setData(tgs["value"]);
|
||||
});
|
||||
|
||||
let mappings: UIEventSource<MappingConfigJson[]> = state.getStoreFor([...path, "mappings"]);
|
||||
|
||||
const topLevelItems: Record<string, ConfigMeta> = {};
|
||||
for (const item of questionableTagRenderingSchemaRaw) {
|
||||
if (item.path.length === 1) {
|
||||
topLevelItems[item.path[0]] = <ConfigMeta>item;
|
||||
}
|
||||
}
|
||||
|
||||
function initMappings() {
|
||||
if (mappings.data === undefined) {
|
||||
mappings.setData([]);
|
||||
}
|
||||
}
|
||||
|
||||
const items = new Set(["question", "questionHint", "multiAnswer", "freeform", "render", "condition", "metacondition", "mappings", "icon"]);
|
||||
const ignored = new Set(["labels", "description", "classes"]);
|
||||
|
||||
const freeformSchemaAll = <ConfigMeta[]>questionableTagRenderingSchemaRaw
|
||||
.filter(schema => schema.path.length == 2 && schema.path[0] === "freeform" && ($allowQuestions || schema.path[1] === "key"));
|
||||
let freeformSchema = $expertMode ? freeformSchemaAll : freeformSchemaAll.filter(schema => schema.hints?.group !== "expert");
|
||||
const missing: string[] = questionableTagRenderingSchemaRaw.filter(schema => schema.path.length >= 1 && !items.has(schema.path[0]) && !ignored.has(schema.path[0])).map(schema => schema.path.join("."));
|
||||
console.log({ state });
|
||||
const items = new Set([
|
||||
"question",
|
||||
"questionHint",
|
||||
"multiAnswer",
|
||||
"freeform",
|
||||
"render",
|
||||
"condition",
|
||||
"metacondition",
|
||||
"mappings",
|
||||
"icon",
|
||||
])
|
||||
const ignored = new Set(["labels", "description", "classes"])
|
||||
|
||||
const freeformSchemaAll = <ConfigMeta[]>(
|
||||
questionableTagRenderingSchemaRaw.filter(
|
||||
(schema) =>
|
||||
schema.path.length == 2 &&
|
||||
schema.path[0] === "freeform" &&
|
||||
($allowQuestions || schema.path[1] === "key")
|
||||
)
|
||||
)
|
||||
let freeformSchema = $expertMode
|
||||
? freeformSchemaAll
|
||||
: freeformSchemaAll.filter((schema) => schema.hints?.group !== "expert")
|
||||
const missing: string[] = questionableTagRenderingSchemaRaw
|
||||
.filter(
|
||||
(schema) =>
|
||||
schema.path.length >= 1 && !items.has(schema.path[0]) && !ignored.has(schema.path[0])
|
||||
)
|
||||
.map((schema) => schema.path.join("."))
|
||||
console.log({ state })
|
||||
</script>
|
||||
|
||||
{#if typeof $store === "string"}
|
||||
<div class="flex low-interaction">
|
||||
<TagRenderingEditable config={configBuiltin} selectedElement={undefined} showQuestionIfUnknown={true} {state}
|
||||
{tags} />
|
||||
<div class="low-interaction flex">
|
||||
<TagRenderingEditable
|
||||
config={configBuiltin}
|
||||
selectedElement={undefined}
|
||||
showQuestionIfUnknown={true}
|
||||
{state}
|
||||
{tags}
|
||||
/>
|
||||
<slot name="upper-right" />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-col w-full p-1 gap-y-1 pr-12">
|
||||
<div class="flex w-full flex-col gap-y-1 p-1 pr-12">
|
||||
<div class="flex justify-end">
|
||||
<slot name="upper-right" />
|
||||
</div>
|
||||
{#if $allowQuestions}
|
||||
<SchemaBasedField startInEditModeIfUnset={true} {state} path={[...path,"question"]}
|
||||
schema={topLevelItems["question"]} />
|
||||
<SchemaBasedField {state} path={[...path,"questionHint"]} schema={topLevelItems["questionHint"]} />
|
||||
<SchemaBasedField
|
||||
startInEditModeIfUnset={true}
|
||||
{state}
|
||||
path={[...path, "question"]}
|
||||
schema={topLevelItems["question"]}
|
||||
/>
|
||||
<SchemaBasedField
|
||||
{state}
|
||||
path={[...path, "questionHint"]}
|
||||
schema={topLevelItems["questionHint"]}
|
||||
/>
|
||||
{/if}
|
||||
{#each ($mappings ?? []) as mapping, i (mapping)}
|
||||
<div class="flex interactive w-full">
|
||||
{#each $mappings ?? [] as mapping, i (mapping)}
|
||||
<div class="interactive flex w-full">
|
||||
<MappingInput {state} path={path.concat(["mappings", i])}>
|
||||
<button slot="delete" class="rounded-full no-image-background" on:click={() => {
|
||||
initMappings();
|
||||
mappings.data.splice(i, 1)
|
||||
mappings.ping()
|
||||
}}>
|
||||
<TrashIcon class="w-4 h-4" />
|
||||
<button
|
||||
slot="delete"
|
||||
class="no-image-background rounded-full"
|
||||
on:click={() => {
|
||||
initMappings()
|
||||
mappings.data.splice(i, 1)
|
||||
mappings.ping()
|
||||
}}
|
||||
>
|
||||
<TrashIcon class="h-4 w-4" />
|
||||
</button>
|
||||
</MappingInput>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
<button class="primary"
|
||||
on:click={() =>{ initMappings(); mappings.data.push({if: undefined, then: {}}); mappings.ping()} }>
|
||||
<button
|
||||
class="primary"
|
||||
on:click={() => {
|
||||
initMappings()
|
||||
mappings.data.push({ if: undefined, then: {} })
|
||||
mappings.ping()
|
||||
}}
|
||||
>
|
||||
Add a predefined option
|
||||
</button>
|
||||
|
||||
<SchemaBasedField {state} path={[...path,"multiAnswer"]} schema={topLevelItems["multiAnswer"]} />
|
||||
<SchemaBasedField
|
||||
{state}
|
||||
path={[...path, "multiAnswer"]}
|
||||
schema={topLevelItems["multiAnswer"]}
|
||||
/>
|
||||
|
||||
<h3>Text field and input element configuration</h3>
|
||||
<div class="border-l pl-2 border-gray-800 border-dashed">
|
||||
<SchemaBasedField {state} path={[...path,"render"]} schema={topLevelItems["render"]} />
|
||||
<div class="border-l border-dashed border-gray-800 pl-2">
|
||||
<SchemaBasedField {state} path={[...path, "render"]} schema={topLevelItems["render"]} />
|
||||
<Region {state} {path} configs={freeformSchema} />
|
||||
<SchemaBasedField {state} path={[...path,"icon"]} schema={topLevelItems["icon"]} />
|
||||
|
||||
<SchemaBasedField {state} path={[...path, "icon"]} schema={topLevelItems["icon"]} />
|
||||
</div>
|
||||
|
||||
<SchemaBasedField {state} path={[...path,"condition"]} schema={topLevelItems["condition"]} />
|
||||
<SchemaBasedField {state} path={[...path, "condition"]} schema={topLevelItems["condition"]} />
|
||||
{#if $expertMode}
|
||||
<SchemaBasedField {state} path={[...path,"metacondition"]} schema={topLevelItems["metacondition"]} />
|
||||
<SchemaBasedField
|
||||
{state}
|
||||
path={[...path, "metacondition"]}
|
||||
schema={topLevelItems["metacondition"]}
|
||||
/>
|
||||
{/if}
|
||||
{#each missing as field}
|
||||
<SchemaBasedField {state} path={[...path,field]} schema={topLevelItems[field]} />
|
||||
<SchemaBasedField {state} path={[...path, field]} schema={topLevelItems[field]} />
|
||||
{/each}
|
||||
|
||||
<NextButton clss="small mt-8" on:click={() => state.showIntro.setData("tagrenderings")}><QuestionMarkCircleIcon class="h-6 w-6"/> Show the introduction again</NextButton>
|
||||
|
||||
<NextButton clss="small mt-8" on:click={() => state.showIntro.setData("tagrenderings")}>
|
||||
<QuestionMarkCircleIcon class="h-6 w-6" /> Show the introduction again
|
||||
</NextButton>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
@ -1,136 +1,157 @@
|
|||
<script lang="ts">
|
||||
import NextButton from "./Base/NextButton.svelte"
|
||||
import { Store, UIEventSource } from "../Logic/UIEventSource"
|
||||
import EditLayerState, { EditThemeState } from "./Studio/EditLayerState"
|
||||
import EditLayer from "./Studio/EditLayer.svelte"
|
||||
import Loading from "../assets/svg/Loading.svelte"
|
||||
import StudioServer from "./Studio/StudioServer"
|
||||
import LoginToggle from "./Base/LoginToggle.svelte"
|
||||
import { OsmConnection } from "../Logic/Osm/OsmConnection"
|
||||
import { QueryParameters } from "../Logic/Web/QueryParameters"
|
||||
|
||||
import layerSchemaRaw from "../../src/assets/schemas/layerconfigmeta.json"
|
||||
import layoutSchemaRaw from "../../src/assets/schemas/layoutconfigmeta.json"
|
||||
|
||||
import NextButton from "./Base/NextButton.svelte";
|
||||
import { Store, UIEventSource } from "../Logic/UIEventSource";
|
||||
import EditLayerState, { EditThemeState } from "./Studio/EditLayerState";
|
||||
import EditLayer from "./Studio/EditLayer.svelte";
|
||||
import Loading from "../assets/svg/Loading.svelte";
|
||||
import StudioServer from "./Studio/StudioServer";
|
||||
import LoginToggle from "./Base/LoginToggle.svelte";
|
||||
import { OsmConnection } from "../Logic/Osm/OsmConnection";
|
||||
import { QueryParameters } from "../Logic/Web/QueryParameters";
|
||||
import If from "./Base/If.svelte"
|
||||
import BackButton from "./Base/BackButton.svelte"
|
||||
import ChooseLayerToEdit from "./Studio/ChooseLayerToEdit.svelte"
|
||||
import { LocalStorageSource } from "../Logic/Web/LocalStorageSource"
|
||||
import FloatOver from "./Base/FloatOver.svelte"
|
||||
import Walkthrough from "./Walkthrough/Walkthrough.svelte"
|
||||
import * as intro from "../assets/studio_introduction.json"
|
||||
import * as intro_tagrenderings from "../assets/studio_tagrenderings_intro.json"
|
||||
|
||||
import layerSchemaRaw from "../../src/assets/schemas/layerconfigmeta.json";
|
||||
import layoutSchemaRaw from "../../src/assets/schemas/layoutconfigmeta.json";
|
||||
import { QuestionMarkCircleIcon } from "@babeard/svelte-heroicons/mini"
|
||||
import type { ConfigMeta } from "./Studio/configMeta"
|
||||
import EditTheme from "./Studio/EditTheme.svelte"
|
||||
import * as meta from "../../package.json"
|
||||
import Checkbox from "./Base/Checkbox.svelte"
|
||||
|
||||
import If from "./Base/If.svelte";
|
||||
import BackButton from "./Base/BackButton.svelte";
|
||||
import ChooseLayerToEdit from "./Studio/ChooseLayerToEdit.svelte";
|
||||
import { LocalStorageSource } from "../Logic/Web/LocalStorageSource";
|
||||
import FloatOver from "./Base/FloatOver.svelte";
|
||||
import Walkthrough from "./Walkthrough/Walkthrough.svelte";
|
||||
import * as intro from "../assets/studio_introduction.json";
|
||||
import * as intro_tagrenderings from "../assets/studio_tagrenderings_intro.json";
|
||||
|
||||
import { QuestionMarkCircleIcon } from "@babeard/svelte-heroicons/mini";
|
||||
import type { ConfigMeta } from "./Studio/configMeta";
|
||||
import EditTheme from "./Studio/EditTheme.svelte";
|
||||
import * as meta from "../../package.json";
|
||||
import Checkbox from "./Base/Checkbox.svelte";
|
||||
export let studioUrl =
|
||||
window.location.hostname === "127.0.0.2"
|
||||
? "http://127.0.0.1:1235"
|
||||
: "https://studio.mapcomplete.org"
|
||||
|
||||
export let studioUrl = window.location.hostname === "127.0.0.2" ? "http://127.0.0.1:1235" : "https://studio.mapcomplete.org";
|
||||
let osmConnection = new OsmConnection(
|
||||
new OsmConnection({
|
||||
oauth_token: QueryParameters.GetQueryParameter(
|
||||
"oauth_token",
|
||||
undefined,
|
||||
"Used to complete the login"
|
||||
),
|
||||
})
|
||||
)
|
||||
const expertMode = UIEventSource.asBoolean(
|
||||
osmConnection.GetPreference("studio-expert-mode", "false", {
|
||||
documentation: "Indicates if more options are shown in mapcomplete studio",
|
||||
})
|
||||
)
|
||||
expertMode.addCallbackAndRunD((expert) => console.log("Expert mode is", expert))
|
||||
const createdBy = osmConnection.userDetails.data.name
|
||||
const uid = osmConnection.userDetails.map((ud) => ud?.uid)
|
||||
const studio = new StudioServer(studioUrl, uid)
|
||||
|
||||
let osmConnection = new OsmConnection(new OsmConnection({
|
||||
oauth_token: QueryParameters.GetQueryParameter(
|
||||
"oauth_token",
|
||||
undefined,
|
||||
"Used to complete the login"
|
||||
)
|
||||
}));
|
||||
const expertMode = UIEventSource.asBoolean(osmConnection.GetPreference("studio-expert-mode", "false", {
|
||||
documentation: "Indicates if more options are shown in mapcomplete studio"
|
||||
}));
|
||||
expertMode.addCallbackAndRunD(expert => console.log("Expert mode is", expert))
|
||||
const createdBy = osmConnection.userDetails.data.name;
|
||||
const uid = osmConnection.userDetails.map(ud => ud?.uid);
|
||||
const studio = new StudioServer(studioUrl, uid);
|
||||
let layersWithErr = UIEventSource.FromPromiseWithErr(studio.fetchOverview())
|
||||
let layers: Store<{ owner: number }[]> = layersWithErr.mapD((l) =>
|
||||
l.success?.filter((l) => l.category === "layers")
|
||||
)
|
||||
let selfLayers = layers.mapD((ls) => ls.filter((l) => l.owner === uid.data), [uid])
|
||||
let otherLayers = layers.mapD(
|
||||
(ls) => ls.filter((l) => l.owner !== undefined && l.owner !== uid.data),
|
||||
[uid]
|
||||
)
|
||||
let officialLayers = layers.mapD((ls) => ls.filter((l) => l.owner === undefined), [uid])
|
||||
|
||||
let layersWithErr = UIEventSource.FromPromiseWithErr(studio.fetchOverview());
|
||||
let layers: Store<{ owner: number }[]> = layersWithErr.mapD(l => l.success?.filter(l => l.category === "layers"));
|
||||
let selfLayers = layers.mapD(ls => ls.filter(l => l.owner === uid.data), [uid]);
|
||||
let otherLayers = layers.mapD(ls => ls.filter(l => l.owner !== undefined && l.owner !== uid.data), [uid]);
|
||||
let officialLayers = layers.mapD(ls => ls.filter(l => l.owner === undefined), [uid]);
|
||||
let themes: Store<{ owner: number }[]> = layersWithErr.mapD((l) =>
|
||||
l.success?.filter((l) => l.category === "themes")
|
||||
)
|
||||
let selfThemes = themes.mapD((ls) => ls.filter((l) => l.owner === uid.data), [uid])
|
||||
let otherThemes = themes.mapD(
|
||||
(ls) => ls.filter((l) => l.owner !== undefined && l.owner !== uid.data),
|
||||
[uid]
|
||||
)
|
||||
let officialThemes = themes.mapD((ls) => ls.filter((l) => l.owner === undefined), [uid])
|
||||
|
||||
let state:
|
||||
| undefined
|
||||
| "edit_layer"
|
||||
| "edit_theme"
|
||||
| "editing_layer"
|
||||
| "editing_theme"
|
||||
| "loading" = undefined
|
||||
|
||||
let themes: Store<{ owner: number }[]> = layersWithErr.mapD(l => l.success?.filter(l => l.category === "themes"));
|
||||
let selfThemes = themes.mapD(ls => ls.filter(l => l.owner === uid.data), [uid]);
|
||||
let otherThemes = themes.mapD(ls => ls.filter(l => l.owner !== undefined && l.owner !== uid.data), [uid]);
|
||||
let officialThemes = themes.mapD(ls => ls.filter(l => l.owner === undefined), [uid]);
|
||||
|
||||
let state: undefined | "edit_layer" | "edit_theme" | "editing_layer" | "editing_theme" | "loading" = undefined;
|
||||
|
||||
const layerSchema: ConfigMeta[] = <any>layerSchemaRaw;
|
||||
let editLayerState = new EditLayerState(layerSchema, studio, osmConnection, { expertMode });
|
||||
const layerSchema: ConfigMeta[] = <any>layerSchemaRaw
|
||||
let editLayerState = new EditLayerState(layerSchema, studio, osmConnection, { expertMode })
|
||||
let showIntro = editLayerState.showIntro
|
||||
|
||||
const layoutSchema: ConfigMeta[] = <any>layoutSchemaRaw;
|
||||
let editThemeState = new EditThemeState(layoutSchema, studio, { expertMode });
|
||||
const layoutSchema: ConfigMeta[] = <any>layoutSchemaRaw
|
||||
let editThemeState = new EditThemeState(layoutSchema, studio, { expertMode })
|
||||
|
||||
let layerId = editLayerState.configuration.map(layerConfig => layerConfig.id);
|
||||
let layerId = editLayerState.configuration.map((layerConfig) => layerConfig.id)
|
||||
|
||||
const version = meta.version;
|
||||
const version = meta.version
|
||||
|
||||
async function editLayer(event: Event) {
|
||||
const layerId: { owner: number, id: string } = event.detail;
|
||||
state = "loading";
|
||||
editLayerState.startSavingUpdates(false);
|
||||
editLayerState.configuration.setData(await studio.fetch(layerId.id, "layers", layerId.owner));
|
||||
editLayerState.startSavingUpdates();
|
||||
state = "editing_layer";
|
||||
const layerId: { owner: number; id: string } = event.detail
|
||||
state = "loading"
|
||||
editLayerState.startSavingUpdates(false)
|
||||
editLayerState.configuration.setData(await studio.fetch(layerId.id, "layers", layerId.owner))
|
||||
editLayerState.startSavingUpdates()
|
||||
state = "editing_layer"
|
||||
}
|
||||
|
||||
async function editTheme(event: Event) {
|
||||
const id: { id: string, owner: number } = event.detail;
|
||||
state = "loading";
|
||||
editThemeState.startSavingUpdates(false);
|
||||
editThemeState.configuration.setData(await studio.fetch(id.id, "themes", id.owner));
|
||||
editThemeState.startSavingUpdates();
|
||||
state = "editing_theme";
|
||||
const id: { id: string; owner: number } = event.detail
|
||||
state = "loading"
|
||||
editThemeState.startSavingUpdates(false)
|
||||
editThemeState.configuration.setData(await studio.fetch(id.id, "themes", id.owner))
|
||||
editThemeState.startSavingUpdates()
|
||||
state = "editing_theme"
|
||||
}
|
||||
|
||||
async function createNewLayer() {
|
||||
state = "loading";
|
||||
state = "loading"
|
||||
const initialLayerConfig = {
|
||||
credits: createdBy,
|
||||
minzoom: 15,
|
||||
pointRendering: [
|
||||
{
|
||||
location: ["point", "centroid"],
|
||||
marker: [{
|
||||
icon: "circle",
|
||||
color: "white"
|
||||
}]
|
||||
}
|
||||
marker: [
|
||||
{
|
||||
icon: "circle",
|
||||
color: "white",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
tagRenderings: ["images"],
|
||||
lineRendering: [{
|
||||
width: 1,
|
||||
color: "blue"
|
||||
}]
|
||||
};
|
||||
editLayerState.configuration.setData(initialLayerConfig);
|
||||
editLayerState.startSavingUpdates();
|
||||
state = "editing_layer";
|
||||
lineRendering: [
|
||||
{
|
||||
width: 1,
|
||||
color: "blue",
|
||||
},
|
||||
],
|
||||
}
|
||||
editLayerState.configuration.setData(initialLayerConfig)
|
||||
editLayerState.startSavingUpdates()
|
||||
state = "editing_layer"
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<If condition={layersWithErr.map(d => d?.error !== undefined)}>
|
||||
<If condition={layersWithErr.map((d) => d?.error !== undefined)}>
|
||||
<div>
|
||||
<div class="alert">
|
||||
Something went wrong while contacting the MapComplete Studio Server: {$layersWithErr["error"]}
|
||||
</div>
|
||||
The server might be offline. Please:
|
||||
<ul>
|
||||
|
||||
<li>Try again in a few minutes</li>
|
||||
<li>
|
||||
Try again in a few minutes
|
||||
</li>
|
||||
<li>
|
||||
Contact <a href="https://app.element.io/#/room/#MapComplete:matrix.org">the MapComplete community via the
|
||||
chat.</a> Someone might be able to help you
|
||||
Contact <a href="https://app.element.io/#/room/#MapComplete:matrix.org">
|
||||
the MapComplete community via the chat.
|
||||
</a>
|
||||
Someone might be able to help you
|
||||
</li>
|
||||
<li>
|
||||
File <a href="https://github.com/pietervdvn/MapComplete/issues">an issue</a>
|
||||
|
@ -140,44 +161,53 @@
|
|||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<LoginToggle ignoreLoading={true} slot="else" state={{osmConnection}}>
|
||||
<LoginToggle ignoreLoading={true} slot="else" state={{ osmConnection }}>
|
||||
<div slot="not-logged-in">
|
||||
<NextButton clss="primary" on:click={() => osmConnection.AttemptLogin()}>
|
||||
Please log in to use MapComplete Studio
|
||||
</NextButton>
|
||||
</div>
|
||||
{#if state === undefined}
|
||||
<div class="p-4 flex flex-col justify-between h-full">
|
||||
<div class="w-full flex flex-col">
|
||||
<div class="flex h-full flex-col justify-between p-4">
|
||||
<div class="flex w-full flex-col">
|
||||
<h1>MapComplete Studio</h1>
|
||||
|
||||
<NextButton on:click={() => state = "edit_layer"}>
|
||||
Edit an existing layer
|
||||
</NextButton>
|
||||
<NextButton on:click={() => createNewLayer()}>
|
||||
Create a new layer
|
||||
</NextButton>
|
||||
<NextButton on:click={() => state = "edit_theme"}>
|
||||
Edit a theme
|
||||
</NextButton>
|
||||
<NextButton on:click={() => {editThemeState.configuration.setData({}); state = "editing_theme"}}>
|
||||
<NextButton on:click={() => (state = "edit_layer")}>Edit an existing layer</NextButton>
|
||||
<NextButton on:click={() => createNewLayer()}>Create a new layer</NextButton>
|
||||
<NextButton on:click={() => (state = "edit_theme")}>Edit a theme</NextButton>
|
||||
<NextButton
|
||||
on:click={() => {
|
||||
editThemeState.configuration.setData({})
|
||||
state = "editing_theme"
|
||||
}}
|
||||
>
|
||||
Create a new theme
|
||||
</NextButton>
|
||||
<NextButton clss="small" on:click={() => {showIntro.setData("intro")} }>
|
||||
<QuestionMarkCircleIcon class="w-6 h-6" />
|
||||
<NextButton
|
||||
clss="small"
|
||||
on:click={() => {
|
||||
showIntro.setData("intro")
|
||||
}}
|
||||
>
|
||||
<QuestionMarkCircleIcon class="h-6 w-6" />
|
||||
Show the introduction again
|
||||
</NextButton>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
<Checkbox selected={expertMode} >Enable more options (expert mode)</Checkbox>
|
||||
<span class="subtle">MapComplete version {version}</span>
|
||||
<Checkbox selected={expertMode}>Enable more options (expert mode)</Checkbox>
|
||||
<span class="subtle">MapComplete version {version}</span>
|
||||
</div>
|
||||
</div>
|
||||
{:else if state === "edit_layer"}
|
||||
|
||||
<div class="flex flex-col m-4">
|
||||
<BackButton clss="small p-1" imageClass="w-8 h-8" on:click={() => {state =undefined}}>MapComplete Studio
|
||||
<div class="m-4 flex flex-col">
|
||||
<BackButton
|
||||
clss="small p-1"
|
||||
imageClass="w-8 h-8"
|
||||
on:click={() => {
|
||||
state = undefined
|
||||
}}
|
||||
>
|
||||
MapComplete Studio
|
||||
</BackButton>
|
||||
<h2>Choose a layer to edit</h2>
|
||||
<ChooseLayerToEdit {osmConnection} layerIds={$selfLayers} on:layerSelected={editLayer}>
|
||||
|
@ -187,12 +217,22 @@
|
|||
<ChooseLayerToEdit {osmConnection} layerIds={$otherLayers} on:layerSelected={editLayer} />
|
||||
|
||||
<h3>Official layers by MapComplete</h3>
|
||||
<ChooseLayerToEdit {osmConnection} layerIds={$officialLayers} on:layerSelected={editLayer} />
|
||||
<ChooseLayerToEdit
|
||||
{osmConnection}
|
||||
layerIds={$officialLayers}
|
||||
on:layerSelected={editLayer}
|
||||
/>
|
||||
</div>
|
||||
{:else if state === "edit_theme"}
|
||||
|
||||
<div class="flex flex-col m-4">
|
||||
<BackButton clss="small p-1" imageClass="w-8 h-8" on:click={() => {state =undefined}}>MapComplete Studio
|
||||
<div class="m-4 flex flex-col">
|
||||
<BackButton
|
||||
clss="small p-1"
|
||||
imageClass="w-8 h-8"
|
||||
on:click={() => {
|
||||
state = undefined
|
||||
}}
|
||||
>
|
||||
MapComplete Studio
|
||||
</BackButton>
|
||||
<h2>Choose a theme to edit</h2>
|
||||
<ChooseLayerToEdit {osmConnection} layerIds={$selfThemes} on:layerSelected={editTheme}>
|
||||
|
@ -201,33 +241,57 @@
|
|||
<h3>Themes by other contributors</h3>
|
||||
<ChooseLayerToEdit {osmConnection} layerIds={$otherThemes} on:layerSelected={editTheme} />
|
||||
<h3>Official themes by MapComplete</h3>
|
||||
<ChooseLayerToEdit {osmConnection} layerIds={$officialThemes} on:layerSelected={editTheme} />
|
||||
|
||||
<ChooseLayerToEdit
|
||||
{osmConnection}
|
||||
layerIds={$officialThemes}
|
||||
on:layerSelected={editTheme}
|
||||
/>
|
||||
</div>
|
||||
{:else if state === "loading"}
|
||||
<div class="w-8 h-8">
|
||||
<div class="h-8 w-8">
|
||||
<Loading />
|
||||
</div>
|
||||
{:else if state === "editing_layer"}
|
||||
<EditLayer state={editLayerState}>
|
||||
<BackButton clss="small p-1" imageClass="w-8 h-8" on:click={() => {state =undefined}}>MapComplete Studio
|
||||
<BackButton
|
||||
clss="small p-1"
|
||||
imageClass="w-8 h-8"
|
||||
on:click={() => {
|
||||
state = undefined
|
||||
}}
|
||||
>
|
||||
MapComplete Studio
|
||||
</BackButton>
|
||||
</EditLayer>
|
||||
{:else if state === "editing_theme"}
|
||||
<EditTheme state={editThemeState}>
|
||||
<BackButton clss="small p-1" imageClass="w-8 h-8" on:click={() => {state =undefined}}>MapComplete Studio
|
||||
<BackButton
|
||||
clss="small p-1"
|
||||
imageClass="w-8 h-8"
|
||||
on:click={() => {
|
||||
state = undefined
|
||||
}}
|
||||
>
|
||||
MapComplete Studio
|
||||
</BackButton>
|
||||
</EditTheme>
|
||||
{/if}
|
||||
</LoginToggle>
|
||||
</If>
|
||||
|
||||
|
||||
{#if {intro, tagrenderings: intro_tagrenderings}[$showIntro]?.sections}
|
||||
<FloatOver on:close={() => {showIntro.setData("no")}}>
|
||||
<div class="flex p-4 h-full pr-12">
|
||||
<Walkthrough pages={{intro, tagrenderings: intro_tagrenderings}[$showIntro]?.sections} on:done={() => {showIntro.setData("no")}} />
|
||||
{#if { intro, tagrenderings: intro_tagrenderings }[$showIntro]?.sections}
|
||||
<FloatOver
|
||||
on:close={() => {
|
||||
showIntro.setData("no")
|
||||
}}
|
||||
>
|
||||
<div class="flex h-full p-4 pr-12">
|
||||
<Walkthrough
|
||||
pages={{ intro, tagrenderings: intro_tagrenderings }[$showIntro]?.sections}
|
||||
on:done={() => {
|
||||
showIntro.setData("no")
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</FloatOver>
|
||||
|
||||
{/if}
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
<div class="information">Some important information</div>
|
||||
<div class="thanks">Thank you! Operation successful</div>
|
||||
|
||||
|
||||
<ToSvelte construct={Svg.login_svg().SetClass("w-12 h-12")} />
|
||||
<Loading>Loading...</Loading>
|
||||
</div>
|
||||
|
@ -46,7 +45,7 @@
|
|||
<ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")} />
|
||||
Main action (disabled)
|
||||
</button>
|
||||
|
||||
|
||||
<button class="small">
|
||||
<ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")} />
|
||||
Small button
|
||||
|
@ -89,7 +88,7 @@
|
|||
<div class="warning">Warning</div>
|
||||
<div class="information">Some important information</div>
|
||||
<div class="thanks">Thank you! Operation successful</div>
|
||||
|
||||
|
||||
<ToSvelte construct={Svg.login_svg().SetClass("w-12 h-12")} />
|
||||
<Loading>Loading...</Loading>
|
||||
</div>
|
||||
|
@ -130,7 +129,6 @@
|
|||
<div class="information">Some important information</div>
|
||||
<div class="thanks">Thank you! Operation successful</div>
|
||||
|
||||
|
||||
<ToSvelte construct={Svg.login_svg().SetClass("w-12 h-12")} />
|
||||
<Loading>Loading...</Loading>
|
||||
<div>
|
||||
|
@ -147,7 +145,7 @@
|
|||
JavaScript
|
||||
</label>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="border-interactive">
|
||||
Area with extreme high interactivity due to `border-interactive`
|
||||
</div>
|
||||
|
|
|
@ -76,7 +76,12 @@
|
|||
}
|
||||
|
||||
const tags = state.featureProperties.getStore(selectedElement.properties.id)
|
||||
return new SvelteUIElement(SelectedElementView, { state, layer, selectedElement, tags }).SetClass("h-full w-full")
|
||||
return new SvelteUIElement(SelectedElementView, {
|
||||
state,
|
||||
layer,
|
||||
selectedElement,
|
||||
tags,
|
||||
}).SetClass("h-full w-full")
|
||||
},
|
||||
[selectedLayer]
|
||||
)
|
||||
|
@ -290,7 +295,9 @@
|
|||
selectedElement.setData(undefined)
|
||||
}}
|
||||
>
|
||||
<ToSvelte construct={new VariableUiElement(selectedElementView).SetClass("h-full w-full flex")} />
|
||||
<ToSvelte
|
||||
construct={new VariableUiElement(selectedElementView).SetClass("h-full w-full flex")}
|
||||
/>
|
||||
</FloatOver>
|
||||
</If>
|
||||
|
||||
|
@ -370,7 +377,7 @@
|
|||
|
||||
<IfHidden condition={state.guistate.backgroundLayerSelectionIsOpened}>
|
||||
<!-- background layer selector -->
|
||||
<FloatOver
|
||||
<FloatOver
|
||||
on:close={() => {
|
||||
state.guistate.backgroundLayerSelectionIsOpened.setData(false)
|
||||
}}
|
||||
|
|
|
@ -1,32 +1,33 @@
|
|||
<script lang="ts">
|
||||
|
||||
import nmd from "nano-markdown";
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import WalkthroughStep from "./WalkthroughStep.svelte";
|
||||
import FromHtml from "../Base/FromHtml.svelte";
|
||||
import nmd from "nano-markdown"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import WalkthroughStep from "./WalkthroughStep.svelte"
|
||||
import FromHtml from "../Base/FromHtml.svelte"
|
||||
|
||||
/**
|
||||
* Markdown
|
||||
*/
|
||||
export let pages: string[];
|
||||
export let pages: string[]
|
||||
|
||||
let currentPage: number = 0;
|
||||
let currentPage: number = 0
|
||||
|
||||
const dispatch = createEventDispatcher<{ done }>();
|
||||
const dispatch = createEventDispatcher<{ done }>()
|
||||
|
||||
function step(incr: number) {
|
||||
if (incr > 0 && currentPage + 1 === pages.length) {
|
||||
dispatch("done");
|
||||
dispatch("done")
|
||||
currentPage = 0
|
||||
return
|
||||
}
|
||||
currentPage = Math.min(Math.max(0, currentPage + incr), pages.length);
|
||||
currentPage = Math.min(Math.max(0, currentPage + incr), pages.length)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<WalkthroughStep on:back={() => step(-1)} on:next={() => step(1)} isFirst={currentPage === 0} islast={currentPage + 1 === pages.length}>
|
||||
<WalkthroughStep
|
||||
on:back={() => step(-1)}
|
||||
on:next={() => step(1)}
|
||||
isFirst={currentPage === 0}
|
||||
islast={currentPage + 1 === pages.length}
|
||||
>
|
||||
<FromHtml src={nmd(pages[currentPage])} />
|
||||
</WalkthroughStep>
|
||||
|
||||
|
|
|
@ -1,29 +1,24 @@
|
|||
<script lang="ts">
|
||||
import BackButton from "../Base/BackButton.svelte"
|
||||
import NextButton from "../Base/NextButton.svelte"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
|
||||
import BackButton from "../Base/BackButton.svelte";
|
||||
import NextButton from "../Base/NextButton.svelte";
|
||||
import { createEventDispatcher } from "svelte";
|
||||
|
||||
const dispatch = createEventDispatcher<{ back, next }>();
|
||||
export let islast = false;
|
||||
const dispatch = createEventDispatcher<{ back; next }>()
|
||||
export let islast = false
|
||||
export let isFirst = false
|
||||
</script>
|
||||
|
||||
|
||||
<div class="flex flex-col h-full w-full justify-between">
|
||||
|
||||
<div class="flex h-full w-full flex-col justify-between">
|
||||
<div class="overflow-y-auto">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<div class="flex w-full">
|
||||
{#if !isFirst}
|
||||
<BackButton clss="w-full" on:click={() => dispatch("back")}>
|
||||
Back
|
||||
</BackButton>
|
||||
{:else}
|
||||
<div class="w-full"/>
|
||||
{/if}
|
||||
<BackButton clss="w-full" on:click={() => dispatch("back")}>Back</BackButton>
|
||||
{:else}
|
||||
<div class="w-full" />
|
||||
{/if}
|
||||
<NextButton clss="primary w-full" on:click={() => dispatch("next")}>
|
||||
{#if islast}
|
||||
Finish
|
||||
|
@ -32,5 +27,4 @@
|
|||
{/if}
|
||||
</NextButton>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
Loading…
Reference in a new issue