Studio: some UX tweaks

This commit is contained in:
Pieter Vander Vennet 2023-11-14 17:35:12 +01:00
parent 5e453d5cf1
commit fb193123e0
29 changed files with 395 additions and 368 deletions

View file

@ -1103,9 +1103,11 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
if (json.tagRenderings !== undefined && json.tagRenderings.length > 0) {
new On("tagRendering", new Each(new ValidateTagRenderings(json)))
if (json.title === undefined && json.source !== "special:library") {
context.err(
"This layer does not have a title defined but it does have tagRenderings. Not having a title will disable the popups, resulting in an unclickable element. Please add a title. If not having a popup is intended and the tagrenderings need to be kept (e.g. in a library layer), set `title: null` to disable this error."
)
context
.enter("title")
.err(
"This layer does not have a title defined but it does have tagRenderings. Not having a title will disable the popups, resulting in an unclickable element. Please add a title. If not having a popup is intended and the tagrenderings need to be kept (e.g. in a library layer), set `title: null` to disable this error."
)
}
if (json.title === null) {
context.info(

View file

@ -210,7 +210,7 @@ export interface LayerConfigJson {
minzoomVisible?: number
/**
* question: What title should be shown on the infobox?
* question: Edit the popup title
* The title shown in a popup for elements of this layer.
*
* group: title
@ -379,7 +379,7 @@ export interface LayerConfigJson {
}[]
/**
* question: Edit this tagRendering
* question: Edit this way this attributed is displayed or queried
*
* A tag rendering is a block that either shows the known value or asks a question.
*

View file

@ -6,7 +6,7 @@ import { Translatable } from "./Translatable"
*/
export interface MinimalTagRenderingConfigJson {
/**
* question: What value should be rendered?
* question: What value should be shown (if no predifined option matches)?
*
* This piece of text will be shown in the infobox.
* Note that "&LBRACEkey&RBRACE"-parts are substituted by the corresponding values of the element.
@ -56,7 +56,7 @@ export interface TagRenderingConfigJson {
*
* Note that this is a HTML-interpreted value, so you can add links as e.g. '&lt;a href='{website}'>{website}&lt;/a>' or include images such as `This is of type A &lt;br>&lt;img src='typeA-icon.svg' />`
* type: rendered
* ifunset: no text is rendered if no predefined options match
* ifunset: No text is shown if no predefined options match.
*/
render?:
| Translatable
@ -67,7 +67,7 @@ export interface TagRenderingConfigJson {
* An icon shown next to the rendering; typically shown pretty small
* This is only shown next to the "render" value
* Type: icon
* ifunset: do not show an icon next to the "render"-value
* ifunset: No additional icon is shown next to the always shown text
*/
icon?:
| string

View file

@ -16,10 +16,10 @@ import SvelteUIElement from "../../UI/Base/SvelteUIElement"
import DynamicMarker from "../../UI/Map/DynamicMarker.svelte"
export class IconConfig extends WithContextLoader {
public static readonly defaultIcon = new IconConfig({ icon: "pin", color: "#ff9939" })
public readonly icon: TagRenderingConfig
public readonly color: TagRenderingConfig
public static readonly defaultIcon = new IconConfig({ icon: "pin", color: "#ff9939" })
constructor(
config: {
icon: string | TagRenderingConfigJson
@ -199,11 +199,13 @@ export default class PointRenderingConfig extends WithContextLoader {
public GetBaseIcon(tags?: Record<string, string>): BaseUIElement {
return new SvelteUIElement(DynamicMarker, { config: this, tags: new ImmutableStore(tags) })
}
public RenderIcon(
tags: Store<Record<string, string>>,
options?: {
noSize?: false | boolean
includeBadges?: true | boolean
metatags?: Store<Record<string, string>>
}
): {
html: BaseUIElement
@ -225,16 +227,14 @@ export default class PointRenderingConfig extends WithContextLoader {
return Utils.SubstituteKeys(str, tags.data).replace(/{.*}/g, "")
}
const iconSize = render(this.iconSize, "40,40").split(",")
const iconW = num(iconSize[0])
let iconH = num(iconSize[1])
const anchor = render(this.anchor, "center")
const mode = anchor?.trim()?.toLowerCase() ?? "center"
// in MapLibre, the offset is relative to the _center_ of the object, with left = [-x, 0] and up = [0,-y]
let anchorW = 0
let anchorH = 0
const anchor = render(this.anchor, "center")
const mode = anchor?.trim()?.toLowerCase() ?? "center"
const size = this.iconSize.GetRenderValue(tags.data).Subs(tags).txt ?? "[40,40]"
const [iconW, iconH] = size.split(",").map((x) => num(x))
if (mode === "left") {
anchorW = -iconW / 2
}
@ -254,15 +254,20 @@ export default class PointRenderingConfig extends WithContextLoader {
)
let badges = undefined
if (options?.includeBadges ?? true) {
badges = this.GetBadges(tags)
badges = this.GetBadges(tags, options.metatags)
}
const iconAndBadges = new Combine([icon, badges]).SetClass("block relative")
if (!options?.noSize) {
iconAndBadges.SetStyle(`width: ${iconW}px; height: ${iconH}px`)
} else {
if (options?.noSize) {
iconAndBadges.SetClass("w-full h-full")
}
tags.map((tags) => this.iconSize.GetRenderValue(tags).Subs(tags).txt ?? "[40,40]").map(
(size) => {
const [iconW, iconH] = size.split(",").map((x) => num(x))
console.log("Setting size to", iconW, iconH)
iconAndBadges.SetStyle(`width: ${iconW}px; height: ${iconH}px`)
}
)
const css = this.cssDef?.GetRenderValue(tags.data)?.txt
const cssClasses = this.cssClasses?.GetRenderValue(tags.data)?.txt
@ -293,41 +298,58 @@ export default class PointRenderingConfig extends WithContextLoader {
}
}
private GetBadges(tags: Store<Record<string, string>>): BaseUIElement {
private GetBadges(
tags: Store<Record<string, string>>,
metaTags?: Store<Record<string, string>>
): BaseUIElement {
if (this.iconBadges.length === 0) {
return undefined
}
return new VariableUiElement(
tags.map((tags) => {
const badgeElements = this.iconBadges.map((badge) => {
if (!badge.if.matchesProperties(tags)) {
// Doesn't match...
return undefined
}
tags.map(
(tags) => {
const badgeElements = this.iconBadges.map((badge) => {
if (!badge.if.matchesProperties(tags)) {
// Doesn't match...
return undefined
}
const metaCondition = badge.then.metacondition
if (
metaCondition &&
metaTags &&
!metaCondition.matchesProperties(metaTags.data)
) {
// Doesn't match
return undefined
}
const htmlDefs = Utils.SubstituteKeys(
badge.then.GetRenderValue(tags)?.txt,
tags
)
if (htmlDefs.startsWith("<") && htmlDefs.endsWith(">")) {
// This is probably an HTML-element
return new FixedUiElement(Utils.SubstituteKeys(htmlDefs, tags))
const htmlDefs = Utils.SubstituteKeys(
badge.then.GetRenderValue(tags)?.txt,
tags
)
if (htmlDefs.startsWith("<") && htmlDefs.endsWith(">")) {
// This is probably an HTML-element
return new FixedUiElement(Utils.SubstituteKeys(htmlDefs, tags))
.SetStyle("width: 1.5rem")
.SetClass("block")
}
const badgeElement = PointRenderingConfig.FromHtmlMulti(
htmlDefs,
"0",
true
)?.SetClass("block relative")
if (badgeElement === undefined) {
return undefined
}
return new Combine([badgeElement])
.SetStyle("width: 1.5rem")
.SetClass("block")
}
const badgeElement = PointRenderingConfig.FromHtmlMulti(
htmlDefs,
"0",
true
)?.SetClass("block relative")
if (badgeElement === undefined) {
return undefined
}
return new Combine([badgeElement]).SetStyle("width: 1.5rem").SetClass("block")
})
})
return new Combine(badgeElements).SetClass("inline-flex h-full")
})
return new Combine(badgeElements).SetClass("inline-flex h-full")
},
[metaTags]
)
).SetClass("absolute bottom-0 right-1/3 h-1/2 w-0")
}