forked from MapComplete/MapComplete
Refactoring: split icons into proper layered icons, fix and rerun shops-thief
This commit is contained in:
parent
d6cd0516bb
commit
19a760178b
307 changed files with 26845 additions and 116541 deletions
|
|
@ -52,11 +52,8 @@
|
|||
<SimpleTagInput { value } />
|
||||
|
||||
{:else if $construct !== undefined}
|
||||
{#if isBaseUIElement}
|
||||
<ToSvelte
|
||||
construct={() =>
|
||||
<ToSvelte
|
||||
construct={() =>
|
||||
new VariableUiElement(construct.mapD((construct) => construct(value, properties)))}
|
||||
/>
|
||||
|
||||
{/if}
|
||||
/>
|
||||
{/if}
|
||||
|
|
|
|||
86
src/UI/Map/Icon.svelte
Normal file
86
src/UI/Map/Icon.svelte
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
<script lang="ts">
|
||||
import { IconConfig } from "../../Models/ThemeConfig/PointRenderingConfig";
|
||||
import { Store } from "../../Logic/UIEventSource";
|
||||
import Pin from "../../assets/svg/Pin.svelte";
|
||||
import Square from "../../assets/svg/Square.svelte";
|
||||
import Circle from "../../assets/svg/Circle.svelte";
|
||||
import Checkmark from "../../assets/svg/Checkmark.svelte";
|
||||
import Clock from "../../assets/svg/Clock.svelte";
|
||||
import Close from "../../assets/svg/Close.svelte";
|
||||
import Crosshair from "../../assets/svg/Crosshair.svelte";
|
||||
import Help from "../../assets/svg/Help.svelte";
|
||||
import Home from "../../assets/svg/Home.svelte";
|
||||
import Invalid from "../../assets/svg/Invalid.svelte";
|
||||
import Location from "../../assets/svg/Location.svelte";
|
||||
import Location_empty from "../../assets/svg/Location_empty.svelte";
|
||||
import Location_locked from "../../assets/svg/Location_locked.svelte";
|
||||
import Note from "../../assets/svg/Note.svelte";
|
||||
import Resolved from "../../assets/svg/Resolved.svelte";
|
||||
import Ring from "../../assets/svg/Ring.svelte";
|
||||
import Scissors from "../../assets/svg/Scissors.svelte";
|
||||
import Teardrop from "../../assets/svg/Teardrop.svelte";
|
||||
import Teardrop_with_hole_green from "../../assets/svg/Teardrop_with_hole_green.svelte";
|
||||
import Triangle from "../../assets/svg/Triangle.svelte";
|
||||
|
||||
/**
|
||||
* Renders a single icon.
|
||||
*
|
||||
* Icons -placed on top of each other- form a 'Marker' together
|
||||
*/
|
||||
export let icon: IconConfig;
|
||||
export let tags: Store<Record<string, string>>;
|
||||
|
||||
let iconItem = icon.icon?.GetRenderValue(tags)?.txt;
|
||||
$: iconItem = icon.icon?.GetRenderValue($tags)?.txt;
|
||||
let color = icon.color?.GetRenderValue(tags)?.txt ?? "#000000";
|
||||
$: color = icon.color?.GetRenderValue($tags)?.txt ?? "#000000";
|
||||
|
||||
</script>
|
||||
|
||||
{#if iconItem}
|
||||
<div class="absolute top-0 left-0 w-full h-full">
|
||||
{#if iconItem === "pin"}
|
||||
<Pin {color} />
|
||||
{:else if iconItem === "square"}
|
||||
<Square {color} />
|
||||
{:else if iconItem === "circle"}
|
||||
<Circle {color} />
|
||||
{:else if iconItem === "checkmark"}
|
||||
<Checkmark {color} />
|
||||
{:else if iconItem === "clock"}
|
||||
<Clock {color} />
|
||||
{:else if iconItem === "close"}
|
||||
<Close {color} />
|
||||
{:else if iconItem === "crosshair"}
|
||||
<Crosshair {color} />
|
||||
{:else if iconItem === "help"}
|
||||
<Help {color} />
|
||||
{:else if iconItem === "home"}
|
||||
<Home {color} />
|
||||
{:else if iconItem === "invalid"}
|
||||
<Invalid {color} />
|
||||
{:else if iconItem === "location"}
|
||||
<Location {color} />
|
||||
{:else if iconItem === "location_empty"}
|
||||
<Location_empty {color} />
|
||||
{:else if iconItem === "location_locked"}
|
||||
<Location_locked {color} />
|
||||
{:else if iconItem === "note"}
|
||||
<Note {color} />
|
||||
{:else if iconItem === "resolved"}
|
||||
<Resolved {color} />
|
||||
{:else if iconItem === "ring"}
|
||||
<Ring {color} />
|
||||
{:else if iconItem === "scissors"}
|
||||
<Scissors {color} />
|
||||
{:else if iconItem === "teardrop"}
|
||||
<Teardrop {color} />
|
||||
{:else if iconItem === "teardrop_with_hole_green"}
|
||||
<Teardrop_with_hole_green {color} />
|
||||
{:else if iconItem === "triangle"}
|
||||
<Triangle {color} />
|
||||
{:else}
|
||||
<img class="w-full h-full" src={iconItem} />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
20
src/UI/Map/Marker.svelte
Normal file
20
src/UI/Map/Marker.svelte
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<script lang="ts">
|
||||
|
||||
import PointRenderingConfig, { IconConfig } from "../../Models/ThemeConfig/PointRenderingConfig";
|
||||
import Icon from "./Icon.svelte";
|
||||
import { Store } from "../../Logic/UIEventSource";
|
||||
|
||||
/**
|
||||
* Renders a 'marker', which consists of multiple 'icons'
|
||||
*/
|
||||
export let config : PointRenderingConfig
|
||||
let icons: IconConfig[] = config.marker;
|
||||
export let tags: Store<Record<string, string>>;
|
||||
|
||||
</script>
|
||||
|
||||
<div class="relative w-full h-full">
|
||||
{#each icons as icon}
|
||||
<Icon {icon} {tags} />
|
||||
{/each}
|
||||
</div>
|
||||
|
|
@ -142,7 +142,7 @@ class PointRenderingLayer {
|
|||
} else {
|
||||
store = new ImmutableStore(<OsmTags>feature.properties)
|
||||
}
|
||||
const { html, iconAnchor } = this._config.RenderIcon(store, true)
|
||||
const { html, iconAnchor } = this._config.RenderIcon(store)
|
||||
html.SetClass("marker")
|
||||
if (this._onClick !== undefined) {
|
||||
html.SetClass("cursor-pointer")
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@
|
|||
const tags = TagUtils.KVtoProperties(preset.tags ?? [])
|
||||
|
||||
const icon: string = layer.mapRendering[0]
|
||||
.RenderIcon(new ImmutableStore<any>(tags), false)
|
||||
.RenderIcon(new ImmutableStore<any>(tags))
|
||||
.html.SetClass("w-12 h-12 block relative")
|
||||
.ConstructElement().innerHTML
|
||||
|
||||
|
|
|
|||
43
src/UI/Studio/ArrayMultiAnswer.svelte
Normal file
43
src/UI/Studio/ArrayMultiAnswer.svelte
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<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 LayerConfig from "../../Models/ThemeConfig/LayerConfig";
|
||||
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig";
|
||||
|
||||
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
|
||||
}
|
||||
const tags = new UIEventSource({})
|
||||
|
||||
{
|
||||
// Setting the initial value
|
||||
const v = <string[]> state.getCurrentValueFor(path)
|
||||
if(v && v.length > 0){
|
||||
tags.setData({value: v.join(";")})
|
||||
}
|
||||
}
|
||||
|
||||
tags.addCallbackD(tags => {
|
||||
const values = tags["value"]?.split(";")
|
||||
if(!values){
|
||||
return
|
||||
}
|
||||
state.setValueAt(path, values)
|
||||
})
|
||||
|
||||
const config = new TagRenderingConfig(configJson)
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<TagRenderingEditable {config} selectedElement={undefined} showQuestionIfUnknown={true} {state} {tags} />
|
||||
</div>
|
||||
|
|
@ -58,7 +58,8 @@
|
|||
|
||||
<div slot="title2">Rendering on the map</div>
|
||||
<div slot="content2">
|
||||
<Region configs={perRegion["maprendering"]} {state} />
|
||||
<Region configs={perRegion["linerendering"]} {state} />
|
||||
<Region configs={perRegion["pointrendering"]} {state} />
|
||||
</div>
|
||||
<div slot="title3">Configuration file</div>
|
||||
<div slot="content3">
|
||||
|
|
|
|||
|
|
@ -120,21 +120,20 @@ export default class EditLayerState {
|
|||
}
|
||||
|
||||
public setValueAt(path: ReadonlyArray<string | number>, v: any) {
|
||||
{
|
||||
let entry = this.configuration.data
|
||||
for (let i = 0; i < path.length - 1; i++) {
|
||||
const breadcrumb = path[i]
|
||||
if (entry[breadcrumb] === undefined) {
|
||||
entry[breadcrumb] = typeof path[i + 1] === "number" ? [] : {}
|
||||
}
|
||||
entry = entry[breadcrumb]
|
||||
let entry = this.configuration.data
|
||||
console.log("Setting value", v, "to", path, "in entry", entry)
|
||||
for (let i = 0; i < path.length - 1; i++) {
|
||||
const breadcrumb = path[i]
|
||||
if (entry[breadcrumb] === undefined) {
|
||||
entry[breadcrumb] = typeof path[i + 1] === "number" ? [] : {}
|
||||
}
|
||||
if (v) {
|
||||
entry[path.at(-1)] = v
|
||||
} else if (entry) {
|
||||
delete entry[path.at(-1)]
|
||||
}
|
||||
this.configuration.ping()
|
||||
entry = entry[breadcrumb]
|
||||
}
|
||||
if (v) {
|
||||
entry[path.at(-1)] = v
|
||||
} else if (entry) {
|
||||
delete entry[path.at(-1)]
|
||||
}
|
||||
this.configuration.ping()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,8 +23,10 @@
|
|||
export let path: (string | number)[] = [];
|
||||
const isTagRenderingBlock = path.length === 1 && path[0] === "tagRenderings";
|
||||
|
||||
const subparts = state.getSchemaStartingWith(schema.path);
|
||||
|
||||
|
||||
const subparts: ConfigMeta = state.getSchemaStartingWith(schema.path)
|
||||
.filter(part => part.path.length - 1 === schema.path.length);
|
||||
console.log("For ", schema.path, "got subparts", subparts)
|
||||
/**
|
||||
* Store the _indices_
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -100,28 +100,33 @@
|
|||
startValue = JSON.stringify(startValue)
|
||||
}
|
||||
const tags = new UIEventSource<Record<string, string>>({value: startValue ?? ""})
|
||||
onDestroy(state.register(path, tags.map(tgs => {
|
||||
const v = tgs["value"];
|
||||
if (schema.type === "boolan") {
|
||||
return v === "true" || v === "yes" || v === "1"
|
||||
}
|
||||
if (schema.type === "number") {
|
||||
return Number(v)
|
||||
}
|
||||
if(isTranslation) {
|
||||
if(v === ""){
|
||||
return {}
|
||||
try {
|
||||
onDestroy(state.register(path, tags.map(tgs => {
|
||||
const v = tgs["value"];
|
||||
if (schema.type === "boolan") {
|
||||
return v === "true" || v === "yes" || v === "1"
|
||||
}
|
||||
return JSON.parse(v)
|
||||
}
|
||||
return v
|
||||
}), isTranslation))
|
||||
if (schema.type === "number") {
|
||||
return Number(v)
|
||||
}
|
||||
if (isTranslation) {
|
||||
if (v === "") {
|
||||
return {}
|
||||
}
|
||||
return JSON.parse(v)
|
||||
}
|
||||
return v
|
||||
}), isTranslation))
|
||||
}catch (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">
|
||||
<span class="subtle">{path.join(".")}</span>
|
||||
<TagRenderingEditable {config} selectedElement={undefined} showQuestionIfUnknown={true} {state} {tags}/>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
import SchemaBasedMultiType from "./SchemaBasedMultiType.svelte";
|
||||
import SchemaBasedTranslationInput from "./SchemaBasedTranslationInput.svelte";
|
||||
import { ConfigMetaUtils } from "./configMeta.ts"
|
||||
import ArrayMultiAnswer from "./ArrayMultiAnswer.svelte";
|
||||
export let schema: ConfigMeta;
|
||||
export let state: EditLayerState;
|
||||
export let path: (string | number)[] = [];
|
||||
|
|
@ -14,6 +15,8 @@
|
|||
{#if schema.hints.typehint === "tagrendering[]"}
|
||||
<!-- We cheat a bit here by matching this 'magical' type... -->
|
||||
<SchemaBasedArray {path} {state} {schema} />
|
||||
{:else if schema.type === "array" && schema.hints.multianswer === "true"}
|
||||
<ArrayMultiAnswer {path} {state} {schema}/>
|
||||
{:else if schema.type === "array"}
|
||||
<SchemaBasedArray {path} {state} {schema} />
|
||||
{:else if schema.hints.types}
|
||||
|
|
|
|||
|
|
@ -29,49 +29,81 @@
|
|||
types.splice(hasBooleanOption);
|
||||
}
|
||||
|
||||
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"));
|
||||
}
|
||||
|
||||
if (lastIsString) {
|
||||
types.splice(types.length - 1, 1);
|
||||
}
|
||||
const configJson: QuestionableTagRenderingConfigJson = {
|
||||
id: "TYPE_OF:" + path.join("_"),
|
||||
question: "Which subcategory is needed?",
|
||||
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: "value=" + i,
|
||||
addExtraTags: ["direct="],
|
||||
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
|
||||
}
|
||||
);
|
||||
}
|
||||
if (schema.hints.suggestions) {
|
||||
configJson.mappings.push(...schema.hints.suggestions);
|
||||
}
|
||||
|
||||
if (hasBooleanOption >= 0) {
|
||||
configJson.mappings.unshift(
|
||||
{
|
||||
if: "direct=true",
|
||||
if: "value=true",
|
||||
then: (schema.hints.iftrue ?? "Yes"),
|
||||
addExtraTags: ["value="]
|
||||
addExtraTags: ["chosen_type_index="]
|
||||
},
|
||||
{
|
||||
if: "direct=false",
|
||||
if: "value=false",
|
||||
then: (schema.hints.iffalse ?? "No"),
|
||||
addExtraTags: ["value="]
|
||||
addExtraTags: ["chosen_type_index="]
|
||||
}
|
||||
);
|
||||
}
|
||||
const config = new TagRenderingConfig(configJson, "config based on " + schema.path.join("."));
|
||||
let chosenOption: number = defaultOption;
|
||||
|
||||
|
||||
const existingValue = state.getCurrentValueFor(path);
|
||||
console.log("Setting direct: ", hasBooleanOption, path.join("."), existingValue);
|
||||
console.log("Initial value is", existingValue);
|
||||
if (hasBooleanOption >= 0 && (existingValue === true || existingValue === false)) {
|
||||
tags.setData({ direct: "" + existingValue });
|
||||
tags.setData({ value: "" + existingValue });
|
||||
} else if (lastIsString && typeof existingValue === "string") {
|
||||
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 }[] = [];
|
||||
outer: for (let i = 0; i < (<[]>schema.type).length; i++) {
|
||||
const type = schema.type[i];
|
||||
let optionalMatches = 0
|
||||
let optionalMatches = 0;
|
||||
for (const key of Object.keys(type.properties ?? {})) {
|
||||
if(!!existingValue[key]){
|
||||
optionalMatches++
|
||||
if (!!existingValue[key]) {
|
||||
optionalMatches++;
|
||||
}
|
||||
}
|
||||
if (type.required) {
|
||||
|
|
@ -85,51 +117,56 @@
|
|||
}
|
||||
numberOfMatches++;
|
||||
}
|
||||
possibleTypes.push({ index: i, matchingPropertiesCount: numberOfMatches , optionalMatches});
|
||||
possibleTypes.push({ index: i, matchingPropertiesCount: numberOfMatches, optionalMatches });
|
||||
} else {
|
||||
possibleTypes.push({ index: i, matchingPropertiesCount: 0, optionalMatches });
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
possibleTypes.sort((a, b) => b.optionalMatches - a.optionalMatches);
|
||||
possibleTypes.sort((a, b) => b.matchingPropertiesCount - a.matchingPropertiesCount);
|
||||
if (possibleTypes.length > 0) {
|
||||
tags.setData({ value: "" + possibleTypes[0].index });
|
||||
tags.setData({ chosen_type_index: "" + possibleTypes[0].index });
|
||||
}
|
||||
} else if (defaultOption !== undefined) {
|
||||
tags.setData({ value: "" + defaultOption });
|
||||
tags.setData({ chosen_type_index: "" + defaultOption });
|
||||
}
|
||||
|
||||
if (hasBooleanOption >= 0) {
|
||||
if (hasBooleanOption >= 0 || lastIsString) {
|
||||
|
||||
const directValue = tags.mapD(tags => {
|
||||
if (tags["value"]) {
|
||||
return undefined;
|
||||
if (tags["chosen_type_index"]) {
|
||||
return "";
|
||||
}
|
||||
return tags["direct"] === "true";
|
||||
if (lastIsString) {
|
||||
return tags["value"];
|
||||
}
|
||||
return tags["value"] === "true";
|
||||
});
|
||||
onDestroy(state.register(path, directValue, true));
|
||||
}
|
||||
|
||||
let chosenOption: number = defaultOption;
|
||||
let subSchemas: ConfigMeta[] = [];
|
||||
|
||||
let subpath = path;
|
||||
|
||||
console.log("Initial chosen option is", chosenOption);
|
||||
onDestroy(tags.addCallbackAndRun(tags => {
|
||||
if (tags["value"] !== "") {
|
||||
chosenOption = undefined;
|
||||
return;
|
||||
}
|
||||
const oldOption = chosenOption;
|
||||
chosenOption = tags["value"] ? Number(tags["value"]) : defaultOption;
|
||||
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) ?? {}
|
||||
console.log({o})
|
||||
for(const key of type?.required ?? []){
|
||||
console.log(key)
|
||||
o[key] ??= {}
|
||||
const o = state.getCurrentValueFor(path) ?? {};
|
||||
console.log({ o });
|
||||
for (const key of type?.required ?? []) {
|
||||
console.log(key);
|
||||
o[key] ??= {};
|
||||
}
|
||||
state.setValueAt(path, o);
|
||||
}
|
||||
|
|
@ -152,13 +189,16 @@
|
|||
for (const crumble of Object.keys(type.properties)) {
|
||||
subSchemas.push(...(state.getSchema([...cleanPath, crumble])));
|
||||
}
|
||||
console.log("Got subschemas for", path, ":", subSchemas)
|
||||
}));
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<div class="p-2 border-2 border-dashed border-gray-300 flex flex-col gap-y-2">
|
||||
<div class="p-2 border-2 border-dashed border-gray-300 flex flex-col gap-y-2 m-1">
|
||||
{#if schema.hints.title !== undefined}
|
||||
<h3>{schema.hints.title}</h3>
|
||||
<div> {schema.description} </div>
|
||||
{/if}
|
||||
<div>
|
||||
<TagRenderingEditable {config} selectedElement={undefined} showQuestionIfUnknown={true} {state} {tags} />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ export interface ConfigMeta {
|
|||
default?: string
|
||||
typesdefault?: string
|
||||
suggestions?: []
|
||||
title?: string
|
||||
multianswer?: "true" | string
|
||||
}
|
||||
required: boolean
|
||||
description: string
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue