Studio: usability tests

This commit is contained in:
Pieter Vander Vennet 2023-10-20 19:04:55 +02:00
parent 0f60977b6d
commit 2041a9245d
37 changed files with 524 additions and 446 deletions

View file

@ -71,6 +71,11 @@ export default class EditLayerState {
sharedLayers: layers,
}
}
const prepare = new Pipe(
new PrepareLayer(state),
new ValidateLayer("dynamic", false, undefined, true)
)
this.messages = this.configuration.mapD((config) => {
const trs = Utils.NoNull(config.tagRenderings ?? [])
for (let i = 0; i < trs.length; i++) {
@ -93,11 +98,6 @@ export default class EditLayerState {
}
}
const prepare = new Pipe(
new PrepareLayer(state),
new ValidateLayer("dynamic", false, undefined)
)
const context = ConversionContext.construct([], ["prepare"])
prepare.convert(<LayerConfigJson>config, context)
return context.messages

View file

@ -1,156 +1,169 @@
<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 { 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";
export let state: EditLayerState
export let path: (string | number)[] = []
export let schema: ConfigMeta
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"
if(isTranslation){
type = "translation"
export let state: EditLayerState;
export let path: (string | number)[] = [];
export let schema: ConfigMeta;
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";
let rendervalue = schema.type === "boolean" ? undefined : ((schema.hints.inline ?? schema.path.join(".")) + " <b>{translated(value)}</b>")
let helperArgs = undefined
let inline = schema.hints.inline !== undefined
if (isTranslation) {
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)]
}
if(type.endsWith("[]")){
type = type.substring(0, type.length - 2)
}
if (type.endsWith("[]")) {
type = type.substring(0, type.length - 2);
}
const configJson: QuestionableTagRenderingConfigJson = {
id: path.join("_"),
render: rendervalue,
question: schema.hints.question,
questionHint: nmd(schema.description),
freeform: schema.type === "boolean" ? undefined : {
key: "value",
type,
inline,
helperArgs
}
};
if (schema.hints.default) {
configJson.mappings = [{
if: "value=", // We leave this blank
then: schema.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: schema.path.at(-1) + " is not set. " + (schema.hints.ifunset ?? "")
}];
}
function mightBeBoolean(type: undefined | JsonSchemaType): boolean {
if (type === undefined) {
return false;
}
if (type["type"]) {
type = type["type"];
}
if (type === "boolean") {
return true;
}
if (!Array.isArray(type)) {
return false;
}
const configJson: QuestionableTagRenderingConfigJson = {
id: path.join("_"),
render: schema.type === "boolean" ? undefined : ((schema.hints.inline ?? schema.path.at(-1) )+ ": translated value: <b>{translated(value)}</b>"),
question: schema.hints.question,
questionHint: nmd(schema.description),
freeform: schema.type === "boolean" ? undefined : {
key: "value",
type,
inline: schema.hints.inline !== undefined
},
}
return type.some(t => mightBeBoolean(t));
}
if (schema.hints.default) {
configJson.mappings = [{
if: "value=", // We leave this blank
then: schema.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: schema.path.at(-1) + " is not set. " + (schema.hints.ifunset ?? ""),
}]
}
if (mightBeBoolean(schema.type)) {
configJson.mappings = configJson.mappings ?? [];
configJson.mappings.push(
{
if: "value=true",
then: schema.hints?.iftrue ?? "Yes"
},
{
if: "value=false",
then: schema.hints?.iffalse ?? "No"
}
);
}
function mightBeBoolean(type: undefined | JsonSchemaType): boolean {
if(type === undefined){
return false
}
if(type["type"]){
type = type["type"]
}
if(type === "boolean"){
return true
}
if(!Array.isArray(type)){
return false
}
return type.some(t => mightBeBoolean(t) )
if (schema.hints.suggestions) {
if (!configJson.mappings) {
configJson.mappings = [];
}
if (mightBeBoolean(schema.type)) {
configJson.mappings = configJson.mappings ?? []
configJson.mappings.push(
{
if: "value=true",
then: "Yes: "+(schema.hints?.iftrue??"")
},
{
if: "value=false",
then: "No: "+(schema.hints?.iffalse??"")
}
)
configJson.mappings.push(...schema.hints.suggestions);
}
let config: TagRenderingConfig;
let err: string = undefined;
let messages = state.messages.mapD(msgs => msgs.filter(msg => {
const pth = msg.context.path;
for (let i = 0; i < Math.min(pth.length, path.length); i++) {
if (pth[i] !== path[i]) {
return false;
}
}
if (schema.hints.suggestions) {
if (!configJson.mappings) {
configJson.mappings = []
return true;
}));
try {
config = new TagRenderingConfig(configJson, "config based on " + schema.path.join("."));
} catch (e) {
console.error(e, config);
err = path.join(".") + " " + e;
}
let startValue = state.getCurrentValueFor(path);
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;
}
configJson.mappings.push(...schema.hints.suggestions)
}
let config: TagRenderingConfig
let err: string = undefined
let messages = state.messages.mapD(msgs => msgs.filter(msg => {
const pth = msg.context.path
for (let i = 0; i < Math.min(pth.length, path.length); i++) {
if(pth[i] !== path[i]){
return false
}
if (v === "false" || v === "no" || v === "0") {
console.log("Setting false...");
return false;
}
return true
}))
try {
config = new TagRenderingConfig(configJson, "config based on " + schema.path.join("."))
} catch (e) {
console.error(e, config)
err = path.join(".") + " " + e
}
let startValue = state.getCurrentValueFor(path)
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"){
console.log("Setting false...")
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)
}
}
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);
}
</script>
{#if err !== undefined}
<span class="alert">{err}</span>
<span class="alert">{err}</span>
{:else}
<div class="w-full flex flex-col">
<TagRenderingEditable {config} selectedElement={undefined} showQuestionIfUnknown={true} {state} {tags}/>
{#if $messages.length > 0}
{#each $messages as msg}
<div class="alert">{msg.message}</div>
{/each}
{/if}
</div>
<div class="w-full flex flex-col">
<TagRenderingEditable {config} selectedElement={undefined} showQuestionIfUnknown={true} {state} {tags} />
{#if $messages.length > 0}
{#each $messages as msg}
<div class="alert">{msg.message}</div>
{/each}
{/if}
<span class="subtle">{schema.path.join(".")}</span>
</div>
{/if}

View file

@ -10,7 +10,7 @@ import type {
} from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson";
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig";
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte";
import { UIEventSource } from "../../Logic/UIEventSource";
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";
@ -22,7 +22,14 @@ export let state: EditLayerState;
export let schema: ConfigMeta;
export let path: (string | number)[];
let value = state.getCurrentValueFor(path) ;
let value = state.getCurrentValueFor(path);
/**
* 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 => config.source?.geoJson === undefined))
let mappingsBuiltin: MappingConfigJson[] = [];
for (const tr of questions.tagRenderings) {
@ -44,13 +51,14 @@ const configBuiltin = new TagRenderingConfig(<QuestionableTagRenderingConfigJson
const tags = new UIEventSource({ value });
const store = state.getStoreFor(path)
const store = state.getStoreFor(path);
tags.addCallbackAndRunD(tgs => {
store.setData(tgs["value"])
store.setData(tgs["value"]);
});
let mappings: UIEventSource<MappingConfigJson[]> = state.getStoreFor([...path, "mappings"]);
$: console.log("Allow questions:", $allowQuestions)
const topLevelItems: Record<string, ConfigMeta> = {};
for (const item of questionableTagRenderingSchemaRaw) {
if (item.path.length === 1) {
@ -64,7 +72,12 @@ function initMappings() {
}
}
const freeformSchema = <ConfigMeta[]> questionableTagRenderingSchemaRaw.filter(schema => schema.path.length >= 1 && schema.path[0] === "freeform");
const items = new Set(["question", "questionHint", "multiAnswer", "freeform", "render", "condition", "metacondition", "mappings", "icon"]);
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("."));
</script>
{#if typeof value === "string"}
@ -80,10 +93,12 @@ const freeformSchema = <ConfigMeta[]> questionableTagRenderingSchemaRaw.filter(
<slot name="upper-right" />
</div>
<SchemaBasedField {state} path={[...path,"question"]} schema={topLevelItems["question"]}></SchemaBasedField>
<SchemaBasedField {state} path={[...path,"questionHint"]} schema={topLevelItems["questionHint"]}></SchemaBasedField>
<SchemaBasedField {state} path={[...path,"render"]} schema={topLevelItems["render"]}></SchemaBasedField>
{#if $allowQuestions}
<SchemaBasedField {state} path={[...path,"question"]} schema={topLevelItems["question"]} />
<SchemaBasedField {state} path={[...path,"questionHint"]} schema={topLevelItems["questionHint"]} />
{:else}
{/if}
{#each ($mappings ?? []) as mapping, i (mapping)}
<div class="flex interactive w-full">
<MappingInput {mapping} {state} path={path.concat(["mappings", i])}>
@ -98,17 +113,26 @@ const freeformSchema = <ConfigMeta[]> questionableTagRenderingSchemaRaw.filter(
</div>
{/each}
<button class="small primary"
<button class="primary"
on:click={() =>{ initMappings(); mappings.data.push({if: undefined, then: {}}); mappings.ping()} }>
Add a mapping
</button>
<SchemaBasedField {state} path={[...path,"multiAnswer"]} schema={topLevelItems["multiAnswer"]}></SchemaBasedField>
<div class="border border-gray-200 border-dashed">
<h3>Text field and input element configuration</h3>
<Region {state} {path} configs={freeformSchema}/>
<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"]} />
<Region {state} {path} configs={freeformSchema} />
<SchemaBasedField {state} path={[...path,"icon"]} schema={topLevelItems["icon"]} />
</div>
<SchemaBasedField {state} path={[...path,"condition"]} schema={topLevelItems["condition"]} />
<SchemaBasedField {state} path={[...path,"metacondition"]} schema={topLevelItems["metacondition"]} />
{#each missing as field}
<SchemaBasedField {state} path={[...path,field]} schema={topLevelItems[field]} />
{/each}
</div>
{/if}