refactoring: move logic of lastclick into special layer, fix labels, fix anchoring

This commit is contained in:
Pieter Vander Vennet 2023-04-02 02:59:20 +02:00
parent 25a98af057
commit 52a0810ea9
47 changed files with 682 additions and 197 deletions

View file

@ -33,6 +33,7 @@ export default class Constants {
"home_location",
"gps_track",
"range",
"last_click",
] as const
/**
* Special layers which are not included in a theme by default
@ -66,10 +67,10 @@ export default class Constants {
themeGeneratorReadOnlyUnlock: 50,
themeGeneratorFullUnlock: 500,
addNewPointWithUnreadMessagesUnlock: 500,
minZoomLevelToAddNewPoints: Constants.isRetina() ? 18 : 19,
importHelperUnlock: 5000,
}
static readonly minZoomLevelToAddNewPoint = Constants.isRetina() ? 18 : 19
/**
* Used by 'PendingChangesUploader', which waits this amount of seconds to upload changes.
* (Note that pendingChanges might upload sooner if the popup is closed or similar)

View file

@ -1,4 +1,4 @@
import { UIEventSource } from "../Logic/UIEventSource"
import { Store, UIEventSource } from "../Logic/UIEventSource"
import { BBox } from "../Logic/BBox"
import { RasterLayerPolygon } from "./RasterLayers"
@ -10,5 +10,7 @@ export interface MapProperties {
readonly maxbounds: UIEventSource<undefined | BBox>
readonly allowMoving: UIEventSource<true | boolean>
readonly lastClickLocation: Store<{ lon: number; lat: number }>
readonly allowZooming: UIEventSource<true | boolean>
}

View file

@ -5,6 +5,7 @@ import metapaths from "../../../assets/layoutconfigmeta.json"
import tagrenderingmetapaths from "../../../assets/questionabletagrenderingconfigmeta.json"
import Translations from "../../../UI/i18n/Translations"
import { parse as parse_html } from "node-html-parser"
export class ExtractImages extends Conversion<
LayoutConfigJson,
{ path: string; context: string }[]
@ -190,6 +191,17 @@ export class ExtractImages extends Conversion<
const cleanedImages: { path: string; context: string }[] = []
for (const foundImage of allFoundImages) {
if (foundImage.path.startsWith("<") && foundImage.path.endsWith(">")) {
// These is probably html - we ignore
const doc = parse_html(foundImage.path)
const images = Array.from(doc.getElementsByTagName("img"))
const paths = images.map((i) => i.getAttribute("src"))
cleanedImages.push(
...paths.map((path) => ({ path, context: foundImage.context + " (in html)" }))
)
continue
}
// Split "circle:white;./assets/layers/.../something.svg" into ["circle", "./assets/layers/.../something.svg"]
const allPaths = Utils.NoNull(
Utils.NoEmpty(foundImage.path?.split(";")?.map((part) => part.split(":")[0]))

View file

@ -101,6 +101,11 @@ export class DoesImageExist extends DesugaringStep<string> {
}
}
if (image.startsWith("<") && image.endsWith(">")) {
// This is probably HTML, you're on your own here
return { result: image }
}
if (!this._knownImagePaths.has(image)) {
if (this.doesPathExist === undefined) {
errors.push(
@ -730,9 +735,9 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
}
}
if (json.minzoom > Constants.userJourney.minZoomLevelToAddNewPoints) {
if (json.minzoom > Constants.minZoomLevelToAddNewPoint) {
;(json.presets?.length > 0 ? errors : warnings).push(
`At ${context}: minzoom is ${json.minzoom}, this should be at most ${Constants.userJourney.minZoomLevelToAddNewPoints} as a preset is set. Why? Selecting the pin for a new item will zoom in to level before adding the point. Having a greater minzoom will hide the points, resulting in possible duplicates`
`At ${context}: minzoom is ${json.minzoom}, this should be at most ${Constants.minZoomLevelToAddNewPoint} as a preset is set. Why? Selecting the pin for a new item will zoom in to level before adding the point. Having a greater minzoom will hide the points, resulting in possible duplicates`
)
}
{

View file

@ -69,15 +69,25 @@ export default interface PointRenderingConfigJson {
label?: string | TagRenderingConfigJson
/**
* A snippet of css code
* A snippet of css code which is applied onto the container of the entire marker
*/
css?: string | TagRenderingConfigJson
/**
* A snippet of css-classes. They can be space-separated
* A snippet of css-classes which are applied onto the container of the entire marker. They can be space-separated
*/
cssClasses?: string | TagRenderingConfigJson
/**
* Css that is applied onto the label
*/
labelCss?: string | TagRenderingConfigJson
/**
* Css classes that are applied onto the label; can be space-separated
*/
labelCssClasses?: string | TagRenderingConfigJson
/**
* If the map is pitched, the marker will stay parallel to the screen.
* Set to 'map' if you want to put it flattened on the map

View file

@ -30,6 +30,7 @@ import { FixedUiElement } from "../../UI/Base/FixedUiElement"
import Svg from "../../Svg"
import { ImmutableStore } from "../../Logic/UIEventSource"
import { OsmTags } from "../OsmFeature"
import Constants from "../Constants"
export default class LayerConfig extends WithContextLoader {
public static readonly syncSelectionAllowed = ["no", "local", "theme-only", "global"] as const
@ -322,7 +323,8 @@ export default class LayerConfig extends WithContextLoader {
} else if (
!hasCenterRendering &&
this.lineRendering.length === 0 &&
!this.source.geojsonSource?.startsWith(
Constants.priviliged_layers.indexOf(<any>this.id) < 0 &&
!this.source?.geojsonSource?.startsWith(
"https://api.openstreetmap.org/api/0.6/notes.json"
)
) {
@ -425,8 +427,10 @@ export default class LayerConfig extends WithContextLoader {
return mapRendering.GetBaseIcon(this.GetBaseTags())
}
public GetBaseTags(): any {
return TagUtils.changeAsProperties(this.source.osmTags.asChange({ id: "node/-1" }))
public GetBaseTags(): Record<string, string> {
return TagUtils.changeAsProperties(
this.source?.osmTags?.asChange({ id: "node/-1" }) ?? [{ k: "id", v: "node/-1" }]
)
}
public GenerateDocumentation(

View file

@ -11,6 +11,7 @@ import { FixedUiElement } from "../../UI/Base/FixedUiElement"
import Img from "../../UI/Base/Img"
import Combine from "../../UI/Base/Combine"
import { VariableUiElement } from "../../UI/Base/VariableUIElement"
import { html } from "svelte/types/compiler/utils/namespaces"
export default class PointRenderingConfig extends WithContextLoader {
private static readonly allowed_location_codes = new Set<string>([
@ -28,6 +29,8 @@ export default class PointRenderingConfig extends WithContextLoader {
public readonly iconBadges: { if: TagsFilter; then: TagRenderingConfig }[]
public readonly iconSize: TagRenderingConfig
public readonly label: TagRenderingConfig
public readonly labelCss: TagRenderingConfig
public readonly labelCssClasses: TagRenderingConfig
public readonly rotation: TagRenderingConfig
public readonly cssDef: TagRenderingConfig
public readonly cssClasses?: TagRenderingConfig
@ -72,6 +75,8 @@ export default class PointRenderingConfig extends WithContextLoader {
this.cssDef = this.tr("css", undefined)
}
this.cssClasses = this.tr("cssClasses", undefined)
this.labelCss = this.tr("labelCss", undefined)
this.labelCssClasses = this.tr("labelCssClasses", undefined)
this.iconBadges = (json.iconBadges ?? []).map((overlay, i) => {
return {
if: TagUtils.Tag(overlay.if),
@ -150,7 +155,7 @@ export default class PointRenderingConfig extends WithContextLoader {
}
}
public GetBaseIcon(tags?: any): BaseUIElement {
public GetBaseIcon(tags?: Record<string, string>): BaseUIElement {
tags = tags ?? { id: "node/-1" }
let defaultPin: BaseUIElement = undefined
if (this.label === undefined) {
@ -168,6 +173,10 @@ export default class PointRenderingConfig extends WithContextLoader {
// This layer doesn't want to show an icon right now
return undefined
}
if (htmlDefs.startsWith("<") && htmlDefs.endsWith(">")) {
// This is probably already prepared HTML
return new FixedUiElement(Utils.SubstituteKeys(htmlDefs, tags))
}
return PointRenderingConfig.FromHtmlMulti(htmlDefs, rotation, false, defaultPin)
}
@ -225,10 +234,10 @@ export default class PointRenderingConfig extends WithContextLoader {
}
if (mode === "top") {
anchorH = -iconH / 2
anchorH = iconH / 2
}
if (mode === "bottom") {
anchorH = iconH / 2
anchorH = -iconH / 2
}
const icon = this.GetSimpleIcon(tags)
@ -244,10 +253,11 @@ export default class PointRenderingConfig extends WithContextLoader {
iconAndBadges.SetClass("w-full h-full")
}
const css = this.cssDef?.GetRenderValue(tags)?.txt
const cssClasses = this.cssClasses?.GetRenderValue(tags)?.txt
const css = this.cssDef?.GetRenderValue(tags.data)?.txt
const cssClasses = this.cssClasses?.GetRenderValue(tags.data)?.txt
let label = this.GetLabel(tags)
let htmlEl: BaseUIElement
if (icon === undefined && label === undefined) {
htmlEl = undefined
@ -288,6 +298,12 @@ export default class PointRenderingConfig extends WithContextLoader {
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",
@ -308,14 +324,20 @@ export default class PointRenderingConfig extends WithContextLoader {
if (this.label === undefined) {
return undefined
}
const cssLabel = this.labelCss?.GetRenderValue(tags.data)?.txt
const cssClassesLabel = this.labelCssClasses?.GetRenderValue(tags.data)?.txt
const self = this
return new VariableUiElement(
tags.map((tags) => {
const label = self.label
?.GetRenderValue(tags)
?.Subs(tags)
?.SetClass("block text-center")
return new Combine([label]).SetClass("flex flex-col items-center mt-1")
?.SetClass("block center absolute text-center ")
?.SetClass(cssClassesLabel)
if (cssLabel) {
label.SetStyle(cssLabel)
}
return new Combine([label]).SetClass("flex flex-col items-center")
})
)
}

View file

@ -1,7 +1,7 @@
import LayoutConfig from "./ThemeConfig/LayoutConfig"
import { SpecialVisualizationState } from "../UI/SpecialVisualization"
import { Changes } from "../Logic/Osm/Changes"
import { Store, UIEventSource } from "../Logic/UIEventSource"
import { ImmutableStore, Store, UIEventSource } from "../Logic/UIEventSource"
import FeatureSource, {
IndexedFeatureSource,
WritableFeatureSource,
@ -38,6 +38,7 @@ import Constants from "./Constants"
import Hotkeys from "../UI/Base/Hotkeys"
import Translations from "../UI/i18n/Translations"
import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore"
import { LastClickFeatureSource } from "../Logic/FeatureSource/Sources/LastClickFeatureSource"
/**
*
@ -105,6 +106,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
this.mapProperties,
this.userRelatedState.gpsLocationHistoryRetentionTime
)
this.availableLayers = AvailableRasterLayers.layersAvailableAt(this.mapProperties.location)
this.layerState = new LayerState(this.osmConnection, layout.layers, layout.id)
@ -203,11 +205,45 @@ export default class ThemeViewState implements SpecialVisualizationState {
*/
private drawSpecialLayers() {
type AddedByDefaultTypes = typeof Constants.added_by_default[number]
const empty = []
{
// The last_click gets a _very_ special treatment
const last_click = new LastClickFeatureSource(
this.mapProperties.lastClickLocation,
this.layout
)
const last_click_layer = this.layerState.filteredLayers.get("last_click")
this.featureProperties.addSpecial(
"last_click",
new UIEventSource<Record<string, string>>(last_click.properties)
)
new ShowDataLayer(this.map, {
features: last_click,
doShowLayer: new ImmutableStore(true),
layer: last_click_layer.layerDef,
selectedElement: this.selectedElement,
selectedLayer: this.selectedLayer,
onClick: (feature: Feature) => {
if (this.mapProperties.zoom.data < Constants.minZoomLevelToAddNewPoint) {
this.map.data.flyTo({
zoom: Constants.minZoomLevelToAddNewPoint,
center: this.mapProperties.lastClickLocation.data,
})
return
}
this.selectedElement.setData(feature)
this.selectedLayer.setData(last_click_layer.layerDef)
},
})
}
/**
* A listing which maps the layerId onto the featureSource
*/
const empty = []
const specialLayers: Record<AddedByDefaultTypes | "current_view", FeatureSource> = {
const specialLayers: Record<
Exclude<AddedByDefaultTypes, "last_click"> | "current_view",
FeatureSource
> = {
home_location: this.userRelatedState.homeLocation,
gps_location: this.geolocation.currentUserLocation,
gps_location_history: this.geolocation.historicalUserLocations,
@ -261,12 +297,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
*/
private initActors() {
// Various actors that we don't need to reference
new TitleHandler(
this.selectedElement,
this.selectedLayer,
this.featureProperties,
this.layout
)
new TitleHandler(this.selectedElement, this.selectedLayer, this.featureProperties, this)
new ChangeToElementsActor(this.changes, this.featureProperties)
new PendingChangesUploader(this.changes, this.selectedElement)
new SelectedElementTagsUpdater({