Chore: formatting

This commit is contained in:
Pieter Vander Vennet 2023-11-09 16:30:26 +01:00
parent 6c3c67af56
commit 286578bfc7
58 changed files with 2199 additions and 1915 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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