Add tutorial for tagRenderings
This commit is contained in:
parent
0da93d6067
commit
7d43bb5983
14 changed files with 99 additions and 18 deletions
59
Docs/Studio/TagRenderingIntro.md
Normal file
59
Docs/Studio/TagRenderingIntro.md
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
# How to work with TagRenderings
|
||||||
|
|
||||||
|
The information box shows various attributes of the selected feature in a human friendly way.
|
||||||
|
|
||||||
|
This is done by a **tagRendering** which converts attributes into text.
|
||||||
|
|
||||||
|
This can be done by using **predefined options** (mappings) or with a **render**-string
|
||||||
|
|
||||||
|
# Predefined options
|
||||||
|
|
||||||
|
A predefined option states that, `if` a certain tag is present, `then` a certain text should be shown.
|
||||||
|
|
||||||
|
For example, a playground may be lit or not.
|
||||||
|
In OpenStreetMap, this is encoded with the tag `lit=yes` or `lit=no`. We might want to show `This playground is lit at night` and `This playground is not lit at night` to users of MapComplete.
|
||||||
|
|
||||||
|
This is what this will look like in the interface:
|
||||||
|
|
||||||
|
<img class="w-1/2" src="../../public/assets/docs/PredefinedOption.png"/>
|
||||||
|
|
||||||
|
# Substituting attributes
|
||||||
|
|
||||||
|
If none of the predefined options match, the string given in the `render`-field is used (under the question _"What text should be rendered?"_).
|
||||||
|
|
||||||
|
A special property about all shown texts is that, **if the name of a key appears between braces, this will be replaced by the corresponding value**.
|
||||||
|
|
||||||
|
For example, if the object has tags `min_age=3` and the text to display is `Accessible to kids older than {min_age} years`, then this will be displayed to the user as **Accessible to kids older than 3 years**
|
||||||
|
|
||||||
|
Note that this also works withing predifined options
|
||||||
|
|
||||||
|
# Special values
|
||||||
|
|
||||||
|
Special components can be summoned by calling them. For example, the relevant wikipedia will be displayed by entering the text `{wikipedia()}`. A table with opening hours is displayed with `{opening_hours()}`. For a full reference, [see the documentation](../SpecialRenderings.md).
|
||||||
|
|
||||||
|
# Requesting data with predefined options
|
||||||
|
|
||||||
|
These renderings can be turned into a way to contribute data easily. If a **question** is provided, then these renderings will be asked if unknown or gain the pencil to make changes.
|
||||||
|
|
||||||
|
A predefined option will show up as an option that can be picked.
|
||||||
|
<img class="w-1/2" src="../../public/assets/docs/QuestionPredefinedOptions.png"/>
|
||||||
|
|
||||||
|
# Requesting data with an input field
|
||||||
|
|
||||||
|
It is also possible to have a text field. For this, the **key** to write into must be given (_What is the name of the attribute that should be written to?_), in this case `max_age`.
|
||||||
|
<img class="w-1/2" src="../../public/assets/docs/QuestionTextField.png"/>
|
||||||
|
|
||||||
|
# Combining predefined options and freeform text
|
||||||
|
|
||||||
|
A text field and predefined options can be combined. The contributor can then choose between a predefined option or filling out something.
|
||||||
|
<img class="w-1/2" src="../../public/assets/docs/QuestionCombined.png"/>
|
||||||
|
|
||||||
|
# Selecting multiple values
|
||||||
|
|
||||||
|
One can set a question to allow multiple answers. This works with predefined options or a freeform text field.
|
||||||
|
|
||||||
|
<img class="w-1/2" src="../../public/assets/docs/QuestionMulti.png"/>
|
||||||
|
|
||||||
|
Note that these will be rendered as a list:
|
||||||
|
|
||||||
|
<img class="w-1/2" src="../../public/assets/docs/RenderMulti.png"/>
|
BIN
public/assets/docs/PredefinedOption.png
Normal file
BIN
public/assets/docs/PredefinedOption.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 43 KiB |
BIN
public/assets/docs/QuestionCombined.png
Normal file
BIN
public/assets/docs/QuestionCombined.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 39 KiB |
BIN
public/assets/docs/QuestionMulti.png
Normal file
BIN
public/assets/docs/QuestionMulti.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 47 KiB |
BIN
public/assets/docs/QuestionPredefinedOptions.png
Normal file
BIN
public/assets/docs/QuestionPredefinedOptions.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
BIN
public/assets/docs/QuestionTextField.png
Normal file
BIN
public/assets/docs/QuestionTextField.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
BIN
public/assets/docs/RenderMulti.png
Normal file
BIN
public/assets/docs/RenderMulti.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
|
@ -946,10 +946,6 @@ video {
|
||||||
margin-right: 0.75rem;
|
margin-right: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mt-16 {
|
|
||||||
margin-top: 4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mr-12 {
|
.mr-12 {
|
||||||
margin-right: 3rem;
|
margin-right: 3rem;
|
||||||
}
|
}
|
||||||
|
|
|
@ -309,8 +309,8 @@ function generateWikipage() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function studioDocs() {
|
function studioDocsFor(source: string, target: string) {
|
||||||
const lines = readFileSync("./Docs/Studio/Introduction.md", "utf8").split("\n")
|
const lines = readFileSync(source, "utf8").split("\n")
|
||||||
|
|
||||||
const sections: string[][] = []
|
const sections: string[][] = []
|
||||||
let currentSection: string[] = []
|
let currentSection: string[] = []
|
||||||
|
@ -325,13 +325,21 @@ function studioDocs() {
|
||||||
}
|
}
|
||||||
sections.push(currentSection)
|
sections.push(currentSection)
|
||||||
writeFileSync(
|
writeFileSync(
|
||||||
"./src/assets/studio_introduction.json",
|
target,
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
sections: sections.map((s) => s.join("\n")).filter((s) => s.length > 0),
|
sections: sections.map((s) => s.join("\n")).filter((s) => s.length > 0),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function studioDocs() {
|
||||||
|
studioDocsFor("./Docs/Studio/Introduction.md", "./src/assets/studio_introduction.json")
|
||||||
|
studioDocsFor(
|
||||||
|
"./Docs/Studio/TagRenderingIntro.md",
|
||||||
|
"./src/assets/studio_tagrenderings_intro.json"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
console.log("Starting documentation generation...")
|
console.log("Starting documentation generation...")
|
||||||
ScriptUtils.fixUtils()
|
ScriptUtils.fixUtils()
|
||||||
studioDocs()
|
studioDocs()
|
||||||
|
|
|
@ -168,7 +168,7 @@
|
||||||
</div>
|
</div>
|
||||||
{#if $highlightedItem !== undefined}
|
{#if $highlightedItem !== undefined}
|
||||||
<FloatOver on:close={() => highlightedItem.setData(undefined)}>
|
<FloatOver on:close={() => highlightedItem.setData(undefined)}>
|
||||||
<div class="mt-16">
|
<div>
|
||||||
<TagRenderingInput path={$highlightedItem.path} {state} schema={$highlightedItem.schema} />
|
<TagRenderingInput path={$highlightedItem.path} {state} schema={$highlightedItem.schema} />
|
||||||
</div>
|
</div>
|
||||||
</FloatOver>
|
</FloatOver>
|
||||||
|
|
|
@ -21,6 +21,7 @@ import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||||
import { LayoutConfigJson } from "../../Models/ThemeConfig/Json/LayoutConfigJson"
|
import { LayoutConfigJson } from "../../Models/ThemeConfig/Json/LayoutConfigJson"
|
||||||
import { PrepareTheme } from "../../Models/ThemeConfig/Conversion/PrepareTheme"
|
import { PrepareTheme } from "../../Models/ThemeConfig/Conversion/PrepareTheme"
|
||||||
import { ConversionContext } from "../../Models/ThemeConfig/Conversion/ConversionContext"
|
import { ConversionContext } from "../../Models/ThemeConfig/Conversion/ConversionContext"
|
||||||
|
import { LocalStorageSource } from "../../Logic/Web/LocalStorageSource"
|
||||||
|
|
||||||
export interface HighlightedTagRendering {
|
export interface HighlightedTagRendering {
|
||||||
path: ReadonlyArray<string | number>
|
path: ReadonlyArray<string | number>
|
||||||
|
@ -31,6 +32,9 @@ export abstract class EditJsonState<T> {
|
||||||
public readonly schema: ConfigMeta[]
|
public readonly schema: ConfigMeta[]
|
||||||
public readonly category: "layers" | "themes"
|
public readonly category: "layers" | "themes"
|
||||||
public readonly server: StudioServer
|
public readonly server: StudioServer
|
||||||
|
public readonly showIntro: UIEventSource<"no" | "intro" | "tagrenderings"> = <any>(
|
||||||
|
LocalStorageSource.Get("studio-show-intro", "intro")
|
||||||
|
)
|
||||||
|
|
||||||
public readonly expertMode: UIEventSource<boolean>
|
public readonly expertMode: UIEventSource<boolean>
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,10 @@ import { TrashIcon } from "@rgossiaux/svelte-heroicons/outline";
|
||||||
import questionableTagRenderingSchemaRaw from "../../assets/schemas/questionabletagrenderingconfigmeta.json";
|
import questionableTagRenderingSchemaRaw from "../../assets/schemas/questionabletagrenderingconfigmeta.json";
|
||||||
import SchemaBasedField from "./SchemaBasedField.svelte";
|
import SchemaBasedField from "./SchemaBasedField.svelte";
|
||||||
import Region from "./Region.svelte";
|
import Region from "./Region.svelte";
|
||||||
import exp from "constants";
|
import NextButton from "../Base/NextButton.svelte";
|
||||||
|
import { QuestionMarkCircleIcon } from "@rgossiaux/svelte-heroicons/solid";
|
||||||
|
import { LocalStorageSource } from "../../Logic/Web/LocalStorageSource";
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
export let state: EditLayerState;
|
export let state: EditLayerState;
|
||||||
export let schema: ConfigMeta;
|
export let schema: ConfigMeta;
|
||||||
|
@ -25,7 +28,13 @@ export let path: (string | number)[];
|
||||||
let expertMode = state.expertMode;
|
let expertMode = state.expertMode;
|
||||||
const store = state.getStoreFor(path);
|
const store = state.getStoreFor(path);
|
||||||
let value = store.data;
|
let value = store.data;
|
||||||
|
let hasSeenIntro = UIEventSource.asBoolean(LocalStorageSource.Get("studio-seen-tagrendering-tutorial", "false"))
|
||||||
|
onMount(() => {
|
||||||
|
if(!hasSeenIntro.data){
|
||||||
|
state.showIntro.setData("tagrenderings")
|
||||||
|
hasSeenIntro.setData(true)
|
||||||
|
}
|
||||||
|
})
|
||||||
/**
|
/**
|
||||||
* Allows the theme builder to create 'writable' themes.
|
* Allows the theme builder to create 'writable' themes.
|
||||||
* Should only be enabled for 'tagrenderings' in the theme, if the source is OSM
|
* Should only be enabled for 'tagrenderings' in the theme, if the source is OSM
|
||||||
|
@ -99,7 +108,7 @@ const ignored = new Set(["labels", "description", "classes"]);
|
||||||
|
|
||||||
const freeformSchemaAll = <ConfigMeta[]>questionableTagRenderingSchemaRaw
|
const freeformSchemaAll = <ConfigMeta[]>questionableTagRenderingSchemaRaw
|
||||||
.filter(schema => schema.path.length == 2 && schema.path[0] === "freeform" && ($allowQuestions || schema.path[1] === "key"));
|
.filter(schema => schema.path.length == 2 && schema.path[0] === "freeform" && ($allowQuestions || schema.path[1] === "key"));
|
||||||
let freeformSchema = $expertMode ? freeformSchemaAll : freeformSchemaAll.filter(schema => schema.hints?.group !== "expert")
|
let freeformSchema = $expertMode ? freeformSchemaAll : freeformSchemaAll.filter(schema => schema.hints?.group !== "expert");
|
||||||
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("."));
|
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 });
|
console.log({ state });
|
||||||
|
|
||||||
|
@ -112,7 +121,7 @@ console.log({ state });
|
||||||
<slot name="upper-right" />
|
<slot name="upper-right" />
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex flex-col w-full p-1 gap-y-1">
|
<div class="flex flex-col w-full p-1 gap-y-1 pr-12">
|
||||||
<div class="flex justify-end">
|
<div class="flex justify-end">
|
||||||
<slot name="upper-right" />
|
<slot name="upper-right" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -157,5 +166,8 @@ console.log({ state });
|
||||||
{#each missing as field}
|
{#each missing as field}
|
||||||
<SchemaBasedField {state} path={[...path,field]} schema={topLevelItems[field]} />
|
<SchemaBasedField {state} path={[...path,field]} schema={topLevelItems[field]} />
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
|
<NextButton clss="small mt-8" on:click={() => state.showIntro.setData("tagrenderings")}><QuestionMarkCircleIcon class="h-6 w-6"/> Show the introduction again</NextButton>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -21,12 +21,13 @@
|
||||||
import FloatOver from "./Base/FloatOver.svelte";
|
import FloatOver from "./Base/FloatOver.svelte";
|
||||||
import Walkthrough from "./Walkthrough/Walkthrough.svelte";
|
import Walkthrough from "./Walkthrough/Walkthrough.svelte";
|
||||||
import * as intro from "../assets/studio_introduction.json";
|
import * as intro from "../assets/studio_introduction.json";
|
||||||
|
import * as intro_tagrenderings from "../assets/studio_tagrenderings_intro.json";
|
||||||
|
|
||||||
import { QuestionMarkCircleIcon } from "@babeard/svelte-heroicons/mini";
|
import { QuestionMarkCircleIcon } from "@babeard/svelte-heroicons/mini";
|
||||||
import type { ConfigMeta } from "./Studio/configMeta";
|
import type { ConfigMeta } from "./Studio/configMeta";
|
||||||
import EditTheme from "./Studio/EditTheme.svelte";
|
import EditTheme from "./Studio/EditTheme.svelte";
|
||||||
import * as meta from "../../package.json";
|
import * as meta from "../../package.json";
|
||||||
import Checkbox from "./Base/Checkbox.svelte";
|
import Checkbox from "./Base/Checkbox.svelte";
|
||||||
import exp from "constants";
|
|
||||||
|
|
||||||
export let studioUrl = window.location.hostname === "127.0.0.2" ? "http://127.0.0.1:1235" : "https://studio.mapcomplete.org";
|
export let studioUrl = window.location.hostname === "127.0.0.2" ? "http://127.0.0.1:1235" : "https://studio.mapcomplete.org";
|
||||||
|
|
||||||
|
@ -61,13 +62,13 @@
|
||||||
|
|
||||||
const layerSchema: ConfigMeta[] = <any>layerSchemaRaw;
|
const layerSchema: ConfigMeta[] = <any>layerSchemaRaw;
|
||||||
let editLayerState = new EditLayerState(layerSchema, studio, osmConnection, { expertMode });
|
let editLayerState = new EditLayerState(layerSchema, studio, osmConnection, { expertMode });
|
||||||
|
let showIntro = editLayerState.showIntro
|
||||||
|
|
||||||
const layoutSchema: ConfigMeta[] = <any>layoutSchemaRaw;
|
const layoutSchema: ConfigMeta[] = <any>layoutSchemaRaw;
|
||||||
let editThemeState = new EditThemeState(layoutSchema, studio, { expertMode });
|
let editThemeState = new EditThemeState(layoutSchema, studio, { expertMode });
|
||||||
|
|
||||||
let layerId = editLayerState.configuration.map(layerConfig => layerConfig.id);
|
let layerId = editLayerState.configuration.map(layerConfig => layerConfig.id);
|
||||||
|
|
||||||
let showIntro = UIEventSource.asBoolean(LocalStorageSource.Get("studio-show-intro", "true"));
|
|
||||||
const version = meta.version;
|
const version = meta.version;
|
||||||
|
|
||||||
async function editLayer(event: Event) {
|
async function editLayer(event: Event) {
|
||||||
|
@ -162,7 +163,7 @@
|
||||||
<NextButton on:click={() => {editThemeState.configuration.setData({}); state = "editing_theme"}}>
|
<NextButton on:click={() => {editThemeState.configuration.setData({}); state = "editing_theme"}}>
|
||||||
Create a new theme
|
Create a new theme
|
||||||
</NextButton>
|
</NextButton>
|
||||||
<NextButton clss="small" on:click={() => {showIntro.setData(true)} }>
|
<NextButton clss="small" on:click={() => {showIntro.setData("intro")} }>
|
||||||
<QuestionMarkCircleIcon class="w-6 h-6" />
|
<QuestionMarkCircleIcon class="w-6 h-6" />
|
||||||
Show the introduction again
|
Show the introduction again
|
||||||
</NextButton>
|
</NextButton>
|
||||||
|
@ -222,10 +223,10 @@
|
||||||
</If>
|
</If>
|
||||||
|
|
||||||
|
|
||||||
{#if $showIntro}
|
{#if {intro, tagrenderings: intro_tagrenderings}[$showIntro]?.sections}
|
||||||
<FloatOver on:close={() => {showIntro.setData(false)}}>
|
<FloatOver on:close={() => {showIntro.setData("no")}}>
|
||||||
<div class="flex p-4 h-full pr-12">
|
<div class="flex p-4 h-full pr-12">
|
||||||
<Walkthrough pages={intro.sections} on:done={() => {showIntro.setData(false)}} />
|
<Walkthrough pages={{intro, tagrenderings: intro_tagrenderings}[$showIntro]?.sections} on:done={() => {showIntro.setData("no")}} />
|
||||||
</div>
|
</div>
|
||||||
</FloatOver>
|
</FloatOver>
|
||||||
|
|
||||||
|
|
1
src/assets/studio_tagrenderings_intro.json
Normal file
1
src/assets/studio_tagrenderings_intro.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"sections":["# How to work with TagRenderings\n\nThe information box shows various attributes of the selected feature in a human friendly way.\n\nThis is done by a **tagRendering** which converts attributes into text.\n\nThis can be done by using **predefined options** (mappings) or with a **render**-string\n","# Predefined options\n\nA predefined option states that, `if` a certain tag is present, `then` a certain text should be shown.\n\nFor example, a playground may be lit or not.\nIn OpenStreetMap, this is encoded with the tag `lit=yes` or `lit=no`. We might want to show `This playground is lit at night` and `This playground is not lit at night` to users of MapComplete.\n\nThis is what this will look like in the interface:\n\n<img src=\"./assets/docs/PredefinedOption.png\"/>\n","# Substituting attributes\n\nIf none of the predefined options match, the string given in the `render`-field is used (under the question _\"What text should be rendered?\"_).\n\nA special property about all shown texts is that, **if the name of a key appears between braces, this will be replaced by the corresponding value**.\n\nFor example, if the object has tags `min_age=3` and the text to display is `Accessible to kids older than {min_age} years`, then this will be displayed to the user as **Accessible to kids older than 3 years**\n\nNote that this also works withing predifined options\n","# Special values\n\nSpecial components can be summoned by calling them. For example, the relevant wikipedia will be displayed by entering the text `{wikipedia()}`. A table with opening hours is displayed with `{opening_hours()}`. For a full reference, [see the documentation](../SpecialRenderings.md).\n","# Requesting data with predefined options\n\nThese renderings can be turned into a way to contribute data easily. If a **question** is provided, then these renderings will be asked if unknown or gain the pencil to make changes. \n\nA predefined option will show up as an option that can be picked.\n<img src=\"./assets/docs/QuestionPredefinedOptions.png\"/>\n","# Requesting data with an input field\n\nIt is also possible to have a text field. For this, the **key** to write into must be given (_What is the name of the attribute that should be written to?_), in this case `max_age`.\n<img src=\"./assets/docs/QuestionTextField.png\"/>\n","# Combining predefined options and freeform text\n\nA text field and predefined options can be combined. The contributor can then choose between a predefined option or filling out something.\n<img src=\"./assets/docs/QuestionCombined.png\"/>\n","# Selecting multiple values\n\nOne can set a question to allow multiple answers. This works with predefined options or a freeform text field.\n\n<img src=\"./assets/docs/QuestionMulti.png\"/>\n\nNote that these will be rendered as a list:\n\n<img src=\"./assets/docs/RenderMulti.png\"/>\n"]}
|
Loading…
Reference in a new issue