forked from MapComplete/MapComplete
refactoring: move logic of lastclick into special layer, fix labels, fix anchoring
This commit is contained in:
parent
25a98af057
commit
52a0810ea9
47 changed files with 682 additions and 197 deletions
|
@ -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)
|
||||
|
|
|
@ -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>
|
||||
}
|
||||
|
|
|
@ -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]))
|
||||
|
|
|
@ -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`
|
||||
)
|
||||
}
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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")
|
||||
})
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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({
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue