Studio: UX-improvements after user testing

This commit is contained in:
Pieter Vander Vennet 2023-10-21 09:35:54 +02:00
parent 2041a9245d
commit 44c1548e89
19 changed files with 100 additions and 35 deletions

View file

@ -210,7 +210,7 @@
]
},
"tags": {
"description": "A single tag (encoded as <code>key=value</code>) out of all the tags to add onto the newly created point.\nNote that the icon in the UI will be chosen automatically based on the tags provided here.\n\nquestion: What tag should be added to the new object?\ntype: simple_tag",
"description": "A single tag (encoded as <code>key=value</code>) out of all the tags to add onto the newly created point.\nNote that the icon in the UI will be chosen automatically based on the tags provided here.\n\nquestion: What tag should be added to the new object?\ntype: simple_tag\ntypeHelper: uploadableOnly",
"type": "array",
"items": {
"type": "string"

View file

@ -210,7 +210,7 @@ export default {
]
},
"tags": {
"description": "A single tag (encoded as <code>key=value</code>) out of all the tags to add onto the newly created point.\nNote that the icon in the UI will be chosen automatically based on the tags provided here.\n\nquestion: What tag should be added to the new object?\ntype: simple_tag",
"description": "A single tag (encoded as <code>key=value</code>) out of all the tags to add onto the newly created point.\nNote that the icon in the UI will be chosen automatically based on the tags provided here.\n\nquestion: What tag should be added to the new object?\ntype: simple_tag\ntypeHelper: uploadableOnly",
"type": "array",
"items": {
"type": "string"

View file

@ -1992,7 +1992,7 @@
]
},
"tags": {
"description": "A single tag (encoded as <code>key=value</code>) out of all the tags to add onto the newly created point.\nNote that the icon in the UI will be chosen automatically based on the tags provided here.\n\nquestion: What tag should be added to the new object?\ntype: simple_tag",
"description": "A single tag (encoded as <code>key=value</code>) out of all the tags to add onto the newly created point.\nNote that the icon in the UI will be chosen automatically based on the tags provided here.\n\nquestion: What tag should be added to the new object?\ntype: simple_tag\ntypeHelper: uploadableOnly",
"type": "array",
"items": {
"type": "string"
@ -2392,7 +2392,7 @@
]
},
"tags": {
"description": "A single tag (encoded as <code>key=value</code>) out of all the tags to add onto the newly created point.\nNote that the icon in the UI will be chosen automatically based on the tags provided here.\n\nquestion: What tag should be added to the new object?\ntype: simple_tag",
"description": "A single tag (encoded as <code>key=value</code>) out of all the tags to add onto the newly created point.\nNote that the icon in the UI will be chosen automatically based on the tags provided here.\n\nquestion: What tag should be added to the new object?\ntype: simple_tag\ntypeHelper: uploadableOnly",
"type": "array",
"items": {
"type": "string"

View file

@ -1970,7 +1970,7 @@ export default {
]
},
"tags": {
"description": "A single tag (encoded as <code>key=value</code>) out of all the tags to add onto the newly created point.\nNote that the icon in the UI will be chosen automatically based on the tags provided here.\n\nquestion: What tag should be added to the new object?\ntype: simple_tag",
"description": "A single tag (encoded as <code>key=value</code>) out of all the tags to add onto the newly created point.\nNote that the icon in the UI will be chosen automatically based on the tags provided here.\n\nquestion: What tag should be added to the new object?\ntype: simple_tag\ntypeHelper: uploadableOnly",
"type": "array",
"items": {
"type": "string"
@ -2369,7 +2369,7 @@ export default {
]
},
"tags": {
"description": "A single tag (encoded as <code>key=value</code>) out of all the tags to add onto the newly created point.\nNote that the icon in the UI will be chosen automatically based on the tags provided here.\n\nquestion: What tag should be added to the new object?\ntype: simple_tag",
"description": "A single tag (encoded as <code>key=value</code>) out of all the tags to add onto the newly created point.\nNote that the icon in the UI will be chosen automatically based on the tags provided here.\n\nquestion: What tag should be added to the new object?\ntype: simple_tag\ntypeHelper: uploadableOnly",
"type": "array",
"items": {
"type": "string"

View file

@ -4,6 +4,8 @@
Create a simple layer specification using MapComplete studio with 'images' and a question. The actual _topic_ of the layer can be chosen by the participant
This participant wanted to create a layer about solar panels.
## Background info
Browser: Librewolf on a linux machine (actually: pietervdvn's dev machine)
@ -30,4 +32,4 @@ As such, many terms and the general structure of Studio were intuitively clear.
## Other misc issues
- The crosshair might be invisible if the aerial imagery is quite dark (fixed in 9dc222be433512d4d1ca530c1d09e28442d976ec)
- [x] The crosshair might be invisible if the aerial imagery is quite dark (fixed in 9dc222be433512d4d1ca530c1d09e28442d976ec)

View file

@ -0,0 +1,22 @@
# User Test of the Studio
## Task
Create a simple layer specification using MapComplete studio with 'images' and a question. The actual _topic_ of the layer can be chosen by the participant
This participant wanted to create a layer to park escooters
## Background info
Browser: Participants machine, browser unknown (but no browser-specific bugs were encountered)
Testurl: hosted.Mapcomplete.org/studio.html
The participant has extensive OpenStreetMap-knowledge but only used MapComplete a few times, long ago.
## Surfaced issues
- [x] In presets, all options can be chosen (e.g. regex, '<', ...). However, these should be uploadable tags
- [x] The 'try it out'-button should be a 'next'-button
- [x] Entering an incorrect ID and pressing enter still takes you to the layer editor with an incorrect ID
- [x] A name and description are obligatory to use the layer as single-layer-theme; but those error messages are unclear.
- [ ]
- [ ]

View file

@ -9,6 +9,7 @@ import { AllSharedLayers } from "../src/Customizations/AllSharedLayers"
const metainfo = {
type: "One of the inputValidator types",
typeHelper: "Helper arguments for the type input, comma-separated. Same as 'args'",
types: "Is multiple types are allowed for this field, then first show a mapping to pick the appropriate subtype. `Types` should be `;`-separated and contain precisely the same amount of subtypes",
typesdefault: "Works in conjuction with `types`: this type will be selected by default",
group: "A kind of label. Items with the same group name will be placed in the same region",

View file

@ -97,8 +97,9 @@ export class TagUtils {
docs: "Both the `key` and `value` part of this specification are interpreted as regexes, both the key and value musth completely match their respective regexes",
},
":=": {
name: "Substitute `{some_key}` should match `key`",
name: "Substitute `... {some_key} ...` and match `key`",
overpassSupport: false,
uploadable: true,
docs:
"**This is an advanced feature - use with caution**\n" +
"\n" +

View file

@ -319,6 +319,7 @@ export interface LayerConfigJson {
*
* question: What tag should be added to the new object?
* type: simple_tag
* typeHelper: uploadableOnly
*/
tags: string[]
/**

View file

@ -3,18 +3,29 @@
*/
import { UIEventSource } from "../../../Logic/UIEventSource";
import BasicTagInput from "../../Studio/TagInput/BasicTagInput.svelte";
import { TagUtils } from "../../../Logic/Tags/TagUtils";
import * as nmd from "nano-markdown"
import FromHtml from "../../Base/FromHtml.svelte";
export let value: UIEventSource<undefined | string>;
export let uploadableOnly: boolean;
export let args: string[] = [];
let uploadableOnly: boolean = args[0] === "uploadableOnly";
export let overpassSupportNeeded: boolean;
/**
* Only show the taginfo-statistics if they are suspicious (thus: less then 250 entries)
*/
export let silent: boolean = false;
let mode: string = "=";
let dropdownFocussed = new UIEventSource(false);
let documentation = TagUtils.modeDocumentation[mode];
$: documentation = TagUtils.modeDocumentation[mode];
</script>
<BasicTagInput {overpassSupportNeeded} {silent} tag={value} {uploadableOnly} />
<BasicTagInput bind:mode={mode} {dropdownFocussed} {overpassSupportNeeded} {silent} tag={value} {uploadableOnly} />
{#if $dropdownFocussed}
<div class="border border-dashed border-black p-2 m-2">
<b>{documentation.name}</b>
<FromHtml src={nmd(documentation.docs)}/>
</div>
{/if}

View file

@ -46,7 +46,7 @@
{:else if type === "tag"}
<TagInput { value } />
{:else if type === "simple_tag"}
<SimpleTagInput { value } />
<SimpleTagInput { value } {args} />
{:else if type === "opening_hours"}
<OpeningHoursInput { value } />
{:else if type === "wikidata"}

View file

@ -17,7 +17,6 @@
export let getCountry: () => string | undefined
export let placeholder: string | Translation | undefined
export let unit: Unit = undefined
export let value: UIEventSource<string>
/**
* Internal state bound to the input element.

View file

@ -10,6 +10,7 @@
import type { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson";
import type { ConversionMessage } from "../../Models/ThemeConfig/Conversion/Conversion";
import ErrorIndicatorForRegion from "./ErrorIndicatorForRegion.svelte";
import { ChevronRightIcon } from "@rgossiaux/svelte-heroicons/solid";
const layerSchema: ConfigMeta[] = <any>layerSchemaRaw;
@ -57,12 +58,15 @@
</script>
<div class="w-full flex justify-between">
<slot />
<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</a>
<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>
<div class="m4">

View file

@ -20,17 +20,17 @@
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
let rendervalue = schema.type === "boolean" ? undefined : ((schema.hints.inline ?? schema.path.join(".")) + " <b>{translated(value)}</b>");
let helperArgs = schema.hints.typehelper?.split(",");
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 (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("[]")) {
@ -164,6 +164,8 @@
<div class="alert">{msg.message}</div>
{/each}
{/if}
<span class="subtle">{schema.path.join(".")}</span>
{#if window.location.hostname === "127.0.0.1"}
<span class="subtle">{schema.path.join(".")}</span>
{/if}
</div>
{/if}

View file

@ -11,9 +11,16 @@
export let tag: UIEventSource<string> = new UIEventSource<string>(undefined)
export let uploadableOnly: boolean
export let overpassSupportNeeded: boolean
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
@ -38,7 +45,7 @@
let valueValue = new UIEventSource<string>(undefined)
let mode: string = "="
export let mode: string = "="
let modes: string[] = []
for (const k in TagUtils.modeDocumentation) {
@ -105,7 +112,7 @@
<ValidatedInput feedback={feedbackKey} placeholder="The key of the tag" type="key"
value={keyValue}></ValidatedInput>
<select bind:value={mode}>
<select bind:value={mode} on:focusin={() => dropdownFocussed.setData(true)} on:focusout={() => dropdownFocussed.setData(false)}>
{#each modes as option}
<option value={option}>
{option}

View file

@ -3,9 +3,13 @@ import { JsonSchema, JsonSchemaType } from "./jsonSchema"
export interface ConfigMeta {
path: string[]
type: JsonSchemaType | JsonSchema[]
/**
* All fields are lowercase, as they should be case-insensitive
*/
hints: {
group?: string
typehint?: string
typehelper?: string
/**
* If multiple subcategories can be chosen
*/

View file

@ -16,8 +16,9 @@
import layerSchemaRaw from "../../src/assets/schemas/layerconfigmeta.json";
import If from "./Base/If.svelte";
import BackButton from "./Base/BackButton.svelte";
export let studioUrl = window.location.hostname === "127.0.0.1" ? "http://127.0.0.1:1235" : "https://studio.mapcomplete.org";
export let studioUrl = window.location.hostname === "127.0.0.1" ? "http://127.0.0.1:1235" : "https://studio.mapcomplete.org";
const studio = new StudioServer(studioUrl);
let layersWithErr = UIEventSource.FromPromiseWithErr(studio.fetchLayerOverview());
let layers = layersWithErr.mapD(l => l.success);
@ -42,15 +43,16 @@
const layerSchema: ConfigMeta[] = <any>layerSchemaRaw;
let editLayerState = new EditLayerState(layerSchema, studio);
let layerId = editLayerState.configuration.map(layerConfig => layerConfig.id);
function fetchIconDescription(layerId): any {
return AllSharedLayers.getSharedLayersConfigs().get(layerId)?._layerIcon;
}
async function createNewLayer() {
if(layerIdFeedback.data !== undefined){
console.warn("There is still some feedback - not starting to create a new layer")
return
if (layerIdFeedback.data !== undefined) {
console.warn("There is still some feedback - not starting to create a new layer");
return;
}
state = "loading";
const id = newLayerId.data;
@ -142,6 +144,9 @@
-->
</div>
{:else if state === "edit_layer"}
<BackButton clss="small p-1" imageClass="w-8 h-8" on:click={() => {state =undefined}}>MapComplete Studio</BackButton>
<h3>Choose a layer to edit</h3>
<div class="flex flex-wrap">
{#each Array.from($layers) as layerId}
<NextButton clss="small" on:click={async () => {
@ -157,6 +162,7 @@
{/each}
</div>
{:else if state === "new_layer"}
<div class="interactive flex m-2 rounded-2xl flex-col p-2">
<h3>Enter the ID for the new layer</h3>
A good ID is:
@ -185,7 +191,9 @@
<Loading />
</div>
{:else if state === "editing_layer"}
<EditLayer {initialLayerConfig} state={editLayerState} />
<EditLayer {initialLayerConfig} state={editLayerState} >
<BackButton clss="small p-1" imageClass="w-8 h-8" on:click={() => {state =undefined}}>MapComplete Studio</BackButton>
</EditLayer>
{/if}
</LoginToggle>
</If>

View file

@ -9769,6 +9769,7 @@
"required": true,
"hints": {
"typehint": "simple_tag",
"typehelper": "uploadableOnly",
"question": "What tag should be added to the new object?"
},
"type": "array",

View file

@ -593,7 +593,7 @@
]
},
"tags": {
"description": "A single tag (encoded as <code>key=value</code>) out of all the tags to add onto the newly created point.\nNote that the icon in the UI will be chosen automatically based on the tags provided here.\n\nquestion: What tag should be added to the new object?\ntype: simple_tag",
"description": "A single tag (encoded as <code>key=value</code>) out of all the tags to add onto the newly created point.\nNote that the icon in the UI will be chosen automatically based on the tags provided here.\n\nquestion: What tag should be added to the new object?\ntype: simple_tag\ntypeHelper: uploadableOnly",
"type": "array",
"items": {
"type": "string"
@ -11193,6 +11193,7 @@
"required": true,
"hints": {
"typehint": "simple_tag",
"typehelper": "uploadableOnly",
"question": "What tag should be added to the new object?"
},
"type": "array",
@ -28077,6 +28078,7 @@
"required": true,
"hints": {
"typehint": "simple_tag",
"typehelper": "uploadableOnly",
"question": "What tag should be added to the new object?"
},
"type": "array",