forked from MapComplete/MapComplete
Studio: UX work
This commit is contained in:
parent
bf4f7d3f88
commit
8685ec8ccc
45 changed files with 532 additions and 312 deletions
|
|
@ -24,7 +24,7 @@
|
|||
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 perRegion: Record<string, ConfigMeta[]> = {};
|
||||
|
|
@ -57,10 +57,10 @@
|
|||
return config;
|
||||
}
|
||||
|
||||
let requiredFields = ["id", "name", "description"];
|
||||
let requiredFields = ["id", "name", "description", "source"];
|
||||
let currentlyMissing = state.configuration.map(config => {
|
||||
if(!config){
|
||||
return []
|
||||
if (!config) {
|
||||
return [];
|
||||
}
|
||||
const missing = [];
|
||||
for (const requiredField of requiredFields) {
|
||||
|
|
@ -73,29 +73,35 @@
|
|||
|
||||
let highlightedItem: UIEventSource<HighlightedTagRendering> = state.highlightedItem;
|
||||
</script>
|
||||
<div class="h-screen flex flex-col">
|
||||
|
||||
{#if $currentlyMissing.length > 0}
|
||||
|
||||
{#each requiredFields as required}
|
||||
<SchemaBasedInput {state}
|
||||
schema={configForRequiredField(required)}
|
||||
path={[required]} />
|
||||
{/each}
|
||||
{:else}
|
||||
<div class="h-screen flex flex-col">
|
||||
|
||||
<div class="w-full flex justify-between my-2">
|
||||
<slot />
|
||||
<div class="w-full flex justify-between my-2">
|
||||
<slot />
|
||||
{#if $title === undefined}
|
||||
<h3>Creating a new layer</h3>
|
||||
{:else}
|
||||
<h3>Editing layer {$title}</h3>
|
||||
{#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">
|
||||
Try it out
|
||||
<ChevronRightIcon class="h-6 w-6 shrink-0" />
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{#if $currentlyMissing.length > 0}
|
||||
<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">
|
||||
Try it out
|
||||
<ChevronRightIcon class="h-6 w-6 shrink-0" />
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if $currentlyMissing.length > 0}
|
||||
|
||||
{#each requiredFields as 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
|
||||
|
|
@ -150,22 +156,23 @@
|
|||
<FromHtml src={JSON.stringify($configuration, null, " ").replaceAll("\n","</br>")} />
|
||||
</div>
|
||||
|
||||
<ShowConversionMessages messages={$messages}/>
|
||||
<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>
|
||||
</div>
|
||||
</TabbedGroup>
|
||||
</div>
|
||||
</div>
|
||||
{#if $highlightedItem !== undefined}
|
||||
<FloatOver on:close={() => highlightedItem.setData(undefined)}>
|
||||
<div class="mt-16">
|
||||
<TagRenderingInput path={$highlightedItem.path} {state} schema={$highlightedItem.schema} />
|
||||
</div>
|
||||
</FloatOver>
|
||||
{/if}
|
||||
{#if $highlightedItem !== undefined}
|
||||
<FloatOver on:close={() => highlightedItem.setData(undefined)}>
|
||||
<div class="mt-16">
|
||||
<TagRenderingInput path={$highlightedItem.path} {state} schema={$highlightedItem.schema} />
|
||||
</div>
|
||||
</FloatOver>
|
||||
{/if}
|
||||
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import { Feature, Point } from "geojson"
|
|||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import { LayoutConfigJson } from "../../Models/ThemeConfig/Json/LayoutConfigJson"
|
||||
import { PrepareTheme } from "../../Models/ThemeConfig/Conversion/PrepareTheme"
|
||||
import { ConversionContext } from "../../Models/ThemeConfig/Conversion/ConversionContext";
|
||||
import { ConversionContext } from "../../Models/ThemeConfig/Conversion/ConversionContext"
|
||||
|
||||
export interface HighlightedTagRendering {
|
||||
path: ReadonlyArray<string | number>
|
||||
|
|
@ -41,9 +41,8 @@ export abstract class EditJsonState<T> {
|
|||
public readonly highlightedItem: UIEventSource<HighlightedTagRendering> = new UIEventSource(
|
||||
undefined
|
||||
)
|
||||
sendingUpdates = false
|
||||
private sendingUpdates = false
|
||||
private readonly _stores = new Map<string, UIEventSource<any>>()
|
||||
private boolean
|
||||
|
||||
constructor(schema: ConfigMeta[], server: StudioServer, category: "layers" | "themes") {
|
||||
this.schema = schema
|
||||
|
|
@ -53,6 +52,7 @@ export abstract class EditJsonState<T> {
|
|||
this.messages = this.setupErrorsForLayers()
|
||||
|
||||
const layerId = this.getId()
|
||||
this.highlightedItem.addCallbackD((hl) => console.log("Highlighted item is", hl))
|
||||
this.configuration
|
||||
.mapD((config) => {
|
||||
if (!this.sendingUpdates) {
|
||||
|
|
@ -105,6 +105,9 @@ export abstract class EditJsonState<T> {
|
|||
this.setValueAt(path, v)
|
||||
})
|
||||
this._stores.set(key, store)
|
||||
this.configuration.addCallbackD((config) => {
|
||||
store.setData(this.getCurrentValueFor(path))
|
||||
})
|
||||
return store
|
||||
}
|
||||
|
||||
|
|
@ -310,7 +313,7 @@ export default class EditLayerState extends EditJsonState<LayerConfigJson> {
|
|||
}
|
||||
if (!tr["id"] && !tr["override"]) {
|
||||
const qtr = <QuestionableTagRenderingConfigJson>tr
|
||||
let id = "" + i
|
||||
let id = "" + i + "_" + Utils.randomString(5)
|
||||
if (qtr?.freeform?.key) {
|
||||
id = qtr?.freeform?.key
|
||||
} else if (qtr.mappings?.[0]?.if) {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
<script lang="ts">
|
||||
|
||||
import type { MappingConfigJson } from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson";
|
||||
import EditLayerState from "./EditLayerState";
|
||||
import { Translation } from "../i18n/Translation";
|
||||
import { UIEventSource } from "../../Logic/UIEventSource";
|
||||
import type { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson";
|
||||
import { TagUtils } from "../../Logic/Tags/TagUtils";
|
||||
|
|
@ -12,6 +10,8 @@
|
|||
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)[];
|
||||
|
|
@ -30,11 +30,11 @@
|
|||
});
|
||||
let uploadableOnly: boolean = true;
|
||||
|
||||
let thenStringified = state.getStoreFor([...path, "then"]).sync(t => t ? JSON.stringify(t) : undefined, [], s => s ? JSON.parse(s) : undefined);
|
||||
let thenParsed = thenStringified.map(s => s ? JSON.parse(s) : s);
|
||||
let editMode = Object.keys(thenParsed.data).length === 0;
|
||||
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;
|
||||
|
||||
const mappingConfigs: ConfigMeta[] = configs.filter(c => c.path[0] === "mappings")
|
||||
let mappingConfigs: ConfigMeta[] = configs.filter(c => c.path[0] === "mappings")
|
||||
.map(c => <ConfigMeta>Utils.Clone(c))
|
||||
.map(c => {
|
||||
c.path.splice(0, 1);
|
||||
|
|
@ -57,9 +57,9 @@
|
|||
</div>
|
||||
{:else}
|
||||
<div>
|
||||
{#if Object.keys($thenParsed).length > 0}
|
||||
{#if Object.keys($thenText).length > 0}
|
||||
<b>
|
||||
{new Translation($thenParsed).txt}
|
||||
{$thenTextEn}
|
||||
</b>
|
||||
{:else}
|
||||
<i>No then is set</i>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
} from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson.js";
|
||||
import type { TagRenderingConfigJson } from "../../Models/ThemeConfig/Json/TagRenderingConfigJson";
|
||||
import FromHtml from "../Base/FromHtml.svelte";
|
||||
import { Utils } from "../../Utils";
|
||||
import ShowConversionMessage from "./ShowConversionMessage.svelte";
|
||||
import NextButton from "../Base/NextButton.svelte";
|
||||
|
||||
|
|
@ -21,9 +20,9 @@
|
|||
let value = state.getStoreFor(path);
|
||||
|
||||
let perId: Record<string, TagRenderingConfigJson[]> = {};
|
||||
for (const tagRendering of questions.tagRenderings) {
|
||||
for (let tagRendering of questions.tagRenderings) {
|
||||
if (tagRendering.labels) {
|
||||
for (const label of tagRendering.labels) {
|
||||
for (let label of tagRendering.labels) {
|
||||
perId[label] = (perId[label] ?? []).concat(tagRendering);
|
||||
}
|
||||
}
|
||||
|
|
@ -37,13 +36,19 @@
|
|||
return [x];
|
||||
}
|
||||
});
|
||||
let configs: Store<TagRenderingConfig[]> =configJson.mapD(configs => Utils.NoNull( configs.map(config => {
|
||||
try{
|
||||
return new TagRenderingConfig(config);
|
||||
}catch (e) {
|
||||
return undefined
|
||||
let configs: Store<TagRenderingConfig[]> = configJson.map(configs => {
|
||||
if (!configs) {
|
||||
return [{ error: "No configuartions found" }];
|
||||
}
|
||||
})));
|
||||
console.log("Regenerating configs");
|
||||
return configs.map(config => {
|
||||
try {
|
||||
return new TagRenderingConfig(config);
|
||||
} catch (e) {
|
||||
return { error: e };
|
||||
}
|
||||
});
|
||||
});
|
||||
let id: Store<string> = value.mapD(c => {
|
||||
if (c?.id) {
|
||||
return c.id;
|
||||
|
|
@ -58,12 +63,12 @@
|
|||
|
||||
let messages = state.messagesFor(path);
|
||||
|
||||
let description = schema.description
|
||||
if(description){
|
||||
try{
|
||||
description = nmd(description)
|
||||
}catch (e) {
|
||||
console.error("Could not convert description to markdown", {description})
|
||||
let description = schema.description;
|
||||
if (description) {
|
||||
try {
|
||||
description = nmd(description);
|
||||
} catch (e) {
|
||||
console.error("Could not convert description to markdown", { description });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -84,7 +89,7 @@
|
|||
<FromHtml src={description} />
|
||||
{/if}
|
||||
{#each $messages as message}
|
||||
<ShowConversionMessage {message}/>
|
||||
<ShowConversionMessage {message} />
|
||||
{/each}
|
||||
|
||||
<slot class="self-end my-4"></slot>
|
||||
|
|
@ -95,11 +100,30 @@
|
|||
<div class="flex flex-col w-full m-4">
|
||||
<h3>Preview of this question</h3>
|
||||
{#each $configs as config}
|
||||
<TagRenderingEditable
|
||||
selectedElement={state.exampleFeature}
|
||||
config={config} editingEnabled={new ImmutableStore(true)} showQuestionIfUnknown={true}
|
||||
{state}
|
||||
{tags}></TagRenderingEditable>
|
||||
{#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}
|
||||
<TagRenderingEditable
|
||||
selectedElement={state.exampleFeature}
|
||||
config={config} editingEnabled={new ImmutableStore(true)} showQuestionIfUnknown={true}
|
||||
{state}
|
||||
{tags}></TagRenderingEditable>
|
||||
{/if}
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -53,10 +53,18 @@
|
|||
function createItem(valueToSet?: any) {
|
||||
values.data.push(createdItems);
|
||||
if (valueToSet) {
|
||||
state.setValueAt([...path, createdItems], valueToSet);
|
||||
state.getStoreFor([...path, createdItems]).setData(valueToSet);
|
||||
}
|
||||
createdItems++;
|
||||
values.ping();
|
||||
|
||||
if(isTagRenderingBlock){
|
||||
if(typeof valueToSet === "string"){
|
||||
// THis is very broken state.highlightedItem.setData({path: [...path, createdItems], schema})
|
||||
}else{
|
||||
state.highlightedItem.setData({path: [...path, createdItems], schema})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function fusePath(i: number, subpartPath: string[]): (string | number)[] {
|
||||
|
|
@ -71,9 +79,9 @@
|
|||
return newPath;
|
||||
}
|
||||
|
||||
function del(value) {
|
||||
const index = values.data.indexOf(value);
|
||||
console.log("Deleting", value, index);
|
||||
function del(i) {
|
||||
const index = i;
|
||||
console.log("Deleting", index);
|
||||
values.data.splice(index, 1);
|
||||
values.ping();
|
||||
|
||||
|
|
@ -134,11 +142,11 @@
|
|||
{/if}
|
||||
{:else if subparts.length === 0}
|
||||
<!-- We need an array of values, so we use the typehint of the _parent_ element as field -->
|
||||
{#each $values as value (value)}
|
||||
{#each $values as value, i (value)}
|
||||
<div class="flex w-full">
|
||||
<SchemaBasedField {state} {schema} path={[...path, value]} />
|
||||
<button class="border-black border rounded-full p-1 w-fit h-fit"
|
||||
on:click={() => {del(value)}}>
|
||||
on:click={() => {del(i)}}>
|
||||
<TrashIcon class="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -150,7 +158,7 @@
|
|||
<div class="flex justify-between items-center">
|
||||
<h3 class="m-0">{singular} {value}</h3>
|
||||
<button class="border-black border rounded-full p-1 w-fit h-fit"
|
||||
on:click={() => {del(value)}}>
|
||||
on:click={() => {del(i)}}>
|
||||
<TrashIcon class="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -91,7 +91,6 @@
|
|||
|
||||
const existingValue = state.getCurrentValueFor(path);
|
||||
let hasOverride = existingValue?.override !== undefined;
|
||||
console.log({existingValue, hasOverride})
|
||||
if (hasBooleanOption >= 0 && (existingValue === true || existingValue === false)) {
|
||||
tags.setData({ value: "" + existingValue });
|
||||
} else if (lastIsString && typeof existingValue === "string") {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
<script lang="ts">
|
||||
import type { ConversionMessage } from "../../Models/ThemeConfig/Conversion/Conversion";
|
||||
import { ExclamationTriangleIcon } from "@babeard/svelte-heroicons/solid";
|
||||
import { ExclamationCircleIcon, ExclamationIcon, InformationCircleIcon } from "@rgossiaux/svelte-heroicons/solid";
|
||||
import { ExclamationIcon, InformationCircleIcon } from "@rgossiaux/svelte-heroicons/solid";
|
||||
|
||||
/**
|
||||
* Single conversion message, styled depending on the type
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
import type { ConversionMessage } from "../../Models/ThemeConfig/Conversion/Conversion";
|
||||
|
||||
export let messages: ConversionMessage[];
|
||||
console.log(messages)
|
||||
</script>
|
||||
|
||||
{#if messages.length === 0}
|
||||
|
|
|
|||
|
|
@ -21,8 +21,9 @@ import Region from "./Region.svelte";
|
|||
export let state: EditLayerState;
|
||||
export let schema: ConfigMeta;
|
||||
export let path: (string | number)[];
|
||||
|
||||
let value = state.getCurrentValueFor(path);
|
||||
const store = state.getStoreFor(path);
|
||||
let value = store.data
|
||||
console.log(">> initial value", value, store)
|
||||
|
||||
/**
|
||||
* Allows the theme builder to create 'writable' themes.
|
||||
|
|
@ -73,7 +74,6 @@ const configBuiltin = new TagRenderingConfig(<QuestionableTagRenderingConfigJson
|
|||
|
||||
const tags = new UIEventSource({ value });
|
||||
|
||||
const store = state.getStoreFor(path);
|
||||
tags.addCallbackAndRunD(tgs => {
|
||||
store.setData(tgs["value"]);
|
||||
});
|
||||
|
|
@ -99,9 +99,11 @@ const ignored = new Set(["labels", "description", "classes"]);
|
|||
const freeformSchema = <ConfigMeta[]>questionableTagRenderingSchemaRaw
|
||||
.filter(schema => schema.path.length == 2 && schema.path[0] === "freeform" && ($allowQuestions || schema.path[1] === "key"));
|
||||
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 value === "string"}
|
||||
{#if typeof $store === "string"}
|
||||
<div class="flex low-interaction">
|
||||
<TagRenderingEditable config={configBuiltin} selectedElement={undefined} showQuestionIfUnknown={true} {state}
|
||||
{tags} />
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue