forked from MapComplete/MapComplete
Merge branch 'develop' into RobinLinde-patch-2
This commit is contained in:
commit
5fbcd28825
384 changed files with 34583 additions and 44834 deletions
17
src/UI/Base/Markdown.svelte
Normal file
17
src/UI/Base/Markdown.svelte
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<script lang="ts">
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { marked } from "marked"
|
||||
export let src: string
|
||||
export let srcWritable: UIEventSource<string> = undefined
|
||||
srcWritable?.addCallbackAndRunD(t => {
|
||||
src = t
|
||||
})
|
||||
if(src !== undefined && typeof src !== "string") {
|
||||
console.trace("Got a non-string object in Markdown", src)
|
||||
throw "Markdown.svelte got a non-string object"
|
||||
}
|
||||
</script>
|
||||
{#if src?.length > 0}
|
||||
{@html marked.parse(src)}
|
||||
{/if}
|
||||
|
||||
|
|
@ -1,73 +1,21 @@
|
|||
import Combine from "./Combine"
|
||||
import BaseUIElement from "../BaseUIElement"
|
||||
import { Translation } from "../i18n/Translation"
|
||||
import { FixedUiElement } from "./FixedUiElement"
|
||||
import Title from "./Title"
|
||||
import List from "./List"
|
||||
import Link from "./Link"
|
||||
import { marked } from "marked"
|
||||
import { parse as parse_html } from "node-html-parser"
|
||||
import {default as turndown} from "turndown"
|
||||
import { Utils } from "../../Utils"
|
||||
|
||||
export default class TableOfContents extends Combine {
|
||||
private readonly titles: Title[]
|
||||
export default class TableOfContents {
|
||||
|
||||
constructor(
|
||||
elements: Combine | Title[],
|
||||
options?: {
|
||||
noTopLevel: false | boolean
|
||||
maxDepth?: number
|
||||
}
|
||||
) {
|
||||
let titles: Title[]
|
||||
if (elements instanceof Combine) {
|
||||
titles = TableOfContents.getTitles(elements.getElements()) ?? []
|
||||
} else {
|
||||
titles = elements ?? []
|
||||
}
|
||||
|
||||
let els: { level: number; content: BaseUIElement }[] = []
|
||||
for (const title of titles) {
|
||||
let content: BaseUIElement
|
||||
if (title.title instanceof Translation) {
|
||||
content = title.title.Clone()
|
||||
} else if (title.title instanceof FixedUiElement) {
|
||||
content = new FixedUiElement(title.title.content)
|
||||
} else if (Utils.runningFromConsole) {
|
||||
content = new FixedUiElement(title.AsMarkdown())
|
||||
} else if (title["title"] !== undefined) {
|
||||
content = new FixedUiElement(title.title.ConstructElement().textContent)
|
||||
} else {
|
||||
console.log("Not generating a title for ", title)
|
||||
continue
|
||||
}
|
||||
|
||||
const vis = new Link(content, "#" + title.id)
|
||||
|
||||
els.push({ level: title.level, content: vis })
|
||||
}
|
||||
const minLevel = Math.min(...els.map((e) => e.level))
|
||||
if (options?.noTopLevel) {
|
||||
els = els.filter((e) => e.level !== minLevel)
|
||||
}
|
||||
|
||||
if (options?.maxDepth) {
|
||||
els = els.filter((e) => e.level <= options.maxDepth + minLevel)
|
||||
}
|
||||
|
||||
super(TableOfContents.mergeLevel(els).map((el) => el.SetClass("mt-2")))
|
||||
this.SetClass("flex flex-col")
|
||||
this.titles = titles
|
||||
}
|
||||
|
||||
private static getTitles(elements: BaseUIElement[]): Title[] {
|
||||
const titles = []
|
||||
for (const uiElement of elements) {
|
||||
if (uiElement instanceof Combine) {
|
||||
titles.push(...TableOfContents.getTitles(uiElement.getElements()))
|
||||
} else if (uiElement instanceof Title) {
|
||||
titles.push(uiElement)
|
||||
}
|
||||
}
|
||||
return titles
|
||||
private static asLinkableId(text: string): string {
|
||||
return text
|
||||
?.replace(/ /g, "-")
|
||||
?.replace(/[?#.;:/]/, "")
|
||||
?.toLowerCase() ?? ""
|
||||
}
|
||||
|
||||
private static mergeLevel(
|
||||
|
|
@ -88,7 +36,7 @@ export default class TableOfContents extends Combine {
|
|||
if (running.length !== undefined) {
|
||||
result.push({
|
||||
content: new List(running),
|
||||
level: maxLevel - 1,
|
||||
level: maxLevel - 1
|
||||
})
|
||||
running = []
|
||||
}
|
||||
|
|
@ -97,24 +45,81 @@ export default class TableOfContents extends Combine {
|
|||
if (running.length !== undefined) {
|
||||
result.push({
|
||||
content: new List(running),
|
||||
level: maxLevel - 1,
|
||||
level: maxLevel - 1
|
||||
})
|
||||
}
|
||||
|
||||
return TableOfContents.mergeLevel(result)
|
||||
}
|
||||
|
||||
AsMarkdown(): string {
|
||||
const depthIcons = ["1.", " -", " +", " *"]
|
||||
const lines = ["## Table of contents\n"]
|
||||
const minLevel = Math.min(...this.titles.map((t) => t.level))
|
||||
for (const title of this.titles) {
|
||||
const prefix = depthIcons[title.level - minLevel] ?? " ~"
|
||||
const text = title.title.AsMarkdown().replace("\n", "")
|
||||
const link = title.id
|
||||
lines.push(prefix + " [" + text + "](#" + link + ")")
|
||||
public static insertTocIntoMd(md: string): string {
|
||||
const htmlSource = <string>marked.parse(md)
|
||||
const el = parse_html(htmlSource)
|
||||
const structure = TableOfContents.generateStructure(<any>el)
|
||||
let firstTitle = structure[1]
|
||||
let minDepth = undefined
|
||||
do {
|
||||
minDepth = Math.min(...structure.map(s => s.depth))
|
||||
const minDepthCount = structure.filter(s => s.depth === minDepth)
|
||||
if (minDepthCount.length > 1) {
|
||||
break
|
||||
}
|
||||
// Erase a single top level heading
|
||||
structure.splice(structure.findIndex(s => s.depth === minDepth), 1)
|
||||
} while (structure.length > 0)
|
||||
|
||||
if (structure.length <= 1) {
|
||||
return md
|
||||
}
|
||||
const separators = {
|
||||
1: " -",
|
||||
2: " +",
|
||||
3: " *"
|
||||
}
|
||||
|
||||
return lines.join("\n") + "\n\n"
|
||||
let toc = ""
|
||||
let topLevelCount = 0
|
||||
for (const el of structure) {
|
||||
const depthDiff = el.depth - minDepth
|
||||
let link = `[${el.title}](#${TableOfContents.asLinkableId(el.title)})`
|
||||
if (depthDiff === 0) {
|
||||
topLevelCount++
|
||||
toc += `${topLevelCount}. ${link}\n`
|
||||
} else if (depthDiff <= 3) {
|
||||
toc += `${separators[depthDiff]} ${link}\n`
|
||||
}
|
||||
}
|
||||
|
||||
const heading = Utils.Times(() => "#", firstTitle.depth)
|
||||
toc = heading +" Table of contents\n\n"+toc
|
||||
|
||||
const original = el.outerHTML
|
||||
const firstTitleIndex = original.indexOf(firstTitle.el.outerHTML)
|
||||
const tocHtml = (<string>marked.parse(toc))
|
||||
const withToc = original.substring(0, firstTitleIndex) + tocHtml + original.substring(firstTitleIndex)
|
||||
|
||||
const htmlToMd = new turndown()
|
||||
return htmlToMd.turndown(withToc)
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static generateStructure(html: Element): { depth: number, title: string, el: Element }[] {
|
||||
if (html === undefined) {
|
||||
return []
|
||||
}
|
||||
return [].concat(...Array.from(html.childNodes ?? []).map(
|
||||
child => {
|
||||
const tag: string = child["tagName"]?.toLowerCase()
|
||||
if (!tag) {
|
||||
return []
|
||||
}
|
||||
if (tag.match(/h[0-9]/)) {
|
||||
const depth = Number(tag.substring(1))
|
||||
return [{ depth, title: child.textContent, el: child }]
|
||||
}
|
||||
return TableOfContents.generateStructure(<Element>child)
|
||||
}
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@
|
|||
<Tr t={t.conflicting.intro} />
|
||||
{/if}
|
||||
{#if $different.length > 0}
|
||||
{#each $different as key}
|
||||
{#each $different as key (key)}
|
||||
<div class="mx-2 rounded-2xl">
|
||||
<ComparisonAction
|
||||
{key}
|
||||
|
|
@ -136,7 +136,7 @@
|
|||
|
||||
{#if $missing.length > 0}
|
||||
{#if currentStep === "init"}
|
||||
{#each $missing as key}
|
||||
{#each $missing as key (key)}
|
||||
<div class:glowing-shadow={applyAllHovered} class="mx-2 rounded-2xl">
|
||||
<ComparisonAction
|
||||
{key}
|
||||
|
|
@ -174,7 +174,7 @@
|
|||
{#if readonly}
|
||||
<div class="w-full overflow-x-auto">
|
||||
<div class="flex h-32 w-max gap-x-2">
|
||||
{#each $unknownImages as image}
|
||||
{#each $unknownImages as image (image)}
|
||||
<AttributedImage
|
||||
imgClass="h-32 w-max shrink-0"
|
||||
image={{ url: image }}
|
||||
|
|
@ -184,7 +184,7 @@
|
|||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
{#each $unknownImages as image}
|
||||
{#each $unknownImages as image (image)}
|
||||
<LinkableImage
|
||||
{tags}
|
||||
{state}
|
||||
|
|
|
|||
|
|
@ -1,46 +0,0 @@
|
|||
import { InputElement } from "./InputElement"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import Translations from "../i18n/Translations"
|
||||
import BaseUIElement from "../BaseUIElement"
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export class FixedInputElement<T> extends InputElement<T> {
|
||||
private readonly value: UIEventSource<T>
|
||||
private readonly _comparator: (t0: T, t1: T) => boolean
|
||||
|
||||
private readonly _el: HTMLElement
|
||||
|
||||
constructor(
|
||||
rendering: BaseUIElement | string,
|
||||
value: T | UIEventSource<T>,
|
||||
comparator: (t0: T, t1: T) => boolean = undefined
|
||||
) {
|
||||
super()
|
||||
this._comparator = comparator ?? ((t0, t1) => t0 == t1)
|
||||
if (value instanceof UIEventSource) {
|
||||
this.value = value
|
||||
} else {
|
||||
this.value = new UIEventSource<T>(value)
|
||||
}
|
||||
|
||||
this._el = document.createElement("span")
|
||||
const e = Translations.W(rendering)?.ConstructElement()
|
||||
if (e) {
|
||||
this._el.appendChild(e)
|
||||
}
|
||||
}
|
||||
|
||||
GetValue(): UIEventSource<T> {
|
||||
return this.value
|
||||
}
|
||||
|
||||
IsValid(t: T): boolean {
|
||||
return this._comparator(t, this.value.data)
|
||||
}
|
||||
|
||||
protected InnerConstructElement(): HTMLElement {
|
||||
return this._el
|
||||
}
|
||||
}
|
||||
|
|
@ -1,161 +0,0 @@
|
|||
import { InputElement } from "./InputElement"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { Utils } from "../../Utils"
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export class RadioButton<T> extends InputElement<T> {
|
||||
private static _nextId = 0
|
||||
|
||||
private readonly value: UIEventSource<T>
|
||||
private _elements: InputElement<T>[]
|
||||
private _selectFirstAsDefault: boolean
|
||||
private _dontStyle: boolean
|
||||
|
||||
constructor(
|
||||
elements: InputElement<T>[],
|
||||
options?: {
|
||||
selectFirstAsDefault?: true | boolean
|
||||
dontStyle?: boolean
|
||||
value?: UIEventSource<T>
|
||||
}
|
||||
) {
|
||||
super()
|
||||
options = options ?? {}
|
||||
this._selectFirstAsDefault = options.selectFirstAsDefault ?? true
|
||||
this._elements = Utils.NoNull(elements)
|
||||
this.value = options?.value ?? new UIEventSource<T>(undefined)
|
||||
this._dontStyle = options.dontStyle ?? false
|
||||
}
|
||||
|
||||
IsValid(t: T): boolean {
|
||||
for (const inputElement of this._elements) {
|
||||
if (inputElement.IsValid(t)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
GetValue(): UIEventSource<T> {
|
||||
return this.value
|
||||
}
|
||||
|
||||
protected InnerConstructElement(): HTMLElement {
|
||||
const elements = this._elements
|
||||
const selectFirstAsDefault = this._selectFirstAsDefault
|
||||
|
||||
const selectedElementIndex: UIEventSource<number> = new UIEventSource<number>(null)
|
||||
|
||||
const value = UIEventSource.flatten(
|
||||
selectedElementIndex.map((selectedIndex) => {
|
||||
if (selectedIndex !== undefined && selectedIndex !== null) {
|
||||
return elements[selectedIndex].GetValue()
|
||||
}
|
||||
}),
|
||||
elements.map((e) => e?.GetValue())
|
||||
)
|
||||
value.syncWith(this.value)
|
||||
|
||||
if (selectFirstAsDefault) {
|
||||
value.addCallbackAndRun((selected) => {
|
||||
if (selected === undefined) {
|
||||
for (const element of elements) {
|
||||
const v = element.GetValue().data
|
||||
if (v !== undefined) {
|
||||
value.setData(v)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
// If an element is clicked, the radio button corresponding with it should be selected as well
|
||||
elements[i]?.onClick(() => {
|
||||
selectedElementIndex.setData(i)
|
||||
})
|
||||
|
||||
elements[i].GetValue().addCallback(() => {
|
||||
selectedElementIndex.setData(i)
|
||||
})
|
||||
}
|
||||
|
||||
const groupId = "radiogroup" + RadioButton._nextId
|
||||
RadioButton._nextId++
|
||||
|
||||
const form = document.createElement("form")
|
||||
|
||||
const inputs = []
|
||||
const wrappers: HTMLElement[] = []
|
||||
|
||||
for (let i1 = 0; i1 < elements.length; i1++) {
|
||||
let element = elements[i1]
|
||||
const labelHtml = element.ConstructElement()
|
||||
if (labelHtml === undefined) {
|
||||
continue
|
||||
}
|
||||
|
||||
const input = document.createElement("input")
|
||||
input.id = "radio" + groupId + "-" + i1
|
||||
input.name = groupId
|
||||
input.type = "radio"
|
||||
input.classList.add("cursor-pointer", "p-1", "mr-2")
|
||||
|
||||
if (!this._dontStyle) {
|
||||
input.classList.add("p-1", "ml-2", "pl-2", "pr-0", "m-3", "mr-0")
|
||||
}
|
||||
input.onchange = () => {
|
||||
if (input.checked) {
|
||||
selectedElementIndex.setData(i1)
|
||||
}
|
||||
}
|
||||
|
||||
inputs.push(input)
|
||||
|
||||
const label = document.createElement("label")
|
||||
label.appendChild(labelHtml)
|
||||
label.htmlFor = input.id
|
||||
label.classList.add("flex", "w-full", "cursor-pointer")
|
||||
|
||||
if (!this._dontStyle) {
|
||||
labelHtml.classList.add("p-2")
|
||||
}
|
||||
|
||||
const block = document.createElement("div")
|
||||
block.appendChild(input)
|
||||
block.appendChild(label)
|
||||
block.classList.add("flex", "w-full")
|
||||
if (!this._dontStyle) {
|
||||
block.classList.add("m-1", "border", "border-gray-400")
|
||||
}
|
||||
block.style.borderRadius = "1.5rem"
|
||||
wrappers.push(block)
|
||||
|
||||
form.appendChild(block)
|
||||
}
|
||||
|
||||
value.addCallbackAndRun((selected: T) => {
|
||||
let somethingChecked = false
|
||||
for (let i = 0; i < inputs.length; i++) {
|
||||
let input = inputs[i]
|
||||
input.checked = !somethingChecked && elements[i].IsValid(selected)
|
||||
somethingChecked = somethingChecked || input.checked
|
||||
|
||||
if (input.checked) {
|
||||
wrappers[i].classList.remove("border-gray-400")
|
||||
wrappers[i].classList.add("border-attention")
|
||||
} else {
|
||||
wrappers[i].classList.add("border-gray-400")
|
||||
wrappers[i].classList.remove("border-attention")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
this.SetClass("flex flex-col")
|
||||
|
||||
return form
|
||||
}
|
||||
}
|
||||
|
|
@ -5,8 +5,8 @@
|
|||
import { UIEventSource } from "../../../Logic/UIEventSource"
|
||||
import BasicTagInput from "../../Studio/TagInput/BasicTagInput.svelte"
|
||||
import { TagUtils } from "../../../Logic/Tags/TagUtils"
|
||||
import nmd from "nano-markdown"
|
||||
import FromHtml from "../../Base/FromHtml.svelte"
|
||||
import Markdown from "../../Base/Markdown.svelte"
|
||||
export let value: UIEventSource<undefined | string>
|
||||
export let args: string[] = []
|
||||
let uploadableOnly: boolean = args[0] === "uploadableOnly"
|
||||
|
|
@ -34,6 +34,6 @@
|
|||
{#if $dropdownFocussed}
|
||||
<div class="m-2 border border-dashed border-black p-2">
|
||||
<b>{documentation.name}</b>
|
||||
<FromHtml src={nmd(documentation.docs)} />
|
||||
<Markdown src={documentation.docs} />
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -91,11 +91,6 @@
|
|||
return
|
||||
}
|
||||
|
||||
if (unit !== undefined && isNaN(Number(v))) {
|
||||
value.setData(undefined)
|
||||
return
|
||||
}
|
||||
|
||||
feedback?.setData(undefined)
|
||||
if (selectedUnit.data) {
|
||||
value.setData(unit.toOsm(v, selectedUnit.data))
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import TagValidator from "./Validators/TagValidator"
|
|||
import IdValidator from "./Validators/IdValidator"
|
||||
import SlopeValidator from "./Validators/SlopeValidator"
|
||||
import VeloparkValidator from "./Validators/VeloparkValidator"
|
||||
import CurrencyValidator from "./Validators/CurrencyValidator"
|
||||
|
||||
export type ValidatorType = (typeof Validators.availableTypes)[number]
|
||||
|
||||
|
|
@ -60,6 +61,7 @@ export default class Validators {
|
|||
"id",
|
||||
"slope",
|
||||
"velopark",
|
||||
"currency"
|
||||
] as const
|
||||
|
||||
public static readonly AllValidators: ReadonlyArray<Validator> = [
|
||||
|
|
@ -89,6 +91,7 @@ export default class Validators {
|
|||
new IdValidator(),
|
||||
new SlopeValidator(),
|
||||
new VeloparkValidator(),
|
||||
new CurrencyValidator()
|
||||
]
|
||||
|
||||
private static _byType = Validators._byTypeConstructor()
|
||||
|
|
|
|||
73
src/UI/InputElement/Validators/CurrencyValidator.ts
Normal file
73
src/UI/InputElement/Validators/CurrencyValidator.ts
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
import { Validator } from "../Validator"
|
||||
import { Utils } from "../../../Utils"
|
||||
|
||||
export default class CurrencyValidator extends Validator {
|
||||
private readonly segmenter: Intl.Segmenter
|
||||
private readonly symbolToCurrencyMapping: Map<string, string>
|
||||
private readonly supportedCurrencies: Set<string>
|
||||
|
||||
constructor() {
|
||||
super("currency", "Validates monetary amounts")
|
||||
if (Intl.Segmenter === undefined || Utils.runningFromConsole) {
|
||||
// Librewolf doesn't support this
|
||||
return
|
||||
}
|
||||
let locale = "en-US"
|
||||
if(!Utils.runningFromConsole){
|
||||
locale??= navigator.language
|
||||
}
|
||||
this.segmenter = new Intl.Segmenter(locale, {
|
||||
granularity: "word"
|
||||
})
|
||||
|
||||
const mapping: Map<string, string> = new Map<string, string>()
|
||||
const supportedCurrencies: Set<string> = new Set(Intl.supportedValuesOf("currency"))
|
||||
this.supportedCurrencies = supportedCurrencies
|
||||
for (const currency of supportedCurrencies) {
|
||||
const symbol = (0).toLocaleString(
|
||||
locale,
|
||||
{
|
||||
style: "currency",
|
||||
currency: currency,
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0
|
||||
}
|
||||
).replace(/\d/g, "").trim()
|
||||
|
||||
mapping.set(symbol.toLowerCase(), currency)
|
||||
}
|
||||
this.symbolToCurrencyMapping = mapping
|
||||
}
|
||||
|
||||
reformat(s: string): string {
|
||||
if (!this.segmenter) {
|
||||
return s
|
||||
}
|
||||
|
||||
const parts = Array.from(this.segmenter.segment(s)).map(i => i.segment).filter(part => part.trim().length > 0)
|
||||
if(parts.length !== 2){
|
||||
return s
|
||||
}
|
||||
const mapping = this.symbolToCurrencyMapping
|
||||
let currency: string = undefined
|
||||
let amount = undefined
|
||||
for (const part of parts) {
|
||||
const lc = part.toLowerCase()
|
||||
if (this.supportedCurrencies.has(part.toUpperCase())) {
|
||||
currency = part.toUpperCase()
|
||||
continue
|
||||
}
|
||||
|
||||
if (mapping.has(lc)) {
|
||||
currency = mapping.get(lc)
|
||||
continue
|
||||
}
|
||||
amount = part
|
||||
}
|
||||
if(amount === undefined || currency === undefined){
|
||||
return s
|
||||
}
|
||||
|
||||
return amount+" "+currency
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ import UrlValidator from "./UrlValidator"
|
|||
|
||||
export default class VeloparkValidator extends UrlValidator {
|
||||
constructor() {
|
||||
super("velopark", "A custom element to allow copy-pasting velopark-pages")
|
||||
super("velopark", "A special URL-validator that checks the domain name and rewrites to the correct velopark format.")
|
||||
}
|
||||
|
||||
getFeedback(s: string): Translation {
|
||||
|
|
|
|||
|
|
@ -38,11 +38,13 @@
|
|||
{#if !allCalculatedTags.has(key)}
|
||||
<tr>
|
||||
<td>{key}</td>
|
||||
<td>
|
||||
<td style="width: 75%">
|
||||
{#if $tags[key] === undefined}
|
||||
<i>undefined</i>
|
||||
{:else if $tags[key] === ""}
|
||||
<i>Empty string</i>
|
||||
{:else if typeof $tags[key] === "object"}
|
||||
<div class="literal-code" >{JSON.stringify($tags[key])}</div>
|
||||
{:else}
|
||||
{$tags[key]}
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,8 @@
|
|||
import { placeholder } from "../../../Utils/placeholder"
|
||||
import { TrashIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
import { Tag } from "../../../Logic/Tags/Tag"
|
||||
import { get, writable } from "svelte/store"
|
||||
import { get } from "svelte/store"
|
||||
import Markdown from "../../Base/Markdown.svelte"
|
||||
|
||||
export let config: TagRenderingConfig
|
||||
export let tags: UIEventSource<Record<string, string>>
|
||||
|
|
@ -68,13 +69,17 @@
|
|||
/**
|
||||
* Prepares and fills the checkedMappings
|
||||
*/
|
||||
function initialize(tgs: Record<string, string>, confg: TagRenderingConfig) {
|
||||
function initialize(tgs: Record<string, string>, confg: TagRenderingConfig): void {
|
||||
mappings = confg.mappings?.filter((m) => {
|
||||
if (typeof m.hideInAnswer === "boolean") {
|
||||
return !m.hideInAnswer
|
||||
}
|
||||
return !m.hideInAnswer.matchesProperties(tgs)
|
||||
})
|
||||
selectedMapping = mappings?.findIndex(mapping => mapping.if.matchesProperties(tgs) || mapping.alsoShowIf?.matchesProperties(tgs))
|
||||
if(selectedMapping < 0){
|
||||
selectedMapping = undefined
|
||||
}
|
||||
// We received a new config -> reinit
|
||||
unit = layer?.units?.find((unit) => unit.appliesToKeys.has(config.freeform?.key))
|
||||
|
||||
|
|
@ -85,7 +90,7 @@
|
|||
checkedMappings?.length < confg.mappings.length + (confg.freeform ? 1 : 0))
|
||||
) {
|
||||
const seenFreeforms = []
|
||||
// Initial setup of the mappings
|
||||
// Initial setup of the mappings; detect checked mappings
|
||||
checkedMappings = [
|
||||
...confg.mappings.map((mapping) => {
|
||||
if (mapping.hideInAnswer === true) {
|
||||
|
|
@ -97,7 +102,7 @@
|
|||
seenFreeforms.push(newProps[confg.freeform.key])
|
||||
}
|
||||
return matches
|
||||
}),
|
||||
})
|
||||
]
|
||||
|
||||
if (tgs !== undefined && confg.freeform) {
|
||||
|
|
@ -128,6 +133,8 @@
|
|||
freeformInput.set(undefined)
|
||||
}
|
||||
feedback.setData(undefined)
|
||||
|
||||
|
||||
}
|
||||
|
||||
$: {
|
||||
|
|
@ -171,6 +178,9 @@
|
|||
checkedMappings,
|
||||
tags.data
|
||||
)
|
||||
if(state.featureSwitches.featureSwitchIsDebugging.data){
|
||||
console.log("Constructing change spec from", {freeform: $freeformInput, selectedMapping, checkedMappings, currentTags: tags.data}, " --> ", selectedTags)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Could not calculate changeSpecification:", e)
|
||||
selectedTags = undefined
|
||||
|
|
@ -203,7 +213,7 @@
|
|||
dispatch("saved", { config, applied: selectedTags })
|
||||
const change = new ChangeTagAction(tags.data.id, selectedTags, tags.data, {
|
||||
theme: tags.data["_orig_theme"] ?? state.layout.id,
|
||||
changeType: "answer",
|
||||
changeType: "answer"
|
||||
})
|
||||
freeformInput.set(undefined)
|
||||
selectedMapping = undefined
|
||||
|
|
@ -255,15 +265,19 @@
|
|||
</div>
|
||||
|
||||
{#if config.questionhint}
|
||||
<div class="max-h-60 overflow-y-auto">
|
||||
<SpecialTranslation
|
||||
t={config.questionhint}
|
||||
{tags}
|
||||
{state}
|
||||
{layer}
|
||||
feature={selectedElement}
|
||||
/>
|
||||
</div>
|
||||
{#if config.questionHintIsMd}
|
||||
<Markdown srcWritable={ config.questionhint.current} />
|
||||
{:else}
|
||||
<div class="max-h-60 overflow-y-auto">
|
||||
<SpecialTranslation
|
||||
t={config.questionhint}
|
||||
{tags}
|
||||
{state}
|
||||
{layer}
|
||||
feature={selectedElement}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</legend>
|
||||
|
||||
|
|
|
|||
|
|
@ -64,10 +64,14 @@
|
|||
)
|
||||
</script>
|
||||
|
||||
{#if unit.inverted}
|
||||
<div class="bold px-2">/</div>
|
||||
{/if}
|
||||
|
||||
<select bind:value={$selectedUnit}>
|
||||
{#each unit.denominations as denom (denom.canonical)}
|
||||
<option value={denom.canonical}>
|
||||
{#if $isSingle}
|
||||
{#if $isSingle || unit.inverted}
|
||||
<Tr t={denom.humanSingular} />
|
||||
{:else}
|
||||
<Tr t={denom.human.Subs({ quantity: "" })} />
|
||||
|
|
|
|||
|
|
@ -20,12 +20,16 @@
|
|||
import NextButton from "../Base/NextButton.svelte"
|
||||
import BackButton from "../Base/BackButton.svelte"
|
||||
import DeleteButton from "./DeleteButton.svelte"
|
||||
import StudioHashSetter from "./StudioHashSetter"
|
||||
|
||||
const layerSchema: ConfigMeta[] = <any>layerSchemaRaw
|
||||
|
||||
export let state: EditLayerState
|
||||
|
||||
export let backToStudio: () => void
|
||||
|
||||
new StudioHashSetter("layer", state.selectedTab, state.getStoreFor(["id"]))
|
||||
|
||||
let messages = state.messages
|
||||
let hasErrors = messages.mapD(
|
||||
(m: ConversionMessage[]) => m.filter((m) => m.level === "error").length
|
||||
|
|
@ -127,14 +131,14 @@
|
|||
{/each}
|
||||
{:else}
|
||||
<div class="m4 h-full overflow-y-auto">
|
||||
<TabbedGroup>
|
||||
<TabbedGroup tab={state.selectedTab}>
|
||||
<div slot="title0" class="flex">
|
||||
General properties
|
||||
<ErrorIndicatorForRegion firstPaths={firstPathsFor("Basic")} {state} />
|
||||
</div>
|
||||
<div class="flex flex-col mb-8" slot="content0">
|
||||
<Region {state} configs={perRegion["Basic"]} />
|
||||
<DeleteButton {state} {backToStudio} objectType="layer"/>
|
||||
<DeleteButton {state} {backToStudio} objectType="layer" />
|
||||
</div>
|
||||
|
||||
<div slot="title1" class="flex">
|
||||
|
|
@ -185,19 +189,18 @@
|
|||
Below, you'll find the raw configuration file in `.json`-format. This is mostly for
|
||||
debugging purposes, but you can also edit the file directly if you want.
|
||||
</div>
|
||||
<div class="literal-code overflow-y-auto h-full" style="min-height: 75%">
|
||||
<RawEditor {state} />
|
||||
</div>
|
||||
<ShowConversionMessages messages={$messages} />
|
||||
<div class="flex h-full w-full flex-row justify-between overflow-y-auto">
|
||||
<div class="literal-code h-full w-5/6 overflow-y-auto">
|
||||
<RawEditor {state} />
|
||||
</div>
|
||||
<div class="h-full w-1/6">
|
||||
<div>
|
||||
The testobject (which is used to render the questions in the 'information panel'
|
||||
item has the following tags:
|
||||
</div>
|
||||
|
||||
<AllTagsPanel tags={state.testTags} />
|
||||
<div class="flex w-full flex-col">
|
||||
<div>
|
||||
The testobject (which is used to render the questions in the 'information panel'
|
||||
item has the following tags:
|
||||
</div>
|
||||
|
||||
<AllTagsPanel tags={state.testTags} />
|
||||
</div>
|
||||
</div>
|
||||
</TabbedGroup>
|
||||
|
|
|
|||
|
|
@ -42,6 +42,11 @@ export abstract class EditJsonState<T> {
|
|||
public readonly configuration: UIEventSource<Partial<T>> = new UIEventSource<Partial<T>>({})
|
||||
public readonly messages: Store<ConversionMessage[]>
|
||||
|
||||
/**
|
||||
* The tab in the UI that is selected, used for deeplinks
|
||||
*/
|
||||
public readonly selectedTab: UIEventSource<number> = new UIEventSource<number>(0)
|
||||
|
||||
/**
|
||||
* The EditLayerUI shows a 'schemaBasedInput' for this path to pop advanced questions out
|
||||
*/
|
||||
|
|
@ -508,6 +513,9 @@ export class EditThemeState extends EditJsonState<LayoutConfigJson> {
|
|||
}
|
||||
const prepare = this.buildValidation(state)
|
||||
const context = ConversionContext.construct([], ["prepare"])
|
||||
if(configuration.layers){
|
||||
Utils.NoNullInplace(configuration.layers)
|
||||
}
|
||||
try {
|
||||
prepare.convert(<LayoutConfigJson>configuration, context)
|
||||
} catch (e) {
|
||||
|
|
|
|||
|
|
@ -9,12 +9,15 @@
|
|||
import RawEditor from "./RawEditor.svelte"
|
||||
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
|
||||
import DeleteButton from "./DeleteButton.svelte"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import StudioHashSetter from "./StudioHashSetter"
|
||||
|
||||
export let state: EditThemeState
|
||||
export let osmConnection: OsmConnection
|
||||
export let backToStudio: () => void
|
||||
|
||||
let schema: ConfigMeta[] = state.schema.filter((schema) => schema.path.length > 0)
|
||||
new StudioHashSetter("theme", state.selectedTab, state.getStoreFor(["id"]))
|
||||
|
||||
export let selfLayers: { owner: number; id: string }[]
|
||||
export let otherLayers: { owner: number; id: string }[]
|
||||
|
|
@ -94,7 +97,7 @@
|
|||
|
||||
<div class="m4 h-full overflow-y-auto">
|
||||
<!-- {Object.keys(perRegion).join(";")} -->
|
||||
<TabbedGroup>
|
||||
<TabbedGroup tab={state.selectedTab}>
|
||||
<div slot="title0">Basic properties</div>
|
||||
<div slot="content0" class="mb-8">
|
||||
<Region configs={perRegion["basic"]} path={[]} {state} title="Basic properties" />
|
||||
|
|
@ -123,11 +126,10 @@
|
|||
Below, you'll find the raw configuration file in `.json`-format. This is mostly for
|
||||
debugging purposes, but you can also edit the file directly if you want.
|
||||
</div>
|
||||
<ShowConversionMessages messages={$messages} />
|
||||
<div class="literal-code h-full w-full">
|
||||
<div class="literal-code overflow-y-auto h-full" style="min-height: 75%">
|
||||
<RawEditor {state} />
|
||||
</div>
|
||||
</div>
|
||||
<ShowConversionMessages messages={$messages} />
|
||||
</TabbedGroup>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@
|
|||
import { ImmutableStore, Store } from "../../Logic/UIEventSource"
|
||||
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.js"
|
||||
import type { TagRenderingConfigJson } from "../../Models/ThemeConfig/Json/TagRenderingConfigJson"
|
||||
import FromHtml from "../Base/FromHtml.svelte"
|
||||
import ShowConversionMessage from "./ShowConversionMessage.svelte"
|
||||
import NextButton from "../Base/NextButton.svelte"
|
||||
import Markdown from "../Base/Markdown.svelte"
|
||||
|
||||
export let state: EditLayerState
|
||||
export let path: ReadonlyArray<string | number>
|
||||
|
|
@ -62,13 +62,6 @@
|
|||
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 })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex">
|
||||
|
|
@ -82,7 +75,7 @@
|
|||
{/if}
|
||||
</NextButton>
|
||||
{#if description}
|
||||
<FromHtml src={description} />
|
||||
<Markdown src={description}/>
|
||||
{/if}
|
||||
{#each $messages as message}
|
||||
<ShowConversionMessage {message} />
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
import QuestionPreview from "./QuestionPreview.svelte"
|
||||
import SchemaBasedMultiType from "./SchemaBasedMultiType.svelte"
|
||||
import ShowConversionMessage from "./ShowConversionMessage.svelte"
|
||||
import Markdown from "../Base/Markdown.svelte"
|
||||
|
||||
export let state: EditLayerState
|
||||
export let schema: ConfigMeta
|
||||
|
|
@ -30,10 +31,9 @@
|
|||
schema.description = undefined
|
||||
}
|
||||
|
||||
const subparts: ConfigMeta = state
|
||||
const subparts: ConfigMeta[] = state
|
||||
.getSchemaStartingWith(schema.path)
|
||||
.filter((part) => part.path.length - 1 === schema.path.length)
|
||||
|
||||
let messages = state.messagesFor(path)
|
||||
|
||||
const currentValue: UIEventSource<any[]> = state.getStoreFor(path)
|
||||
|
|
@ -97,9 +97,7 @@
|
|||
<h3>{schema.path.at(-1)}</h3>
|
||||
|
||||
{#if subparts.length > 0}
|
||||
<span class="subtle">
|
||||
{schema.description}
|
||||
</span>
|
||||
<Markdown src={schema.description}/>
|
||||
{/if}
|
||||
{#if $currentValue === undefined}
|
||||
No array defined
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
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"
|
||||
|
|
@ -68,11 +67,12 @@
|
|||
type = type.substring(0, type.length - 2)
|
||||
}
|
||||
|
||||
const configJson: QuestionableTagRenderingConfigJson = {
|
||||
const configJson: QuestionableTagRenderingConfigJson & {questionHintIsMd: boolean} = {
|
||||
id: path.join("_"),
|
||||
render: rendervalue,
|
||||
question: schema.hints.question,
|
||||
questionHint: nmd(schema.description),
|
||||
questionHint: schema.description,
|
||||
questionHintIsMd: true,
|
||||
freeform:
|
||||
schema.type === "boolean"
|
||||
? undefined
|
||||
|
|
|
|||
|
|
@ -8,9 +8,8 @@
|
|||
import { onDestroy } from "svelte"
|
||||
import SchemaBasedInput from "./SchemaBasedInput.svelte"
|
||||
import type { JsonSchemaType } from "./jsonSchema"
|
||||
// @ts-ignore
|
||||
import nmd from "nano-markdown"
|
||||
import ShowConversionMessage from "./ShowConversionMessage.svelte"
|
||||
import type { Translatable } from "../../Models/ThemeConfig/Json/Translatable"
|
||||
|
||||
/**
|
||||
* If 'types' is defined: allow the user to pick one of the types to input.
|
||||
|
|
@ -41,10 +40,11 @@
|
|||
if (lastIsString) {
|
||||
types.splice(types.length - 1, 1)
|
||||
}
|
||||
const configJson: QuestionableTagRenderingConfigJson = {
|
||||
const configJson: QuestionableTagRenderingConfigJson & {questionHintIsMd: boolean}= {
|
||||
id: "TYPE_OF:" + path.join("_"),
|
||||
question: "Which subcategory is needed for " + schema.path.at(-1) + "?",
|
||||
questionHint: nmd(schema.description),
|
||||
question: schema.hints.question ?? "Which subcategory is needed for " + schema.path.at(-1) + "?",
|
||||
questionHint: schema.description,
|
||||
questionHintIsMd: true,
|
||||
mappings: types
|
||||
.map((opt) => opt.trim())
|
||||
.filter((opt) => opt.length > 0)
|
||||
|
|
|
|||
11
src/UI/Studio/StudioHashSetter.ts
Normal file
11
src/UI/Studio/StudioHashSetter.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import Hash from "../../Logic/Web/Hash"
|
||||
|
||||
export default class StudioHashSetter {
|
||||
constructor(mode: "layer" | "theme", tab: Store<number>, name: Store<string>) {
|
||||
tab.mapD(tab => {
|
||||
Hash.hash.setData(mode + "/" + name.data + "/" + tab)
|
||||
}
|
||||
, [name])
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import NextButton from "./Base/NextButton.svelte"
|
||||
import { Store, UIEventSource } from "../Logic/UIEventSource"
|
||||
import EditLayerState, { EditThemeState } from "./Studio/EditLayerState"
|
||||
import EditLayerState, { EditJsonState, EditThemeState } from "./Studio/EditLayerState"
|
||||
import EditLayer from "./Studio/EditLayer.svelte"
|
||||
import Loading from "../assets/svg/Loading.svelte"
|
||||
import StudioServer from "./Studio/StudioServer"
|
||||
|
|
@ -30,6 +30,7 @@
|
|||
import Tr from "./Base/Tr.svelte"
|
||||
import Add from "../assets/svg/Add.svelte"
|
||||
import { SearchIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
import Hash from "../Logic/Web/Hash"
|
||||
|
||||
export let studioUrl =
|
||||
window.location.hostname === "127.0.0.2"
|
||||
|
|
@ -43,11 +44,11 @@
|
|||
)
|
||||
let osmConnection = new OsmConnection({
|
||||
oauth_token,
|
||||
checkOnlineRegularly: true,
|
||||
checkOnlineRegularly: true
|
||||
})
|
||||
const expertMode = UIEventSource.asBoolean(
|
||||
osmConnection.GetPreference("studio-expert-mode", "false", {
|
||||
documentation: "Indicates if more options are shown in mapcomplete studio",
|
||||
documentation: "Indicates if more options are shown in mapcomplete studio"
|
||||
})
|
||||
)
|
||||
expertMode.addCallbackAndRunD((expert) => console.log("Expert mode is", expert))
|
||||
|
|
@ -61,12 +62,12 @@
|
|||
l["success"]?.filter((l) => l.category === "layers")
|
||||
)
|
||||
$: selfLayers = layers.mapD(
|
||||
(ls) =>
|
||||
ls.filter(
|
||||
(l) => l.owner === uid.data && l.id.toLowerCase().includes(layerFilterTerm.toLowerCase())
|
||||
),
|
||||
[uid]
|
||||
)
|
||||
(ls) =>
|
||||
ls.filter(
|
||||
(l) => l.owner === uid.data && l.id.toLowerCase().includes(layerFilterTerm.toLowerCase())
|
||||
),
|
||||
[uid]
|
||||
)
|
||||
$: otherLayers = layers.mapD(
|
||||
(ls) =>
|
||||
ls.filter(
|
||||
|
|
@ -132,16 +133,17 @@
|
|||
|
||||
const version = meta.version
|
||||
|
||||
async function editLayer(event: Event) {
|
||||
async function editLayer(event: { detail }): Promise<EditLayerState> {
|
||||
const layerId: { owner: number; id: string } = event["detail"]
|
||||
state = "loading"
|
||||
editLayerState.startSavingUpdates(false)
|
||||
editLayerState.configuration.setData(await studio.fetch(layerId.id, "layers", layerId.owner))
|
||||
editLayerState.startSavingUpdates()
|
||||
state = "editing_layer"
|
||||
return editLayerState
|
||||
}
|
||||
|
||||
async function editTheme(event: Event) {
|
||||
async function editTheme(event: { detail }): Promise<EditThemeState> {
|
||||
const id: { id: string; owner: number } = event["detail"]
|
||||
state = "loading"
|
||||
editThemeState.startSavingUpdates(false)
|
||||
|
|
@ -149,6 +151,7 @@
|
|||
editThemeState.configuration.setData(layout)
|
||||
editThemeState.startSavingUpdates()
|
||||
state = "editing_theme"
|
||||
return editThemeState
|
||||
}
|
||||
|
||||
async function createNewLayer() {
|
||||
|
|
@ -162,23 +165,50 @@
|
|||
marker: [
|
||||
{
|
||||
icon: "circle",
|
||||
color: "white",
|
||||
},
|
||||
],
|
||||
},
|
||||
color: "white"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
tagRenderings: ["images"],
|
||||
lineRendering: [
|
||||
{
|
||||
width: 1,
|
||||
color: "blue",
|
||||
},
|
||||
],
|
||||
color: "blue"
|
||||
}
|
||||
]
|
||||
}
|
||||
editLayerState.configuration.setData(initialLayerConfig)
|
||||
editLayerState.startSavingUpdates()
|
||||
state = "editing_layer"
|
||||
}
|
||||
|
||||
async function selectStateBasedOnHash() {
|
||||
const hash = Hash.hash.data
|
||||
if (!hash) {
|
||||
return
|
||||
}
|
||||
console.log("Selecting state based on ", hash)
|
||||
const [mode, id, tab] = hash.split("/")
|
||||
// Not really an event, we just set the 'detail'
|
||||
const event = {
|
||||
detail: {
|
||||
id,
|
||||
owner: uid.data
|
||||
}
|
||||
}
|
||||
const statePromise: Promise<EditJsonState<any>> = mode === "layer" ? editLayer(event) : editTheme(event)
|
||||
const state = await statePromise
|
||||
state.selectedTab.setData(Number(tab))
|
||||
}
|
||||
|
||||
selectStateBasedOnHash()
|
||||
|
||||
function backToStudio() {
|
||||
console.log("Back to studio")
|
||||
state = undefined
|
||||
Hash.hash.setData(undefined)
|
||||
}
|
||||
</script>
|
||||
|
||||
<If condition={layersWithErr.map((d) => d?.["error"] !== undefined)}>
|
||||
|
|
@ -191,8 +221,8 @@
|
|||
<li>Try again in a few minutes</li>
|
||||
<li>
|
||||
Contact <a href="https://app.element.io/#/room/#MapComplete:matrix.org">
|
||||
the MapComplete community via the chat.
|
||||
</a>
|
||||
the MapComplete community via the chat.
|
||||
</a>
|
||||
Someone might be able to help you
|
||||
</li>
|
||||
<li>
|
||||
|
|
@ -257,9 +287,7 @@
|
|||
<BackButton
|
||||
clss="small p-1"
|
||||
imageClass="w-8 h-8"
|
||||
on:click={() => {
|
||||
state = undefined
|
||||
}}
|
||||
on:click={() => backToStudio()}
|
||||
>
|
||||
MapComplete Studio
|
||||
</BackButton>
|
||||
|
|
@ -306,9 +334,7 @@
|
|||
<BackButton
|
||||
clss="small p-1"
|
||||
imageClass="w-8 h-8"
|
||||
on:click={() => {
|
||||
state = undefined
|
||||
}}
|
||||
on:click={() => backToStudio()}
|
||||
>
|
||||
MapComplete Studio
|
||||
</BackButton>
|
||||
|
|
@ -348,30 +374,23 @@
|
|||
{:else if state === "editing_layer"}
|
||||
<EditLayer
|
||||
state={editLayerState}
|
||||
backToStudio={() => {
|
||||
state = undefined
|
||||
}}
|
||||
{backToStudio}
|
||||
>
|
||||
<BackButton
|
||||
clss="small p-1"
|
||||
imageClass="w-8 h-8"
|
||||
on:click={() => {
|
||||
state = undefined
|
||||
}}
|
||||
on:click={() => backToStudio()}
|
||||
>
|
||||
MapComplete Studio
|
||||
</BackButton>
|
||||
</EditLayer>
|
||||
{:else if state === "editing_theme"}
|
||||
<EditTheme state={editThemeState} selfLayers={$selfLayers} otherLayers={$otherLayers} {osmConnection} backToStudio={() => {
|
||||
state = undefined
|
||||
}}>
|
||||
<EditTheme state={editThemeState} selfLayers={$selfLayers} otherLayers={$otherLayers} {osmConnection}
|
||||
{backToStudio}>
|
||||
<BackButton
|
||||
clss="small p-1"
|
||||
imageClass="w-8 h-8"
|
||||
on:click={() => {
|
||||
state = undefined
|
||||
}}
|
||||
on:click={() => backToStudio()}
|
||||
>
|
||||
MapComplete Studio
|
||||
</BackButton>
|
||||
|
|
|
|||
|
|
@ -285,9 +285,9 @@
|
|||
<div class="flex w-full items-end justify-between px-4">
|
||||
<div class="flex flex-col">
|
||||
<If condition={featureSwitches.featureSwitchEnableLogin}>
|
||||
{#if state.layout.hasPresets() || state.layout.hasNoteLayer()}
|
||||
{#if (state.layout.hasPresets() && state.layout.enableAddNewPoints) || state.layout.hasNoteLayer()}
|
||||
<button
|
||||
class="pointer-events-auto w-fit"
|
||||
class="pointer-events-auto w-fit low-interaction"
|
||||
class:disabled={$currentZoom < Constants.minZoomLevelToAddNewPoint}
|
||||
on:click={() => {
|
||||
state.openNewDialog()
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<script lang="ts">
|
||||
import nmd from "nano-markdown"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import WalkthroughStep from "./WalkthroughStep.svelte"
|
||||
import FromHtml from "../Base/FromHtml.svelte"
|
||||
import Markdown from "../Base/Markdown.svelte"
|
||||
|
||||
/**
|
||||
* Markdown
|
||||
|
|
@ -31,5 +31,5 @@
|
|||
totalPages={pages.length}
|
||||
pageNumber={currentPage}
|
||||
>
|
||||
<FromHtml src={nmd(pages[currentPage])} />
|
||||
<Markdown src={pages[currentPage]} />
|
||||
</WalkthroughStep>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
</script>
|
||||
|
||||
<div class="link-underline flex h-full w-full flex-col justify-between">
|
||||
<div class="overflow-y-auto">
|
||||
<div class="overflow-y-auto h-full">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue