Studio: UX work

This commit is contained in:
Pieter Vander Vennet 2023-11-05 12:05:00 +01:00
parent bf4f7d3f88
commit 8685ec8ccc
45 changed files with 532 additions and 312 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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") {

View file

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

View file

@ -2,7 +2,6 @@
import type { ConversionMessage } from "../../Models/ThemeConfig/Conversion/Conversion";
export let messages: ConversionMessage[];
console.log(messages)
</script>
{#if messages.length === 0}

View file

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