forked from MapComplete/MapComplete
Chore: formatting
This commit is contained in:
parent
6c3c67af56
commit
286578bfc7
58 changed files with 2199 additions and 1915 deletions
|
|
@ -1,44 +1,48 @@
|
|||
<script lang="ts">
|
||||
import type { ConfigMeta } from "./configMeta";
|
||||
import EditLayerState from "./EditLayerState";
|
||||
import type {
|
||||
QuestionableTagRenderingConfigJson
|
||||
} from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson";
|
||||
import { UIEventSource } from "../../Logic/UIEventSource";
|
||||
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte";
|
||||
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig";
|
||||
import type { ConfigMeta } from "./configMeta"
|
||||
import EditLayerState from "./EditLayerState"
|
||||
import type { QuestionableTagRenderingConfigJson } from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte"
|
||||
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
|
||||
|
||||
export let schema: ConfigMeta;
|
||||
export let state: EditLayerState;
|
||||
export let path: (string | number)[] = [];
|
||||
export let schema: ConfigMeta
|
||||
export let state: EditLayerState
|
||||
export let path: (string | number)[] = []
|
||||
|
||||
const configJson: QuestionableTagRenderingConfigJson = {
|
||||
mappings: schema.hints.suggestions,
|
||||
multiAnswer: true,
|
||||
id: "multi_anwser_"+path.join("_"),
|
||||
question: schema.hints.question
|
||||
id: "multi_anwser_" + path.join("_"),
|
||||
question: schema.hints.question,
|
||||
}
|
||||
const tags = new UIEventSource({})
|
||||
|
||||
{
|
||||
// Setting the initial value
|
||||
const v = <string[]> state.getCurrentValueFor(path)
|
||||
if(v && v.length > 0){
|
||||
tags.setData({value: v.join(";")})
|
||||
const v = <string[]>state.getCurrentValueFor(path)
|
||||
if (v && v.length > 0) {
|
||||
tags.setData({ value: v.join(";") })
|
||||
}
|
||||
}
|
||||
|
||||
tags.addCallbackD(tags => {
|
||||
|
||||
tags.addCallbackD((tags) => {
|
||||
const values = tags["value"]?.split(";")
|
||||
if(!values){
|
||||
if (!values) {
|
||||
return
|
||||
}
|
||||
state.setValueAt(path, values)
|
||||
})
|
||||
|
||||
|
||||
const config = new TagRenderingConfig(configJson)
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<TagRenderingEditable {config} selectedElement={undefined} showQuestionIfUnknown={true} {state} {tags} />
|
||||
<TagRenderingEditable
|
||||
{config}
|
||||
selectedElement={undefined}
|
||||
showQuestionIfUnknown={true}
|
||||
{state}
|
||||
{tags}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,18 +1,17 @@
|
|||
<script lang="ts">
|
||||
import { OsmConnection } from "../../Logic/Osm/OsmConnection";
|
||||
import EditItemButton from "./EditItemButton.svelte";
|
||||
|
||||
export let layerIds: { id: string, owner: number }[];
|
||||
export let category: "layers" | "themes" = "layers";
|
||||
export let osmConnection: OsmConnection;
|
||||
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
|
||||
import EditItemButton from "./EditItemButton.svelte"
|
||||
|
||||
export let layerIds: { id: string; owner: number }[]
|
||||
export let category: "layers" | "themes" = "layers"
|
||||
export let osmConnection: OsmConnection
|
||||
</script>
|
||||
|
||||
{#if layerIds.length > 0}
|
||||
<slot name="title" />
|
||||
<div class="flex flex-wrap">
|
||||
{#each Array.from(layerIds) as layer}
|
||||
<EditItemButton info={layer} {category} {osmConnection} on:layerSelected/>
|
||||
<EditItemButton info={layer} {category} {osmConnection} on:layerSelected />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -1,35 +1,36 @@
|
|||
<script lang="ts">
|
||||
import { UIEventSource } from "../../Logic/UIEventSource";
|
||||
import { OsmConnection } from "../../Logic/Osm/OsmConnection";
|
||||
import Marker from "../Map/Marker.svelte";
|
||||
import NextButton from "../Base/NextButton.svelte";
|
||||
import { AllKnownLayouts } from "../../Customizations/AllKnownLayouts";
|
||||
import { AllSharedLayers } from "../../Customizations/AllSharedLayers";
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
|
||||
import Marker from "../Map/Marker.svelte"
|
||||
import NextButton from "../Base/NextButton.svelte"
|
||||
import { AllKnownLayouts } from "../../Customizations/AllKnownLayouts"
|
||||
import { AllSharedLayers } from "../../Customizations/AllSharedLayers"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
|
||||
export let info: { id: string, owner: number };
|
||||
export let category: "layers" | "themes";
|
||||
export let osmConnection: OsmConnection;
|
||||
export let info: { id: string; owner: number }
|
||||
export let category: "layers" | "themes"
|
||||
export let osmConnection: OsmConnection
|
||||
|
||||
let displayName = UIEventSource.FromPromise(osmConnection.getInformationAboutUser(info.owner)).mapD(response => response.display_name);
|
||||
let displayName = UIEventSource.FromPromise(
|
||||
osmConnection.getInformationAboutUser(info.owner)
|
||||
).mapD((response) => response.display_name)
|
||||
|
||||
let selfId = osmConnection.userDetails.mapD(ud => ud.uid)
|
||||
let selfId = osmConnection.userDetails.mapD((ud) => ud.uid)
|
||||
function fetchIconDescription(layerId): any {
|
||||
if (category === "themes") {
|
||||
return AllKnownLayouts.allKnownLayouts.get(layerId).icon;
|
||||
return AllKnownLayouts.allKnownLayouts.get(layerId).icon
|
||||
}
|
||||
return AllSharedLayers.getSharedLayersConfigs().get(layerId)?._layerIcon;
|
||||
return AllSharedLayers.getSharedLayersConfigs().get(layerId)?._layerIcon
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher<{ layerSelected: string }>();
|
||||
|
||||
const dispatch = createEventDispatcher<{ layerSelected: string }>()
|
||||
</script>
|
||||
|
||||
<NextButton clss="small" on:click={() => dispatch("layerSelected", info)}>
|
||||
<div class="w-4 h-4 mr-1">
|
||||
<div class="mr-1 h-4 w-4">
|
||||
<Marker icons={fetchIconDescription(info.id)} />
|
||||
</div>
|
||||
<b class="px-1"> {info.id}</b>
|
||||
<b class="px-1">{info.id}</b>
|
||||
{#if info.owner && info.owner !== $selfId}
|
||||
(made by {$displayName ?? info.owner})
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -1,81 +1,81 @@
|
|||
<script lang="ts">
|
||||
import type { HighlightedTagRendering } from "./EditLayerState";
|
||||
import EditLayerState from "./EditLayerState";
|
||||
import layerSchemaRaw from "../../assets/schemas/layerconfigmeta.json";
|
||||
import Region from "./Region.svelte";
|
||||
import TabbedGroup from "../Base/TabbedGroup.svelte";
|
||||
import { Store, UIEventSource } from "../../Logic/UIEventSource";
|
||||
import type { ConfigMeta } from "./configMeta";
|
||||
import { Utils } from "../../Utils";
|
||||
import type { ConversionMessage } from "../../Models/ThemeConfig/Conversion/Conversion";
|
||||
import ErrorIndicatorForRegion from "./ErrorIndicatorForRegion.svelte";
|
||||
import { ChevronRightIcon } from "@rgossiaux/svelte-heroicons/solid";
|
||||
import SchemaBasedInput from "./SchemaBasedInput.svelte";
|
||||
import FloatOver from "../Base/FloatOver.svelte";
|
||||
import TagRenderingInput from "./TagRenderingInput.svelte";
|
||||
import FromHtml from "../Base/FromHtml.svelte";
|
||||
import AllTagsPanel from "../Popup/AllTagsPanel.svelte";
|
||||
import QuestionPreview from "./QuestionPreview.svelte";
|
||||
import ShowConversionMessages from "./ShowConversionMessages.svelte";
|
||||
import type { HighlightedTagRendering } from "./EditLayerState"
|
||||
import EditLayerState from "./EditLayerState"
|
||||
import layerSchemaRaw from "../../assets/schemas/layerconfigmeta.json"
|
||||
import Region from "./Region.svelte"
|
||||
import TabbedGroup from "../Base/TabbedGroup.svelte"
|
||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import type { ConfigMeta } from "./configMeta"
|
||||
import { Utils } from "../../Utils"
|
||||
import type { ConversionMessage } from "../../Models/ThemeConfig/Conversion/Conversion"
|
||||
import ErrorIndicatorForRegion from "./ErrorIndicatorForRegion.svelte"
|
||||
import { ChevronRightIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
import SchemaBasedInput from "./SchemaBasedInput.svelte"
|
||||
import FloatOver from "../Base/FloatOver.svelte"
|
||||
import TagRenderingInput from "./TagRenderingInput.svelte"
|
||||
import FromHtml from "../Base/FromHtml.svelte"
|
||||
import AllTagsPanel from "../Popup/AllTagsPanel.svelte"
|
||||
import QuestionPreview from "./QuestionPreview.svelte"
|
||||
import ShowConversionMessages from "./ShowConversionMessages.svelte"
|
||||
|
||||
const layerSchema: ConfigMeta[] = <any>layerSchemaRaw;
|
||||
const layerSchema: ConfigMeta[] = <any>layerSchemaRaw
|
||||
|
||||
export let state: EditLayerState;
|
||||
let messages = state.messages;
|
||||
let hasErrors = messages.mapD((m: ConversionMessage[]) => m.filter(m => m.level === "error").length);
|
||||
const configuration = state.configuration;
|
||||
export let state: EditLayerState
|
||||
let messages = state.messages
|
||||
let hasErrors = messages.mapD(
|
||||
(m: ConversionMessage[]) => m.filter((m) => m.level === "error").length
|
||||
)
|
||||
const configuration = state.configuration
|
||||
|
||||
const allNames = Utils.Dedup(layerSchema.map(meta => meta.hints.group));
|
||||
const allNames = Utils.Dedup(layerSchema.map((meta) => meta.hints.group))
|
||||
|
||||
const perRegion: Record<string, ConfigMeta[]> = {};
|
||||
const perRegion: Record<string, ConfigMeta[]> = {}
|
||||
for (const region of allNames) {
|
||||
perRegion[region] = layerSchema.filter(meta => meta.hints.group === region);
|
||||
perRegion[region] = layerSchema.filter((meta) => meta.hints.group === region)
|
||||
}
|
||||
|
||||
|
||||
let title: Store<string> = state.getStoreFor(["id"]);
|
||||
const wl = window.location;
|
||||
const baseUrl = wl.protocol + "//" + wl.host + "/theme.html?userlayout=";
|
||||
let title: Store<string> = state.getStoreFor(["id"])
|
||||
const wl = window.location
|
||||
const baseUrl = wl.protocol + "//" + wl.host + "/theme.html?userlayout="
|
||||
|
||||
function firstPathsFor(...regionNames: string[]): Set<string> {
|
||||
const pathNames = new Set<string>();
|
||||
const pathNames = new Set<string>()
|
||||
for (const regionName of regionNames) {
|
||||
const region: ConfigMeta[] = perRegion[regionName];
|
||||
const region: ConfigMeta[] = perRegion[regionName]
|
||||
for (const configMeta of region) {
|
||||
pathNames.add(configMeta.path[0]);
|
||||
pathNames.add(configMeta.path[0])
|
||||
}
|
||||
}
|
||||
return pathNames;
|
||||
|
||||
return pathNames
|
||||
}
|
||||
|
||||
function configForRequiredField(id: string): ConfigMeta {
|
||||
let config = layerSchema.find(config => config.path.length === 1 && config.path[0] === id);
|
||||
config = Utils.Clone(config);
|
||||
config.required = true;
|
||||
config.hints.ifunset = undefined;
|
||||
return config;
|
||||
let config = layerSchema.find((config) => config.path.length === 1 && config.path[0] === id)
|
||||
config = Utils.Clone(config)
|
||||
config.required = true
|
||||
config.hints.ifunset = undefined
|
||||
return config
|
||||
}
|
||||
|
||||
let requiredFields = ["id", "name", "description", "source"];
|
||||
let currentlyMissing = state.configuration.map(config => {
|
||||
let requiredFields = ["id", "name", "description", "source"]
|
||||
let currentlyMissing = state.configuration.map((config) => {
|
||||
if (!config) {
|
||||
return [];
|
||||
return []
|
||||
}
|
||||
const missing = [];
|
||||
const missing = []
|
||||
for (const requiredField of requiredFields) {
|
||||
if (!config[requiredField]) {
|
||||
missing.push(requiredField);
|
||||
missing.push(requiredField)
|
||||
}
|
||||
}
|
||||
return missing;
|
||||
});
|
||||
return missing
|
||||
})
|
||||
|
||||
let highlightedItem: UIEventSource<HighlightedTagRendering> = state.highlightedItem;
|
||||
let highlightedItem: UIEventSource<HighlightedTagRendering> = state.highlightedItem
|
||||
</script>
|
||||
<div class="h-screen flex flex-col">
|
||||
|
||||
<div class="w-full flex justify-between my-2">
|
||||
<div class="flex h-screen flex-col">
|
||||
<div class="my-2 flex w-full justify-between">
|
||||
<slot />
|
||||
{#if $title === undefined}
|
||||
<h3>Creating a new layer</h3>
|
||||
|
|
@ -83,11 +83,17 @@
|
|||
<h3>Editing layer {$title}</h3>
|
||||
{/if}
|
||||
{#if $currentlyMissing.length > 0}
|
||||
<div class="w-16"/> <!-- Empty div, simply hide this -->
|
||||
<div class="w-16" />
|
||||
<!-- Empty div, simply hide this -->
|
||||
{:else if $hasErrors > 0}
|
||||
<div class="alert">{$hasErrors} errors detected</div>
|
||||
{:else}
|
||||
<a class="primary button" href={baseUrl+state.server.layerUrl(title.data)} target="_blank" rel="noopener">
|
||||
<a
|
||||
class="primary button"
|
||||
href={baseUrl + state.server.layerUrl(title.data)}
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
Try it out
|
||||
<ChevronRightIcon class="h-6 w-6 shrink-0" />
|
||||
</a>
|
||||
|
|
@ -95,29 +101,29 @@
|
|||
</div>
|
||||
|
||||
{#if $currentlyMissing.length > 0}
|
||||
|
||||
{#each requiredFields as required}
|
||||
<SchemaBasedInput {state}
|
||||
schema={configForRequiredField(required)}
|
||||
path={[required]} />
|
||||
<SchemaBasedInput {state} schema={configForRequiredField(required)} path={[required]} />
|
||||
{/each}
|
||||
{:else}
|
||||
<div class="m4 h-full overflow-y-auto">
|
||||
<TabbedGroup>
|
||||
<div slot="title0" class="flex">General properties
|
||||
<div slot="title0" class="flex">
|
||||
General properties
|
||||
<ErrorIndicatorForRegion firstPaths={firstPathsFor("Basic")} {state} />
|
||||
</div>
|
||||
<div class="flex flex-col" slot="content0">
|
||||
<Region {state} configs={perRegion["Basic"]} />
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div slot="title1" class="flex">Information panel (questions and answers)
|
||||
<ErrorIndicatorForRegion firstPaths={firstPathsFor("title","tagrenderings","editing")} {state} />
|
||||
<div slot="title1" class="flex">
|
||||
Information panel (questions and answers)
|
||||
<ErrorIndicatorForRegion
|
||||
firstPaths={firstPathsFor("title", "tagrenderings", "editing")}
|
||||
{state}
|
||||
/>
|
||||
</div>
|
||||
<div slot="content1">
|
||||
<QuestionPreview path={["title"]} {state} schema={perRegion["title"][0]}></QuestionPreview>
|
||||
<QuestionPreview path={["title"]} {state} schema={perRegion["title"][0]} />
|
||||
<Region configs={perRegion["tagrenderings"]} {state} title="Popup contents" />
|
||||
<Region configs={perRegion["editing"]} {state} title="Other editing elements" />
|
||||
</div>
|
||||
|
|
@ -131,16 +137,21 @@
|
|||
<Region {state} configs={perRegion["presets"]} />
|
||||
</div>
|
||||
|
||||
<div slot="title3" class="flex">Rendering on the map
|
||||
<ErrorIndicatorForRegion firstPaths={firstPathsFor("linerendering","pointrendering")} {state} />
|
||||
<div slot="title3" class="flex">
|
||||
Rendering on the map
|
||||
<ErrorIndicatorForRegion
|
||||
firstPaths={firstPathsFor("linerendering", "pointrendering")}
|
||||
{state}
|
||||
/>
|
||||
</div>
|
||||
<div slot="content3">
|
||||
<Region configs={perRegion["linerendering"]} {state} />
|
||||
<Region configs={perRegion["pointrendering"]} {state} />
|
||||
</div>
|
||||
|
||||
<div slot="title4" class="flex">Advanced functionality
|
||||
<ErrorIndicatorForRegion firstPaths={firstPathsFor("advanced","expert")} {state} />
|
||||
<div slot="title4" class="flex">
|
||||
Advanced functionality
|
||||
<ErrorIndicatorForRegion firstPaths={firstPathsFor("advanced", "expert")} {state} />
|
||||
</div>
|
||||
<div slot="content4">
|
||||
<Region configs={perRegion["advanced"]} {state} />
|
||||
|
|
@ -149,30 +160,33 @@
|
|||
<div slot="title5">Configuration file</div>
|
||||
<div slot="content5">
|
||||
<div>
|
||||
Below, you'll find the raw configuration file in `.json`-format.
|
||||
This is mostly for debugging purposes
|
||||
Below, you'll find the raw configuration file in `.json`-format. This is mostly for
|
||||
debugging purposes
|
||||
</div>
|
||||
<div class="literal-code">
|
||||
<FromHtml src={JSON.stringify($configuration, null, " ").replaceAll("\n","</br>")} />
|
||||
<FromHtml src={JSON.stringify($configuration, null, " ").replaceAll("\n", "</br>")} />
|
||||
</div>
|
||||
|
||||
<ShowConversionMessages messages={$messages} />
|
||||
<div>
|
||||
The testobject (which is used to render the questions in the 'information panel' item has the following
|
||||
tags:
|
||||
The testobject (which is used to render the questions in the 'information panel' item
|
||||
has the following tags:
|
||||
</div>
|
||||
|
||||
<AllTagsPanel tags={state.testTags}></AllTagsPanel>
|
||||
<AllTagsPanel tags={state.testTags} />
|
||||
</div>
|
||||
</TabbedGroup>
|
||||
</div>
|
||||
{#if $highlightedItem !== undefined}
|
||||
<FloatOver on:close={() => highlightedItem.setData(undefined)}>
|
||||
<div>
|
||||
<TagRenderingInput path={$highlightedItem.path} {state} schema={$highlightedItem.schema} />
|
||||
<TagRenderingInput
|
||||
path={$highlightedItem.path}
|
||||
{state}
|
||||
schema={$highlightedItem.schema}
|
||||
/>
|
||||
</div>
|
||||
</FloatOver>
|
||||
{/if}
|
||||
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,37 +1,45 @@
|
|||
<script lang="ts">
|
||||
import { EditThemeState } from "./EditLayerState";
|
||||
import type { ConfigMeta } from "./configMeta";
|
||||
import { ChevronRightIcon } from "@rgossiaux/svelte-heroicons/solid";
|
||||
import type { ConversionMessage } from "../../Models/ThemeConfig/Conversion/Conversion";
|
||||
import TabbedGroup from "../Base/TabbedGroup.svelte";
|
||||
import ShowConversionMessages from "./ShowConversionMessages.svelte";
|
||||
import Region from "./Region.svelte";
|
||||
import { EditThemeState } from "./EditLayerState"
|
||||
import type { ConfigMeta } from "./configMeta"
|
||||
import { ChevronRightIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
import type { ConversionMessage } from "../../Models/ThemeConfig/Conversion/Conversion"
|
||||
import TabbedGroup from "../Base/TabbedGroup.svelte"
|
||||
import ShowConversionMessages from "./ShowConversionMessages.svelte"
|
||||
import Region from "./Region.svelte"
|
||||
|
||||
export let state: EditThemeState;
|
||||
let schema: ConfigMeta[] = state.schema.filter(schema => schema.path.length > 0);
|
||||
let config = state.configuration;
|
||||
let messages = state.messages;
|
||||
let hasErrors = messages.map((m: ConversionMessage[]) => m.filter(m => m.level === "error").length);
|
||||
let title = state.getStoreFor(["id"]);
|
||||
const wl = window.location;
|
||||
const baseUrl = wl.protocol + "//" + wl.host + "/theme.html?userlayout=";
|
||||
export let state: EditThemeState
|
||||
let schema: ConfigMeta[] = state.schema.filter((schema) => schema.path.length > 0)
|
||||
let config = state.configuration
|
||||
let messages = state.messages
|
||||
let hasErrors = messages.map(
|
||||
(m: ConversionMessage[]) => m.filter((m) => m.level === "error").length
|
||||
)
|
||||
let title = state.getStoreFor(["id"])
|
||||
const wl = window.location
|
||||
const baseUrl = wl.protocol + "//" + wl.host + "/theme.html?userlayout="
|
||||
|
||||
const perRegion: Record<string, ConfigMeta[]> = {};
|
||||
const perRegion: Record<string, ConfigMeta[]> = {}
|
||||
for (const schemaElement of schema) {
|
||||
const key = schemaElement.hints.group ?? "no-group";
|
||||
const list = perRegion[key] ?? (perRegion[key] = []);
|
||||
list.push(schemaElement);
|
||||
const key = schemaElement.hints.group ?? "no-group"
|
||||
const list = perRegion[key] ?? (perRegion[key] = [])
|
||||
list.push(schemaElement)
|
||||
}
|
||||
console.log({perRegion, schema})
|
||||
console.log({ perRegion, schema })
|
||||
</script>
|
||||
<div class="flex flex-col h-screen">
|
||||
<div class="w-full flex justify-between my-2">
|
||||
|
||||
<div class="flex h-screen flex-col">
|
||||
<div class="my-2 flex w-full justify-between">
|
||||
<slot />
|
||||
<h3>Editing theme {$title}</h3>
|
||||
{#if $hasErrors > 0}
|
||||
<div class="alert">{$hasErrors} errors detected</div>
|
||||
{:else}
|
||||
<a class="primary button" href={baseUrl+state.server.urlFor($title, "themes")} target="_blank" rel="noopener">
|
||||
<a
|
||||
class="primary button"
|
||||
href={baseUrl + state.server.urlFor($title, "themes")}
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
Try it out
|
||||
<ChevronRightIcon class="h-6 w-6 shrink-0" />
|
||||
</a>
|
||||
|
|
@ -43,36 +51,32 @@ console.log({perRegion, schema})
|
|||
<TabbedGroup>
|
||||
<div slot="title0">Basic properties</div>
|
||||
<div slot="content0">
|
||||
<Region configs={perRegion["basic"]} path={[]} {state} title="Basic properties"/>
|
||||
<Region configs={perRegion["start_location"]} path={[]} {state} title="Start location"/>
|
||||
|
||||
<Region configs={perRegion["basic"]} path={[]} {state} title="Basic properties" />
|
||||
<Region configs={perRegion["start_location"]} path={[]} {state} title="Start location" />
|
||||
</div>
|
||||
|
||||
|
||||
<div slot="title1">Layers</div>
|
||||
<div slot="content1">
|
||||
<Region configs={perRegion["layers"]} path={[]} {state} />
|
||||
|
||||
</div>
|
||||
<div slot="title2">Feature switches</div>
|
||||
<div slot="content2">
|
||||
<Region configs={perRegion["feature_switches"]} path={[]} {state}></Region>
|
||||
<Region configs={perRegion["feature_switches"]} path={[]} {state} />
|
||||
</div>
|
||||
|
||||
<div slot="title3">Advanced options</div>
|
||||
<div slot="content3">
|
||||
<Region configs={perRegion["advanced"]} path={[]} {state}></Region>
|
||||
<Region configs={perRegion["advanced"]} path={[]} {state} />
|
||||
</div>
|
||||
|
||||
|
||||
<div slot="title4">Configuration file</div>
|
||||
<div slot="content4">
|
||||
<div class="literal-code">
|
||||
{JSON.stringify($config)}
|
||||
</div>
|
||||
|
||||
<ShowConversionMessages messages={$messages}></ShowConversionMessages>
|
||||
|
||||
<ShowConversionMessages messages={$messages} />
|
||||
</div>
|
||||
</TabbedGroup>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,19 +1,21 @@
|
|||
<script lang="ts">
|
||||
import EditLayerState from "./EditLayerState";
|
||||
import { ExclamationIcon } from "@rgossiaux/svelte-heroicons/solid";
|
||||
|
||||
export let firstPaths: Set<string>;
|
||||
export let state: EditLayerState;
|
||||
let messagesCount = state.messages.map(msgs => msgs.filter(msg => {
|
||||
const pth = msg.context.path
|
||||
return firstPaths.has(pth[0]) || (pth.length > 1 && firstPaths.has(pth[1]));
|
||||
}).length);
|
||||
import EditLayerState from "./EditLayerState"
|
||||
import { ExclamationIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
|
||||
export let firstPaths: Set<string>
|
||||
export let state: EditLayerState
|
||||
let messagesCount = state.messages.map(
|
||||
(msgs) =>
|
||||
msgs.filter((msg) => {
|
||||
const pth = msg.context.path
|
||||
return firstPaths.has(pth[0]) || (pth.length > 1 && firstPaths.has(pth[1]))
|
||||
}).length
|
||||
)
|
||||
</script>
|
||||
|
||||
{#if $messagesCount > 0}
|
||||
<span class="alert flex w-min">
|
||||
<ExclamationIcon class="w-6 h-6" />
|
||||
<ExclamationIcon class="h-6 w-6" />
|
||||
{$messagesCount}
|
||||
</span>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -1,56 +1,62 @@
|
|||
<script lang="ts">
|
||||
import EditLayerState from "./EditLayerState"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import type { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson"
|
||||
import { TagUtils } from "../../Logic/Tags/TagUtils"
|
||||
import FromHtml from "../Base/FromHtml.svelte"
|
||||
import { PencilIcon } from "@rgossiaux/svelte-heroicons/outline"
|
||||
import Region from "./Region.svelte"
|
||||
import type { ConfigMeta } from "./configMeta"
|
||||
import configs from "../../assets/schemas/questionabletagrenderingconfigmeta.json"
|
||||
import { Utils } from "../../Utils"
|
||||
import ToSvelte from "../Base/ToSvelte.svelte"
|
||||
import { VariableUiElement } from "../Base/VariableUIElement"
|
||||
|
||||
import EditLayerState from "./EditLayerState";
|
||||
import { UIEventSource } from "../../Logic/UIEventSource";
|
||||
import type { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson";
|
||||
import { TagUtils } from "../../Logic/Tags/TagUtils";
|
||||
import FromHtml from "../Base/FromHtml.svelte";
|
||||
import { PencilIcon } from "@rgossiaux/svelte-heroicons/outline";
|
||||
import Region from "./Region.svelte";
|
||||
import type { ConfigMeta } from "./configMeta";
|
||||
import configs from "../../assets/schemas/questionabletagrenderingconfigmeta.json";
|
||||
import { Utils } from "../../Utils";
|
||||
import ToSvelte from "../Base/ToSvelte.svelte";
|
||||
import { VariableUiElement } from "../Base/VariableUIElement";
|
||||
|
||||
export let state: EditLayerState;
|
||||
export let path: (string | number)[];
|
||||
let tag: UIEventSource<TagConfigJson> = state.getStoreFor([...path, "if"]);
|
||||
let parsedTag = tag.map(t => t ? TagUtils.Tag(t) : undefined);
|
||||
let exampleTags = parsedTag.map(pt => {
|
||||
export let state: EditLayerState
|
||||
export let path: (string | number)[]
|
||||
let tag: UIEventSource<TagConfigJson> = state.getStoreFor([...path, "if"])
|
||||
let parsedTag = tag.map((t) => (t ? TagUtils.Tag(t) : undefined))
|
||||
let exampleTags = parsedTag.map((pt) => {
|
||||
if (!pt) {
|
||||
return {};
|
||||
return {}
|
||||
}
|
||||
const keys = pt.usedKeys();
|
||||
const o = {};
|
||||
const keys = pt.usedKeys()
|
||||
const o = {}
|
||||
for (const key of keys) {
|
||||
o[key] = "value";
|
||||
o[key] = "value"
|
||||
}
|
||||
return o;
|
||||
});
|
||||
let uploadableOnly: boolean = true;
|
||||
return o
|
||||
})
|
||||
let uploadableOnly: boolean = true
|
||||
|
||||
let thenText: UIEventSource<Record<string, string>> = state.getStoreFor([...path, "then"])
|
||||
let thenTextEn = thenText .mapD(translation => typeof translation === "string" ? translation : translation["en"] )
|
||||
let editMode = Object.keys($thenText ?? {})?.length === 0;
|
||||
let thenTextEn = thenText.mapD((translation) =>
|
||||
typeof translation === "string" ? translation : translation["en"]
|
||||
)
|
||||
let editMode = Object.keys($thenText ?? {})?.length === 0
|
||||
|
||||
let mappingConfigs: ConfigMeta[] = configs.filter(c => c.path[0] === "mappings")
|
||||
.map(c => <ConfigMeta>Utils.Clone(c))
|
||||
.map(c => {
|
||||
c.path.splice(0, 1);
|
||||
return c;
|
||||
let mappingConfigs: ConfigMeta[] = configs
|
||||
.filter((c) => c.path[0] === "mappings")
|
||||
.map((c) => <ConfigMeta>Utils.Clone(c))
|
||||
.map((c) => {
|
||||
c.path.splice(0, 1)
|
||||
return c
|
||||
})
|
||||
.filter(c => c.path.length == 1 && c.hints.group !== "hidden");
|
||||
.filter((c) => c.path.length == 1 && c.hints.group !== "hidden")
|
||||
</script>
|
||||
|
||||
<button on:click={() => {editMode = !editMode}}>
|
||||
<PencilIcon class="w-6 h-6" />
|
||||
<button
|
||||
on:click={() => {
|
||||
editMode = !editMode
|
||||
}}
|
||||
>
|
||||
<PencilIcon class="h-6 w-6" />
|
||||
</button>
|
||||
|
||||
{#if editMode}
|
||||
<div class="flex justify-between items-start w-full">
|
||||
<div class="flex flex-col w-full">
|
||||
<Region {state} configs={mappingConfigs} path={path} />
|
||||
<div class="flex w-full items-start justify-between">
|
||||
<div class="flex w-full flex-col">
|
||||
<Region {state} configs={mappingConfigs} {path} />
|
||||
</div>
|
||||
|
||||
<slot name="delete" />
|
||||
|
|
@ -64,7 +70,6 @@
|
|||
{:else}
|
||||
<i>No then is set</i>
|
||||
{/if}
|
||||
<FromHtml src={ $parsedTag?.asHumanString(false, false, $exampleTags)} />
|
||||
<FromHtml src={$parsedTag?.asHumanString(false, false, $exampleTags)} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,86 +1,82 @@
|
|||
<script lang="ts">
|
||||
import type { ConfigMeta } from "./configMeta";
|
||||
import EditLayerState from "./EditLayerState";
|
||||
import * as questions from "../../assets/generated/layers/questions.json";
|
||||
import { ImmutableStore, Store } from "../../Logic/UIEventSource";
|
||||
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte";
|
||||
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig";
|
||||
import nmd from "nano-markdown";
|
||||
import type {
|
||||
QuestionableTagRenderingConfigJson
|
||||
} from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson.js";
|
||||
import type { TagRenderingConfigJson } from "../../Models/ThemeConfig/Json/TagRenderingConfigJson";
|
||||
import FromHtml from "../Base/FromHtml.svelte";
|
||||
import ShowConversionMessage from "./ShowConversionMessage.svelte";
|
||||
import NextButton from "../Base/NextButton.svelte";
|
||||
import type { ConfigMeta } from "./configMeta"
|
||||
import EditLayerState from "./EditLayerState"
|
||||
import * as questions from "../../assets/generated/layers/questions.json"
|
||||
import { ImmutableStore, Store } from "../../Logic/UIEventSource"
|
||||
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte"
|
||||
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
|
||||
import nmd from "nano-markdown"
|
||||
import type { QuestionableTagRenderingConfigJson } from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson.js"
|
||||
import type { TagRenderingConfigJson } from "../../Models/ThemeConfig/Json/TagRenderingConfigJson"
|
||||
import FromHtml from "../Base/FromHtml.svelte"
|
||||
import ShowConversionMessage from "./ShowConversionMessage.svelte"
|
||||
import NextButton from "../Base/NextButton.svelte"
|
||||
|
||||
export let state: EditLayerState;
|
||||
export let path: ReadonlyArray<string | number>;
|
||||
export let schema: ConfigMeta;
|
||||
let value = state.getStoreFor(path);
|
||||
export let state: EditLayerState
|
||||
export let path: ReadonlyArray<string | number>
|
||||
export let schema: ConfigMeta
|
||||
let value = state.getStoreFor(path)
|
||||
|
||||
let perId: Record<string, TagRenderingConfigJson[]> = {};
|
||||
let perId: Record<string, TagRenderingConfigJson[]> = {}
|
||||
for (let tagRendering of questions.tagRenderings) {
|
||||
if (tagRendering.labels) {
|
||||
for (let label of tagRendering.labels) {
|
||||
perId[label] = (perId[label] ?? []).concat(tagRendering);
|
||||
perId[label] = (perId[label] ?? []).concat(tagRendering)
|
||||
}
|
||||
}
|
||||
perId[tagRendering.id] = [tagRendering];
|
||||
perId[tagRendering.id] = [tagRendering]
|
||||
}
|
||||
|
||||
let configJson: Store<QuestionableTagRenderingConfigJson[]> = value.map(x => {
|
||||
let configJson: Store<QuestionableTagRenderingConfigJson[]> = value.map((x) => {
|
||||
if (typeof x === "string") {
|
||||
return perId[x];
|
||||
return perId[x]
|
||||
} else {
|
||||
return [x];
|
||||
return [x]
|
||||
}
|
||||
});
|
||||
let configs: Store<TagRenderingConfig[]> = configJson.map(configs => {
|
||||
})
|
||||
let configs: Store<TagRenderingConfig[]> = configJson.map((configs) => {
|
||||
if (!configs) {
|
||||
return [{ error: "No configuartions found" }];
|
||||
return [{ error: "No configuartions found" }]
|
||||
}
|
||||
console.log("Regenerating configs");
|
||||
return configs.map(config => {
|
||||
console.log("Regenerating configs")
|
||||
return configs.map((config) => {
|
||||
try {
|
||||
return new TagRenderingConfig(config);
|
||||
return new TagRenderingConfig(config)
|
||||
} catch (e) {
|
||||
return { error: e };
|
||||
return { error: e }
|
||||
}
|
||||
});
|
||||
});
|
||||
let id: Store<string> = value.mapD(c => {
|
||||
})
|
||||
})
|
||||
let id: Store<string> = value.mapD((c) => {
|
||||
if (c?.id) {
|
||||
return c.id;
|
||||
return c.id
|
||||
}
|
||||
if (typeof c === "string") {
|
||||
return c;
|
||||
return c
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
return undefined
|
||||
})
|
||||
|
||||
let tags = state.testTags;
|
||||
let tags = state.testTags
|
||||
|
||||
let messages = state.messagesFor(path);
|
||||
let messages = state.messagesFor(path)
|
||||
|
||||
let description = schema.description;
|
||||
let description = schema.description
|
||||
if (description) {
|
||||
try {
|
||||
description = nmd(description);
|
||||
description = nmd(description)
|
||||
} catch (e) {
|
||||
console.error("Could not convert description to markdown", { description });
|
||||
console.error("Could not convert description to markdown", { description })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex">
|
||||
|
||||
<div class="flex flex-col interactive border-interactive m-4 w-full">
|
||||
|
||||
<div class="interactive border-interactive m-4 flex w-full flex-col">
|
||||
{#if $id}
|
||||
TagRendering {$id}
|
||||
{/if}
|
||||
<NextButton clss="primary" on:click={() => state.highlightedItem.setData({path, schema})}>
|
||||
<NextButton clss="primary" on:click={() => state.highlightedItem.setData({ path, schema })}>
|
||||
{#if schema.hints.question}
|
||||
{schema.hints.question}
|
||||
{/if}
|
||||
|
|
@ -92,40 +88,40 @@
|
|||
<ShowConversionMessage {message} />
|
||||
{/each}
|
||||
|
||||
<slot class="self-end my-4"></slot>
|
||||
|
||||
|
||||
<slot class="my-4 self-end" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col w-full m-4">
|
||||
<div class="m-4 flex w-full flex-col">
|
||||
<h3>Preview of this question</h3>
|
||||
{#each $configs as config}
|
||||
{#if config.error !== undefined}
|
||||
<div class="alert">Could not create a preview of this tagRendering: {config.error}</div>
|
||||
{:else if config.condition && !config.condition.matchesProperties($tags)}
|
||||
This tagRendering is currently not shown. It will appear if the feature matches the
|
||||
condition
|
||||
<b>
|
||||
<FromHtml src={config.condition.asHumanString(true, false, {})} />
|
||||
</b>
|
||||
|
||||
Try to answer the relevant question above
|
||||
{:else if config.metacondition && !config.metacondition.matchesProperties($tags)}
|
||||
This tagRendering is currently not shown. It will appear if the feature matches the
|
||||
metacondition
|
||||
<b>
|
||||
<FromHtml src={config.metacondition.asHumanString(true, false, {})} />
|
||||
</b>
|
||||
For a breakdown of usable meta conditions, go to a mapcomplete theme > settings and enable debug-data.
|
||||
The meta-tags will appear at the bottom
|
||||
{:else}
|
||||
{#if config.condition && !config.condition.matchesProperties($tags)}
|
||||
This tagRendering is currently not shown. It will appear if the feature matches the condition
|
||||
<b>
|
||||
<FromHtml src={config.condition.asHumanString(true, false, {})} />
|
||||
</b>
|
||||
|
||||
Try to answer the relevant question above
|
||||
{:else if config.metacondition && !config.metacondition.matchesProperties($tags)}
|
||||
This tagRendering is currently not shown. It will appear if the feature matches the metacondition
|
||||
<b>
|
||||
<FromHtml src={config.metacondition.asHumanString(true, false, {})} />
|
||||
</b>
|
||||
For a breakdown of usable meta conditions, go to a mapcomplete theme > settings and enable debug-data. The meta-tags will appear at the bottom
|
||||
{:else}
|
||||
<TagRenderingEditable
|
||||
selectedElement={state.exampleFeature}
|
||||
config={config} editingEnabled={new ImmutableStore(true)} showQuestionIfUnknown={true}
|
||||
{state}
|
||||
{tags}></TagRenderingEditable>
|
||||
{/if}
|
||||
<TagRenderingEditable
|
||||
selectedElement={state.exampleFeature}
|
||||
{config}
|
||||
editingEnabled={new ImmutableStore(true)}
|
||||
showQuestionIfUnknown={true}
|
||||
{state}
|
||||
{tags}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,30 +1,33 @@
|
|||
<script lang="ts">/***
|
||||
* A 'region' is a collection of properties that can be edited which are somewhat related.
|
||||
* They will typically be a subset of some properties
|
||||
*/
|
||||
import type { ConfigMeta } from "./configMeta";
|
||||
import EditLayerState from "./EditLayerState";
|
||||
import SchemaBasedInput from "./SchemaBasedInput.svelte";
|
||||
<script lang="ts">
|
||||
/***
|
||||
* A 'region' is a collection of properties that can be edited which are somewhat related.
|
||||
* They will typically be a subset of some properties
|
||||
*/
|
||||
import type { ConfigMeta } from "./configMeta"
|
||||
import EditLayerState from "./EditLayerState"
|
||||
import SchemaBasedInput from "./SchemaBasedInput.svelte"
|
||||
|
||||
export let state: EditLayerState;
|
||||
export let configs: ConfigMeta[];
|
||||
export let title: string | undefined = undefined;
|
||||
export let state: EditLayerState
|
||||
export let configs: ConfigMeta[]
|
||||
export let title: string | undefined = undefined
|
||||
|
||||
export let path: (string | number)[] = [];
|
||||
|
||||
let expertMode = state.expertMode
|
||||
let configsNoHidden = configs.filter(schema => schema.hints?.group !== "hidden")
|
||||
let configsFiltered = $expertMode ? configsNoHidden : configsNoHidden.filter(schema => schema.hints?.group !== "expert")
|
||||
export let path: (string | number)[] = []
|
||||
|
||||
let expertMode = state.expertMode
|
||||
let configsNoHidden = configs.filter((schema) => schema.hints?.group !== "hidden")
|
||||
let configsFiltered = $expertMode
|
||||
? configsNoHidden
|
||||
: configsNoHidden.filter((schema) => schema.hints?.group !== "expert")
|
||||
</script>
|
||||
|
||||
{#if configs === undefined}
|
||||
Bug: 'Region' received 'undefined'
|
||||
{:else if configs.length === 0}
|
||||
Bug: Region received empty list as configuration
|
||||
{:else if title}
|
||||
<div class="w-full flex flex-col">
|
||||
<div class="flex w-full flex-col">
|
||||
<h3>{title}</h3>
|
||||
<div class="pl-2 border border-black flex flex-col gap-y-1 w-full">
|
||||
<div class="flex w-full flex-col gap-y-1 border border-black pl-2">
|
||||
<slot name="description" />
|
||||
{#each configsFiltered as config}
|
||||
<SchemaBasedInput {state} path={config.path} schema={config} />
|
||||
|
|
@ -32,10 +35,9 @@ let configsFiltered = $expertMode ? configsNoHidden : configsNoHidden.filter(sch
|
|||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="pl-2 flex flex-col gap-y-1 w-full">
|
||||
<div class="flex w-full flex-col gap-y-1 pl-2">
|
||||
{#each configsFiltered as config}
|
||||
<SchemaBasedInput {state} path={path.concat(config.path)} schema={config} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,66 +1,70 @@
|
|||
<script lang="ts">
|
||||
import EditLayerState from "./EditLayerState";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import type {TagConfigJson} from "../../Models/ThemeConfig/Json/TagConfigJson";
|
||||
import FullTagInput from "./TagInput/FullTagInput.svelte";
|
||||
import type {ConfigMeta} from "./configMeta";
|
||||
import {PencilAltIcon} from "@rgossiaux/svelte-heroicons/solid";
|
||||
import { onDestroy } from "svelte";
|
||||
import EditLayerState from "./EditLayerState"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import type { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson"
|
||||
import FullTagInput from "./TagInput/FullTagInput.svelte"
|
||||
import type { ConfigMeta } from "./configMeta"
|
||||
import { PencilAltIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
import { onDestroy } from "svelte"
|
||||
|
||||
/**
|
||||
* Thin wrapper around 'TagInput' which registers the output with the state
|
||||
*/
|
||||
export let path: (string | number)[]
|
||||
export let state: EditLayerState
|
||||
/**
|
||||
* Thin wrapper around 'TagInput' which registers the output with the state
|
||||
*/
|
||||
export let path: (string | number)[]
|
||||
export let state: EditLayerState
|
||||
|
||||
export let schema: ConfigMeta
|
||||
export let schema: ConfigMeta
|
||||
|
||||
const initialValue = state.getCurrentValueFor(path)
|
||||
let tag: UIEventSource<TagConfigJson> = new UIEventSource<TagConfigJson>(initialValue)
|
||||
const initialValue = state.getCurrentValueFor(path)
|
||||
let tag: UIEventSource<TagConfigJson> = new UIEventSource<TagConfigJson>(initialValue)
|
||||
|
||||
onDestroy(state.register(path, tag))
|
||||
onDestroy(state.register(path, tag))
|
||||
|
||||
let mode: "editing" | "set" = tag.data === undefined ? "editing" : "set"
|
||||
let mode: "editing" | "set" = tag.data === undefined ? "editing" : "set"
|
||||
|
||||
function simplify(tag: TagConfigJson): string {
|
||||
if (typeof tag === "string") {
|
||||
return tag
|
||||
}
|
||||
if (tag["and"]) {
|
||||
return "{ and: " + simplify(tag["and"].map(simplify).join(" ; ") + " }")
|
||||
}
|
||||
if (tag["or"]) {
|
||||
return "{ or: " + simplify(tag["or"].map(simplify).join(" ; ") + " }")
|
||||
}
|
||||
function simplify(tag: TagConfigJson): string {
|
||||
if (typeof tag === "string") {
|
||||
return tag
|
||||
}
|
||||
|
||||
if (tag["and"]) {
|
||||
return "{ and: " + simplify(tag["and"].map(simplify).join(" ; ") + " }")
|
||||
}
|
||||
if (tag["or"]) {
|
||||
return "{ or: " + simplify(tag["or"].map(simplify).join(" ; ") + " }")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if mode === "editing"}
|
||||
<div class="interactive border-interactive">
|
||||
<h3>{schema.hints.question ?? "What tags should be applied?"}</h3>
|
||||
{schema.description}
|
||||
<FullTagInput {tag}/>
|
||||
<div class="flex justify-end">
|
||||
|
||||
<button class="primary w-fit" on:click={() => {mode = "set"}}>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
<div class="subtle">RegisteredTagInput based on schema: {JSON.stringify(schema)}</div>
|
||||
<div class="interactive border-interactive">
|
||||
<h3>{schema.hints.question ?? "What tags should be applied?"}</h3>
|
||||
{schema.description}
|
||||
<FullTagInput {tag} />
|
||||
<div class="flex justify-end">
|
||||
<button
|
||||
class="primary w-fit"
|
||||
on:click={() => {
|
||||
mode = "set"
|
||||
}}
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
<div class="subtle">RegisteredTagInput based on schema: {JSON.stringify(schema)}</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="low-interaction flex justify-between">
|
||||
<div>
|
||||
|
||||
{schema.path.at(-1)}
|
||||
{simplify($tag)}
|
||||
</div>
|
||||
<button
|
||||
on:click={() => {mode = "editing"}}
|
||||
class="secondary h-8 w-8 shrink-0 self-start rounded-full p-1"
|
||||
>
|
||||
<PencilAltIcon/>
|
||||
</button>
|
||||
<div class="low-interaction flex justify-between">
|
||||
<div>
|
||||
{schema.path.at(-1)}
|
||||
{simplify($tag)}
|
||||
</div>
|
||||
<button
|
||||
on:click={() => {
|
||||
mode = "editing"
|
||||
}}
|
||||
class="secondary h-8 w-8 shrink-0 self-start rounded-full p-1"
|
||||
>
|
||||
<PencilAltIcon />
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -1,117 +1,114 @@
|
|||
<script lang="ts">
|
||||
import EditLayerState from "./EditLayerState";
|
||||
import type { ConfigMeta } from "./configMeta";
|
||||
import { UIEventSource } from "../../Logic/UIEventSource";
|
||||
import SchemaBasedInput from "./SchemaBasedInput.svelte";
|
||||
import SchemaBasedField from "./SchemaBasedField.svelte";
|
||||
import { TrashIcon } from "@babeard/svelte-heroicons/mini";
|
||||
import QuestionPreview from "./QuestionPreview.svelte";
|
||||
import SchemaBasedMultiType from "./SchemaBasedMultiType.svelte";
|
||||
import ShowConversionMessage from "./ShowConversionMessage.svelte";
|
||||
import EditLayerState from "./EditLayerState"
|
||||
import type { ConfigMeta } from "./configMeta"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import SchemaBasedInput from "./SchemaBasedInput.svelte"
|
||||
import SchemaBasedField from "./SchemaBasedField.svelte"
|
||||
import { TrashIcon } from "@babeard/svelte-heroicons/mini"
|
||||
import QuestionPreview from "./QuestionPreview.svelte"
|
||||
import SchemaBasedMultiType from "./SchemaBasedMultiType.svelte"
|
||||
import ShowConversionMessage from "./ShowConversionMessage.svelte"
|
||||
|
||||
export let state: EditLayerState;
|
||||
export let schema: ConfigMeta;
|
||||
export let state: EditLayerState
|
||||
export let schema: ConfigMeta
|
||||
|
||||
|
||||
let title = schema.path.at(-1);
|
||||
let singular = title;
|
||||
let title = schema.path.at(-1)
|
||||
let singular = title
|
||||
if (title?.endsWith("s")) {
|
||||
singular = title.slice(0, title.length - 1);
|
||||
singular = title.slice(0, title.length - 1)
|
||||
}
|
||||
let article = "a";
|
||||
let article = "a"
|
||||
if (singular?.match(/^[aeoui]/)) {
|
||||
article = "an";
|
||||
article = "an"
|
||||
}
|
||||
export let path: (string | number)[] = [];
|
||||
|
||||
const isTagRenderingBlock = path.length === 1 && path[0] === "tagRenderings";
|
||||
export let path: (string | number)[] = []
|
||||
|
||||
const isTagRenderingBlock = path.length === 1 && path[0] === "tagRenderings"
|
||||
|
||||
if (isTagRenderingBlock) {
|
||||
schema = { ...schema };
|
||||
schema.description = undefined;
|
||||
schema = { ...schema }
|
||||
schema.description = undefined
|
||||
}
|
||||
|
||||
const subparts: ConfigMeta = state.getSchemaStartingWith(schema.path)
|
||||
.filter(part => part.path.length - 1 === schema.path.length);
|
||||
|
||||
const subparts: ConfigMeta = state
|
||||
.getSchemaStartingWith(schema.path)
|
||||
.filter((part) => part.path.length - 1 === schema.path.length)
|
||||
|
||||
let messages = state.messagesFor(path)
|
||||
|
||||
|
||||
const currentValue : UIEventSource<any[]> = state.getStoreFor(path);
|
||||
if(currentValue.data === undefined){
|
||||
|
||||
const currentValue: UIEventSource<any[]> = state.getStoreFor(path)
|
||||
if (currentValue.data === undefined) {
|
||||
currentValue.setData([])
|
||||
}
|
||||
|
||||
function createItem(valueToSet?: any) {
|
||||
if(currentValue.data === undefined){
|
||||
if (currentValue.data === undefined) {
|
||||
currentValue.setData([])
|
||||
}
|
||||
currentValue.data.push(valueToSet)
|
||||
currentValue.ping()
|
||||
|
||||
if(isTagRenderingBlock){
|
||||
state.highlightedItem.setData({path: [...path, currentValue.data.length - 1], schema})
|
||||
|
||||
if (isTagRenderingBlock) {
|
||||
state.highlightedItem.setData({ path: [...path, currentValue.data.length - 1], schema })
|
||||
}
|
||||
}
|
||||
|
||||
function fusePath(i: number, subpartPath: string[]): (string | number)[] {
|
||||
const newPath = [...path, i];
|
||||
const toAdd = [...subpartPath];
|
||||
const newPath = [...path, i]
|
||||
const toAdd = [...subpartPath]
|
||||
for (const part of path) {
|
||||
if (toAdd[0] === part) {
|
||||
toAdd.splice(0, 1);
|
||||
}else{
|
||||
toAdd.splice(0, 1)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
newPath.push(...toAdd);
|
||||
console.log({newPath})
|
||||
return newPath;
|
||||
newPath.push(...toAdd)
|
||||
console.log({ newPath })
|
||||
return newPath
|
||||
}
|
||||
|
||||
function schemaForMultitype() {
|
||||
const sch = {...schema}
|
||||
const sch = { ...schema }
|
||||
sch.hints.typehint = undefined
|
||||
return sch
|
||||
}
|
||||
|
||||
|
||||
function del(i: number){
|
||||
|
||||
function del(i: number) {
|
||||
currentValue.data.splice(i, 1)
|
||||
currentValue.ping()
|
||||
}
|
||||
|
||||
|
||||
function swap(i: number, j: number) {
|
||||
const x = currentValue.data[i]
|
||||
currentValue.data[i] = currentValue.data[j]
|
||||
currentValue.data[i] = currentValue.data[j]
|
||||
currentValue.data[j] = x
|
||||
currentValue.ping()
|
||||
}
|
||||
|
||||
function moveTo(source: number, target: number){
|
||||
|
||||
function moveTo(source: number, target: number) {
|
||||
const x = currentValue.data[source]
|
||||
currentValue.data.splice(source, 1)
|
||||
currentValue.data.splice(target, 0, x)
|
||||
currentValue.ping()
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<div class="pl-2">
|
||||
<h3>{schema.path.at(-1)}</h3>
|
||||
|
||||
{#if subparts.length > 0}
|
||||
<span class="subtle">
|
||||
{schema.description}
|
||||
</span>
|
||||
<span class="subtle">
|
||||
{schema.description}
|
||||
</span>
|
||||
{/if}
|
||||
{#if $currentValue === undefined}
|
||||
No array defined
|
||||
No array defined
|
||||
{:else if $currentValue.length === 0}
|
||||
No values are defined
|
||||
{#if $messages.length > 0}
|
||||
{#each $messages as message}
|
||||
<ShowConversionMessage {message}/>
|
||||
<ShowConversionMessage {message} />
|
||||
{/each}
|
||||
{/if}
|
||||
{:else if subparts.length === 0}
|
||||
|
|
@ -119,52 +116,79 @@
|
|||
{#each $currentValue as value, i}
|
||||
<div class="flex w-full">
|
||||
<SchemaBasedField {state} {schema} path={[...path, i]} />
|
||||
<button class="border-black border rounded-full p-1 w-fit h-fit"
|
||||
on:click={() => {del(i)}}>
|
||||
<TrashIcon class="w-4 h-4" />
|
||||
<button
|
||||
class="h-fit w-fit rounded-full border border-black p-1"
|
||||
on:click={() => {
|
||||
del(i)
|
||||
}}
|
||||
>
|
||||
<TrashIcon class="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
{:else}
|
||||
{#each $currentValue as value, i}
|
||||
{#if !isTagRenderingBlock}
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="m-0">{singular} {i}</h3>
|
||||
<button class="border-black border rounded-full p-1 w-fit h-fit"
|
||||
on:click={() => {del(i)}}>
|
||||
<TrashIcon class="w-4 h-4" />
|
||||
<button
|
||||
class="h-fit w-fit rounded-full border border-black p-1"
|
||||
on:click={() => {
|
||||
del(i)
|
||||
}}
|
||||
>
|
||||
<TrashIcon class="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="border border-black">
|
||||
{#if isTagRenderingBlock}
|
||||
<QuestionPreview {state} path={[...path, i]} {schema}>
|
||||
<button on:click={() => {del(i)}}>
|
||||
<TrashIcon class="w-4 h-4" />
|
||||
<button
|
||||
on:click={() => {
|
||||
del(i)
|
||||
}}
|
||||
>
|
||||
<TrashIcon class="h-4 w-4" />
|
||||
Delete this question
|
||||
</button>
|
||||
|
||||
{#if i > 0}
|
||||
<button on:click={() => {moveTo(i, 0)}}>
|
||||
<button
|
||||
on:click={() => {
|
||||
moveTo(i, 0)
|
||||
}}
|
||||
>
|
||||
Move to front
|
||||
</button>
|
||||
|
||||
<button on:click={() => {swap(i, i-1)}}>
|
||||
<button
|
||||
on:click={() => {
|
||||
swap(i, i - 1)
|
||||
}}
|
||||
>
|
||||
Move up
|
||||
</button>
|
||||
{/if}
|
||||
{#if i + 1 < $currentValue.length}
|
||||
<button on:click={() => {swap(i, i+1)}}>
|
||||
<button
|
||||
on:click={() => {
|
||||
swap(i, i + 1)
|
||||
}}
|
||||
>
|
||||
Move down
|
||||
</button>
|
||||
<button on:click={() => {moveTo(i, $currentValue.length-1)}}>
|
||||
<button
|
||||
on:click={() => {
|
||||
moveTo(i, $currentValue.length - 1)
|
||||
}}
|
||||
>
|
||||
Move to back
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
</QuestionPreview>
|
||||
{:else if schema.hints.types}
|
||||
<SchemaBasedMultiType {state} path={fusePath(i, [])} schema={schemaForMultitype()}/>
|
||||
{:else if schema.hints.types}
|
||||
<SchemaBasedMultiType {state} path={fusePath(i, [])} schema={schemaForMultitype()} />
|
||||
{:else}
|
||||
{#each subparts as subpart}
|
||||
<SchemaBasedInput {state} path={fusePath(i, subpart.path)} schema={subpart} />
|
||||
|
|
@ -176,7 +200,13 @@
|
|||
<div class="flex">
|
||||
<button on:click={() => createItem()}>Add {article} {singular}</button>
|
||||
{#if path.length === 1 && path[0] === "tagRenderings"}
|
||||
<button on:click={() => {createItem("images");}}>Add a builtin tagRendering</button>
|
||||
<button
|
||||
on:click={() => {
|
||||
createItem("images")
|
||||
}}
|
||||
>
|
||||
Add a builtin tagRendering
|
||||
</button>
|
||||
{/if}
|
||||
<slot name="extra-button" />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,50 +1,55 @@
|
|||
<script lang="ts">
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import type { ConfigMeta } from "./configMeta"
|
||||
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte"
|
||||
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
|
||||
import nmd from "nano-markdown"
|
||||
import type { QuestionableTagRenderingConfigJson } from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
|
||||
import EditLayerState from "./EditLayerState"
|
||||
import { onDestroy } from "svelte"
|
||||
import type { JsonSchemaType } from "./jsonSchema"
|
||||
import { ConfigMetaUtils } from "./configMeta.ts"
|
||||
import ShowConversionMessage from "./ShowConversionMessage.svelte"
|
||||
|
||||
import { UIEventSource } from "../../Logic/UIEventSource";
|
||||
import type { ConfigMeta } from "./configMeta";
|
||||
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte";
|
||||
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig";
|
||||
import nmd from "nano-markdown";
|
||||
import type {
|
||||
QuestionableTagRenderingConfigJson
|
||||
} from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson";
|
||||
import EditLayerState from "./EditLayerState";
|
||||
import { onDestroy } from "svelte";
|
||||
import type { JsonSchemaType } from "./jsonSchema";
|
||||
import { ConfigMetaUtils } from "./configMeta.ts";
|
||||
import ShowConversionMessage from "./ShowConversionMessage.svelte";
|
||||
export let state: EditLayerState
|
||||
export let path: (string | number)[] = []
|
||||
export let schema: ConfigMeta
|
||||
export let startInEditModeIfUnset: boolean = schema.hints && !schema.hints.ifunset
|
||||
let value = new UIEventSource<string | any>(undefined)
|
||||
|
||||
export let state: EditLayerState;
|
||||
export let path: (string | number)[] = [];
|
||||
export let schema: ConfigMeta;
|
||||
export let startInEditModeIfUnset: boolean = schema.hints && !schema.hints.ifunset;
|
||||
let value = new UIEventSource<string | any>(undefined);
|
||||
const isTranslation =
|
||||
schema.hints?.typehint === "translation" ||
|
||||
schema.hints?.typehint === "rendered" ||
|
||||
ConfigMetaUtils.isTranslation(schema)
|
||||
let type = schema.hints.typehint ?? "string"
|
||||
|
||||
const isTranslation = schema.hints?.typehint === "translation" || schema.hints?.typehint === "rendered" || ConfigMetaUtils.isTranslation(schema);
|
||||
let type = schema.hints.typehint ?? "string";
|
||||
|
||||
let rendervalue = (schema.hints.inline ?? schema.path.join(".")) + (isTranslation ? " <b>{translated(value)}</b>" : " <b>{value}</b>");
|
||||
let rendervalue =
|
||||
(schema.hints.inline ?? schema.path.join(".")) +
|
||||
(isTranslation ? " <b>{translated(value)}</b>" : " <b>{value}</b>")
|
||||
|
||||
if (schema.type === "boolean") {
|
||||
rendervalue = undefined;
|
||||
rendervalue = undefined
|
||||
}
|
||||
if (schema.hints.typehint === "tag" || schema.hints.typehint === "simple_tag") {
|
||||
rendervalue = "{tags()}";
|
||||
rendervalue = "{tags()}"
|
||||
}
|
||||
|
||||
let helperArgs = schema.hints.typehelper?.split(",");
|
||||
let inline = schema.hints.inline !== undefined;
|
||||
let helperArgs = schema.hints.typehelper?.split(",")
|
||||
let inline = schema.hints.inline !== undefined
|
||||
if (isTranslation) {
|
||||
type = "translation";
|
||||
type = "translation"
|
||||
if (schema.hints.inline) {
|
||||
const inlineValue = schema.hints.inline;
|
||||
rendervalue = inlineValue;
|
||||
inline = false;
|
||||
helperArgs = [inlineValue.substring(0, inlineValue.indexOf("{")), inlineValue.substring(inlineValue.indexOf("}") + 1)];
|
||||
const inlineValue = schema.hints.inline
|
||||
rendervalue = inlineValue
|
||||
inline = false
|
||||
helperArgs = [
|
||||
inlineValue.substring(0, inlineValue.indexOf("{")),
|
||||
inlineValue.substring(inlineValue.indexOf("}") + 1),
|
||||
]
|
||||
}
|
||||
}
|
||||
if (type.endsWith("[]")) {
|
||||
type = type.substring(0, type.length - 2);
|
||||
type = type.substring(0, type.length - 2)
|
||||
}
|
||||
|
||||
const configJson: QuestionableTagRenderingConfigJson = {
|
||||
|
|
@ -52,125 +57,154 @@
|
|||
render: rendervalue,
|
||||
question: schema.hints.question,
|
||||
questionHint: nmd(schema.description),
|
||||
freeform: schema.type === "boolean" ? undefined : {
|
||||
key: "value",
|
||||
type,
|
||||
inline,
|
||||
helperArgs
|
||||
}
|
||||
};
|
||||
freeform:
|
||||
schema.type === "boolean"
|
||||
? undefined
|
||||
: {
|
||||
key: "value",
|
||||
type,
|
||||
inline,
|
||||
helperArgs,
|
||||
},
|
||||
}
|
||||
|
||||
if (schema.hints.default) {
|
||||
configJson.mappings = [{
|
||||
if: "value=", // We leave this blank
|
||||
then: path.at(-1) + " is not set. The default value <b>" + schema.hints.default + "</b> will be used. " + (schema.hints.ifunset ?? "")
|
||||
}];
|
||||
configJson.mappings = [
|
||||
{
|
||||
if: "value=", // We leave this blank
|
||||
then:
|
||||
path.at(-1) +
|
||||
" is not set. The default value <b>" +
|
||||
schema.hints.default +
|
||||
"</b> will be used. " +
|
||||
(schema.hints.ifunset ?? ""),
|
||||
},
|
||||
]
|
||||
} else if (!schema.required) {
|
||||
configJson.mappings = [{
|
||||
if: "value=",
|
||||
then: path.at(-1) + " is not set. " + (schema.hints.ifunset ?? "")
|
||||
}];
|
||||
configJson.mappings = [
|
||||
{
|
||||
if: "value=",
|
||||
then: path.at(-1) + " is not set. " + (schema.hints.ifunset ?? ""),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
function mightBeBoolean(type: undefined | JsonSchemaType): boolean {
|
||||
if (type === undefined) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
if (type["type"]) {
|
||||
type = type["type"];
|
||||
type = type["type"]
|
||||
}
|
||||
if (type === "boolean") {
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
if (!Array.isArray(type)) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
|
||||
return type.some(t => mightBeBoolean(t));
|
||||
return type.some((t) => mightBeBoolean(t))
|
||||
}
|
||||
|
||||
if (mightBeBoolean(schema.type)) {
|
||||
configJson.mappings = configJson.mappings ?? [];
|
||||
configJson.mappings = configJson.mappings ?? []
|
||||
configJson.mappings.push(
|
||||
{
|
||||
if: "value=true",
|
||||
then: schema.hints?.iftrue ?? "Yes"
|
||||
then: schema.hints?.iftrue ?? "Yes",
|
||||
},
|
||||
{
|
||||
if: "value=false",
|
||||
then: schema.hints?.iffalse ?? "No"
|
||||
then: schema.hints?.iffalse ?? "No",
|
||||
}
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
if (schema.hints.suggestions) {
|
||||
if (!configJson.mappings) {
|
||||
configJson.mappings = [];
|
||||
configJson.mappings = []
|
||||
}
|
||||
configJson.mappings.push(...schema.hints.suggestions);
|
||||
configJson.mappings.push(...schema.hints.suggestions)
|
||||
}
|
||||
let config: TagRenderingConfig;
|
||||
let err: string = undefined;
|
||||
let messages = state.messagesFor(path);
|
||||
let config: TagRenderingConfig
|
||||
let err: string = undefined
|
||||
let messages = state.messagesFor(path)
|
||||
try {
|
||||
config = new TagRenderingConfig(configJson, "config based on " + schema.path.join("."));
|
||||
config = new TagRenderingConfig(configJson, "config based on " + schema.path.join("."))
|
||||
} catch (e) {
|
||||
console.error(e, config);
|
||||
err = path.join(".") + " " + e;
|
||||
console.error(e, config)
|
||||
err = path.join(".") + " " + e
|
||||
}
|
||||
let startValue = state.getCurrentValueFor(path);
|
||||
let startInEditMode = !startValue && startInEditModeIfUnset;
|
||||
const tags = new UIEventSource<Record<string, string>>({ value: startValue });
|
||||
let startValue = state.getCurrentValueFor(path)
|
||||
let startInEditMode = !startValue && startInEditModeIfUnset
|
||||
const tags = new UIEventSource<Record<string, string>>({ value: startValue })
|
||||
try {
|
||||
onDestroy(state.register(path, tags.map(tgs => {
|
||||
const v = tgs["value"];
|
||||
if (typeof v !== "string") {
|
||||
return { ...v };
|
||||
}
|
||||
if (schema.type === "boolan") {
|
||||
return v === "true" || v === "yes" || v === "1";
|
||||
}
|
||||
if (mightBeBoolean(schema.type)) {
|
||||
if (v === "true" || v === "yes" || v === "1") {
|
||||
return true;
|
||||
}
|
||||
if (v === "false" || v === "no" || v === "0") {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (schema.type === "number") {
|
||||
if (v === "") {
|
||||
return undefined;
|
||||
}
|
||||
return Number(v);
|
||||
}
|
||||
if (isTranslation && typeof v === "string") {
|
||||
if (v === "") {
|
||||
return {};
|
||||
}
|
||||
return JSON.parse(v);
|
||||
}
|
||||
return v;
|
||||
}), isTranslation));
|
||||
onDestroy(
|
||||
state.register(
|
||||
path,
|
||||
tags.map((tgs) => {
|
||||
const v = tgs["value"]
|
||||
if (typeof v !== "string") {
|
||||
return { ...v }
|
||||
}
|
||||
if (schema.type === "boolan") {
|
||||
return v === "true" || v === "yes" || v === "1"
|
||||
}
|
||||
if (mightBeBoolean(schema.type)) {
|
||||
if (v === "true" || v === "yes" || v === "1") {
|
||||
return true
|
||||
}
|
||||
if (v === "false" || v === "no" || v === "0") {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if (schema.type === "number") {
|
||||
if (v === "") {
|
||||
return undefined
|
||||
}
|
||||
return Number(v)
|
||||
}
|
||||
if (isTranslation && typeof v === "string") {
|
||||
if (v === "") {
|
||||
return {}
|
||||
}
|
||||
return JSON.parse(v)
|
||||
}
|
||||
return v
|
||||
}),
|
||||
isTranslation
|
||||
)
|
||||
)
|
||||
} catch (e) {
|
||||
console.error("Could not register", path, "due to", e);
|
||||
console.error("Could not register", path, "due to", e)
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if err !== undefined}
|
||||
<span class="alert">{err}</span>
|
||||
{:else}
|
||||
<div class="w-full flex flex-col">
|
||||
<TagRenderingEditable editMode={startInEditMode} {config} selectedElement={undefined} showQuestionIfUnknown={true}
|
||||
{state} {tags} />
|
||||
<div class="flex w-full flex-col">
|
||||
<TagRenderingEditable
|
||||
editMode={startInEditMode}
|
||||
{config}
|
||||
selectedElement={undefined}
|
||||
showQuestionIfUnknown={true}
|
||||
{state}
|
||||
{tags}
|
||||
/>
|
||||
{#if $messages.length > 0}
|
||||
{#each $messages as message}
|
||||
<ShowConversionMessage {message} />
|
||||
{/each}
|
||||
{/if}
|
||||
{#if window.location.hostname === "127.0.0.1"}
|
||||
<span class="subtle" on:click={() => console.log(schema)}>SchemaBasedField <b>{path.join(".")}</b> <span class="cursor-pointer"
|
||||
on:click={() => console.log(schema)}>{schema.hints.typehint}</span> Group: {schema.hints.group}</span>
|
||||
<span class="subtle" on:click={() => console.log(schema)}>
|
||||
SchemaBasedField <b>{path.join(".")}</b>
|
||||
<span class="cursor-pointer" on:click={() => console.log(schema)}>
|
||||
{schema.hints.typehint}
|
||||
</span>
|
||||
Group: {schema.hints.group}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
<script lang="ts">
|
||||
import type { ConfigMeta } from "./configMeta";
|
||||
import SchemaBasedField from "./SchemaBasedField.svelte";
|
||||
import EditLayerState from "./EditLayerState";
|
||||
import SchemaBasedArray from "./SchemaBasedArray.svelte";
|
||||
import SchemaBasedMultiType from "./SchemaBasedMultiType.svelte";
|
||||
import ArrayMultiAnswer from "./ArrayMultiAnswer.svelte";
|
||||
import type { ConfigMeta } from "./configMeta"
|
||||
import SchemaBasedField from "./SchemaBasedField.svelte"
|
||||
import EditLayerState from "./EditLayerState"
|
||||
import SchemaBasedArray from "./SchemaBasedArray.svelte"
|
||||
import SchemaBasedMultiType from "./SchemaBasedMultiType.svelte"
|
||||
import ArrayMultiAnswer from "./ArrayMultiAnswer.svelte"
|
||||
|
||||
export let schema: ConfigMeta;
|
||||
export let state: EditLayerState;
|
||||
export let path: (string | number)[] = [];
|
||||
let expertMode = state.expertMode;
|
||||
export let schema: ConfigMeta
|
||||
export let state: EditLayerState
|
||||
export let path: (string | number)[] = []
|
||||
let expertMode = state.expertMode
|
||||
</script>
|
||||
|
||||
{#if (schema.hints?.group !== "expert" || $expertMode) && schema.hints.group !== "hidden"}
|
||||
{#if schema.hints?.typehint?.endsWith("[]")}
|
||||
<!-- We cheat a bit here by matching this 'magical' type... -->
|
||||
|
|
@ -25,5 +26,7 @@
|
|||
<SchemaBasedField {path} {state} {schema} />
|
||||
{/if}
|
||||
{:else if window.location.hostname === "127.0.0.1"}
|
||||
<div class="subtle">Not showing SBI {schema.path.join(".")} due to group {schema.hints.group}</div>
|
||||
<div class="subtle">
|
||||
Not showing SBI {schema.path.join(".")} due to group {schema.hints.group}
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -1,223 +1,236 @@
|
|||
<script lang="ts">
|
||||
|
||||
import EditLayerState from "./EditLayerState";
|
||||
import type { ConfigMeta } from "./configMeta";
|
||||
import { UIEventSource } from "../../Logic/UIEventSource";
|
||||
import type {
|
||||
QuestionableTagRenderingConfigJson
|
||||
} from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson";
|
||||
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte";
|
||||
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig";
|
||||
import { onDestroy } from "svelte";
|
||||
import SchemaBasedInput from "./SchemaBasedInput.svelte";
|
||||
import type { JsonSchemaType } from "./jsonSchema";
|
||||
import EditLayerState from "./EditLayerState"
|
||||
import type { ConfigMeta } from "./configMeta"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import type { QuestionableTagRenderingConfigJson } from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
|
||||
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte"
|
||||
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
|
||||
import { onDestroy } from "svelte"
|
||||
import SchemaBasedInput from "./SchemaBasedInput.svelte"
|
||||
import type { JsonSchemaType } from "./jsonSchema"
|
||||
// @ts-ignore
|
||||
import nmd from "nano-markdown";
|
||||
import ShowConversionMessage from "./ShowConversionMessage.svelte";
|
||||
import nmd from "nano-markdown"
|
||||
import ShowConversionMessage from "./ShowConversionMessage.svelte"
|
||||
|
||||
/**
|
||||
* If 'types' is defined: allow the user to pick one of the types to input.
|
||||
*/
|
||||
|
||||
export let state: EditLayerState;
|
||||
export let path: (string | number)[] = [];
|
||||
export let schema: ConfigMeta;
|
||||
let expertMode = state.expertMode;
|
||||
const defaultOption = schema.hints.typesdefault ? Number(schema.hints.typesdefault) : undefined;
|
||||
export let state: EditLayerState
|
||||
export let path: (string | number)[] = []
|
||||
export let schema: ConfigMeta
|
||||
let expertMode = state.expertMode
|
||||
const defaultOption = schema.hints.typesdefault ? Number(schema.hints.typesdefault) : undefined
|
||||
|
||||
const hasBooleanOption = (<JsonSchemaType[]>schema.type)?.findIndex(t => t["type"] === "boolean");
|
||||
const types = schema.hints.types.split(";");
|
||||
const hasBooleanOption = (<JsonSchemaType[]>schema.type)?.findIndex(
|
||||
(t) => t["type"] === "boolean"
|
||||
)
|
||||
const types = schema.hints.types.split(";")
|
||||
if (hasBooleanOption >= 0) {
|
||||
types.splice(hasBooleanOption);
|
||||
types.splice(hasBooleanOption)
|
||||
}
|
||||
|
||||
|
||||
let lastIsString = false;
|
||||
let lastIsString = false
|
||||
{
|
||||
const types: string | string[] = Array.isArray(schema.type) ? schema.type[schema.type.length - 1].type : [];
|
||||
lastIsString = types === "string" || (Array.isArray(types) && types.some(i => i === "string"));
|
||||
const types: string | string[] = Array.isArray(schema.type)
|
||||
? schema.type[schema.type.length - 1].type
|
||||
: []
|
||||
lastIsString = types === "string" || (Array.isArray(types) && types.some((i) => i === "string"))
|
||||
}
|
||||
|
||||
if (lastIsString) {
|
||||
types.splice(types.length - 1, 1);
|
||||
types.splice(types.length - 1, 1)
|
||||
}
|
||||
const configJson: QuestionableTagRenderingConfigJson = {
|
||||
id: "TYPE_OF:" + path.join("_"),
|
||||
question: "Which subcategory is needed for " + schema.path.at(-1) + "?",
|
||||
questionHint: nmd(schema.description),
|
||||
mappings: types.map(opt => opt.trim()).filter(opt => opt.length > 0).map((opt, i) => ({
|
||||
if: "chosen_type_index=" + i,
|
||||
addExtraTags: ["value="],
|
||||
then: opt + (i === defaultOption ? " (Default)" : "")
|
||||
})),
|
||||
render: !lastIsString ? undefined : (schema.hints.inline ?? "Use a hardcoded value: <b>{value}</b>"),
|
||||
freeform: !lastIsString ? undefined : {
|
||||
key: "value",
|
||||
inline: true,
|
||||
type: schema.hints.typehint,
|
||||
addExtraTags: ["chosen_type_index="]
|
||||
}
|
||||
};
|
||||
let tags = new UIEventSource<Record<string, string>>({});
|
||||
mappings: types
|
||||
.map((opt) => opt.trim())
|
||||
.filter((opt) => opt.length > 0)
|
||||
.map((opt, i) => ({
|
||||
if: "chosen_type_index=" + i,
|
||||
addExtraTags: ["value="],
|
||||
then: opt + (i === defaultOption ? " (Default)" : ""),
|
||||
})),
|
||||
render: !lastIsString
|
||||
? undefined
|
||||
: schema.hints.inline ?? "Use a hardcoded value: <b>{value}</b>",
|
||||
freeform: !lastIsString
|
||||
? undefined
|
||||
: {
|
||||
key: "value",
|
||||
inline: true,
|
||||
type: schema.hints.typehint,
|
||||
addExtraTags: ["chosen_type_index="],
|
||||
},
|
||||
}
|
||||
let tags = new UIEventSource<Record<string, string>>({})
|
||||
|
||||
if (schema.hints.ifunset) {
|
||||
configJson.mappings.push(
|
||||
{
|
||||
if: { and: ["value=", "chosen_type_index="] },
|
||||
then: schema.hints.ifunset
|
||||
}
|
||||
);
|
||||
configJson.mappings.push({
|
||||
if: { and: ["value=", "chosen_type_index="] },
|
||||
then: schema.hints.ifunset,
|
||||
})
|
||||
}
|
||||
if (schema.hints.suggestions) {
|
||||
configJson.mappings.push(...schema.hints.suggestions);
|
||||
configJson.mappings.push(...schema.hints.suggestions)
|
||||
}
|
||||
|
||||
if (hasBooleanOption >= 0) {
|
||||
configJson.mappings.unshift(
|
||||
{
|
||||
if: "value=true",
|
||||
then: (schema.hints.iftrue ?? "Yes"),
|
||||
addExtraTags: ["chosen_type_index="]
|
||||
then: schema.hints.iftrue ?? "Yes",
|
||||
addExtraTags: ["chosen_type_index="],
|
||||
},
|
||||
{
|
||||
if: "value=false",
|
||||
then: (schema.hints.iffalse ?? "No"),
|
||||
addExtraTags: ["chosen_type_index="]
|
||||
then: schema.hints.iffalse ?? "No",
|
||||
addExtraTags: ["chosen_type_index="],
|
||||
}
|
||||
);
|
||||
)
|
||||
}
|
||||
const config = new TagRenderingConfig(configJson, "config based on " + schema.path.join("."));
|
||||
let chosenOption: number = (defaultOption);
|
||||
const config = new TagRenderingConfig(configJson, "config based on " + schema.path.join("."))
|
||||
let chosenOption: number = defaultOption
|
||||
|
||||
|
||||
const existingValue = state.getCurrentValueFor(path);
|
||||
let hasOverride = existingValue?.override !== undefined;
|
||||
const existingValue = state.getCurrentValueFor(path)
|
||||
let hasOverride = existingValue?.override !== undefined
|
||||
if (hasBooleanOption >= 0 && (existingValue === true || existingValue === false)) {
|
||||
tags.setData({ value: "" + existingValue });
|
||||
tags.setData({ value: "" + existingValue })
|
||||
} else if (lastIsString && typeof existingValue === "string") {
|
||||
tags.setData({ value: existingValue });
|
||||
chosenOption = undefined;
|
||||
tags.setData({ value: existingValue })
|
||||
chosenOption = undefined
|
||||
} else if (existingValue) {
|
||||
// We found an existing value. Let's figure out what type it matches and select that one
|
||||
// We run over all possibilities and check what is required
|
||||
const possibleTypes: { index: number, matchingPropertiesCount: number, optionalMatches: number }[] = [];
|
||||
const possibleTypes: {
|
||||
index: number
|
||||
matchingPropertiesCount: number
|
||||
optionalMatches: number
|
||||
}[] = []
|
||||
outer: for (let i = 0; i < (<[]>schema.type).length; i++) {
|
||||
const type = schema.type[i];
|
||||
let optionalMatches = 0;
|
||||
const type = schema.type[i]
|
||||
let optionalMatches = 0
|
||||
for (const key of Object.keys(type.properties ?? {})) {
|
||||
if (!!existingValue[key]) {
|
||||
optionalMatches++;
|
||||
optionalMatches++
|
||||
}
|
||||
}
|
||||
if (type.required) {
|
||||
let numberOfMatches = 0;
|
||||
let numberOfMatches = 0
|
||||
|
||||
for (const requiredAttribute of type.required) {
|
||||
if (existingValue[requiredAttribute] === undefined) {
|
||||
// The 'existingValue' does _not_ have this required attribute, so it cannot be of this type
|
||||
continue outer;
|
||||
continue outer
|
||||
}
|
||||
numberOfMatches++;
|
||||
numberOfMatches++
|
||||
}
|
||||
possibleTypes.push({ index: i, matchingPropertiesCount: numberOfMatches, optionalMatches });
|
||||
possibleTypes.push({ index: i, matchingPropertiesCount: numberOfMatches, optionalMatches })
|
||||
} else {
|
||||
possibleTypes.push({ index: i, matchingPropertiesCount: 0, optionalMatches });
|
||||
possibleTypes.push({ index: i, matchingPropertiesCount: 0, optionalMatches })
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
possibleTypes.sort((a, b) => b.optionalMatches - a.optionalMatches);
|
||||
possibleTypes.sort((a, b) => b.matchingPropertiesCount - a.matchingPropertiesCount);
|
||||
possibleTypes.sort((a, b) => b.optionalMatches - a.optionalMatches)
|
||||
possibleTypes.sort((a, b) => b.matchingPropertiesCount - a.matchingPropertiesCount)
|
||||
if (possibleTypes.length > 0) {
|
||||
chosenOption = possibleTypes[0].index;
|
||||
tags.setData({ chosen_type_index: "" + chosenOption });
|
||||
|
||||
chosenOption = possibleTypes[0].index
|
||||
tags.setData({ chosen_type_index: "" + chosenOption })
|
||||
}
|
||||
} else if (defaultOption !== undefined) {
|
||||
tags.setData({ chosen_type_index: "" + defaultOption });
|
||||
tags.setData({ chosen_type_index: "" + defaultOption })
|
||||
} else {
|
||||
chosenOption = defaultOption;
|
||||
chosenOption = defaultOption
|
||||
}
|
||||
|
||||
if (hasBooleanOption >= 0 || lastIsString) {
|
||||
|
||||
const directValue = tags.mapD(tags => {
|
||||
const directValue = tags.mapD((tags) => {
|
||||
if (tags["chosen_type_index"]) {
|
||||
return "";
|
||||
return ""
|
||||
}
|
||||
if (lastIsString) {
|
||||
return tags["value"];
|
||||
return tags["value"]
|
||||
}
|
||||
return tags["value"] === "true";
|
||||
});
|
||||
onDestroy(state.register(path, directValue));
|
||||
return tags["value"] === "true"
|
||||
})
|
||||
onDestroy(state.register(path, directValue))
|
||||
}
|
||||
|
||||
let subSchemas: ConfigMeta[] = [];
|
||||
let subSchemas: ConfigMeta[] = []
|
||||
|
||||
let subpath = path;
|
||||
const store = state.getStoreFor(path);
|
||||
onDestroy(tags.addCallbackAndRun(tags => {
|
||||
if (tags["value"] !== undefined && tags["value"] !== "") {
|
||||
chosenOption = undefined;
|
||||
return;
|
||||
}
|
||||
const oldOption = chosenOption;
|
||||
chosenOption = tags["chosen_type_index"] ? Number(tags["chosen_type_index"]) : defaultOption;
|
||||
const type = schema.type[chosenOption];
|
||||
if (chosenOption !== oldOption) {
|
||||
// Reset the values beneath
|
||||
subSchemas = [];
|
||||
const o = state.getCurrentValueFor(path) ?? {};
|
||||
for (const key of type?.required ?? []) {
|
||||
o[key] ??= {};
|
||||
let subpath = path
|
||||
const store = state.getStoreFor(path)
|
||||
onDestroy(
|
||||
tags.addCallbackAndRun((tags) => {
|
||||
if (tags["value"] !== undefined && tags["value"] !== "") {
|
||||
chosenOption = undefined
|
||||
return
|
||||
}
|
||||
store.setData(o);
|
||||
}
|
||||
if (!type) {
|
||||
return;
|
||||
}
|
||||
subpath = path;
|
||||
const cleanPath = <string[]>path.filter(p => typeof p === "string");
|
||||
if (type["$ref"] === "#/definitions/Record<string,string>") {
|
||||
// The subtype is a translation object
|
||||
const schema = state.getTranslationAt(cleanPath);
|
||||
subSchemas.push(schema);
|
||||
subpath = path.slice(0, path.length - 2);
|
||||
return;
|
||||
}
|
||||
if (!type.properties) {
|
||||
return;
|
||||
}
|
||||
for (const crumble of Object.keys(type.properties)) {
|
||||
subSchemas.push(...(state.getSchema([...cleanPath, crumble])));
|
||||
}
|
||||
}));
|
||||
let messages = state.messagesFor(path);
|
||||
|
||||
const oldOption = chosenOption
|
||||
chosenOption = tags["chosen_type_index"] ? Number(tags["chosen_type_index"]) : defaultOption
|
||||
const type = schema.type[chosenOption]
|
||||
if (chosenOption !== oldOption) {
|
||||
// Reset the values beneath
|
||||
subSchemas = []
|
||||
const o = state.getCurrentValueFor(path) ?? {}
|
||||
for (const key of type?.required ?? []) {
|
||||
o[key] ??= {}
|
||||
}
|
||||
store.setData(o)
|
||||
}
|
||||
if (!type) {
|
||||
return
|
||||
}
|
||||
subpath = path
|
||||
const cleanPath = <string[]>path.filter((p) => typeof p === "string")
|
||||
if (type["$ref"] === "#/definitions/Record<string,string>") {
|
||||
// The subtype is a translation object
|
||||
const schema = state.getTranslationAt(cleanPath)
|
||||
subSchemas.push(schema)
|
||||
subpath = path.slice(0, path.length - 2)
|
||||
return
|
||||
}
|
||||
if (!type.properties) {
|
||||
return
|
||||
}
|
||||
for (const crumble of Object.keys(type.properties)) {
|
||||
subSchemas.push(...state.getSchema([...cleanPath, crumble]))
|
||||
}
|
||||
})
|
||||
)
|
||||
let messages = state.messagesFor(path)
|
||||
</script>
|
||||
|
||||
<div class="p-2 border-2 border-dashed border-gray-300 flex flex-col gap-y-2 m-1">
|
||||
<div class="m-1 flex flex-col gap-y-2 border-2 border-dashed border-gray-300 p-2">
|
||||
{#if schema.hints.title !== undefined}
|
||||
<h3>{schema.hints.title}</h3>
|
||||
<div> {schema.description} </div>
|
||||
<div>{schema.description}</div>
|
||||
{/if}
|
||||
{#if hasOverride}
|
||||
This object refers to {existingValue.builtin} and overrides some properties. This cannot be edited with MapComplete
|
||||
Studio
|
||||
This object refers to {existingValue.builtin} and overrides some properties. This cannot be edited
|
||||
with MapComplete Studio
|
||||
{:else}
|
||||
<div>
|
||||
<TagRenderingEditable {config} selectedElement={undefined} showQuestionIfUnknown={!schema.hints?.ifunset} {state} {tags} />
|
||||
<TagRenderingEditable
|
||||
{config}
|
||||
selectedElement={undefined}
|
||||
showQuestionIfUnknown={!schema.hints?.ifunset}
|
||||
{state}
|
||||
{tags}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if chosenOption !== undefined}
|
||||
{#each subSchemas as subschema}
|
||||
{#if $expertMode || subschema.hints?.group !== "expert"}
|
||||
<SchemaBasedInput {state} schema={subschema}
|
||||
path={[...subpath, (subschema?.path?.at(-1) ?? "???")]}></SchemaBasedInput>
|
||||
<SchemaBasedInput
|
||||
{state}
|
||||
schema={subschema}
|
||||
path={[...subpath, subschema?.path?.at(-1) ?? "???"]}
|
||||
/>
|
||||
{:else if window.location.hostname === "127.0.0.1"}
|
||||
<span class="subtle">Omitted expert question {subschema.path.join(".")}</span>
|
||||
|
||||
{/if}
|
||||
{/each}
|
||||
{:else if $messages.length > 0}
|
||||
|
|
@ -227,7 +240,11 @@
|
|||
{/if}
|
||||
{/if}
|
||||
{#if window.location.hostname === "127.0.0.1"}
|
||||
<span class="subtle">SchemaBasedMultiType <b>{path.join(".")}</b> <span class="cursor-pointer"
|
||||
on:click={() => console.log(schema)}>{schema.hints.typehint}</span></span>
|
||||
<span class="subtle">
|
||||
SchemaBasedMultiType <b>{path.join(".")}</b>
|
||||
<span class="cursor-pointer" on:click={() => console.log(schema)}>
|
||||
{schema.hints.typehint}
|
||||
</span>
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,19 @@
|
|||
<script lang="ts">
|
||||
import EditLayerState from "./EditLayerState";
|
||||
import type { ConfigMeta } from "./configMeta";
|
||||
import { UIEventSource } from "../../Logic/UIEventSource";
|
||||
import TranslationInput from "../InputElement/Helpers/TranslationInput.svelte";
|
||||
import EditLayerState from "./EditLayerState"
|
||||
import type { ConfigMeta } from "./configMeta"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import TranslationInput from "../InputElement/Helpers/TranslationInput.svelte"
|
||||
|
||||
export let state: EditLayerState;
|
||||
export let path: (string | number)[] = [];
|
||||
export let schema: ConfigMeta;
|
||||
export let state: EditLayerState
|
||||
export let path: (string | number)[] = []
|
||||
export let schema: ConfigMeta
|
||||
|
||||
let value = new UIEventSource<string>({});
|
||||
let value = new UIEventSource<string>({})
|
||||
console.log("Registering translation to path", path)
|
||||
state.register(path, value.mapD(v => JSON.parse(value.data )));
|
||||
state.register(
|
||||
path,
|
||||
value.mapD((v) => JSON.parse(value.data))
|
||||
)
|
||||
</script>
|
||||
|
||||
<TranslationInput {value} />
|
||||
|
|
|
|||
|
|
@ -1,29 +1,29 @@
|
|||
<script lang="ts">
|
||||
import type { ConversionMessage } from "../../Models/ThemeConfig/Conversion/Conversion";
|
||||
import { ExclamationIcon, InformationCircleIcon } from "@rgossiaux/svelte-heroicons/solid";
|
||||
import type { ConversionMessage } from "../../Models/ThemeConfig/Conversion/Conversion"
|
||||
import { ExclamationIcon, InformationCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
|
||||
/**
|
||||
* Single conversion message, styled depending on the type
|
||||
*/
|
||||
export let message: ConversionMessage;
|
||||
export let message: ConversionMessage
|
||||
</script>
|
||||
|
||||
{#if message.level === "error"}
|
||||
<div class="alert flex justify-between items-center">
|
||||
<ExclamationIcon class="w-6 h-6 mx-1 shrink-0" />
|
||||
<div class="alert flex items-center justify-between">
|
||||
<ExclamationIcon class="mx-1 h-6 w-6 shrink-0" />
|
||||
{message.message}
|
||||
<div/>
|
||||
<div />
|
||||
</div>
|
||||
{:else if message.level === "warning"}
|
||||
<div class="warning flex justify-between items-center">
|
||||
<ExclamationIcon class="w-6 h-6 mx-1 shrink-0" />
|
||||
<div class="warning flex items-center justify-between">
|
||||
<ExclamationIcon class="mx-1 h-6 w-6 shrink-0" />
|
||||
{message.message}
|
||||
<div/>
|
||||
<div />
|
||||
</div>
|
||||
{:else if message.level === "information"}
|
||||
<div class="information flex justify-between items-center">
|
||||
<InformationCircleIcon class="w-6 h-6 mx-1 shrink-0" />
|
||||
<div class="information flex items-center justify-between">
|
||||
<InformationCircleIcon class="mx-1 h-6 w-6 shrink-0" />
|
||||
{message.message}
|
||||
<div/>
|
||||
<div />
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
<script lang="ts">
|
||||
import type { ConversionMessage } from "../../Models/ThemeConfig/Conversion/Conversion";
|
||||
import type { ConversionMessage } from "../../Models/ThemeConfig/Conversion/Conversion"
|
||||
|
||||
export let messages: ConversionMessage[];
|
||||
export let messages: ConversionMessage[]
|
||||
</script>
|
||||
|
||||
{#if messages.length === 0}
|
||||
<div class="thanks">
|
||||
No errors, warnings or messages
|
||||
</div>
|
||||
{/if}
|
||||
<div class="thanks">No errors, warnings or messages</div>
|
||||
{/if}
|
||||
|
||||
{#each messages as message}
|
||||
<li>
|
||||
|
|
@ -16,7 +14,7 @@
|
|||
<span class="literal-code">{message.context.path.join(".")}</span>
|
||||
{message.message}
|
||||
<span class="literal-code">
|
||||
{message.context.operation.join(".")}
|
||||
</span>
|
||||
{message.context.operation.join(".")}
|
||||
</span>
|
||||
</li>
|
||||
{/each}
|
||||
|
|
|
|||
|
|
@ -1,134 +1,130 @@
|
|||
<script lang="ts">/**
|
||||
* Allows to create `and` and `or` expressions graphically
|
||||
*/
|
||||
<script lang="ts">
|
||||
/**
|
||||
* Allows to create `and` and `or` expressions graphically
|
||||
*/
|
||||
|
||||
import { UIEventSource } from "../../Logic/UIEventSource";
|
||||
import type { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson";
|
||||
import BasicTagInput from "./TagInput/BasicTagInput.svelte";
|
||||
import FullTagInput from "./TagInput/FullTagInput.svelte";
|
||||
import { TrashIcon } from "@babeard/svelte-heroicons/mini";
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import type { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson"
|
||||
import BasicTagInput from "./TagInput/BasicTagInput.svelte"
|
||||
import FullTagInput from "./TagInput/FullTagInput.svelte"
|
||||
import { TrashIcon } from "@babeard/svelte-heroicons/mini"
|
||||
|
||||
export let tag: UIEventSource<TagConfigJson>;
|
||||
let mode: "and" | "or" = "and";
|
||||
export let tag: UIEventSource<TagConfigJson>
|
||||
let mode: "and" | "or" = "and"
|
||||
|
||||
let basicTags: UIEventSource<UIEventSource<string>[]> = new UIEventSource([]);
|
||||
let basicTags: UIEventSource<UIEventSource<string>[]> = new UIEventSource([])
|
||||
|
||||
/**
|
||||
* Sub-expressions
|
||||
*/
|
||||
let expressions: UIEventSource<UIEventSource<TagConfigJson>[]> = new UIEventSource([]);
|
||||
/**
|
||||
* Sub-expressions
|
||||
*/
|
||||
let expressions: UIEventSource<UIEventSource<TagConfigJson>[]> = new UIEventSource([])
|
||||
|
||||
export let uploadableOnly: boolean;
|
||||
export let overpassSupportNeeded: boolean;
|
||||
export let uploadableOnly: boolean
|
||||
export let overpassSupportNeeded: boolean
|
||||
|
||||
export let silent: boolean;
|
||||
export let silent: boolean
|
||||
|
||||
function update(_) {
|
||||
let config: TagConfigJson = <any>{};
|
||||
if (!mode) {
|
||||
return;
|
||||
}
|
||||
const tags = [];
|
||||
|
||||
const subpartSources = (<UIEventSource<string | TagConfigJson>[]>basicTags.data).concat(expressions.data);
|
||||
for (const src of subpartSources) {
|
||||
const t = src.data;
|
||||
if (!t) {
|
||||
// We indicate upstream that this value is invalid
|
||||
tag.setData(undefined);
|
||||
return;
|
||||
function update(_) {
|
||||
let config: TagConfigJson = <any>{}
|
||||
if (!mode) {
|
||||
return
|
||||
}
|
||||
const tags = []
|
||||
|
||||
const subpartSources = (<UIEventSource<string | TagConfigJson>[]>basicTags.data).concat(
|
||||
expressions.data
|
||||
)
|
||||
for (const src of subpartSources) {
|
||||
const t = src.data
|
||||
if (!t) {
|
||||
// We indicate upstream that this value is invalid
|
||||
tag.setData(undefined)
|
||||
return
|
||||
}
|
||||
tags.push(t)
|
||||
}
|
||||
if (tags.length === 1) {
|
||||
tag.setData(tags[0])
|
||||
} else {
|
||||
config[mode] = tags
|
||||
tag.setData(config)
|
||||
}
|
||||
tags.push(t);
|
||||
}
|
||||
if (tags.length === 1) {
|
||||
tag.setData(tags[0]);
|
||||
|
||||
function addBasicTag(value?: string) {
|
||||
const src = new UIEventSource(value)
|
||||
basicTags.data.push(src)
|
||||
basicTags.ping()
|
||||
src.addCallbackAndRunD((_) => update(_))
|
||||
}
|
||||
|
||||
function removeTag(basicTag: UIEventSource<any>) {
|
||||
const index = basicTags.data.indexOf(basicTag)
|
||||
console.log("Removing", index, basicTag)
|
||||
if (index >= 0) {
|
||||
basicTag.setData(undefined)
|
||||
basicTags.data.splice(index, 1)
|
||||
basicTags.ping()
|
||||
}
|
||||
}
|
||||
|
||||
function removeExpression(expr: UIEventSource<any>) {
|
||||
const index = expressions.data.indexOf(expr)
|
||||
if (index >= 0) {
|
||||
expr.setData(undefined)
|
||||
expressions.data.splice(index, 1)
|
||||
expressions.ping()
|
||||
}
|
||||
}
|
||||
|
||||
function addExpression(expr?: TagConfigJson) {
|
||||
const src = new UIEventSource(expr)
|
||||
expressions.data.push(src)
|
||||
expressions.ping()
|
||||
src.addCallbackAndRunD((_) => update(_))
|
||||
}
|
||||
|
||||
$: update(mode)
|
||||
expressions.addCallback((_) => update(_))
|
||||
basicTags.addCallback((_) => update(_))
|
||||
|
||||
let initialTag: TagConfigJson = tag.data
|
||||
|
||||
function initWith(initialTag: TagConfigJson) {
|
||||
if (typeof initialTag === "string") {
|
||||
if (initialTag.startsWith("{")) {
|
||||
initialTag = JSON.parse(initialTag)
|
||||
} else {
|
||||
addBasicTag(initialTag)
|
||||
return
|
||||
}
|
||||
}
|
||||
mode = <"or" | "and">Object.keys(initialTag)[0]
|
||||
const subExprs = <TagConfigJson[]>initialTag[mode]
|
||||
if (!subExprs || subExprs.length == 0) {
|
||||
return
|
||||
}
|
||||
if (subExprs.length == 1) {
|
||||
initWith(subExprs[0])
|
||||
return
|
||||
}
|
||||
for (const subExpr of subExprs) {
|
||||
if (typeof subExpr === "string") {
|
||||
addBasicTag(subExpr)
|
||||
} else {
|
||||
addExpression(subExpr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!initialTag) {
|
||||
addBasicTag()
|
||||
} else {
|
||||
config[mode] = tags;
|
||||
tag.setData(config);
|
||||
initWith(initialTag)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function addBasicTag(value?: string) {
|
||||
const src = new UIEventSource(value);
|
||||
basicTags.data.push(src);
|
||||
basicTags.ping();
|
||||
src.addCallbackAndRunD(_ => update(_));
|
||||
}
|
||||
|
||||
function removeTag(basicTag: UIEventSource<any>) {
|
||||
const index = basicTags.data.indexOf(basicTag);
|
||||
console.log("Removing", index, basicTag);
|
||||
if (index >= 0) {
|
||||
basicTag.setData(undefined);
|
||||
basicTags.data.splice(index, 1);
|
||||
basicTags.ping();
|
||||
}
|
||||
}
|
||||
|
||||
function removeExpression(expr: UIEventSource<any>) {
|
||||
const index = expressions.data.indexOf(expr);
|
||||
if (index >= 0) {
|
||||
expr.setData(undefined);
|
||||
expressions.data.splice(index, 1);
|
||||
expressions.ping();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function addExpression(expr?: TagConfigJson) {
|
||||
const src = new UIEventSource(expr);
|
||||
expressions.data.push(src);
|
||||
expressions.ping();
|
||||
src.addCallbackAndRunD(_ => update(_));
|
||||
}
|
||||
|
||||
|
||||
$: update(mode);
|
||||
expressions.addCallback(_ => update(_));
|
||||
basicTags.addCallback(_ => update(_));
|
||||
|
||||
let initialTag: TagConfigJson = tag.data;
|
||||
|
||||
function initWith(initialTag: TagConfigJson) {
|
||||
if (typeof initialTag === "string") {
|
||||
if (initialTag.startsWith("{")) {
|
||||
initialTag = JSON.parse(initialTag);
|
||||
} else {
|
||||
addBasicTag(initialTag);
|
||||
return;
|
||||
}
|
||||
}
|
||||
mode = <"or" | "and">Object.keys(initialTag)[0];
|
||||
const subExprs = (<TagConfigJson[]>initialTag[mode]);
|
||||
if (!subExprs || subExprs.length == 0) {
|
||||
return;
|
||||
}
|
||||
if (subExprs.length == 1) {
|
||||
initWith(subExprs[0]);
|
||||
return;
|
||||
}
|
||||
for (const subExpr of subExprs) {
|
||||
if (typeof subExpr === "string") {
|
||||
addBasicTag(subExpr);
|
||||
} else {
|
||||
addExpression(subExpr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!initialTag) {
|
||||
addBasicTag();
|
||||
} else {
|
||||
initWith(initialTag);
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<div class="flex items-center">
|
||||
|
||||
{#if !uploadableOnly}
|
||||
<select bind:value={mode}>
|
||||
<option value="and">and</option>
|
||||
|
|
@ -136,14 +132,16 @@ if (!initialTag) {
|
|||
</select>
|
||||
{/if}
|
||||
|
||||
<div class="border-l-4 border-black flex flex-col ml-1 pl-1">
|
||||
<div class="ml-1 flex flex-col border-l-4 border-black pl-1">
|
||||
{#each $basicTags as basicTag (basicTag)}
|
||||
<div class="flex">
|
||||
<BasicTagInput {silent} {overpassSupportNeeded} {uploadableOnly} tag={basicTag} on:submit />
|
||||
{#if $basicTags.length + $expressions.length > 1}
|
||||
<button class="border border-black rounded-full w-fit h-fit p-0"
|
||||
on:click={() => removeTag(basicTag)}>
|
||||
<TrashIcon class="w-4 h-4 p-1" />
|
||||
<button
|
||||
class="h-fit w-fit rounded-full border border-black p-0"
|
||||
on:click={() => removeTag(basicTag)}
|
||||
>
|
||||
<TrashIcon class="h-4 w-4 p-1" />
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
@ -151,23 +149,18 @@ if (!initialTag) {
|
|||
{#each $expressions as expression}
|
||||
<FullTagInput {silent} {overpassSupportNeeded} {uploadableOnly} tag={expression}>
|
||||
<button class="small" slot="delete" on:click={() => removeExpression(expression)}>
|
||||
<TrashIcon class="w-3 h-3 p-0" />
|
||||
<TrashIcon class="h-3 w-3 p-0" />
|
||||
Delete subexpression
|
||||
</button>
|
||||
</FullTagInput>
|
||||
{/each}
|
||||
<div class="flex">
|
||||
<button class="w-fit small" on:click={() => addBasicTag()}>
|
||||
Add a tag
|
||||
</button>
|
||||
<button class="small w-fit" on:click={() => addBasicTag()}>Add a tag</button>
|
||||
{#if !uploadableOnly}
|
||||
<!-- Do not allow to add an expression, as everything is 'and' anyway -->
|
||||
<button class="w-fit small" on:click={() => addExpression()}>
|
||||
Add an expression
|
||||
</button>
|
||||
<button class="small w-fit" on:click={() => addExpression()}>Add an expression</button>
|
||||
{/if}
|
||||
<slot name="delete" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,63 +1,67 @@
|
|||
<script lang="ts">/**
|
||||
* A small component showing statistics from tagInfo.
|
||||
* Will show this in an 'alert' if very little (<250) tags are known
|
||||
*/
|
||||
import { TagUtils } from "../../Logic/Tags/TagUtils";
|
||||
import { Store, UIEventSource } from "../../Logic/UIEventSource";
|
||||
import type { TagInfoStats } from "../../Logic/Web/TagInfo";
|
||||
import TagInfo from "../../Logic/Web/TagInfo";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import Loading from "../Base/Loading.svelte";
|
||||
<script lang="ts">
|
||||
/**
|
||||
* A small component showing statistics from tagInfo.
|
||||
* Will show this in an 'alert' if very little (<250) tags are known
|
||||
*/
|
||||
import { TagUtils } from "../../Logic/Tags/TagUtils"
|
||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import type { TagInfoStats } from "../../Logic/Web/TagInfo"
|
||||
import TagInfo from "../../Logic/Web/TagInfo"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
import Loading from "../Base/Loading.svelte"
|
||||
|
||||
export let silent = false;
|
||||
export let tag: UIEventSource<string>;
|
||||
const tagStabilized = tag.stabilized(500);
|
||||
const tagInfoStats: Store<TagInfoStats> = tagStabilized.bind(tag => {
|
||||
export let silent = false
|
||||
export let tag: UIEventSource<string>
|
||||
const tagStabilized = tag.stabilized(500)
|
||||
const tagInfoStats: Store<TagInfoStats> = tagStabilized.bind((tag) => {
|
||||
if (!tag) {
|
||||
return undefined;
|
||||
return undefined
|
||||
}
|
||||
try {
|
||||
|
||||
const t = TagUtils.Tag(tag);
|
||||
const k = t["key"];
|
||||
let v = t["value"];
|
||||
if (typeof v !== "string") {
|
||||
v = undefined;
|
||||
}
|
||||
if (!k) {
|
||||
return undefined;
|
||||
}
|
||||
return UIEventSource.FromPromise(TagInfo.global.getStats(k, v));
|
||||
const t = TagUtils.Tag(tag)
|
||||
const k = t["key"]
|
||||
let v = t["value"]
|
||||
if (typeof v !== "string") {
|
||||
v = undefined
|
||||
}
|
||||
if (!k) {
|
||||
return undefined
|
||||
}
|
||||
return UIEventSource.FromPromise(TagInfo.global.getStats(k, v))
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
return undefined
|
||||
}
|
||||
});
|
||||
const tagInfoUrl: Store<string> = tagStabilized.mapD(tag => {
|
||||
})
|
||||
const tagInfoUrl: Store<string> = tagStabilized.mapD((tag) => {
|
||||
try {
|
||||
|
||||
const t = TagUtils.Tag(tag);
|
||||
const k = t["key"];
|
||||
let v = t["value"];
|
||||
if (typeof v !== "string") {
|
||||
v = undefined;
|
||||
}
|
||||
if (!k) {
|
||||
return undefined;
|
||||
}
|
||||
return TagInfo.global.webUrl(k, v);
|
||||
const t = TagUtils.Tag(tag)
|
||||
const k = t["key"]
|
||||
let v = t["value"]
|
||||
if (typeof v !== "string") {
|
||||
v = undefined
|
||||
}
|
||||
if (!k) {
|
||||
return undefined
|
||||
}
|
||||
return TagInfo.global.webUrl(k, v)
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
return undefined
|
||||
}
|
||||
});
|
||||
const total = tagInfoStats.mapD(data => data.data.find(i => i.type === "all").count);
|
||||
})
|
||||
const total = tagInfoStats.mapD((data) => data.data.find((i) => i.type === "all").count)
|
||||
</script>
|
||||
|
||||
{#if $tagStabilized !== $tag}
|
||||
{#if !silent}
|
||||
<Loading />
|
||||
{/if}
|
||||
{:else if $tagInfoStats && (!silent || $total < 250) }
|
||||
<a href={$tagInfoUrl} target="_blank" class={twMerge(($total < 250) ? "alert" : "thanks", "w-fit link-underline")}>
|
||||
{$total} features have <span class="literal-code">{$tag}</span>
|
||||
</a>
|
||||
{#if !silent}
|
||||
<Loading />
|
||||
{/if}
|
||||
{:else if $tagInfoStats && (!silent || $total < 250)}
|
||||
<a
|
||||
href={$tagInfoUrl}
|
||||
target="_blank"
|
||||
class={twMerge($total < 250 ? "alert" : "thanks", "link-underline w-fit")}
|
||||
>
|
||||
{$total} features have
|
||||
<span class="literal-code">{$tag}</span>
|
||||
</a>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -1,134 +1,138 @@
|
|||
<script lang="ts">
|
||||
import ValidatedInput from "../../InputElement/ValidatedInput.svelte"
|
||||
import { UIEventSource } from "../../../Logic/UIEventSource"
|
||||
import { onDestroy } from "svelte"
|
||||
import Tr from "../../Base/Tr.svelte"
|
||||
import { TagUtils } from "../../../Logic/Tags/TagUtils"
|
||||
import TagInfoStats from "../TagInfoStats.svelte"
|
||||
import { Translation } from "../../i18n/Translation"
|
||||
|
||||
import ValidatedInput from "../../InputElement/ValidatedInput.svelte";
|
||||
import {UIEventSource} from "../../../Logic/UIEventSource";
|
||||
import {onDestroy} from "svelte";
|
||||
import Tr from "../../Base/Tr.svelte";
|
||||
import {TagUtils} from "../../../Logic/Tags/TagUtils";
|
||||
import TagInfoStats from "../TagInfoStats.svelte";
|
||||
import { Translation } from "../../i18n/Translation";
|
||||
export let tag: UIEventSource<string> = new UIEventSource<string>(undefined)
|
||||
export let uploadableOnly: boolean
|
||||
export let overpassSupportNeeded: boolean
|
||||
|
||||
export let tag: UIEventSource<string> = new UIEventSource<string>(undefined)
|
||||
export let uploadableOnly: boolean
|
||||
export let overpassSupportNeeded: boolean
|
||||
|
||||
export let dropdownFocussed = new UIEventSource(false)
|
||||
export let dropdownFocussed = new UIEventSource(false)
|
||||
|
||||
/**
|
||||
* If set, do not show tagInfo if there are many features matching
|
||||
*/
|
||||
export let silent : boolean = false
|
||||
|
||||
export let selected: UIEventSource<boolean> = new UIEventSource<boolean>(false)
|
||||
|
||||
let feedbackGlobal = tag.map(tag => {
|
||||
if (!tag) {
|
||||
return undefined
|
||||
}
|
||||
try {
|
||||
TagUtils.Tag(tag)
|
||||
return undefined
|
||||
} catch (e) {
|
||||
return e
|
||||
}
|
||||
/**
|
||||
* If set, do not show tagInfo if there are many features matching
|
||||
*/
|
||||
export let silent: boolean = false
|
||||
|
||||
})
|
||||
export let selected: UIEventSource<boolean> = new UIEventSource<boolean>(false)
|
||||
|
||||
let feedbackKey = new UIEventSource<Translation>(undefined)
|
||||
let keyValue = new UIEventSource<string>(undefined)
|
||||
|
||||
|
||||
let feedbackValue = new UIEventSource<Translation>(undefined)
|
||||
/**
|
||||
* The value of the tag. The name is a bit confusing
|
||||
*/
|
||||
let valueValue = new UIEventSource<string>(undefined)
|
||||
|
||||
|
||||
export let mode: string = "="
|
||||
let modes: string[] = []
|
||||
|
||||
for (const k in TagUtils.modeDocumentation) {
|
||||
const docs = TagUtils.modeDocumentation[k]
|
||||
if (overpassSupportNeeded && !docs.overpassSupport) {
|
||||
continue
|
||||
}
|
||||
if (uploadableOnly && !docs.uploadable) {
|
||||
continue
|
||||
}
|
||||
modes.push(k)
|
||||
let feedbackGlobal = tag.map((tag) => {
|
||||
if (!tag) {
|
||||
return undefined
|
||||
}
|
||||
if (!uploadableOnly && !overpassSupportNeeded) {
|
||||
modes.push(...TagUtils.comparators.map(c => c[0]))
|
||||
try {
|
||||
TagUtils.Tag(tag)
|
||||
return undefined
|
||||
} catch (e) {
|
||||
return e
|
||||
}
|
||||
})
|
||||
|
||||
let feedbackKey = new UIEventSource<Translation>(undefined)
|
||||
let keyValue = new UIEventSource<string>(undefined)
|
||||
|
||||
if (tag.data) {
|
||||
const sortedModes = [...modes]
|
||||
sortedModes.sort((a, b) => b.length - a.length)
|
||||
const t = tag.data
|
||||
console.log(t)
|
||||
for (const m of sortedModes) {
|
||||
if (t.indexOf(m) >= 0) {
|
||||
const [k, v] = t.split(m)
|
||||
keyValue.setData(k)
|
||||
valueValue.setData(v)
|
||||
mode = m
|
||||
break
|
||||
}
|
||||
}
|
||||
let feedbackValue = new UIEventSource<Translation>(undefined)
|
||||
/**
|
||||
* The value of the tag. The name is a bit confusing
|
||||
*/
|
||||
let valueValue = new UIEventSource<string>(undefined)
|
||||
|
||||
export let mode: string = "="
|
||||
let modes: string[] = []
|
||||
|
||||
for (const k in TagUtils.modeDocumentation) {
|
||||
const docs = TagUtils.modeDocumentation[k]
|
||||
if (overpassSupportNeeded && !docs.overpassSupport) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
onDestroy(valueValue.addCallbackAndRun(setTag))
|
||||
onDestroy(keyValue.addCallbackAndRun(setTag))
|
||||
|
||||
$: {
|
||||
setTag(mode)
|
||||
if (uploadableOnly && !docs.uploadable) {
|
||||
continue
|
||||
}
|
||||
modes.push(k)
|
||||
}
|
||||
if (!uploadableOnly && !overpassSupportNeeded) {
|
||||
modes.push(...TagUtils.comparators.map((c) => c[0]))
|
||||
}
|
||||
|
||||
function setTag(_) {
|
||||
const k = keyValue.data
|
||||
const v = valueValue.data ?? ""
|
||||
if(k === undefined || k === ""){
|
||||
tag.setData(undefined)
|
||||
return
|
||||
}
|
||||
const t = k + mode + v
|
||||
try {
|
||||
TagUtils.Tag(t)
|
||||
tag.setData(t)
|
||||
} catch (e) {
|
||||
tag.setData(undefined)
|
||||
}
|
||||
if (tag.data) {
|
||||
const sortedModes = [...modes]
|
||||
sortedModes.sort((a, b) => b.length - a.length)
|
||||
const t = tag.data
|
||||
console.log(t)
|
||||
for (const m of sortedModes) {
|
||||
if (t.indexOf(m) >= 0) {
|
||||
const [k, v] = t.split(m)
|
||||
keyValue.setData(k)
|
||||
valueValue.setData(v)
|
||||
mode = m
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onDestroy(valueValue.addCallbackAndRun(setTag))
|
||||
onDestroy(keyValue.addCallbackAndRun(setTag))
|
||||
|
||||
$: {
|
||||
setTag(mode)
|
||||
}
|
||||
|
||||
function setTag(_) {
|
||||
const k = keyValue.data
|
||||
const v = valueValue.data ?? ""
|
||||
if (k === undefined || k === "") {
|
||||
tag.setData(undefined)
|
||||
return
|
||||
}
|
||||
const t = k + mode + v
|
||||
try {
|
||||
TagUtils.Tag(t)
|
||||
tag.setData(t)
|
||||
} catch (e) {
|
||||
tag.setData(undefined)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<div class="flex items-center">
|
||||
<div class="flex h-fit">
|
||||
<ValidatedInput
|
||||
feedback={feedbackKey}
|
||||
placeholder="The key of the tag"
|
||||
type="key"
|
||||
value={keyValue}
|
||||
on:submit
|
||||
/>
|
||||
<select
|
||||
bind:value={mode}
|
||||
on:focusin={() => dropdownFocussed.setData(true)}
|
||||
on:focusout={() => dropdownFocussed.setData(false)}
|
||||
>
|
||||
{#each modes as option}
|
||||
<option value={option}>
|
||||
{option}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
<ValidatedInput
|
||||
feedback={feedbackValue}
|
||||
placeholder="The value of the tag"
|
||||
type="string"
|
||||
value={valueValue}
|
||||
on:submit
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex h-fit ">
|
||||
|
||||
<ValidatedInput feedback={feedbackKey} placeholder="The key of the tag" type="key"
|
||||
value={keyValue} on:submit></ValidatedInput>
|
||||
<select bind:value={mode} on:focusin={() => dropdownFocussed.setData(true)} on:focusout={() => dropdownFocussed.setData(false)}>
|
||||
{#each modes as option}
|
||||
<option value={option}>
|
||||
{option}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
<ValidatedInput feedback={feedbackValue} placeholder="The value of the tag" type="string"
|
||||
value={valueValue} on:submit></ValidatedInput>
|
||||
</div>
|
||||
|
||||
{#if $feedbackKey}
|
||||
<Tr cls="alert" t={$feedbackKey}/>
|
||||
{:else if $feedbackValue}
|
||||
<Tr cls="alert" t={$feedbackValue}/>
|
||||
{:else if $feedbackGlobal}
|
||||
<Tr cls="alert" t={$feedbackGlobal}/>
|
||||
{/if}
|
||||
<TagInfoStats {silent} {tag}/>
|
||||
{#if $feedbackKey}
|
||||
<Tr cls="alert" t={$feedbackKey} />
|
||||
{:else if $feedbackValue}
|
||||
<Tr cls="alert" t={$feedbackValue} />
|
||||
{:else if $feedbackGlobal}
|
||||
<Tr cls="alert" t={$feedbackGlobal} />
|
||||
{/if}
|
||||
<TagInfoStats {silent} {tag} />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
<script lang="ts">/**
|
||||
* An element input a tag; has `and`, `or`, `regex`, ...
|
||||
*/
|
||||
import type { TagConfigJson } from "../../../Models/ThemeConfig/Json/TagConfigJson";
|
||||
import { UIEventSource } from "../../../Logic/UIEventSource";
|
||||
import TagExpression from "../TagExpression.svelte";
|
||||
<script lang="ts">
|
||||
/**
|
||||
* An element input a tag; has `and`, `or`, `regex`, ...
|
||||
*/
|
||||
import type { TagConfigJson } from "../../../Models/ThemeConfig/Json/TagConfigJson"
|
||||
import { UIEventSource } from "../../../Logic/UIEventSource"
|
||||
import TagExpression from "../TagExpression.svelte"
|
||||
|
||||
|
||||
export let tag: UIEventSource<string | TagConfigJson>
|
||||
export let uploadableOnly: boolean
|
||||
export let overpassSupportNeeded: boolean
|
||||
export let silent: boolean
|
||||
export let tag: UIEventSource<string | TagConfigJson>
|
||||
export let uploadableOnly: boolean
|
||||
export let overpassSupportNeeded: boolean
|
||||
export let silent: boolean
|
||||
</script>
|
||||
|
||||
<div class="m-2">
|
||||
<TagExpression {silent} {overpassSupportNeeded} {tag} {uploadableOnly} on:submit>
|
||||
<slot name="delete" slot="delete"/>
|
||||
</TagExpression>
|
||||
<TagExpression {silent} {overpassSupportNeeded} {tag} {uploadableOnly} on:submit>
|
||||
<slot name="delete" slot="delete" />
|
||||
</TagExpression>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
<script lang="ts">
|
||||
|
||||
import SchemaBasedInput from "./SchemaBasedInput.svelte";
|
||||
import EditLayerState from "./EditLayerState";
|
||||
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig";
|
||||
import SchemaBasedInput from "./SchemaBasedInput.svelte"
|
||||
import EditLayerState from "./EditLayerState"
|
||||
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
|
||||
|
||||
export let state: EditLayerState
|
||||
export let path : (number | string)[]
|
||||
|
||||
let schema : TagRenderingConfig
|
||||
export let state: EditLayerState
|
||||
export let path: (number | string)[]
|
||||
|
||||
let schema: TagRenderingConfig
|
||||
</script>
|
||||
|
||||
XYZ
|
||||
|
|
|
|||
|
|
@ -1,173 +1,227 @@
|
|||
<script lang="ts">/**
|
||||
* Little helper class to deal with choosing a builtin tagRendering or defining one yourself.
|
||||
* Breaks the ideology that everything should be schema based
|
||||
*/
|
||||
import EditLayerState from "./EditLayerState";
|
||||
import type { ConfigMeta } from "./configMeta";
|
||||
import type {
|
||||
MappingConfigJson,
|
||||
QuestionableTagRenderingConfigJson
|
||||
} from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson";
|
||||
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig";
|
||||
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte";
|
||||
import { Store, UIEventSource } from "../../Logic/UIEventSource";
|
||||
import * as questions from "../../assets/generated/layers/questions.json";
|
||||
import MappingInput from "./MappingInput.svelte";
|
||||
import { TrashIcon } from "@rgossiaux/svelte-heroicons/outline";
|
||||
import questionableTagRenderingSchemaRaw from "../../assets/schemas/questionabletagrenderingconfigmeta.json";
|
||||
import SchemaBasedField from "./SchemaBasedField.svelte";
|
||||
import Region from "./Region.svelte";
|
||||
import NextButton from "../Base/NextButton.svelte";
|
||||
import { QuestionMarkCircleIcon } from "@rgossiaux/svelte-heroicons/solid";
|
||||
import { LocalStorageSource } from "../../Logic/Web/LocalStorageSource";
|
||||
import { onMount } from "svelte";
|
||||
<script lang="ts">
|
||||
/**
|
||||
* Little helper class to deal with choosing a builtin tagRendering or defining one yourself.
|
||||
* Breaks the ideology that everything should be schema based
|
||||
*/
|
||||
import EditLayerState from "./EditLayerState"
|
||||
import type { ConfigMeta } from "./configMeta"
|
||||
import type {
|
||||
MappingConfigJson,
|
||||
QuestionableTagRenderingConfigJson,
|
||||
} from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
|
||||
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
|
||||
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte"
|
||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import * as questions from "../../assets/generated/layers/questions.json"
|
||||
import MappingInput from "./MappingInput.svelte"
|
||||
import { TrashIcon } from "@rgossiaux/svelte-heroicons/outline"
|
||||
import questionableTagRenderingSchemaRaw from "../../assets/schemas/questionabletagrenderingconfigmeta.json"
|
||||
import SchemaBasedField from "./SchemaBasedField.svelte"
|
||||
import Region from "./Region.svelte"
|
||||
import NextButton from "../Base/NextButton.svelte"
|
||||
import { QuestionMarkCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
import { LocalStorageSource } from "../../Logic/Web/LocalStorageSource"
|
||||
import { onMount } from "svelte"
|
||||
|
||||
export let state: EditLayerState;
|
||||
export let schema: ConfigMeta;
|
||||
export let path: (string | number)[];
|
||||
let expertMode = state.expertMode;
|
||||
const store = state.getStoreFor(path);
|
||||
let value = store.data;
|
||||
let hasSeenIntro = UIEventSource.asBoolean(LocalStorageSource.Get("studio-seen-tagrendering-tutorial", "false"))
|
||||
onMount(() => {
|
||||
if(!hasSeenIntro.data){
|
||||
state.showIntro.setData("tagrenderings")
|
||||
hasSeenIntro.setData(true)
|
||||
}
|
||||
})
|
||||
/**
|
||||
* Allows the theme builder to create 'writable' themes.
|
||||
* Should only be enabled for 'tagrenderings' in the theme, if the source is OSM
|
||||
*/
|
||||
let allowQuestions: Store<boolean> = (state.configuration.mapD(config => path.at(0) === "tagRenderings" && config.source?.geoJson === undefined));
|
||||
export let state: EditLayerState
|
||||
export let schema: ConfigMeta
|
||||
export let path: (string | number)[]
|
||||
let expertMode = state.expertMode
|
||||
const store = state.getStoreFor(path)
|
||||
let value = store.data
|
||||
let hasSeenIntro = UIEventSource.asBoolean(
|
||||
LocalStorageSource.Get("studio-seen-tagrendering-tutorial", "false")
|
||||
)
|
||||
onMount(() => {
|
||||
if (!hasSeenIntro.data) {
|
||||
state.showIntro.setData("tagrenderings")
|
||||
hasSeenIntro.setData(true)
|
||||
}
|
||||
})
|
||||
/**
|
||||
* Allows the theme builder to create 'writable' themes.
|
||||
* Should only be enabled for 'tagrenderings' in the theme, if the source is OSM
|
||||
*/
|
||||
let allowQuestions: Store<boolean> = state.configuration.mapD(
|
||||
(config) => path.at(0) === "tagRenderings" && config.source?.geoJson === undefined
|
||||
)
|
||||
|
||||
let mappingsBuiltin: MappingConfigJson[] = []
|
||||
let perLabel: Record<string, MappingConfigJson> = {}
|
||||
for (const tr of questions.tagRenderings) {
|
||||
let description = tr["description"] ?? tr["question"] ?? "No description available"
|
||||
description = description["en"] ?? description
|
||||
if (tr["labels"]) {
|
||||
const labels: string[] = tr["labels"]
|
||||
for (const label of labels) {
|
||||
let labelMapping: MappingConfigJson = perLabel[label]
|
||||
|
||||
let mappingsBuiltin: MappingConfigJson[] = [];
|
||||
let perLabel: Record<string, MappingConfigJson> = {};
|
||||
for (const tr of questions.tagRenderings) {
|
||||
let description = tr["description"] ?? tr["question"] ?? "No description available";
|
||||
description = description["en"] ?? description;
|
||||
if (tr["labels"]) {
|
||||
const labels: string[] = tr["labels"];
|
||||
for (const label of labels) {
|
||||
let labelMapping: MappingConfigJson = perLabel[label];
|
||||
|
||||
if (!labelMapping) {
|
||||
labelMapping = {
|
||||
if: "value=" + label,
|
||||
then: {
|
||||
en: "Builtin collection <b>" + label + "</b>:"
|
||||
if (!labelMapping) {
|
||||
labelMapping = {
|
||||
if: "value=" + label,
|
||||
then: {
|
||||
en: "Builtin collection <b>" + label + "</b>:",
|
||||
},
|
||||
}
|
||||
};
|
||||
perLabel[label] = labelMapping;
|
||||
mappingsBuiltin.push(labelMapping);
|
||||
perLabel[label] = labelMapping
|
||||
mappingsBuiltin.push(labelMapping)
|
||||
}
|
||||
labelMapping.then.en = labelMapping.then.en + "<div>" + description + "</div>"
|
||||
}
|
||||
labelMapping.then.en = labelMapping.then.en + "<div>" + description + "</div>";
|
||||
}
|
||||
|
||||
mappingsBuiltin.push({
|
||||
if: "value=" + tr["id"],
|
||||
then: {
|
||||
en: "Builtin <b>" + tr["id"] + "</b> <div class='subtle'>" + description + "</div>",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const configBuiltin = new TagRenderingConfig(<QuestionableTagRenderingConfigJson>{
|
||||
question: "Which builtin element should be shown?",
|
||||
mappings: mappingsBuiltin,
|
||||
})
|
||||
|
||||
const tags = new UIEventSource({ value })
|
||||
|
||||
tags.addCallbackAndRunD((tgs) => {
|
||||
store.setData(tgs["value"])
|
||||
})
|
||||
|
||||
let mappings: UIEventSource<MappingConfigJson[]> = state.getStoreFor([...path, "mappings"])
|
||||
|
||||
const topLevelItems: Record<string, ConfigMeta> = {}
|
||||
for (const item of questionableTagRenderingSchemaRaw) {
|
||||
if (item.path.length === 1) {
|
||||
topLevelItems[item.path[0]] = <ConfigMeta>item
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
mappingsBuiltin.push({
|
||||
if: "value=" + tr["id"],
|
||||
then: {
|
||||
"en": "Builtin <b>" + tr["id"] + "</b> <div class='subtle'>" + description + "</div>"
|
||||
function initMappings() {
|
||||
if (mappings.data === undefined) {
|
||||
mappings.setData([])
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const configBuiltin = new TagRenderingConfig(<QuestionableTagRenderingConfigJson>{
|
||||
question: "Which builtin element should be shown?",
|
||||
mappings: mappingsBuiltin
|
||||
});
|
||||
|
||||
|
||||
const tags = new UIEventSource({ value });
|
||||
|
||||
tags.addCallbackAndRunD(tgs => {
|
||||
store.setData(tgs["value"]);
|
||||
});
|
||||
|
||||
let mappings: UIEventSource<MappingConfigJson[]> = state.getStoreFor([...path, "mappings"]);
|
||||
|
||||
const topLevelItems: Record<string, ConfigMeta> = {};
|
||||
for (const item of questionableTagRenderingSchemaRaw) {
|
||||
if (item.path.length === 1) {
|
||||
topLevelItems[item.path[0]] = <ConfigMeta>item;
|
||||
}
|
||||
}
|
||||
|
||||
function initMappings() {
|
||||
if (mappings.data === undefined) {
|
||||
mappings.setData([]);
|
||||
}
|
||||
}
|
||||
|
||||
const items = new Set(["question", "questionHint", "multiAnswer", "freeform", "render", "condition", "metacondition", "mappings", "icon"]);
|
||||
const ignored = new Set(["labels", "description", "classes"]);
|
||||
|
||||
const freeformSchemaAll = <ConfigMeta[]>questionableTagRenderingSchemaRaw
|
||||
.filter(schema => schema.path.length == 2 && schema.path[0] === "freeform" && ($allowQuestions || schema.path[1] === "key"));
|
||||
let freeformSchema = $expertMode ? freeformSchemaAll : freeformSchemaAll.filter(schema => schema.hints?.group !== "expert");
|
||||
const missing: string[] = questionableTagRenderingSchemaRaw.filter(schema => schema.path.length >= 1 && !items.has(schema.path[0]) && !ignored.has(schema.path[0])).map(schema => schema.path.join("."));
|
||||
console.log({ state });
|
||||
const items = new Set([
|
||||
"question",
|
||||
"questionHint",
|
||||
"multiAnswer",
|
||||
"freeform",
|
||||
"render",
|
||||
"condition",
|
||||
"metacondition",
|
||||
"mappings",
|
||||
"icon",
|
||||
])
|
||||
const ignored = new Set(["labels", "description", "classes"])
|
||||
|
||||
const freeformSchemaAll = <ConfigMeta[]>(
|
||||
questionableTagRenderingSchemaRaw.filter(
|
||||
(schema) =>
|
||||
schema.path.length == 2 &&
|
||||
schema.path[0] === "freeform" &&
|
||||
($allowQuestions || schema.path[1] === "key")
|
||||
)
|
||||
)
|
||||
let freeformSchema = $expertMode
|
||||
? freeformSchemaAll
|
||||
: freeformSchemaAll.filter((schema) => schema.hints?.group !== "expert")
|
||||
const missing: string[] = questionableTagRenderingSchemaRaw
|
||||
.filter(
|
||||
(schema) =>
|
||||
schema.path.length >= 1 && !items.has(schema.path[0]) && !ignored.has(schema.path[0])
|
||||
)
|
||||
.map((schema) => schema.path.join("."))
|
||||
console.log({ state })
|
||||
</script>
|
||||
|
||||
{#if typeof $store === "string"}
|
||||
<div class="flex low-interaction">
|
||||
<TagRenderingEditable config={configBuiltin} selectedElement={undefined} showQuestionIfUnknown={true} {state}
|
||||
{tags} />
|
||||
<div class="low-interaction flex">
|
||||
<TagRenderingEditable
|
||||
config={configBuiltin}
|
||||
selectedElement={undefined}
|
||||
showQuestionIfUnknown={true}
|
||||
{state}
|
||||
{tags}
|
||||
/>
|
||||
<slot name="upper-right" />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-col w-full p-1 gap-y-1 pr-12">
|
||||
<div class="flex w-full flex-col gap-y-1 p-1 pr-12">
|
||||
<div class="flex justify-end">
|
||||
<slot name="upper-right" />
|
||||
</div>
|
||||
{#if $allowQuestions}
|
||||
<SchemaBasedField startInEditModeIfUnset={true} {state} path={[...path,"question"]}
|
||||
schema={topLevelItems["question"]} />
|
||||
<SchemaBasedField {state} path={[...path,"questionHint"]} schema={topLevelItems["questionHint"]} />
|
||||
<SchemaBasedField
|
||||
startInEditModeIfUnset={true}
|
||||
{state}
|
||||
path={[...path, "question"]}
|
||||
schema={topLevelItems["question"]}
|
||||
/>
|
||||
<SchemaBasedField
|
||||
{state}
|
||||
path={[...path, "questionHint"]}
|
||||
schema={topLevelItems["questionHint"]}
|
||||
/>
|
||||
{/if}
|
||||
{#each ($mappings ?? []) as mapping, i (mapping)}
|
||||
<div class="flex interactive w-full">
|
||||
{#each $mappings ?? [] as mapping, i (mapping)}
|
||||
<div class="interactive flex w-full">
|
||||
<MappingInput {state} path={path.concat(["mappings", i])}>
|
||||
<button slot="delete" class="rounded-full no-image-background" on:click={() => {
|
||||
initMappings();
|
||||
mappings.data.splice(i, 1)
|
||||
mappings.ping()
|
||||
}}>
|
||||
<TrashIcon class="w-4 h-4" />
|
||||
<button
|
||||
slot="delete"
|
||||
class="no-image-background rounded-full"
|
||||
on:click={() => {
|
||||
initMappings()
|
||||
mappings.data.splice(i, 1)
|
||||
mappings.ping()
|
||||
}}
|
||||
>
|
||||
<TrashIcon class="h-4 w-4" />
|
||||
</button>
|
||||
</MappingInput>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
<button class="primary"
|
||||
on:click={() =>{ initMappings(); mappings.data.push({if: undefined, then: {}}); mappings.ping()} }>
|
||||
<button
|
||||
class="primary"
|
||||
on:click={() => {
|
||||
initMappings()
|
||||
mappings.data.push({ if: undefined, then: {} })
|
||||
mappings.ping()
|
||||
}}
|
||||
>
|
||||
Add a predefined option
|
||||
</button>
|
||||
|
||||
<SchemaBasedField {state} path={[...path,"multiAnswer"]} schema={topLevelItems["multiAnswer"]} />
|
||||
<SchemaBasedField
|
||||
{state}
|
||||
path={[...path, "multiAnswer"]}
|
||||
schema={topLevelItems["multiAnswer"]}
|
||||
/>
|
||||
|
||||
<h3>Text field and input element configuration</h3>
|
||||
<div class="border-l pl-2 border-gray-800 border-dashed">
|
||||
<SchemaBasedField {state} path={[...path,"render"]} schema={topLevelItems["render"]} />
|
||||
<div class="border-l border-dashed border-gray-800 pl-2">
|
||||
<SchemaBasedField {state} path={[...path, "render"]} schema={topLevelItems["render"]} />
|
||||
<Region {state} {path} configs={freeformSchema} />
|
||||
<SchemaBasedField {state} path={[...path,"icon"]} schema={topLevelItems["icon"]} />
|
||||
|
||||
<SchemaBasedField {state} path={[...path, "icon"]} schema={topLevelItems["icon"]} />
|
||||
</div>
|
||||
|
||||
<SchemaBasedField {state} path={[...path,"condition"]} schema={topLevelItems["condition"]} />
|
||||
<SchemaBasedField {state} path={[...path, "condition"]} schema={topLevelItems["condition"]} />
|
||||
{#if $expertMode}
|
||||
<SchemaBasedField {state} path={[...path,"metacondition"]} schema={topLevelItems["metacondition"]} />
|
||||
<SchemaBasedField
|
||||
{state}
|
||||
path={[...path, "metacondition"]}
|
||||
schema={topLevelItems["metacondition"]}
|
||||
/>
|
||||
{/if}
|
||||
{#each missing as field}
|
||||
<SchemaBasedField {state} path={[...path,field]} schema={topLevelItems[field]} />
|
||||
<SchemaBasedField {state} path={[...path, field]} schema={topLevelItems[field]} />
|
||||
{/each}
|
||||
|
||||
<NextButton clss="small mt-8" on:click={() => state.showIntro.setData("tagrenderings")}><QuestionMarkCircleIcon class="h-6 w-6"/> Show the introduction again</NextButton>
|
||||
|
||||
<NextButton clss="small mt-8" on:click={() => state.showIntro.setData("tagrenderings")}>
|
||||
<QuestionMarkCircleIcon class="h-6 w-6" /> Show the introduction again
|
||||
</NextButton>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue