forked from MapComplete/MapComplete
		
	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…
	
	Add table
		Add a link
		
	
		Reference in a new issue