Merge branch 'master' into feature/studio

This commit is contained in:
Pieter Vander Vennet 2023-07-28 14:38:12 +02:00
commit 6ff2c629f0
994 changed files with 5917 additions and 4262 deletions

View file

@ -21,11 +21,9 @@ import questions from "../assets/generated/layers/questions.json"
import {
DoesImageExist,
PrevalidateTheme,
ValidateTagRenderings,
ValidateThemeAndLayers,
} from "../Models/ThemeConfig/Conversion/Validation"
import { DesugaringContext } from "../Models/ThemeConfig/Conversion/Conversion"
import { RewriteSpecial } from "../Models/ThemeConfig/Conversion/PrepareLayer"
import { TagRenderingConfigJson } from "../Models/ThemeConfig/Json/TagRenderingConfigJson"
import Hash from "./Web/Hash"

View file

@ -10,7 +10,12 @@ import { Utils } from "../../Utils"
class FeatureSwitchUtils {
static initSwitch(key: string, deflt: boolean, documentation: string): UIEventSource<boolean> {
const defaultValue = deflt
const queryParam = QueryParameters.GetQueryParameter(key, "" + defaultValue, documentation)
const queryParam = QueryParameters.GetQueryParameter(
key,
"" + defaultValue,
documentation,
{ stackOffset: -1 }
)
// It takes the current layout, extracts the default value for this query parameter. A query parameter event source is then retrieved and flattened
return queryParam.sync(

View file

@ -4,6 +4,7 @@
import { UIEventSource } from "../UIEventSource"
import Hash from "./Hash"
import { Utils } from "../../Utils"
import doc = Mocha.reporters.doc
export class QueryParameters {
static defaults: Record<string, string> = {}
@ -16,11 +17,27 @@ export class QueryParameters {
public static GetQueryParameter(
key: string,
deflt: string,
documentation?: string
documentation?: string,
options?: {
stackOffset?: number
}
): UIEventSource<string> {
if (!this.initialized) {
this.init()
}
if (Utils.runningFromConsole) {
const location = Utils.getLocationInCode(-1 + (options?.stackOffset ?? 0))
documentation +=
"\n\nThis documentation is defined in the source code at [" +
location.filename +
"](" +
location.markdownLocation +
")" +
"\n\n"
}
QueryParameters.documentation.set(key, documentation)
if (deflt !== undefined) {
QueryParameters.defaults[key] = deflt
@ -49,7 +66,7 @@ export class QueryParameters {
documentation?: string
): UIEventSource<boolean> {
return UIEventSource.asBoolean(
QueryParameters.GetQueryParameter(key, "" + deflt, documentation)
QueryParameters.GetQueryParameter(key, "" + deflt, documentation, { stackOffset: -1 })
)
}

View file

@ -1,14 +1,14 @@
import {Store, UIEventSource} from "../Logic/UIEventSource"
import { Store, UIEventSource } from "../Logic/UIEventSource"
import LayerConfig from "./ThemeConfig/LayerConfig"
import {OsmConnection} from "../Logic/Osm/OsmConnection"
import {LocalStorageSource} from "../Logic/Web/LocalStorageSource"
import {QueryParameters} from "../Logic/Web/QueryParameters"
import {FilterConfigOption} from "./ThemeConfig/FilterConfig"
import {TagsFilter} from "../Logic/Tags/TagsFilter"
import {Utils} from "../Utils"
import {TagUtils} from "../Logic/Tags/TagUtils"
import {And} from "../Logic/Tags/And"
import {GlobalFilter} from "./GlobalFilter"
import { OsmConnection } from "../Logic/Osm/OsmConnection"
import { LocalStorageSource } from "../Logic/Web/LocalStorageSource"
import { QueryParameters } from "../Logic/Web/QueryParameters"
import { FilterConfigOption } from "./ThemeConfig/FilterConfig"
import { TagsFilter } from "../Logic/Tags/TagsFilter"
import { Utils } from "../Utils"
import { TagUtils } from "../Logic/Tags/TagUtils"
import { And } from "../Logic/Tags/And"
import { GlobalFilter } from "./GlobalFilter"
export default class FilteredLayer {
/**

View file

@ -1,21 +1,22 @@
import { DesugaringStep, Each, Fuse, On } from "./Conversion"
import { LayerConfigJson } from "../Json/LayerConfigJson"
import {DesugaringStep, Each, Fuse, On} from "./Conversion"
import {LayerConfigJson} from "../Json/LayerConfigJson"
import LayerConfig from "../LayerConfig"
import { Utils } from "../../../Utils"
import {Utils} from "../../../Utils"
import Constants from "../../Constants"
import { Translation } from "../../../UI/i18n/Translation"
import { LayoutConfigJson } from "../Json/LayoutConfigJson"
import {Translation} from "../../../UI/i18n/Translation"
import {LayoutConfigJson} from "../Json/LayoutConfigJson"
import LayoutConfig from "../LayoutConfig"
import { TagRenderingConfigJson } from "../Json/TagRenderingConfigJson"
import { TagUtils } from "../../../Logic/Tags/TagUtils"
import { ExtractImages } from "./FixImages"
import { And } from "../../../Logic/Tags/And"
import {TagRenderingConfigJson} from "../Json/TagRenderingConfigJson"
import {TagUtils} from "../../../Logic/Tags/TagUtils"
import {ExtractImages} from "./FixImages"
import {And} from "../../../Logic/Tags/And"
import Translations from "../../../UI/i18n/Translations"
import Svg from "../../../Svg"
import FilterConfigJson from "../Json/FilterConfigJson"
import DeleteConfig from "../DeleteConfig"
import { QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson"
import {QuestionableTagRenderingConfigJson} from "../Json/QuestionableTagRenderingConfigJson"
import Validators from "../../../UI/InputElement/Validators"
import TagRenderingConfig from "../TagRenderingConfig";
class ValidateLanguageCompleteness extends DesugaringStep<any> {
private readonly _languages: string[]
@ -46,12 +47,12 @@ class ValidateLanguageCompleteness extends DesugaringStep<any> {
.forEach((missing) => {
errors.push(
context +
"A theme should be translation-complete for " +
neededLanguage +
", but it lacks a translation for " +
missing.context +
".\n\tThe known translation is " +
missing.tr.textFor("en")
"A theme should be translation-complete for " +
neededLanguage +
", but it lacks a translation for " +
missing.context +
".\n\tThe known translation is " +
missing.tr.textFor("en")
)
})
}
@ -85,7 +86,7 @@ export class DoesImageExist extends DesugaringStep<string> {
context: string
): { result: string; errors?: string[]; warnings?: string[]; information?: string[] } {
if (this._ignore?.has(image)) {
return { result: image }
return {result: image}
}
const errors = []
@ -93,22 +94,22 @@ export class DoesImageExist extends DesugaringStep<string> {
const information = []
if (image.indexOf("{") >= 0) {
information.push("Ignoring image with { in the path: " + image)
return { result: image }
return {result: image}
}
if (image === "assets/SocialImage.png") {
return { result: image }
return {result: image}
}
if (image.match(/[a-z]*/)) {
if (Svg.All[image + ".svg"] !== undefined) {
// This is a builtin img, e.g. 'checkmark' or 'crosshair'
return { result: image }
return {result: image}
}
}
if (image.startsWith("<") && image.endsWith(">")) {
// This is probably HTML, you're on your own here
return { result: image }
return {result: image}
}
if (!this._knownImagePaths.has(image)) {
@ -177,15 +178,15 @@ class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
if (json["units"] !== undefined) {
errors.push(
"The theme " +
json.id +
" has units defined - these should be defined on the layer instead. (Hint: use overrideAll: { '+units': ... }) "
json.id +
" has units defined - these should be defined on the layer instead. (Hint: use overrideAll: { '+units': ... }) "
)
}
if (json["roamingRenderings"] !== undefined) {
errors.push(
"Theme " +
json.id +
" contains an old 'roamingRenderings'. Use an 'overrideAll' instead"
json.id +
" contains an old 'roamingRenderings'. Use an 'overrideAll' instead"
)
}
}
@ -197,10 +198,10 @@ class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
for (const remoteImage of remoteImages) {
errors.push(
"Found a remote image: " +
remoteImage +
" in theme " +
json.id +
", please download it."
remoteImage +
" in theme " +
json.id +
", please download it."
)
}
for (const image of images) {
@ -227,12 +228,12 @@ class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
if (theme.id !== filename) {
errors.push(
"Theme ids should be the same as the name.json, but we got id: " +
theme.id +
" and filename " +
filename +
" (" +
this._path +
")"
theme.id +
" and filename " +
filename +
" (" +
this._path +
")"
)
}
this._validateImage.convertJoin(
@ -312,7 +313,7 @@ class OverrideShadowingCheck extends DesugaringStep<LayoutConfigJson> {
): { result: LayoutConfigJson; errors?: string[]; warnings?: string[] } {
const overrideAll = json.overrideAll
if (overrideAll === undefined) {
return { result: json }
return {result: json}
}
const errors = []
@ -339,7 +340,7 @@ class OverrideShadowingCheck extends DesugaringStep<LayoutConfigJson> {
}
}
return { result: json, errors }
return {result: json, errors}
}
}
@ -383,6 +384,51 @@ export class PrevalidateTheme extends Fuse<LayoutConfigJson> {
}
}
export class DetectConflictingAddExtraTags extends DesugaringStep<TagRenderingConfigJson> {
constructor() {
super("The `if`-part in a mapping might set some keys. Those key are not allowed to be set in the `addExtraTags`, as this might result in conflicting values", [], "DetectConflictingAddExtraTags");
}
convert(json: TagRenderingConfigJson, context: string): {
result: TagRenderingConfigJson;
errors?: string[];
warnings?: string[];
information?: string[]
} {
if (!(json.mappings?.length > 0)) {
return {result: json}
}
const tagRendering = new TagRenderingConfig(json)
const errors = []
for (let i = 0; i < tagRendering.mappings.length; i++) {
const mapping = tagRendering.mappings[i];
if (!mapping.addExtraTags) {
continue
}
const keysInMapping = new Set(mapping.if.usedKeys())
const keysInAddExtraTags = mapping.addExtraTags.map(t => t.key)
const duplicateKeys = keysInAddExtraTags.filter(k => keysInMapping.has(k))
if (duplicateKeys.length > 0) {
errors.push(
"At " + context + ".mappings[" + i + "]: AddExtraTags overrides a key that is set in the `if`-clause of this mapping. Selecting this answer might thus first set one value (needed to match as answer) and then override it with a different value, resulting in an unsaveable question. The offending `addExtraTags` is " + duplicateKeys.join(", ")
)
}
}
return {
result: json,
errors
};
}
}
export class DetectShadowedMappings extends DesugaringStep<TagRenderingConfigJson> {
private readonly _calculatedTagNames: string[]
@ -449,7 +495,7 @@ export class DetectShadowedMappings extends DesugaringStep<TagRenderingConfigJso
const errors = []
const warnings = []
if (json.mappings === undefined || json.mappings.length === 0) {
return { result: json }
return {result: json}
}
const defaultProperties = {}
for (const calculatedTagName of this._calculatedTagNames) {
@ -475,7 +521,7 @@ export class DetectShadowedMappings extends DesugaringStep<TagRenderingConfigJso
}
const keyValues = parsedConditions[i].asChange(defaultProperties)
const properties = {}
keyValues.forEach(({ k, v }) => {
keyValues.forEach(({k, v}) => {
properties[k] = v
})
for (let j = 0; j < i; j++) {
@ -492,10 +538,10 @@ export class DetectShadowedMappings extends DesugaringStep<TagRenderingConfigJso
// The current mapping is shadowed!
errors.push(`At ${context}: Mapping ${i} is shadowed by mapping ${j} and will thus never be shown:
The mapping ${parsedConditions[i].asHumanString(
false,
false,
{}
)} is fully matched by a previous mapping (namely ${j}), which matches:
false,
false,
{}
)} is fully matched by a previous mapping (namely ${j}), which matches:
${parsedConditions[j].asHumanString(false, false, {})}.
To fix this problem, you can try to:
@ -564,7 +610,7 @@ export class DetectMappingsWithImages extends DesugaringStep<TagRenderingConfigJ
const warnings: string[] = []
const information: string[] = []
if (json.mappings === undefined || json.mappings.length === 0) {
return { result: json }
return {result: json}
}
const ignoreToken = "ignore-image-in-then"
for (let i = 0; i < json.mappings.length; i++) {
@ -626,17 +672,17 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> {
if (json["special"] !== undefined) {
errors.push(
"At " +
context +
': detected `special` on the top level. Did you mean `{"render":{ "special": ... }}`'
context +
': detected `special` on the top level. Did you mean `{"render":{ "special": ... }}`'
)
}
if (json["group"]) {
errors.push(
"At " +
context +
': groups are deprecated, use `"label": ["' +
json["group"] +
'"]` instead'
context +
': groups are deprecated, use `"label": ["' +
json["group"] +
'"]` instead'
)
}
const freeformType = json["freeform"]?.["type"]
@ -669,6 +715,7 @@ export class ValidateTagRenderings extends Fuse<TagRenderingConfigJson> {
super(
"Various validation on tagRenderingConfigs",
new DetectShadowedMappings(layerConfig),
new DetectConflictingAddExtraTags(),
new DetectMappingsWithImages(doesImageExist),
new MiscTagRenderingChecks(options)
)
@ -711,9 +758,9 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
if (!Constants.priviliged_layers.find((x) => x == json.id)) {
errors.push(
context +
": layer " +
json.id +
" uses 'special' as source.osmTags. However, this layer is not a priviliged layer"
": layer " +
json.id +
" uses 'special' as source.osmTags. However, this layer is not a priviliged layer"
)
}
}
@ -722,13 +769,13 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
if (json.title === undefined && json.source !== "special:library") {
errors.push(
context +
": 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."
": 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) {
information.push(
context +
": title is `null`. This results in an element that cannot be clicked - even though tagRenderings is set."
": title is `null`. This results in an element that cannot be clicked - even though tagRenderings is set."
)
}
}
@ -755,9 +802,9 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
console.log(json.tagRenderings)
errors.push(
"At " +
context +
": some tagrenderings have a duplicate id: " +
duplicates.join(", ")
context +
": some tagrenderings have a duplicate id: " +
duplicates.join(", ")
)
}
}
@ -775,8 +822,8 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
if (json["overpassTags"] !== undefined) {
errors.push(
"Layer " +
json.id +
'still uses the old \'overpassTags\'-format. Please use "source": {"osmTags": <tags>}\' instead of "overpassTags": <tags> (note: this isn\'t your fault, the custom theme generator still spits out the old format)'
json.id +
'still uses the old \'overpassTags\'-format. Please use "source": {"osmTags": <tags>}\' instead of "overpassTags": <tags> (note: this isn\'t your fault, the custom theme generator still spits out the old format)'
)
}
const forbiddenTopLevel = [
@ -794,18 +841,18 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
if (json[forbiddenKey] !== undefined)
errors.push(
context +
": layer " +
json.id +
" still has a forbidden key " +
forbiddenKey
": layer " +
json.id +
" still has a forbidden key " +
forbiddenKey
)
}
if (json["hideUnderlayingFeaturesMinPercentage"] !== undefined) {
errors.push(
context +
": layer " +
json.id +
" contains an old 'hideUnderlayingFeaturesMinPercentage'"
": layer " +
json.id +
" contains an old 'hideUnderlayingFeaturesMinPercentage'"
)
}
@ -822,9 +869,9 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
if (this._path != undefined && this._path.indexOf(expected) < 0) {
errors.push(
"Layer is in an incorrect place. The path is " +
this._path +
", but expected " +
expected
this._path +
", but expected " +
expected
)
}
}
@ -865,6 +912,13 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
}
}
if (json.filter) {
const r = new On("filter", new Each( new ValidateFilter())).convert(json, context)
warnings.push(...(r.warnings ?? []))
errors.push(...(r.errors ?? []))
information.push(...(r.information ?? []))
}
if (json.tagRenderings !== undefined) {
const r = new On(
"tagRenderings",
@ -886,9 +940,9 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
if (hasCondition?.length > 0) {
errors.push(
"At " +
context +
":\n One or more icons in the mapRenderings have a condition set. Don't do this, as this will result in an invisible but clickable element. Use extra filters in the source instead. The offending mapRenderings are:\n" +
JSON.stringify(hasCondition, null, " ")
context +
":\n One or more icons in the mapRenderings have a condition set. Don't do this, as this will result in an invisible but clickable element. Use extra filters in the source instead. The offending mapRenderings are:\n" +
JSON.stringify(hasCondition, null, " ")
)
}
}
@ -903,7 +957,7 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
const preset = json.presets[i]
const tags: { k: string; v: string }[] = new And(
preset.tags.map((t) => TagUtils.Tag(t))
).asChange({ id: "node/-1" })
).asChange({id: "node/-1"})
const properties = {}
for (const tag of tags) {
properties[tag.k] = tag.v
@ -912,12 +966,12 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
if (!doMatch) {
errors.push(
context +
".presets[" +
i +
"]: This preset does not match the required tags of this layer. This implies that a newly added point will not show up.\n A newly created point will have properties: " +
JSON.stringify(properties) +
"\n The required tags are: " +
baseTags.asHumanString(false, false, {})
".presets[" +
i +
"]: This preset does not match the required tags of this layer. This implies that a newly added point will not show up.\n A newly created point will have properties: " +
JSON.stringify(properties) +
"\n The required tags are: " +
baseTags.asHumanString(false, false, {})
)
}
}
@ -949,9 +1003,14 @@ export class ValidateFilter extends DesugaringStep<FilterConfigJson> {
warnings?: string[]
information?: string[]
} {
if (typeof filter === "string") {
// Calling another filter, we skip
return {result: filter}
}
const errors = []
for (const option of filter.options) {
for (let i = 0; i < option.fields.length; i++) {
for (let i = 0; i < option.fields?.length ?? 0; i++) {
const field = option.fields[i]
const type = field.type ?? "string"
if (Validators.availableTypes.find((t) => t === type) === undefined) {
@ -962,7 +1021,7 @@ export class ValidateFilter extends DesugaringStep<FilterConfigJson> {
}
}
}
return { result: filter, errors }
return {result: filter, errors}
}
}
@ -991,7 +1050,7 @@ export class DetectDuplicateFilters extends DesugaringStep<{
const warnings: string[] = []
const information: string[] = []
const { layers, themes } = json
const {layers, themes} = json
const perOsmTag = new Map<
string,
{
@ -1027,7 +1086,7 @@ export class DetectDuplicateFilters extends DesugaringStep<{
return
}
let msg = "Possible duplicate filter: " + key
for (const { filter, layer, layout } of value) {
for (const {filter, layer, layout} of value) {
let id = ""
if (layout !== undefined) {
id = layout.id + ":"

View file

@ -7,7 +7,7 @@
import type { LayoutInformation } from "../../Models/ThemeConfig/LayoutConfig"
import Tr from "../Base/Tr.svelte"
import SubtleLink from "../Base/SubtleLink.svelte"
import Translations from "../i18n/Translations"
import Translations from "../i18n/Translations"
export let theme: LayoutInformation
export let isCustom: boolean = false

View file

@ -29,7 +29,7 @@
<div class="gap-4 md:grid md:grid-flow-row md:grid-cols-2 lg:grid-cols-3">
{#each filteredThemes as theme (theme.id)}
{#if theme !== undefined && !(hideThemes && theme?.hideFromOverview)}
<!-- TODO: doesn't work if first theme is hidden -->
<!-- TODO: doesn't work if first theme is hidden -->
{#if theme === firstTheme && !isCustom && $search !== "" && $search !== undefined}
<ThemeButton
{theme}

View file

@ -1,6 +1,6 @@
import BaseUIElement from "../BaseUIElement"
import {InputElement} from "./InputElement"
import {UIEventSource} from "../../Logic/UIEventSource"
import { InputElement } from "./InputElement"
import { UIEventSource } from "../../Logic/UIEventSource"
/**
* @deprecated
@ -67,20 +67,18 @@ export default class FileSelectorButton extends InputElement<FileList> {
if (actualInputElement.files !== null) {
self._value.setData(actualInputElement.files)
}
actualInputElement.classList.remove("glowing-shadow");
actualInputElement.classList.remove("glowing-shadow")
e.preventDefault()
})
el.appendChild(actualInputElement)
function setDrawAttention(isOn: boolean){
if(isOn){
function setDrawAttention(isOn: boolean) {
if (isOn) {
label.classList.add("glowing-shadow")
}else{
} else {
label.classList.remove("glowing-shadow")
}
}
@ -90,10 +88,9 @@ export default class FileSelectorButton extends InputElement<FileList> {
setDrawAttention(true)
// Style the drag-and-drop as a "copy file" operation.
event.dataTransfer.dropEffect = "copy"
})
window.document.addEventListener("dragenter", () =>{
window.document.addEventListener("dragenter", () => {
setDrawAttention(true)
})
@ -101,7 +98,6 @@ export default class FileSelectorButton extends InputElement<FileList> {
setDrawAttention(false)
})
el.addEventListener("drop", (event) => {
event.stopPropagation()
event.preventDefault()

View file

@ -1,79 +1,73 @@
<script lang="ts">
import {onDestroy, onMount} from "svelte"
import * as maplibre from "maplibre-gl"
import type {Map} from "maplibre-gl"
import type {Readable, Writable} from "svelte/store"
import {get, writable} from "svelte/store"
import {AvailableRasterLayers} from "../../Models/RasterLayers"
import {Utils} from "../../Utils";
import { onDestroy, onMount } from "svelte"
import * as maplibre from "maplibre-gl"
import type { Map } from "maplibre-gl"
import type { Readable, Writable } from "svelte/store"
import { get, writable } from "svelte/store"
import { AvailableRasterLayers } from "../../Models/RasterLayers"
import { Utils } from "../../Utils"
/**
* The 'MaplibreMap' maps various event sources onto MapLibre.
*/
/**
* The 'MaplibreMap' maps various event sources onto MapLibre.
*/
/**
* Beware: this map will _only_ be set by this component
* It should thus be treated as a 'store' by external parties
*/
export let map: Writable<Map>
/**
* Beware: this map will _only_ be set by this component
* It should thus be treated as a 'store' by external parties
*/
export let map: Writable<Map>
let container: HTMLElement
let container: HTMLElement
export let attribution = false
export let center: { lng: number; lat: number } | Readable<{ lng: number; lat: number }> =
writable({ lng: 0, lat: 0 })
export let zoom: Readable<number> = writable(1)
const styleUrl = AvailableRasterLayers.maplibre.properties.url
export let attribution = false
export let center: {lng: number, lat: number} | Readable<{ lng: number; lat: number }> = writable({lng: 0, lat: 0})
export let zoom: Readable<number> = writable(1)
const styleUrl = AvailableRasterLayers.maplibre.properties.url
let _map: Map
onMount(() => {
let _center: {lng: number, lat: number}
if(typeof center["lng"] === "number" && typeof center["lat"] === "number"){
_center = <any> center
}else{
_center = get(<any> center)
}
_map = new maplibre.Map({
container,
style: styleUrl,
zoom: get(zoom),
center: _center,
maxZoom: 24,
interactive: true,
attributionControl: false,
});
_map.on("load", function () {
_map.resize()
})
map.set(_map)
let _map: Map
onMount(() => {
let _center: { lng: number; lat: number }
if (typeof center["lng"] === "number" && typeof center["lat"] === "number") {
_center = <any>center
} else {
_center = get(<any>center)
}
_map = new maplibre.Map({
container,
style: styleUrl,
zoom: get(zoom),
center: _center,
maxZoom: 24,
interactive: true,
attributionControl: false,
})
onDestroy(async () => {
await Utils.waitFor(250);
if (_map) _map.remove();
map = null;
});
_map.on("load", function () {
_map.resize()
})
map.set(_map)
})
onDestroy(async () => {
await Utils.waitFor(250)
if (_map) _map.remove()
map = null
})
</script>
<svelte:head>
<link
href="./maplibre-gl.css"
rel="stylesheet"
/>
<link href="./maplibre-gl.css" rel="stylesheet" />
</svelte:head>
<div bind:this={container} class="map" id="map" style=" position: relative;
<div
bind:this={container}
class="map"
id="map"
style=" position: relative;
top: 0;
bottom: 0;
width: 100%;
height: 100%;"></div>
height: 100%;"
/>

View file

@ -161,13 +161,16 @@ class PointRenderingLayer {
})
}
const marker = new Marker({ element: el}).setLngLat(loc).setOffset(iconAnchor).addTo(this._map)
const marker = new Marker({ element: el })
.setLngLat(loc)
.setOffset(iconAnchor)
.addTo(this._map)
store
.map((tags) => this._config.pitchAlignment.GetRenderValue(tags).Subs(tags).txt)
.addCallbackAndRun((pitchAligment) => marker.setPitchAlignment(<any> pitchAligment))
.addCallbackAndRun((pitchAligment) => marker.setPitchAlignment(<any>pitchAligment))
store
.map((tags) => this._config.rotationAlignment.GetRenderValue(tags).Subs(tags).txt)
.addCallbackAndRun((pitchAligment) => marker.setRotationAlignment(<any> pitchAligment))
.addCallbackAndRun((pitchAligment) => marker.setRotationAlignment(<any>pitchAligment))
if (feature.geometry.type === "Point") {
// When the tags get 'pinged', check that the location didn't change
store.addCallbackAndRunD(() => {
@ -458,7 +461,6 @@ export default class ShowDataLayer {
features: FeatureSource,
doShowLayer?: Store<boolean>
): ShowDataLayer {
return new ShowDataLayer(map, {
layer: ShowDataLayer.rangeLayer,
features,

View file

@ -37,7 +37,7 @@
}
</script>
<div class="inline-flex flex-col w-full">
<div class="inline-flex w-full flex-col">
{#if inline}
<Inline key={config.freeform.key} {tags} template={config.render}>
<ValidatedInput

View file

@ -413,7 +413,7 @@
<div class="flex" slot="title2">
<ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")} />
<Tr t={Translations.t.communityIndex.title}/>
<Tr t={Translations.t.communityIndex.title} />
</div>
<div class="m-2" slot="content2">
<CommunityIndexView location={state.mapProperties.location} />

View file

@ -39,7 +39,7 @@ export default class Locale {
"The language to display MapComplete in.",
"The user display language is determined in the following order:",
"",
"1. Use the language as set by the URL-parameter `language`. This will _disable_ setting the language by the user",
"1. Use the language as set by the URL-parameter `language` (following ISO 639-1 | ex. `language=nl`). This will _disable_ setting the language by the user",
"2. If the user did log in and did set their language before with MapComplete, use this language. This language selection is synchronized accross devices using the openstreetmap.org user preferences.",
"3. If the user visited MapComplete before and did change their language manually, this changed language will be saved in local storage. Use the language from local storage",
"4. Use the navigator-language (if available)",

View file

@ -1,5 +1,5 @@
import colors from "./assets/colors.json"
import {HTMLElement} from "node-html-parser"
import { HTMLElement } from "node-html-parser"
export class Utils {
/**
@ -459,7 +459,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
let match = txt.match(regex)
if(!match){
if (!match) {
return txt
}
let result = ""
@ -502,7 +502,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
result += normal + v
match = leftover.match(regex)
if(!match){
if (!match) {
result += leftover
}
}
@ -699,10 +699,10 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
if (Array.isArray(leaf)) {
for (let i = 0; i < (<any[]>leaf).length; i++) {
const l = (<any[]>leaf)[i]
collectedList.push({leaf: l, path: [...travelledPath, "" + i]})
collectedList.push({ leaf: l, path: [...travelledPath, "" + i] })
}
} else {
collectedList.push({leaf, path: travelledPath})
collectedList.push({ leaf, path: travelledPath })
}
return collectedList
}
@ -780,7 +780,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
})
}
const cp = {...json}
const cp = { ...json }
for (const key in json) {
cp[key] = Utils.WalkJson(json[key], f, isLeaf, [...path, key])
}
@ -910,11 +910,11 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
const xhr = new XMLHttpRequest()
xhr.onload = () => {
if (xhr.status == 200) {
resolve({content: xhr.response})
resolve({ content: xhr.response })
} else if (xhr.status === 302) {
resolve({redirect: xhr.getResponseHeader("location")})
resolve({ redirect: xhr.getResponseHeader("location") })
} else if (xhr.status === 509 || xhr.status === 429) {
resolve({error: "rate limited", url, statuscode: xhr.status})
resolve({ error: "rate limited", url, statuscode: xhr.status })
} else {
resolve({
error: "other error: " + xhr.statusText,
@ -984,10 +984,10 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
}
const promise =
/*NO AWAIT as we work with the promise directly */ Utils.downloadJsonAdvanced(
url,
headers
)
Utils._download_cache.set(url, {promise, timestamp: new Date().getTime()})
url,
headers
)
Utils._download_cache.set(url, { promise, timestamp: new Date().getTime() })
return await promise
}
@ -1006,11 +1006,11 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
const injected = Utils.injectedDownloads[url]
if (injected !== undefined) {
console.log("Using injected resource for test for URL", url)
return new Promise((resolve, _) => resolve({content: injected}))
return new Promise((resolve, _) => resolve({ content: injected }))
}
const result = await Utils.downloadAdvanced(
url,
Utils.Merge({accept: "application/json"}, headers ?? {})
Utils.Merge({ accept: "application/json" }, headers ?? {})
)
if (result["error"] !== undefined) {
return <{ error: string; url: string; statuscode?: number }>result
@ -1018,12 +1018,12 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
const data = result["content"]
try {
if (typeof data === "string") {
return {content: JSON.parse(data)}
return { content: JSON.parse(data) }
}
return {content: data}
return { content: data }
} catch (e) {
console.error("Could not parse ", data, "due to", e, "\n", e.stack)
return {error: "malformed", url}
return { error: "malformed", url }
}
}
@ -1047,7 +1047,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
const element = document.createElement("a")
let file
if (typeof contents === "string") {
file = new Blob([contents], {type: options?.mimetype ?? "text/plain"})
file = new Blob([contents], { type: options?.mimetype ?? "text/plain" })
} else {
file = contents
}
@ -1318,7 +1318,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
if (match == undefined) {
return undefined
}
return {r: Number(match[1]), g: Number(match[2]), b: Number(match[3])}
return { r: Number(match[1]), g: Number(match[2]), b: Number(match[3]) }
}
if (!hex.startsWith("#")) {
@ -1378,7 +1378,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
if (inView) {
return
}
element.scrollIntoView({behavior: "smooth", block: "nearest"})
element.scrollIntoView({ behavior: "smooth", block: "nearest" })
}
public static findParentWithScrolling(element: HTMLBaseElement): HTMLBaseElement {
@ -1470,18 +1470,59 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
const postParts = prepart.split("}")
if (postParts.length === 1) {
// This was a normal part
spec.push({message: postParts[0]})
spec.push({ message: postParts[0] })
} else {
const [subs, message] = postParts
spec.push({subs})
spec.push({ subs })
if (message !== "") {
spec.push({message})
spec.push({ message })
}
}
}
return spec
}
/**
* Returns the file and line number of the code calling this
*/
public static getLocationInCode(offset: number = 0): {
path: string
line: number
column: number
markdownLocation: string
filename: string
functionName: string
} {
const error = new Error("No error")
const stack = error.stack.split("\n")
stack.shift() // Remove "Error: No error"
const regex = /at (.*) \(([a-zA-Z0-9/.]+):([0-9]+):([0-9]+)\)/
const stackItem = stack[Math.abs(offset) + 1]
let functionName: string
let path: string
let line: string
let column: string
let _: string
const matchWithFuncName = stackItem.match(regex)
if (matchWithFuncName) {
;[_, functionName, path, line, column] = matchWithFuncName
} else {
let regexNoFuncName: RegExp = new RegExp("at ([a-zA-Z0-9/.]+):([0-9]+):([0-9]+)")
;[_, path, line, column] = stackItem.match(regexNoFuncName)
}
const markdownLocation = path.substring(path.indexOf("MapComplete/src") + 11) + "#L" + line
return {
path,
functionName,
line: Number(line),
column: Number(column),
markdownLocation,
filename: path.substring(path.lastIndexOf("/") + 1),
}
}
private static colorDiff(
c0: { r: number; g: number; b: number },
c1: { r: number; g: number; b: number }

View file

@ -1,11 +1,11 @@
{
"contributors": [
{
"commits": 5753,
"commits": 5849,
"contributor": "Pieter Vander Vennet"
},
{
"commits": 371,
"commits": 388,
"contributor": "Robin van der Linde"
},
{
@ -61,7 +61,7 @@
"contributor": "AlexanderRebai"
},
{
"commits": 19,
"commits": 20,
"contributor": "dependabot[bot]"
},
{
@ -110,11 +110,11 @@
},
{
"commits": 10,
"contributor": "LiamSimons"
"contributor": "Thibault Molleman"
},
{
"commits": 9,
"contributor": "Thibault Molleman"
"commits": 10,
"contributor": "LiamSimons"
},
{
"commits": 9,
@ -128,6 +128,10 @@
"commits": 8,
"contributor": "Mateusz Konieczny"
},
{
"commits": 7,
"contributor": "pelderson"
},
{
"commits": 7,
"contributor": "OliNau"
@ -148,10 +152,6 @@
"commits": 6,
"contributor": "danieldegroot2"
},
{
"commits": 6,
"contributor": "pelderson"
},
{
"commits": 4,
"contributor": "Nadhem"

View file

@ -10,7 +10,7 @@
"fr": "français",
"gl": "lingua galega",
"hu": "magyar",
"id": "bahasa Indonesia",
"id": "Bahasa Indonesia",
"it": "italiano",
"ja": "日本語",
"nb_NO": "bokmål",

File diff suppressed because it is too large Load diff

View file

@ -1,11 +1,11 @@
{
"contributors": [
{
"commits": 283,
"commits": 289,
"contributor": "kjon"
},
{
"commits": 275,
"commits": 277,
"contributor": "Pieter Vander Vennet"
},
{
@ -50,11 +50,11 @@
},
{
"commits": 25,
"contributor": "Reza Almanda"
"contributor": "Lucas"
},
{
"commits": 23,
"contributor": "Lucas"
"commits": 25,
"contributor": "Reza Almanda"
},
{
"commits": 22,
@ -124,6 +124,10 @@
"commits": 10,
"contributor": "Irina"
},
{
"commits": 9,
"contributor": "deep map"
},
{
"commits": 9,
"contributor": "Jaime Marquínez Ferrándiz"
@ -244,6 +248,10 @@
"commits": 5,
"contributor": "Alexey Shabanov"
},
{
"commits": 4,
"contributor": "Emory Shaw"
},
{
"commits": 4,
"contributor": "André Marcelo Alvarenga"
@ -328,10 +336,6 @@
"commits": 3,
"contributor": "SiegbjornSitumeang"
},
{
"commits": 2,
"contributor": "Emory Shaw"
},
{
"commits": 2,
"contributor": "lmagreault"
@ -440,10 +444,6 @@
"commits": 1,
"contributor": "Stéphane De Greef"
},
{
"commits": 1,
"contributor": "deep map"
},
{
"commits": 1,
"contributor": "Falk Rund"