diff --git a/.DS_Store b/.DS_Store index e1002670c3..54d492894b 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/Customizations/JSON/Denomination.ts b/Customizations/JSON/Denomination.ts index 2b9779f944..09c5ab9775 100644 --- a/Customizations/JSON/Denomination.ts +++ b/Customizations/JSON/Denomination.ts @@ -3,7 +3,6 @@ import UnitConfigJson from "./UnitConfigJson"; import Translations from "../../UI/i18n/Translations"; import BaseUIElement from "../../UI/BaseUIElement"; import Combine from "../../UI/Base/Combine"; -import {FixedUiElement} from "../../UI/Base/FixedUiElement"; export class Unit { public readonly appliesToKeys: Set; @@ -82,10 +81,7 @@ export class Unit { return undefined; } const [stripped, denom] = this.findDenomination(value) - const human = denom?.human - if(human === undefined){ - return new FixedUiElement(stripped ?? value); - } + const human = denom.human const elems = denom.prefix ? [human, stripped] : [stripped, human]; return new Combine(elems) @@ -156,7 +152,7 @@ export class Denomination { if (stripped === null) { return null; } - return (stripped + " " + this.canonical.trim()).trim(); + return stripped + " " + this.canonical.trim() } /** diff --git a/Customizations/JSON/FilterConfig.ts b/Customizations/JSON/FilterConfig.ts deleted file mode 100644 index 4993baf4e3..0000000000 --- a/Customizations/JSON/FilterConfig.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { TagsFilter } from "../../Logic/Tags/TagsFilter"; -import { Translation } from "../../UI/i18n/Translation"; -import Translations from "../../UI/i18n/Translations"; -import FilterConfigJson from "./FilterConfigJson"; -import { FromJSON } from "./FromJSON"; - -export default class FilterConfig { - readonly options: { - question: Translation; - osmTags: TagsFilter; - }[]; - - constructor(json: FilterConfigJson, context: string) { - this.options = json.options.map((option, i) => { - const question = Translations.T( - option.question, - context + ".options-[" + i + "].question" - ); - const osmTags = FromJSON.Tag( - option.osmTags, - `${context}.options-[${i}].osmTags` - ); - - return { question: question, osmTags: osmTags }; - }); - } -} diff --git a/Customizations/JSON/FilterConfigJson.ts b/Customizations/JSON/FilterConfigJson.ts deleted file mode 100644 index 082fd7fe0a..0000000000 --- a/Customizations/JSON/FilterConfigJson.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { AndOrTagConfigJson } from "./TagConfigJson"; - -export default interface FilterConfigJson { - /** - * The options for a filter - * If there are multiple options these will be a list of radio buttons - * If there is only one option this will be a checkbox - * Filtering is done based on the given osmTags that are compared to the objects in that layer. - */ - options: { question: string | any; osmTags: AndOrTagConfigJson | string }[]; -} diff --git a/Customizations/JSON/LayerConfig.ts b/Customizations/JSON/LayerConfig.ts index 0aa1fc8111..0acf8198f1 100644 --- a/Customizations/JSON/LayerConfig.ts +++ b/Customizations/JSON/LayerConfig.ts @@ -18,18 +18,19 @@ import {Tag} from "../../Logic/Tags/Tag"; import BaseUIElement from "../../UI/BaseUIElement"; import {Unit} from "./Denomination"; import DeleteConfig from "./DeleteConfig"; -import FilterConfig from "./FilterConfig"; export default class LayerConfig { + + static WAYHANDLING_DEFAULT = 0; static WAYHANDLING_CENTER_ONLY = 1; static WAYHANDLING_CENTER_AND_WAY = 2; id: string; - name: Translation; + name: Translation description: Translation; source: SourceConfig; - calculatedTags: [string, string][]; + calculatedTags: [string, string][] doNotDownload: boolean; passAllFeatures: boolean; isShown: TagRenderingConfig; @@ -38,7 +39,7 @@ export default class LayerConfig { title?: TagRenderingConfig; titleIcons: TagRenderingConfig[]; icon: TagRenderingConfig; - iconOverlays: { if: TagsFilter; then: TagRenderingConfig; badge: boolean }[]; + iconOverlays: { if: TagsFilter, then: TagRenderingConfig, badge: boolean }[] iconSize: TagRenderingConfig; label: TagRenderingConfig; rotation: TagRenderingConfig; @@ -47,40 +48,33 @@ export default class LayerConfig { dashArray: TagRenderingConfig; wayHandling: number; public readonly units: Unit[]; - public readonly deletion: DeleteConfig | null; + public readonly deletion: DeleteConfig | null presets: { title: Translation, tags: Tag[], description?: Translation, - preciseInput?: { preferredBackground?: string } }[]; - tagRenderings: TagRenderingConfig[]; - filters: FilterConfig[]; + tagRenderings: TagRenderingConfig []; - constructor( - json: LayerConfigJson, - units?: Unit[], - context?: string, - official: boolean = true - ) { + constructor(json: LayerConfigJson, + units?:Unit[], + context?: string, + official: boolean = true,) { this.units = units ?? []; context = context + "." + json.id; const self = this; this.id = json.id; this.name = Translations.T(json.name, context + ".name"); - - if (json.description !== undefined) { - if (Object.keys(json.description).length === 0) { + + if(json.description !== undefined){ + if(Object.keys(json.description).length === 0){ json.description = undefined; } } - - this.description = Translations.T( - json.description, - context + ".description" - ); + + this.description =Translations.T(json.description, context + ".description") ; let legacy = undefined; if (json["overpassTags"] !== undefined) { @@ -89,54 +83,45 @@ export default class LayerConfig { } if (json.source !== undefined) { if (legacy !== undefined) { - throw ( - context + - "Both the legacy 'layer.overpasstags' and the new 'layer.source'-field are defined" - ); + throw context + "Both the legacy 'layer.overpasstags' and the new 'layer.source'-field are defined" } let osmTags: TagsFilter = legacy; if (json.source["osmTags"]) { - osmTags = FromJSON.Tag( - json.source["osmTags"], - context + "source.osmTags" - ); + osmTags = FromJSON.Tag(json.source["osmTags"], context + "source.osmTags"); } - if (json.source["geoJsonSource"] !== undefined) { - throw context + "Use 'geoJson' instead of 'geoJsonSource'"; + if(json.source["geoJsonSource"] !== undefined){ + throw context + "Use 'geoJson' instead of 'geoJsonSource'" } - - this.source = new SourceConfig( - { - osmTags: osmTags, - geojsonSource: json.source["geoJson"], - geojsonSourceLevel: json.source["geoJsonZoomLevel"], - overpassScript: json.source["overpassScript"], - isOsmCache: json.source["isOsmCache"], - }, - this.id - ); + + this.source = new SourceConfig({ + osmTags: osmTags, + geojsonSource: json.source["geoJson"], + geojsonSourceLevel: json.source["geoJsonZoomLevel"], + overpassScript: json.source["overpassScript"], + isOsmCache: json.source["isOsmCache"] + }, this.id); } else { this.source = new SourceConfig({ - osmTags: legacy, - }); + osmTags: legacy + }) } + this.calculatedTags = undefined; if (json.calculatedTags !== undefined) { if (!official) { - console.warn( - `Unofficial theme ${this.id} with custom javascript! This is a security risk` - ); + console.warn(`Unofficial theme ${this.id} with custom javascript! This is a security risk`) } this.calculatedTags = []; for (const kv of json.calculatedTags) { - const index = kv.indexOf("="); + + const index = kv.indexOf("=") const key = kv.substring(0, index); const code = kv.substring(index + 1); - - this.calculatedTags.push([key, code]); + + this.calculatedTags.push([key, code]) } } @@ -145,19 +130,13 @@ export default class LayerConfig { this.minzoom = json.minzoom ?? 0; this.maxzoom = json.maxzoom ?? 1000; this.wayHandling = json.wayHandling ?? 0; - this.presets = (json.presets ?? []).map((pr, i) => { - if (pr.preciseInput === true) { - pr.preciseInput = { - preferredBackground: undefined - } - } - return { + this.presets = (json.presets ?? []).map((pr, i) => + ({ title: Translations.T(pr.title, `${context}.presets[${i}].title`), - tags: pr.tags.map((t) => FromJSON.SimpleTag(t)), - description: Translations.T(pr.description, `${context}.presets[${i}].description`), - preciseInput: pr.preciseInput - } - }); + tags: pr.tags.map(t => FromJSON.SimpleTag(t)), + description: Translations.T(pr.description, `${context}.presets[${i}].description`) + })) + /** Given a key, gets the corresponding property from the json (or the default if not found * @@ -169,11 +148,7 @@ export default class LayerConfig { if (deflt === undefined) { return undefined; } - return new TagRenderingConfig( - deflt, - self.source.osmTags, - `${context}.${key}.default value` - ); + return new TagRenderingConfig(deflt, self.source.osmTags, `${context}.${key}.default value`); } if (typeof v === "string") { const shared = SharedTagRenderings.SharedTagRendering.get(v); @@ -181,80 +156,54 @@ export default class LayerConfig { return shared; } } - return new TagRenderingConfig( - v, - self.source.osmTags, - `${context}.${key}` - ); + return new TagRenderingConfig(v, self.source.osmTags, `${context}.${key}`); } /** * Converts a list of tagRenderingCOnfigJSON in to TagRenderingConfig * A string is interpreted as a name to call */ - function trs( - tagRenderings?: (string | TagRenderingConfigJson)[], - readOnly = false - ) { + function trs(tagRenderings?: (string | TagRenderingConfigJson)[], readOnly = false) { if (tagRenderings === undefined) { return []; } - return Utils.NoNull( - tagRenderings.map((renderingJson, i) => { + return Utils.NoNull(tagRenderings.map( + (renderingJson, i) => { if (typeof renderingJson === "string") { + if (renderingJson === "questions") { if (readOnly) { - throw `A tagrendering has a question, but asking a question does not make sense here: is it a title icon or a geojson-layer? ${context}. The offending tagrendering is ${JSON.stringify( - renderingJson - )}`; + throw `A tagrendering has a question, but asking a question does not make sense here: is it a title icon or a geojson-layer? ${context}. The offending tagrendering is ${JSON.stringify(renderingJson)}` } - return new TagRenderingConfig("questions", undefined); + return new TagRenderingConfig("questions", undefined) } - const shared = - SharedTagRenderings.SharedTagRendering.get(renderingJson); + + const shared = SharedTagRenderings.SharedTagRendering.get(renderingJson); if (shared !== undefined) { return shared; } - - const keys = Array.from( - SharedTagRenderings.SharedTagRendering.keys() - ); - - if (Utils.runningFromConsole) { + + const keys = Array.from(SharedTagRenderings.SharedTagRendering.keys()) + + if(Utils.runningFromConsole){ return undefined; } - - throw `Predefined tagRendering ${renderingJson} not found in ${context}.\n Try one of ${keys.join( - ", " - )}\n If you intent to output this text literally, use {\"render\": } instead"}`; + + throw `Predefined tagRendering ${renderingJson} not found in ${context}.\n Try one of ${(keys.join(", "))}\n If you intent to output this text literally, use {\"render\": } instead"}`; } - return new TagRenderingConfig( - renderingJson, - self.source.osmTags, - `${context}.tagrendering[${i}]` - ); - }) - ); + return new TagRenderingConfig(renderingJson, self.source.osmTags, `${context}.tagrendering[${i}]`); + })); } this.tagRenderings = trs(json.tagRenderings, false); - this.filters = (json.filter ?? []).map((option, i) => { - return new FilterConfig(option, `${context}.filter-[${i}]`) - }); const titleIcons = []; - const defaultIcons = [ - "phonelink", - "emaillink", - "wikipedialink", - "osmlink", - "sharelink", - ]; - for (const icon of json.titleIcons ?? defaultIcons) { + const defaultIcons = ["phonelink", "emaillink", "wikipedialink", "osmlink", "sharelink"]; + for (const icon of (json.titleIcons ?? defaultIcons)) { if (icon === "defaults") { titleIcons.push(...defaultIcons); } else { @@ -264,85 +213,74 @@ export default class LayerConfig { this.titleIcons = trs(titleIcons, true); + this.title = tr("title", undefined); this.icon = tr("icon", ""); this.iconOverlays = (json.iconOverlays ?? []).map((overlay, i) => { - let tr = new TagRenderingConfig( - overlay.then, - self.source.osmTags, - `iconoverlays.${i}` - ); - if ( - typeof overlay.then === "string" && - SharedTagRenderings.SharedIcons.get(overlay.then) !== undefined - ) { + let tr = new TagRenderingConfig(overlay.then, self.source.osmTags, `iconoverlays.${i}`); + if (typeof overlay.then === "string" && SharedTagRenderings.SharedIcons.get(overlay.then) !== undefined) { tr = SharedTagRenderings.SharedIcons.get(overlay.then); } return { if: FromJSON.Tag(overlay.if), then: tr, - badge: overlay.badge ?? false, - }; + badge: overlay.badge ?? false + } }); const iconPath = this.icon.GetRenderValue({id: "node/-1"}).txt; if (iconPath.startsWith(Utils.assets_path)) { const iconKey = iconPath.substr(Utils.assets_path.length); if (Svg.All[iconKey] === undefined) { - throw "Builtin SVG asset not found: " + iconPath; + throw "Builtin SVG asset not found: " + iconPath } } this.isShown = tr("isShown", "yes"); this.iconSize = tr("iconSize", "40,40,center"); - this.label = tr("label", ""); + this.label = tr("label", "") this.color = tr("color", "#0000ff"); this.width = tr("width", "7"); this.rotation = tr("rotation", "0"); this.dashArray = tr("dashArray", ""); - + this.deletion = null; - if (json.deletion === true) { - json.deletion = {}; + if(json.deletion === true){ + json.deletion = { + } } - if (json.deletion !== undefined && json.deletion !== false) { - this.deletion = new DeleteConfig(json.deletion, `${context}.deletion`); + if(json.deletion !== undefined && json.deletion !== false){ + this.deletion = new DeleteConfig(json.deletion, `${context}.deletion`) } + if (json["showIf"] !== undefined) { - throw ( - "Invalid key on layerconfig " + - this.id + - ": showIf. Did you mean 'isShown' instead?" - ); + throw "Invalid key on layerconfig " + this.id + ": showIf. Did you mean 'isShown' instead?"; } } public CustomCodeSnippets(): string[] { if (this.calculatedTags === undefined) { - return []; + return [] } - return this.calculatedTags.map((code) => code[1]); + return this.calculatedTags.map(code => code[1]); } public AddRoamingRenderings(addAll: { - tagRenderings: TagRenderingConfig[]; - titleIcons: TagRenderingConfig[]; - iconOverlays: { - if: TagsFilter; - then: TagRenderingConfig; - badge: boolean; - }[]; + tagRenderings: TagRenderingConfig[], + titleIcons: TagRenderingConfig[], + iconOverlays: { "if": TagsFilter, then: TagRenderingConfig, badge: boolean }[] + }): LayerConfig { - let insertionPoint = this.tagRenderings - .map((tr) => tr.IsQuestionBoxElement()) - .indexOf(true); + + let insertionPoint = this.tagRenderings.map(tr => tr.IsQuestionBoxElement()).indexOf(true) if (insertionPoint < 0) { // No 'questions' defined - we just add them all to the end insertionPoint = this.tagRenderings.length; } this.tagRenderings.splice(insertionPoint, 0, ...addAll.tagRenderings); + this.iconOverlays.push(...addAll.iconOverlays); for (const icon of addAll.titleIcons) { this.titleIcons.splice(0, 0, icon); @@ -351,42 +289,40 @@ export default class LayerConfig { } public GetRoamingRenderings(): { - tagRenderings: TagRenderingConfig[]; - titleIcons: TagRenderingConfig[]; - iconOverlays: { - if: TagsFilter; - then: TagRenderingConfig; - badge: boolean; - }[]; + tagRenderings: TagRenderingConfig[], + titleIcons: TagRenderingConfig[], + iconOverlays: { "if": TagsFilter, then: TagRenderingConfig, badge: boolean }[] + } { - const tagRenderings = this.tagRenderings.filter((tr) => tr.roaming); - const titleIcons = this.titleIcons.filter((tr) => tr.roaming); - const iconOverlays = this.iconOverlays.filter((io) => io.then.roaming); + + const tagRenderings = this.tagRenderings.filter(tr => tr.roaming); + const titleIcons = this.titleIcons.filter(tr => tr.roaming); + const iconOverlays = this.iconOverlays.filter(io => io.then.roaming) return { tagRenderings: tagRenderings, titleIcons: titleIcons, - iconOverlays: iconOverlays, - }; + iconOverlays: iconOverlays + } + } - public GenerateLeafletStyle( - tags: UIEventSource, - clickable: boolean, - widthHeight = "100%" - ): { - icon: { - html: BaseUIElement; - iconSize: [number, number]; - iconAnchor: [number, number]; - popupAnchor: [number, number]; - iconUrl: string; - className: string; - }; - color: string; - weight: number; - dashArray: number[]; - } { + public GenerateLeafletStyle(tags: UIEventSource, clickable: boolean, widthHeight= "100%"): + { + icon: + { + html: BaseUIElement, + iconSize: [number, number], + iconAnchor: [number, number], + popupAnchor: [number, number], + iconUrl: string, + className: string + }, + color: string, + weight: number, + dashArray: number[] + } { + function num(str, deflt = 40) { const n = Number(str); if (isNaN(n)) { @@ -405,7 +341,7 @@ export default class LayerConfig { } function render(tr: TagRenderingConfig, deflt?: string) { - const str = tr?.GetRenderValue(tags.data)?.txt ?? deflt; + const str = (tr?.GetRenderValue(tags.data)?.txt ?? deflt); return Utils.SubstituteKeys(str, tags.data).replace(/{.*}/g, ""); } @@ -414,16 +350,14 @@ export default class LayerConfig { let color = render(this.color, "#00f"); if (color.startsWith("--")) { - color = getComputedStyle(document.body).getPropertyValue( - "--catch-detail-color" - ); + color = getComputedStyle(document.body).getPropertyValue("--catch-detail-color") } const weight = rendernum(this.width, 5); const iconW = num(iconSize[0]); let iconH = num(iconSize[1]); - const mode = iconSize[2]?.trim()?.toLowerCase() ?? "center"; + const mode = iconSize[2]?.trim()?.toLowerCase() ?? "center" let anchorW = iconW / 2; let anchorH = iconH / 2; @@ -443,35 +377,31 @@ export default class LayerConfig { const iconUrlStatic = render(this.icon); const self = this; - const mappedHtml = tags.map((tgs) => { + const mappedHtml = tags.map(tgs => { function genHtmlFromString(sourcePart: string): BaseUIElement { + const style = `width:100%;height:100%;transform: rotate( ${rotation} );display:block;position: absolute; top: 0; left: 0`; - let html: BaseUIElement = new FixedUiElement( - `` - ); - const match = sourcePart.match(/([a-zA-Z0-9_]*):([^;]*)/); + let html: BaseUIElement = new FixedUiElement(``); + const match = sourcePart.match(/([a-zA-Z0-9_]*):([^;]*)/) if (match !== null && Svg.All[match[1] + ".svg"] !== undefined) { html = new Combine([ - (Svg.All[match[1] + ".svg"] as string).replace( - /#000000/g, - match[2] - ), + (Svg.All[match[1] + ".svg"] as string) + .replace(/#000000/g, match[2]) ]).SetStyle(style); } return html; } + // What do you mean, 'tgs' is never read? // It is read implicitly in the 'render' method const iconUrl = render(self.icon); const rotation = render(self.rotation, "0deg"); let htmlParts: BaseUIElement[] = []; - let sourceParts = Utils.NoNull( - iconUrl.split(";").filter((prt) => prt != "") - ); + let sourceParts = Utils.NoNull(iconUrl.split(";").filter(prt => prt != "")); for (const sourcePart of sourceParts) { - htmlParts.push(genHtmlFromString(sourcePart)); + htmlParts.push(genHtmlFromString(sourcePart)) } let badges = []; @@ -481,88 +411,79 @@ export default class LayerConfig { } if (iconOverlay.badge) { const badgeParts: BaseUIElement[] = []; - const partDefs = iconOverlay.then - .GetRenderValue(tgs) - .txt.split(";") - .filter((prt) => prt != ""); + const partDefs = iconOverlay.then.GetRenderValue(tgs).txt.split(";").filter(prt => prt != ""); for (const badgePartStr of partDefs) { - badgeParts.push(genHtmlFromString(badgePartStr)); + badgeParts.push(genHtmlFromString(badgePartStr)) } - const badgeCompound = new Combine(badgeParts).SetStyle( - "display:flex;position:relative;width:100%;height:100%;" - ); + const badgeCompound = new Combine(badgeParts) + .SetStyle("display:flex;position:relative;width:100%;height:100%;"); + + badges.push(badgeCompound) - badges.push(badgeCompound); } else { - htmlParts.push( - genHtmlFromString(iconOverlay.then.GetRenderValue(tgs).txt) - ); + htmlParts.push(genHtmlFromString( + iconOverlay.then.GetRenderValue(tgs).txt)); } } if (badges.length > 0) { - const badgesComponent = new Combine(badges).SetStyle( - "display:flex;height:50%;width:100%;position:absolute;top:50%;left:50%;" - ); - htmlParts.push(badgesComponent); + const badgesComponent = new Combine(badges) + .SetStyle("display:flex;height:50%;width:100%;position:absolute;top:50%;left:50%;"); + htmlParts.push(badgesComponent) } if (sourceParts.length == 0) { - iconH = 0; + iconH = 0 } try { - const label = self.label - ?.GetRenderValue(tgs) - ?.Subs(tgs) + + const label = self.label?.GetRenderValue(tgs)?.Subs(tgs) ?.SetClass("block text-center") - ?.SetStyle("margin-top: " + (iconH + 2) + "px"); + ?.SetStyle("margin-top: " + (iconH + 2) + "px") if (label !== undefined) { - htmlParts.push( - new Combine([label]).SetClass("flex flex-col items-center") - ); + htmlParts.push(new Combine([label]).SetClass("flex flex-col items-center")) } } catch (e) { - console.error(e, tgs); + console.error(e, tgs) } return new Combine(htmlParts); - }); + }) + return { - icon: { - html: new VariableUiElement(mappedHtml), - iconSize: [iconW, iconH], - iconAnchor: [anchorW, anchorH], - popupAnchor: [0, 3 - anchorH], - iconUrl: iconUrlStatic, - className: clickable - ? "leaflet-div-icon" - : "leaflet-div-icon unclickable", - }, + icon: + { + html: new VariableUiElement(mappedHtml), + iconSize: [iconW, iconH], + iconAnchor: [anchorW, anchorH], + popupAnchor: [0, 3 - anchorH], + iconUrl: iconUrlStatic, + className: clickable ? "leaflet-div-icon" : "leaflet-div-icon unclickable" + }, color: color, weight: weight, - dashArray: dashArray, + dashArray: dashArray }; } public ExtractImages(): Set { - const parts: Set[] = []; - parts.push(...this.tagRenderings?.map((tr) => tr.ExtractImages(false))); - parts.push(...this.titleIcons?.map((tr) => tr.ExtractImages(true))); - parts.push(this.icon?.ExtractImages(true)); - parts.push( - ...this.iconOverlays?.map((overlay) => overlay.then.ExtractImages(true)) - ); + const parts: Set[] = [] + parts.push(...this.tagRenderings?.map(tr => tr.ExtractImages(false))) + parts.push(...this.titleIcons?.map(tr => tr.ExtractImages(true))) + parts.push(this.icon?.ExtractImages(true)) + parts.push(...this.iconOverlays?.map(overlay => overlay.then.ExtractImages(true))) for (const preset of this.presets) { - parts.push(new Set(preset.description?.ExtractImages(false))); + parts.push(new Set(preset.description?.ExtractImages(false))) } const allIcons = new Set(); for (const part of parts) { - part?.forEach(allIcons.add, allIcons); + part?.forEach(allIcons.add, allIcons) } return allIcons; } -} + +} \ No newline at end of file diff --git a/Customizations/JSON/LayerConfigJson.ts b/Customizations/JSON/LayerConfigJson.ts index b2dd13c74f..d81307fd9f 100644 --- a/Customizations/JSON/LayerConfigJson.ts +++ b/Customizations/JSON/LayerConfigJson.ts @@ -1,7 +1,6 @@ import {TagRenderingConfigJson} from "./TagRenderingConfigJson"; import {AndOrTagConfigJson} from "./TagConfigJson"; import {DeleteConfigJson} from "./DeleteConfigJson"; -import FilterConfigJson from "./FilterConfigJson"; /** * Configuration for a single layer @@ -218,16 +217,6 @@ export interface LayerConfigJson { * (The first sentence is until the first '.'-character in the description) */ description?: string | any, - - /** - * If set, the user will prompted to confirm the location before actually adding the data. - * THis will be with a 'drag crosshair'-method. - * - * If 'preferredBackgroundCategory' is set, the element will attempt to pick a background layer of that category. - */ - preciseInput?: true | { - preferredBackground: "osmbasedmap" | "photo" | "historicphoto" | "map" | string - } }[], /** @@ -244,12 +233,6 @@ export interface LayerConfigJson { */ tagRenderings?: (string | TagRenderingConfigJson) [], - - /** - * All the extra questions for filtering - */ - filter?: (FilterConfigJson) [], - /** * This block defines under what circumstances the delete dialog is shown for objects of this layer. * If set, a dialog is shown to the user to (soft) delete the point. diff --git a/Customizations/JSON/LayoutConfig.ts b/Customizations/JSON/LayoutConfig.ts index e76c68ac88..12b9d5f766 100644 --- a/Customizations/JSON/LayoutConfig.ts +++ b/Customizations/JSON/LayoutConfig.ts @@ -42,7 +42,6 @@ export default class LayoutConfig { public readonly enableGeolocation: boolean; public readonly enableBackgroundLayerSelection: boolean; public readonly enableShowAllQuestions: boolean; - public readonly enableExportButton: boolean; public readonly customCss?: string; /* How long is the cache valid, in seconds? @@ -153,7 +152,6 @@ export default class LayoutConfig { this.enableAddNewPoints = json.enableAddNewPoints ?? true; this.enableBackgroundLayerSelection = json.enableBackgroundLayerSelection ?? true; this.enableShowAllQuestions = json.enableShowAllQuestions ?? false; - this.enableExportButton = json.enableExportButton ?? false; this.customCss = json.customCss; this.cacheTimeout = json.cacheTimout ?? (60 * 24 * 60 * 60) diff --git a/Customizations/JSON/LayoutConfigJson.ts b/Customizations/JSON/LayoutConfigJson.ts index d36a8463d6..8ced24bd7e 100644 --- a/Customizations/JSON/LayoutConfigJson.ts +++ b/Customizations/JSON/LayoutConfigJson.ts @@ -15,7 +15,6 @@ import UnitConfigJson from "./UnitConfigJson"; * General remark: a type (string | any) indicates either a fixed or a translatable string. */ export interface LayoutConfigJson { - /** * The id of this layout. * @@ -226,10 +225,6 @@ export interface LayoutConfigJson { * * Not only do we want to write consistent data to OSM, we also want to present this consistently to the user. * This is handled by defining units. - * - * # Rendering - * - * To render a value with long (human) denomination, use {canonical(key)} * * # Usage * @@ -336,5 +331,4 @@ export interface LayoutConfigJson { enableGeolocation?: boolean; enableBackgroundLayerSelection?: boolean; enableShowAllQuestions?: boolean; - enableExportButton?: boolean; } diff --git a/Customizations/JSON/TagRenderingConfig.ts b/Customizations/JSON/TagRenderingConfig.ts index 7b36dae44b..d7e55ed8d8 100644 --- a/Customizations/JSON/TagRenderingConfig.ts +++ b/Customizations/JSON/TagRenderingConfig.ts @@ -26,9 +26,6 @@ export default class TagRenderingConfig { readonly key: string, readonly type: string, readonly addExtraTags: TagsFilter[]; - readonly inline: boolean, - readonly default?: string, - readonly helperArgs?: (string | number | boolean)[] }; readonly multiAnswer: boolean; @@ -76,9 +73,7 @@ export default class TagRenderingConfig { type: json.freeform.type ?? "string", addExtraTags: json.freeform.addExtraTags?.map((tg, i) => FromJSON.Tag(tg, `${context}.extratag[${i}]`)) ?? [], - inline: json.freeform.inline ?? false, - default: json.freeform.default, - helperArgs: json.freeform.helperArgs + } if (json.freeform["extraTags"] !== undefined) { @@ -337,20 +332,20 @@ export default class TagRenderingConfig { * Note: this might be hidden by conditions */ public hasMinimap(): boolean { - const translations: Translation[] = Utils.NoNull([this.render, ...(this.mappings ?? []).map(m => m.then)]); + const translations : Translation[]= Utils.NoNull([this.render, ...(this.mappings ?? []).map(m => m.then)]); for (const translation of translations) { for (const key in translation.translations) { - if (!translation.translations.hasOwnProperty(key)) { + if(!translation.translations.hasOwnProperty(key)){ continue } const template = translation.translations[key] const parts = SubstitutedTranslation.ExtractSpecialComponents(template) - const hasMiniMap = parts.filter(part => part.special !== undefined).some(special => special.special.func.funcName === "minimap") - if (hasMiniMap) { + const hasMiniMap = parts.filter(part =>part.special !== undefined ).some(special => special.special.func.funcName === "minimap") + if(hasMiniMap){ return true; } } } return false; - } + } } \ No newline at end of file diff --git a/Customizations/JSON/TagRenderingConfigJson.ts b/Customizations/JSON/TagRenderingConfigJson.ts index 8438895255..7dfaae82bf 100644 --- a/Customizations/JSON/TagRenderingConfigJson.ts +++ b/Customizations/JSON/TagRenderingConfigJson.ts @@ -30,7 +30,6 @@ export interface TagRenderingConfigJson { * Allow freeform text input from the user */ freeform?: { - /** * If this key is present, then 'render' is used to display the value. * If this is undefined, the rendering is _always_ shown @@ -41,30 +40,13 @@ export interface TagRenderingConfigJson { * See Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values */ type?: string, - /** - * Extra parameters to initialize the input helper arguments. - * For semantics, see the 'SpecialInputElements.md' - */ - helperArgs?: (string | number | boolean)[]; /** * If a value is added with the textfield, these extra tag is addded. * Useful to add a 'fixme=freeform textfield used - to be checked' **/ addExtraTags?: string[]; - /** - * When set, influences the way a question is asked. - * Instead of showing a full-widht text field, the text field will be shown within the rendering of the question. - * - * This combines badly with special input elements, as it'll distort the layout. - */ - inline?: boolean - - /** - * default value to enter if no previous tagging is present. - * Normally undefined (aka do not enter anything) - */ - default?: string + }, /** diff --git a/Docs/Development_deployment.md b/Docs/Development_deployment.md index 732336b571..ffa69eab3f 100644 --- a/Docs/Development_deployment.md +++ b/Docs/Development_deployment.md @@ -18,9 +18,9 @@ Development ----------- - **Windows users**: All scripts are made for linux devices. Use the Ubuntu terminal for Windows (or even better - make the switch ;) ). If you are using Visual Studio Code you can use a [WSL Remote](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-wsl) window, or use the Devcontainer (see more details later). + **Windows users**: All scripts are made for linux devices. Use the Ubuntu terminal for Windows (or even better - make the switch ;) ). If you are using Visual Studio, open everything in a 'new WSL Window'. - To develop and build MapComplete, you + To develop and build MapComplete, yo 0. Make sure you have a recent version of nodejs - at least 12.0, preferably 15 0. Make a fork and clone the repository. @@ -29,30 +29,6 @@ 4. Run `npm run start` to host a local testversion at http://localhost:1234/index.html 5. By default, a landing page with available themes is served. In order to load a single theme, use `layout=themename` or `userlayout=true#` as [Query parameter](URL_Parameters.md). Note that the shorter URLs (e.g. `bookcases.html`, `aed.html`, ...) _don't_ exist on the development version. - Development using Windows - ------------------------ - - For Windows you can use the devcontainer, or the WSL subsystem. - - To use the devcontainer in Visual Studio Code: - -0. Make sure you have installed the [Remote - Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) extension and it's dependencies. -1. Make a fork and clone the repository. -2. After cloning, Visual Studio Code will ask you if you want to use the devcontainer. -3. Then you can either clone it again in a volume (for better performance), or open the current folder in a container. -4. By now, you should be able to run `npm run start` to host a local testversion at http://localhost:1234/index.html -5. By default, a landing page with available themes is served. In order to load a single theme, use `layout=themename` or `userlayout=true#` as [Query parameter](URL_Parameters.md). Note that the shorter URLs (e.g. `bookcases.html`, `aed.html`, ...) _don't_ exist on the development version. - - To use the WSL in Visual Studio Code: - -0. Make sure you have installed the [Remote - WSL]() extension and it's dependencies. -1. Open a remote WSL window using the button in the bottom left. -2. Make a fork and clone the repository. -3. Install `npm` using `sudo apt install npm`. -4. Run `npm run init` and generate some additional dependencies and generated files. Note that it'll install the dependencies too -5. Run `npm run start` to host a local testversion at http://localhost:1234/index.html -6. By default, a landing page with available themes is served. In order to load a single theme, use `layout=themename` or `userlayout=true#` as [Query parameter](URL_Parameters.md). Note that the shorter URLs (e.g. `bookcases.html`, `aed.html`, ...) _don't_ exist on the development version. - Automatic deployment -------------------- diff --git a/Docs/URL_Parameters.md b/Docs/URL_Parameters.md index 5c3158fd7e..6f299adcf9 100644 --- a/Docs/URL_Parameters.md +++ b/Docs/URL_Parameters.md @@ -20,158 +20,126 @@ the URL-parameters are stated in the part between the `?` and the `#`. There are Finally, the URL-hash is the part after the `#`. It is `node/1234` in this case. -backend ---------- - -The OSM backend to use - can be used to redirect mapcomplete to the testing backend when using 'osm-test' The default value is _osm_ - - -test ------- - -If true, 'dryrun' mode is activated. The app will behave as normal, except that changes to OSM will be printed onto the console instead of actually uploaded to osm.org The default value is _false_ - - -layout --------- - -The layout to load into MapComplete The default value is __ - - -userlayout ------------- - -If not 'false', a custom (non-official) theme is loaded. This custom layout can be done in multiple ways: - -- The hash of the URL contains a base64-encoded .json-file containing the theme definition -- The hash of the URL contains a lz-compressed .json-file, as generated by the custom theme generator -- The parameter itself is an URL, in which case that URL will be downloaded. It should point to a .json of a theme The default value is _false_ - - -layer-control-toggle + layer-control-toggle ---------------------- -Whether or not the layer control is shown The default value is _false_ + Whether or not the layer control is shown The default value is _false_ -tab + tab ----- -The tab that is shown in the welcome-message. 0 = the explanation of the theme,1 = OSM-credits, 2 = sharescreen, 3 = more themes, 4 = about mapcomplete (user must be logged in and have >50 changesets) The default value is _0_ + The tab that is shown in the welcome-message. 0 = the explanation of the theme,1 = OSM-credits, 2 = sharescreen, 3 = more themes, 4 = about mapcomplete (user must be logged in and have >50 changesets) The default value is _0_ -z + z --- -The initial/current zoom level The default value is _14_ + The initial/current zoom level The default value is _0_ -lat + lat ----- -The initial/current latitude The default value is _51.2095_ + The initial/current latitude The default value is _0_ -lon + lon ----- -The initial/current longitude of the app The default value is _3.2228_ + The initial/current longitude of the app The default value is _0_ -fs-userbadge + fs-userbadge -------------- -Disables/Enables the user information pill (userbadge) at the top left. Disabling this disables logging in and thus disables editing all together, effectively putting MapComplete into read-only mode. The default value is _true_ + Disables/Enables the user information pill (userbadge) at the top left. Disabling this disables logging in and thus disables editing all together, effectively putting MapComplete into read-only mode. The default value is _true_ -fs-search + fs-search ----------- -Disables/Enables the search bar The default value is _true_ + Disables/Enables the search bar The default value is _true_ -fs-layers + fs-layers ----------- -Disables/Enables the layer control The default value is _true_ + Disables/Enables the layer control The default value is _true_ -fs-add-new + fs-add-new ------------ -Disables/Enables the 'add new feature'-popup. (A theme without presets might not have it in the first place) The default value is _true_ + Disables/Enables the 'add new feature'-popup. (A theme without presets might not have it in the first place) The default value is _true_ -fs-welcome-message + fs-welcome-message -------------------- -Disables/enables the help menu or welcome message The default value is _true_ + Disables/enables the help menu or welcome message The default value is _true_ -fs-iframe + fs-iframe ----------- -Disables/Enables the iframe-popup The default value is _false_ + Disables/Enables the iframe-popup The default value is _false_ -fs-more-quests + fs-more-quests ---------------- -Disables/Enables the 'More Quests'-tab in the welcome message The default value is _true_ + Disables/Enables the 'More Quests'-tab in the welcome message The default value is _true_ -fs-share-screen + fs-share-screen ----------------- -Disables/Enables the 'Share-screen'-tab in the welcome message The default value is _true_ + Disables/Enables the 'Share-screen'-tab in the welcome message The default value is _true_ -fs-geolocation + fs-geolocation ---------------- -Disables/Enables the geolocation button The default value is _true_ + Disables/Enables the geolocation button The default value is _true_ -fs-all-questions + fs-all-questions ------------------ -Always show all questions The default value is _false_ + Always show all questions The default value is _false_ -fs-export ------------ + test +------ -If set, enables the 'download'-button to download everything as geojson The default value is _false_ + If true, 'dryrun' mode is activated. The app will behave as normal, except that changes to OSM will be printed onto the console instead of actually uploaded to osm.org The default value is _false_ -fake-user ------------ - -If true, 'dryrun' mode is activated and a fake user account is loaded The default value is _false_ - - -debug + debug ------- -If true, shows some extra debugging help such as all the available tags on every object The default value is _false_ + If true, shows some extra debugging help such as all the available tags on every object The default value is _false_ -custom-css + backend +--------- + + The OSM backend to use - can be used to redirect mapcomplete to the testing backend when using 'osm-test' The default value is _osm_ + + + custom-css ------------ -If specified, the custom css from the given link will be loaded additionaly The default value is __ + If specified, the custom css from the given link will be loaded additionaly The default value is __ -background + background ------------ -The id of the background layer to start with The default value is _osm_ + The id of the background layer to start with The default value is _osm_ -oauth_token -------------- - -Used to complete the login No default value set layer- ------------------ diff --git a/InitUiElements.ts b/InitUiElements.ts index dae9ac3479..b2f8fe09a1 100644 --- a/InitUiElements.ts +++ b/InitUiElements.ts @@ -1,19 +1,19 @@ -import {CenterFlexedElement} from "./UI/Base/CenterFlexedElement"; -import {FixedUiElement} from "./UI/Base/FixedUiElement"; +import { CenterFlexedElement } from "./UI/Base/CenterFlexedElement"; +import { FixedUiElement } from "./UI/Base/FixedUiElement"; import Toggle from "./UI/Input/Toggle"; -import {Basemap} from "./UI/BigComponents/Basemap"; +import { Basemap } from "./UI/BigComponents/Basemap"; import State from "./State"; import LoadFromOverpass from "./Logic/Actors/OverpassFeatureSource"; -import {UIEventSource} from "./Logic/UIEventSource"; -import {QueryParameters} from "./Logic/Web/QueryParameters"; +import { UIEventSource } from "./Logic/UIEventSource"; +import { QueryParameters } from "./Logic/Web/QueryParameters"; import StrayClickHandler from "./Logic/Actors/StrayClickHandler"; import SimpleAddUI from "./UI/BigComponents/SimpleAddUI"; import CenterMessageBox from "./UI/CenterMessageBox"; import UserBadge from "./UI/BigComponents/UserBadge"; import SearchAndGo from "./UI/BigComponents/SearchAndGo"; import GeoLocationHandler from "./Logic/Actors/GeoLocationHandler"; -import {LocalStorageSource} from "./Logic/Web/LocalStorageSource"; -import {Utils} from "./Utils"; +import { LocalStorageSource } from "./Logic/Web/LocalStorageSource"; +import { Utils } from "./Utils"; import Svg from "./Svg"; import Link from "./UI/Base/Link"; import * as personal from "./assets/themes/personalLayout/personalLayout.json"; @@ -34,570 +34,543 @@ import MapControlButton from "./UI/MapControlButton"; import Combine from "./UI/Base/Combine"; import SelectedFeatureHandler from "./Logic/Actors/SelectedFeatureHandler"; import LZString from "lz-string"; -import {LayoutConfigJson} from "./Customizations/JSON/LayoutConfigJson"; +import { LayoutConfigJson } from "./Customizations/JSON/LayoutConfigJson"; import AttributionPanel from "./UI/BigComponents/AttributionPanel"; import ContributorCount from "./Logic/ContributorCount"; import FeatureSource from "./Logic/FeatureSource/FeatureSource"; import AllKnownLayers from "./Customizations/AllKnownLayers"; import LayerConfig from "./Customizations/JSON/LayerConfig"; import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers"; -import {SimpleMapScreenshoter} from "leaflet-simple-map-screenshoter"; -import jsPDF from "jspdf"; import FilterView from "./UI/BigComponents/FilterView"; export class InitUiElements { - static InitAll( - layoutToUse: LayoutConfig, - layoutFromBase64: string, - testing: UIEventSource, - layoutName: string, - layoutDefinition: string = "" + static InitAll( + layoutToUse: LayoutConfig, + layoutFromBase64: string, + testing: UIEventSource, + layoutName: string, + layoutDefinition: string = "" + ) { + if (layoutToUse === undefined) { + console.log("Incorrect layout"); + new FixedUiElement( + `Error: incorrect layout ${layoutName}
Go back` + ) + .AttachTo("centermessage") + .onClick(() => {}); + throw "Incorrect layout"; + } + + console.log( + "Using layout: ", + layoutToUse.id, + "LayoutFromBase64 is ", + layoutFromBase64 + ); + + State.state = new State(layoutToUse); + + // This 'leaks' the global state via the window object, useful for debugging + // @ts-ignore + window.mapcomplete_state = State.state; + + if (layoutToUse.hideFromOverview) { + State.state.osmConnection + .GetPreference("hidden-theme-" + layoutToUse.id + "-enabled") + .setData("true"); + } + + if (layoutFromBase64 !== "false") { + State.state.layoutDefinition = layoutDefinition; + console.log( + "Layout definition:", + Utils.EllipsesAfter(State.state.layoutDefinition, 100) + ); + if (testing.data !== "true") { + State.state.osmConnection.OnLoggedIn(() => { + State.state.osmConnection + .GetLongPreference("installed-theme-" + layoutToUse.id) + .setData(State.state.layoutDefinition); + }); + } else { + console.warn( + "NOT saving custom layout to OSM as we are tesing -> probably in an iFrame" + ); + } + } + + function updateFavs() { + // This is purely for the personal theme to load the layers there + const favs = State.state.favouriteLayers.data ?? []; + + const neededLayers = new Set(); + + console.log("Favourites are: ", favs); + layoutToUse.layers.splice(0, layoutToUse.layers.length); + let somethingChanged = false; + for (const fav of favs) { + if (AllKnownLayers.sharedLayers.has(fav)) { + const layer = AllKnownLayers.sharedLayers.get(fav); + if (!neededLayers.has(layer)) { + neededLayers.add(layer); + somethingChanged = true; + } + } + + for (const layouts of State.state.installedThemes.data) { + for (const layer of layouts.layout.layers) { + if (typeof layer === "string") { + continue; + } + if (layer.id === fav) { + if (!neededLayers.has(layer)) { + neededLayers.add(layer); + somethingChanged = true; + } + } + } + } + } + if (somethingChanged) { + console.log("layoutToUse.layers:", layoutToUse.layers); + State.state.layoutToUse.data.layers = Array.from(neededLayers); + State.state.layoutToUse.ping(); + State.state.layerUpdater?.ForceRefresh(); + } + } + + if (layoutToUse.customCss !== undefined) { + Utils.LoadCustomCss(layoutToUse.customCss); + } + + InitUiElements.InitBaseMap(); + + InitUiElements.OnlyIf(State.state.featureSwitchUserbadge, () => { + new UserBadge().AttachTo("userbadge"); + }); + + InitUiElements.OnlyIf(State.state.featureSwitchSearch, () => { + new SearchAndGo().AttachTo("searchbox"); + }); + + InitUiElements.OnlyIf(State.state.featureSwitchWelcomeMessage, () => { + InitUiElements.InitWelcomeMessage(); + }); + + if ( + (window != window.top && !State.state.featureSwitchWelcomeMessage.data) || + State.state.featureSwitchIframe.data ) { - if (layoutToUse === undefined) { - console.log("Incorrect layout"); - new FixedUiElement( - `Error: incorrect layout ${layoutName}
Go back` - ) - .AttachTo("centermessage") - .onClick(() => { - }); - throw "Incorrect layout"; - } - - console.log( - "Using layout: ", - layoutToUse.id, - "LayoutFromBase64 is ", - layoutFromBase64 - ); - - State.state = new State(layoutToUse); - - // This 'leaks' the global state via the window object, useful for debugging - // @ts-ignore - window.mapcomplete_state = State.state; - - if (layoutToUse.hideFromOverview) { - State.state.osmConnection - .GetPreference("hidden-theme-" + layoutToUse.id + "-enabled") - .setData("true"); - } - - if (layoutFromBase64 !== "false") { - State.state.layoutDefinition = layoutDefinition; - console.log( - "Layout definition:", - Utils.EllipsesAfter(State.state.layoutDefinition, 100) - ); - if (testing.data !== "true") { - State.state.osmConnection.OnLoggedIn(() => { - State.state.osmConnection - .GetLongPreference("installed-theme-" + layoutToUse.id) - .setData(State.state.layoutDefinition); - }); - } else { - console.warn( - "NOT saving custom layout to OSM as we are tesing -> probably in an iFrame" - ); - } - } - - function updateFavs() { - // This is purely for the personal theme to load the layers there - const favs = State.state.favouriteLayers.data ?? []; - - const neededLayers = new Set(); - - console.log("Favourites are: ", favs); - layoutToUse.layers.splice(0, layoutToUse.layers.length); - let somethingChanged = false; - for (const fav of favs) { - if (AllKnownLayers.sharedLayers.has(fav)) { - const layer = AllKnownLayers.sharedLayers.get(fav); - if (!neededLayers.has(layer)) { - neededLayers.add(layer); - somethingChanged = true; - } - } - - for (const layouts of State.state.installedThemes.data) { - for (const layer of layouts.layout.layers) { - if (typeof layer === "string") { - continue; - } - if (layer.id === fav) { - if (!neededLayers.has(layer)) { - neededLayers.add(layer); - somethingChanged = true; - } - } - } - } - } - if (somethingChanged) { - console.log("layoutToUse.layers:", layoutToUse.layers); - State.state.layoutToUse.data.layers = Array.from(neededLayers); - State.state.layoutToUse.ping(); - State.state.layerUpdater?.ForceRefresh(); - } - } - - if (layoutToUse.customCss !== undefined) { - Utils.LoadCustomCss(layoutToUse.customCss); - } - - InitUiElements.InitBaseMap(); - - InitUiElements.OnlyIf(State.state.featureSwitchUserbadge, () => { - new UserBadge().AttachTo("userbadge"); - }); - - InitUiElements.OnlyIf(State.state.featureSwitchSearch, () => { - new SearchAndGo().AttachTo("searchbox"); - }); - - InitUiElements.OnlyIf(State.state.featureSwitchWelcomeMessage, () => { - InitUiElements.InitWelcomeMessage(); - }); - - if ( - (window != window.top && !State.state.featureSwitchWelcomeMessage.data) || - State.state.featureSwitchIframe.data - ) { - const currentLocation = State.state.locationControl; - const url = `${window.location.origin}${window.location.pathname}?z=${ - currentLocation.data.zoom ?? 0 - }&lat=${currentLocation.data.lat ?? 0}&lon=${ - currentLocation.data.lon ?? 0 - }`; - new MapControlButton( - new Link(Svg.pop_out_img, url, true).SetClass( - "block w-full h-full p-1.5" - ) - ).AttachTo("messagesbox"); - } - - State.state.osmConnection.userDetails - .map((userDetails: UserDetails) => userDetails?.home) - .addCallbackAndRunD((home) => { - const color = getComputedStyle(document.body).getPropertyValue( - "--subtle-detail-color" - ); - const icon = L.icon({ - iconUrl: Img.AsData(Svg.home_white_bg.replace(/#ffffff/g, color)), - iconSize: [30, 30], - iconAnchor: [15, 15], - }); - const marker = L.marker([home.lat, home.lon], {icon: icon}); - marker.addTo(State.state.leafletMap.data); - }); - - const geolocationButton = new Toggle( - new MapControlButton( - new GeoLocationHandler( - State.state.currentGPSLocation, - State.state.leafletMap, - State.state.layoutToUse - ) - ), - undefined, - State.state.featureSwitchGeolocation - ); - - const plus = new MapControlButton( - new CenterFlexedElement( - Img.AsImageElement(Svg.plus_zoom, "", "width:1.25rem;height:1.25rem") - ) - ).onClick(() => { - State.state.locationControl.data.zoom++; - State.state.locationControl.ping(); - }); - - const min = new MapControlButton( - new CenterFlexedElement( - Img.AsImageElement(Svg.min_zoom, "", "width:1.25rem;height:1.25rem") - ) - ).onClick(() => { - State.state.locationControl.data.zoom--; - State.state.locationControl.ping(); - }); - - - // To download pdf of leaflet you need to turn it into and image first - // Then export that image as a pdf - // leaflet-simple-map-screenshoter: to make image - // jsPDF: to make pdf - - const screenshot = new MapControlButton( - new CenterFlexedElement( - Img.AsImageElement(Svg.bug, "", "width:1.25rem;height:1.25rem") - ) - ).onClick(() => { - const screenshotter = new SimpleMapScreenshoter(); - console.log("Debug - Screenshot"); - screenshotter.addTo(State.state.leafletMap.data); - let doc = new jsPDF(); - screenshotter.takeScreen('image').then(image => { - // TO DO: scale image on pdf to its original size - doc.addImage(image, 'PNG', 0, 0, screen.width / 10, screen.height / 10); - doc.setDisplayMode('fullheight'); - doc.save("Screenshot"); - }); - //screenshotter.remove(); - // The line below is for downloading the png - //screenshotter.takeScreen().then(blob => Utils.offerContentsAsDownloadableFile(blob, "Screenshot.png")); - }); - - new Combine( - [plus, min, geolocationButton, screenshot].map((el) => el.SetClass("m-0.5 md:m-1")) + const currentLocation = State.state.locationControl; + const url = `${window.location.origin}${window.location.pathname}?z=${ + currentLocation.data.zoom ?? 0 + }&lat=${currentLocation.data.lat ?? 0}&lon=${ + currentLocation.data.lon ?? 0 + }`; + new MapControlButton( + new Link(Svg.pop_out_img, url, true).SetClass( + "block w-full h-full p-1.5" ) - .SetClass("flex flex-col") - .AttachTo("bottom-right"); - - if (layoutToUse.id === personal.id) { - updateFavs(); - } - InitUiElements.setupAllLayerElements(); - - if (layoutToUse.id === personal.id) { - State.state.favouriteLayers.addCallback(updateFavs); - State.state.installedThemes.addCallback(updateFavs); - } else { - State.state.locationControl.ping(); - } - - // Reset the loading message once things are loaded - new CenterMessageBox().AttachTo("centermessage"); - document - .getElementById("centermessage") - .classList.add("pointer-events-none"); + ).AttachTo("messagesbox"); } - static LoadLayoutFromHash( - userLayoutParam: UIEventSource - ): [LayoutConfig, string] { - try { - let hash = location.hash.substr(1); - const layoutFromBase64 = userLayoutParam.data; - // layoutFromBase64 contains the name of the theme. This is partly to do tracking with goat counter - - const dedicatedHashFromLocalStorage = LocalStorageSource.Get( - "user-layout-" + layoutFromBase64.replace(" ", "_") - ); - if (dedicatedHashFromLocalStorage.data?.length < 10) { - dedicatedHashFromLocalStorage.setData(undefined); - } - - const hashFromLocalStorage = LocalStorageSource.Get( - "last-loaded-user-layout" - ); - if (hash.length < 10) { - hash = dedicatedHashFromLocalStorage.data ?? hashFromLocalStorage.data; - } else { - console.log("Saving hash to local storage"); - hashFromLocalStorage.setData(hash); - dedicatedHashFromLocalStorage.setData(hash); - } - - let json: {}; - try { - json = JSON.parse(atob(hash)); - } catch (e) { - // We try to decode with lz-string - json = JSON.parse( - Utils.UnMinify(LZString.decompressFromBase64(hash)) - ) as LayoutConfigJson; - } - - // @ts-ignore - const layoutToUse = new LayoutConfig(json, false); - userLayoutParam.setData(layoutToUse.id); - return [layoutToUse, btoa(Utils.MinifyJSON(JSON.stringify(json)))]; - } catch (e) { - new FixedUiElement( - "Error: could not parse the custom layout:
" + e - ).AttachTo("centermessage"); - throw e; - } - } - - private static OnlyIf( - featureSwitch: UIEventSource, - callback: () => void - ) { - featureSwitch.addCallbackAndRun(() => { - if (featureSwitch.data) { - callback(); - } - }); - } - - private static InitWelcomeMessage() { - const isOpened = new UIEventSource(false); - const fullOptions = new FullWelcomePaneWithTabs(isOpened); - - // ?-Button on Desktop, opens panel with close-X. - const help = new MapControlButton(Svg.help_svg()); - help.onClick(() => isOpened.setData(true)); - new Toggle(fullOptions.SetClass("welcomeMessage"), help, isOpened).AttachTo( - "messagesbox" + State.state.osmConnection.userDetails + .map((userDetails: UserDetails) => userDetails?.home) + .addCallbackAndRunD((home) => { + const color = getComputedStyle(document.body).getPropertyValue( + "--subtle-detail-color" ); - const openedTime = new Date().getTime(); - State.state.locationControl.addCallback(() => { - if (new Date().getTime() - openedTime < 15 * 1000) { - // Don't autoclose the first 15 secs when the map is moving - return; - } - isOpened.setData(false); + const icon = L.icon({ + iconUrl: Img.AsData(Svg.home_white_bg.replace(/#ffffff/g, color)), + iconSize: [30, 30], + iconAnchor: [15, 15], }); + const marker = L.marker([home.lat, home.lon], { icon: icon }); + marker.addTo(State.state.leafletMap.data); + }); - State.state.selectedElement.addCallbackAndRunD((_) => { - isOpened.setData(false); - }); - isOpened.setData( - Hash.hash.data === undefined || - Hash.hash.data === "" || - Hash.hash.data == "welcome" - ); - } - - private static InitLayerSelection(featureSource: FeatureSource) { - const copyrightNotice = new ScrollableFullScreen( - () => Translations.t.general.attribution.attributionTitle.Clone(), - () => - new AttributionPanel( - State.state.layoutToUse, - new ContributorCount(featureSource).Contributors - ), - "copyright" - ); - - const copyrightButton = new Toggle( - copyrightNotice, - new MapControlButton(Svg.osm_copyright_svg()), - copyrightNotice.isShown + const geolocationButton = new Toggle( + new MapControlButton( + new GeoLocationHandler( + State.state.currentGPSLocation, + State.state.leafletMap, + State.state.layoutToUse ) - .ToggleOnClick() - .SetClass("p-0.5"); + ), + undefined, + State.state.featureSwitchGeolocation + ); - const layerControlPanel = new LayerControlPanel( - State.state.layerControlIsOpened - ).SetClass("block p-1 rounded-full"); + const plus = new MapControlButton( + new CenterFlexedElement( + Img.AsImageElement(Svg.plus_zoom, "", "width:1.25rem;height:1.25rem") + ) + ).onClick(() => { + State.state.locationControl.data.zoom++; + State.state.locationControl.ping(); + }); - const layerControlButton = new Toggle( - layerControlPanel, - new MapControlButton(Svg.layers_svg()), - State.state.layerControlIsOpened - ).ToggleOnClick(); + const min = new MapControlButton( + new CenterFlexedElement( + Img.AsImageElement(Svg.min_zoom, "", "width:1.25rem;height:1.25rem") + ) + ).onClick(() => { + State.state.locationControl.data.zoom--; + State.state.locationControl.ping(); + }); - const layerControl = new Toggle( - layerControlButton, - "", - State.state.featureSwitchLayers - ); + new Combine( + [plus, min, geolocationButton].map((el) => el.SetClass("m-0.5 md:m-1")) + ) + .SetClass("flex flex-col") + .AttachTo("bottom-right"); - const filterView = new FilterView(State.state.FilterIsOpened).SetClass( - "block p-1 rounded-full" - ); + if (layoutToUse.id === personal.id) { + updateFavs(); + } + InitUiElements.setupAllLayerElements(); - const filterMapControlButton = new MapControlButton( - new CenterFlexedElement( - Img.AsImageElement(Svg.filter, "", "width:1.25rem;height:1.25rem") - ) - ); - - const filterButton = new Toggle( - filterView, - filterMapControlButton, - State.state.FilterIsOpened - ).ToggleOnClick(); - - const filterControl = new Toggle( - filterButton, - "", - State.state.featureSwitchFilter - ); - - new Combine([copyrightButton, layerControl, filterControl]).AttachTo( - "bottom-left" - ); - - State.state.locationControl.addCallback(() => { - // Close the layer selection when the map is moved - layerControlButton.isEnabled.setData(false); - copyrightButton.isEnabled.setData(false); - }); - - State.state.selectedElement.addCallbackAndRunD((_) => { - layerControlButton.isEnabled.setData(false); - copyrightButton.isEnabled.setData(false); - }); + if (layoutToUse.id === personal.id) { + State.state.favouriteLayers.addCallback(updateFavs); + State.state.installedThemes.addCallback(updateFavs); + } else { + State.state.locationControl.ping(); } - private static InitBaseMap() { - State.state.availableBackgroundLayers = AvailableBaseLayers.AvailableLayersAt(State.state.locationControl); - State.state.backgroundLayer = State.state.backgroundLayerId.map( - (selectedId: string) => { - if (selectedId === undefined) { - return AvailableBaseLayers.osmCarto; - } + // Reset the loading message once things are loaded + new CenterMessageBox().AttachTo("centermessage"); + document + .getElementById("centermessage") + .classList.add("pointer-events-none"); + } - const available = State.state.availableBackgroundLayers.data; - for (const layer of available) { - if (layer.id === selectedId) { - return layer; - } - } - return AvailableBaseLayers.osmCarto; - }, - [State.state.availableBackgroundLayers], - (layer) => layer.id - ); + static LoadLayoutFromHash( + userLayoutParam: UIEventSource + ): [LayoutConfig, string] { + try { + let hash = location.hash.substr(1); + const layoutFromBase64 = userLayoutParam.data; + // layoutFromBase64 contains the name of the theme. This is partly to do tracking with goat counter - new LayerResetter( - State.state.backgroundLayer, - State.state.locationControl, - State.state.availableBackgroundLayers, - State.state.layoutToUse.map( - (layout: LayoutConfig) => layout.defaultBackgroundId - ) - ); + const dedicatedHashFromLocalStorage = LocalStorageSource.Get( + "user-layout-" + layoutFromBase64.replace(" ", "_") + ); + if (dedicatedHashFromLocalStorage.data?.length < 10) { + dedicatedHashFromLocalStorage.setData(undefined); + } - const attr = new Attribution( - State.state.locationControl, - State.state.osmConnection.userDetails, - State.state.layoutToUse, - State.state.leafletMap - ); + const hashFromLocalStorage = LocalStorageSource.Get( + "last-loaded-user-layout" + ); + if (hash.length < 10) { + hash = dedicatedHashFromLocalStorage.data ?? hashFromLocalStorage.data; + } else { + console.log("Saving hash to local storage"); + hashFromLocalStorage.setData(hash); + dedicatedHashFromLocalStorage.setData(hash); + } - const bm = new Basemap( - "leafletDiv", - State.state.locationControl, - State.state.backgroundLayer, - State.state.LastClickLocation, - attr - ); - State.state.leafletMap.setData(bm.map); - const layout = State.state.layoutToUse.data; - if (layout.lockLocation) { - if (layout.lockLocation === true) { - const tile = Utils.embedded_tile( - layout.startLat, - layout.startLon, - layout.startZoom - 1 - ); - const bounds = Utils.tile_bounds(tile.z, tile.x, tile.y); - // We use the bounds to get a sense of distance for this zoom level - const latDiff = bounds[0][0] - bounds[1][0]; - const lonDiff = bounds[0][1] - bounds[1][1]; - layout.lockLocation = [ - [layout.startLat - latDiff, layout.startLon - lonDiff], - [layout.startLat + latDiff, layout.startLon + lonDiff], - ]; - } - console.warn("Locking the bounds to ", layout.lockLocation); - bm.map.setMaxBounds(layout.lockLocation); - bm.map.setMinZoom(layout.startZoom); + let json: {}; + try { + json = JSON.parse(atob(hash)); + } catch (e) { + // We try to decode with lz-string + json = JSON.parse( + Utils.UnMinify(LZString.decompressFromBase64(hash)) + ) as LayoutConfigJson; + } + + // @ts-ignore + const layoutToUse = new LayoutConfig(json, false); + userLayoutParam.setData(layoutToUse.id); + return [layoutToUse, btoa(Utils.MinifyJSON(JSON.stringify(json)))]; + } catch (e) { + new FixedUiElement( + "Error: could not parse the custom layout:
" + e + ).AttachTo("centermessage"); + throw e; + } + } + + private static OnlyIf( + featureSwitch: UIEventSource, + callback: () => void + ) { + featureSwitch.addCallbackAndRun(() => { + if (featureSwitch.data) { + callback(); + } + }); + } + + private static InitWelcomeMessage() { + const isOpened = new UIEventSource(false); + const fullOptions = new FullWelcomePaneWithTabs(isOpened); + + // ?-Button on Desktop, opens panel with close-X. + const help = new MapControlButton(Svg.help_svg()); + help.onClick(() => isOpened.setData(true)); + new Toggle(fullOptions.SetClass("welcomeMessage"), help, isOpened).AttachTo( + "messagesbox" + ); + const openedTime = new Date().getTime(); + State.state.locationControl.addCallback(() => { + if (new Date().getTime() - openedTime < 15 * 1000) { + // Don't autoclose the first 15 secs when the map is moving + return; + } + isOpened.setData(false); + }); + + State.state.selectedElement.addCallbackAndRunD((_) => { + isOpened.setData(false); + }); + isOpened.setData( + Hash.hash.data === undefined || + Hash.hash.data === "" || + Hash.hash.data == "welcome" + ); + } + + private static InitLayerSelection(featureSource: FeatureSource) { + const copyrightNotice = new ScrollableFullScreen( + () => Translations.t.general.attribution.attributionTitle.Clone(), + () => + new AttributionPanel( + State.state.layoutToUse, + new ContributorCount(featureSource).Contributors + ), + "copyright" + ); + + const copyrightButton = new Toggle( + copyrightNotice, + new MapControlButton(Svg.osm_copyright_svg()), + copyrightNotice.isShown + ) + .ToggleOnClick() + .SetClass("p-0.5"); + + const layerControlPanel = new LayerControlPanel( + State.state.layerControlIsOpened + ).SetClass("block p-1 rounded-full"); + + const layerControlButton = new Toggle( + layerControlPanel, + new MapControlButton(Svg.layers_svg()), + State.state.layerControlIsOpened + ).ToggleOnClick(); + + const layerControl = new Toggle( + layerControlButton, + "", + State.state.featureSwitchLayers + ); + + const filterView = new FilterView(State.state.FilterIsOpened).SetClass( + "block p-1 rounded-full" + ); + + const filterMapControlButton = new MapControlButton( + new CenterFlexedElement( + Img.AsImageElement(Svg.filter, "", "width:1.25rem;height:1.25rem") + ) + ); + + const filterButton = new Toggle( + filterView, + filterMapControlButton, + State.state.FilterIsOpened + ).ToggleOnClick(); + + const filterControl = new Toggle( + filterButton, + "", + State.state.featureSwitchFilter + ); + + new Combine([copyrightButton, layerControl, filterControl]).AttachTo( + "bottom-left" + ); + + State.state.locationControl.addCallback(() => { + // Close the layer selection when the map is moved + layerControlButton.isEnabled.setData(false); + copyrightButton.isEnabled.setData(false); + }); + + State.state.selectedElement.addCallbackAndRunD((_) => { + layerControlButton.isEnabled.setData(false); + copyrightButton.isEnabled.setData(false); + }); + } + + private static InitBaseMap() { + State.state.availableBackgroundLayers = new AvailableBaseLayers( + State.state.locationControl + ).availableEditorLayers; + + State.state.backgroundLayer = State.state.backgroundLayerId.map( + (selectedId: string) => { + if (selectedId === undefined) { + return AvailableBaseLayers.osmCarto; } + + const available = State.state.availableBackgroundLayers.data; + for (const layer of available) { + if (layer.id === selectedId) { + return layer; + } + } + return AvailableBaseLayers.osmCarto; + }, + [State.state.availableBackgroundLayers], + (layer) => layer.id + ); + + new LayerResetter( + State.state.backgroundLayer, + State.state.locationControl, + State.state.availableBackgroundLayers, + State.state.layoutToUse.map( + (layout: LayoutConfig) => layout.defaultBackgroundId + ) + ); + + const attr = new Attribution( + State.state.locationControl, + State.state.osmConnection.userDetails, + State.state.layoutToUse, + State.state.leafletMap + ); + + const bm = new Basemap( + "leafletDiv", + State.state.locationControl, + State.state.backgroundLayer, + State.state.LastClickLocation, + attr + ); + State.state.leafletMap.setData(bm.map); + const layout = State.state.layoutToUse.data; + if (layout.lockLocation) { + if (layout.lockLocation === true) { + const tile = Utils.embedded_tile( + layout.startLat, + layout.startLon, + layout.startZoom - 1 + ); + const bounds = Utils.tile_bounds(tile.z, tile.x, tile.y); + // We use the bounds to get a sense of distance for this zoom level + const latDiff = bounds[0][0] - bounds[1][0]; + const lonDiff = bounds[0][1] - bounds[1][1]; + layout.lockLocation = [ + [layout.startLat - latDiff, layout.startLon - lonDiff], + [layout.startLat + latDiff, layout.startLon + lonDiff], + ]; + } + console.warn("Locking the bounds to ", layout.lockLocation); + bm.map.setMaxBounds(layout.lockLocation); + bm.map.setMinZoom(layout.startZoom); } + } - private static InitLayers(): FeatureSource { - const state = State.state; - state.filteredLayers = state.layoutToUse.map((layoutToUse) => { - const flayers = []; + private static InitLayers(): FeatureSource { + const state = State.state; + state.filteredLayers = state.layoutToUse.map((layoutToUse) => { + const flayers = []; - for (const layer of layoutToUse.layers) { - const isDisplayed = QueryParameters.GetQueryParameter( - "layer-" + layer.id, - "true", - "Wether or not layer " + layer.id + " is shown" - ).map( - (str) => str !== "false", - [], - (b) => b.toString() - ); - const flayer = { - isDisplayed: isDisplayed, - layerDef: layer, - }; - flayers.push(flayer); - } - return flayers; - }); - - const updater = new LoadFromOverpass( - state.locationControl, - state.layoutToUse, - state.leafletMap + for (const layer of layoutToUse.layers) { + const isDisplayed = QueryParameters.GetQueryParameter( + "layer-" + layer.id, + "true", + "Wether or not layer " + layer.id + " is shown" + ).map( + (str) => str !== "false", + [], + (b) => b.toString() ); - State.state.layerUpdater = updater; + const flayer = { + isDisplayed: isDisplayed, + layerDef: layer, + }; + flayers.push(flayer); + } + return flayers; + }); - const source = new FeaturePipeline( - state.filteredLayers, - updater, - state.osmApiFeatureSource, - state.layoutToUse, - state.changes, - state.locationControl, - state.selectedElement - ); + const updater = new LoadFromOverpass( + state.locationControl, + state.layoutToUse, + state.leafletMap + ); + State.state.layerUpdater = updater; - State.state.featurePipeline = source; - new ShowDataLayer( - source.features, - State.state.leafletMap, - State.state.layoutToUse - ); + const source = new FeaturePipeline( + state.filteredLayers, + updater, + state.osmApiFeatureSource, + state.layoutToUse, + state.changes, + state.locationControl, + state.selectedElement + ); - const selectedFeatureHandler = new SelectedFeatureHandler( - Hash.hash, - State.state.selectedElement, - source, - State.state.osmApiFeatureSource - ); - selectedFeatureHandler.zoomToSelectedFeature(State.state.locationControl); - return source; - } + new ShowDataLayer( + source.features, + State.state.leafletMap, + State.state.layoutToUse + ); - private static setupAllLayerElements() { - // ------------- Setup the layers ------------------------------- + const selectedFeatureHandler = new SelectedFeatureHandler( + Hash.hash, + State.state.selectedElement, + source, + State.state.osmApiFeatureSource + ); + selectedFeatureHandler.zoomToSelectedFeature(State.state.locationControl); + return source; + } - const source = InitUiElements.InitLayers(); - InitUiElements.InitLayerSelection(source); + private static setupAllLayerElements() { + // ------------- Setup the layers ------------------------------- - // ------------------ Setup various other UI elements ------------ + const source = InitUiElements.InitLayers(); + InitUiElements.InitLayerSelection(source); - InitUiElements.OnlyIf(State.state.featureSwitchAddNew, () => { - let presetCount = 0; - for (const layer of State.state.filteredLayers.data) { - for (const preset of layer.layerDef.presets) { - presetCount++; - } - } - if (presetCount == 0) { - return; - } + // ------------------ Setup various other UI elements ------------ - const newPointDialogIsShown = new UIEventSource(false); - const addNewPoint = new ScrollableFullScreen( - () => Translations.t.general.add.title.Clone(), - () => new SimpleAddUI(newPointDialogIsShown), - "new", - newPointDialogIsShown - ); - addNewPoint.isShown.addCallback((isShown) => { - if (!isShown) { - State.state.LastClickLocation.setData(undefined); - } - }); + InitUiElements.OnlyIf(State.state.featureSwitchAddNew, () => { + let presetCount = 0; + for (const layer of State.state.filteredLayers.data) { + for (const preset of layer.layerDef.presets) { + presetCount++; + } + } + if (presetCount == 0) { + return; + } - new StrayClickHandler( - State.state.LastClickLocation, - State.state.selectedElement, - State.state.filteredLayers, - State.state.leafletMap, - addNewPoint - ); - }); - } + const newPointDialogIsShown = new UIEventSource(false); + const addNewPoint = new ScrollableFullScreen( + () => Translations.t.general.add.title.Clone(), + () => new SimpleAddUI(newPointDialogIsShown), + "new", + newPointDialogIsShown + ); + addNewPoint.isShown.addCallback((isShown) => { + if (!isShown) { + State.state.LastClickLocation.setData(undefined); + } + }); + + new StrayClickHandler( + State.state.LastClickLocation, + State.state.selectedElement, + State.state.filteredLayers, + State.state.leafletMap, + addNewPoint + ); + }); + } } diff --git a/Logic/Actors/AvailableBaseLayers.ts b/Logic/Actors/AvailableBaseLayers.ts index e84ecc6356..2fd679571d 100644 --- a/Logic/Actors/AvailableBaseLayers.ts +++ b/Logic/Actors/AvailableBaseLayers.ts @@ -1,12 +1,11 @@ import * as editorlayerindex from "../../assets/editor-layer-index.json" import BaseLayer from "../../Models/BaseLayer"; import * as L from "leaflet"; -import {TileLayer} from "leaflet"; import * as X from "leaflet-providers"; import {UIEventSource} from "../UIEventSource"; import {GeoOperations} from "../GeoOperations"; +import {TileLayer} from "leaflet"; import {Utils} from "../../Utils"; -import Loc from "../../Models/Loc"; /** * Calculates which layers are available at the current location @@ -25,87 +24,45 @@ export default class AvailableBaseLayers { false, false), feature: null, max_zoom: 19, - min_zoom: 0, - isBest: false, // This is a lie! Of course OSM is the best map! (But not in this context) - category: "osmbasedmap" + min_zoom: 0 } + public static layerOverview = AvailableBaseLayers.LoadRasterIndex().concat(AvailableBaseLayers.LoadProviderIndex()); + public availableEditorLayers: UIEventSource; - public static AvailableLayersAt(location: UIEventSource): UIEventSource { - const source = location.map( - (currentLocation) => { + constructor(location: UIEventSource<{ lat: number, lon: number, zoom: number }>) { + const self = this; + this.availableEditorLayers = + location.map( + (currentLocation) => { - if (currentLocation === undefined) { - return AvailableBaseLayers.layerOverview; - } + if (currentLocation === undefined) { + return AvailableBaseLayers.layerOverview; + } - const currentLayers = source?.data; // A bit unorthodox - I know - const newLayers = AvailableBaseLayers.CalculateAvailableLayersAt(currentLocation?.lon, currentLocation?.lat); + const currentLayers = self.availableEditorLayers?.data; + const newLayers = AvailableBaseLayers.AvailableLayersAt(currentLocation?.lon, currentLocation?.lat); - if (currentLayers === undefined) { - return newLayers; - } - if (newLayers.length !== currentLayers.length) { - return newLayers; - } - for (let i = 0; i < newLayers.length; i++) { - if (newLayers[i].name !== currentLayers[i].name) { + if (currentLayers === undefined) { return newLayers; } - } + if (newLayers.length !== currentLayers.length) { + return newLayers; + } + for (let i = 0; i < newLayers.length; i++) { + if (newLayers[i].name !== currentLayers[i].name) { + return newLayers; + } + } + + return currentLayers; + }); + - return currentLayers; - }); - return source; } - public static SelectBestLayerAccordingTo(location: UIEventSource, preferedCategory: UIEventSource): UIEventSource { - return AvailableBaseLayers.AvailableLayersAt(location).map(available => { - // First float all 'best layers' to the top - available.sort((a, b) => { - if (a.isBest && b.isBest) { - return 0; - } - if (!a.isBest) { - return 1 - } - - return -1; - } - ) - - if (preferedCategory.data === undefined) { - return available[0] - } - - let prefered: string [] - if (typeof preferedCategory.data === "string") { - prefered = [preferedCategory.data] - } else { - prefered = preferedCategory.data; - } - - prefered.reverse(); - for (const category of prefered) { - //Then sort all 'photo'-layers to the top. Stability of the sorting will force a 'best' photo layer on top - available.sort((a, b) => { - if (a.category === category && b.category === category) { - return 0; - } - if (a.category !== category) { - return 1 - } - - return -1; - } - ) - } - return available[0] - }) - } - - private static CalculateAvailableLayersAt(lon: number, lat: number): BaseLayer[] { + private static AvailableLayersAt(lon: number, lat: number): BaseLayer[] { const availableLayers = [AvailableBaseLayers.osmCarto] const globalLayers = []; for (const layerOverviewItem of AvailableBaseLayers.layerOverview) { @@ -183,9 +140,7 @@ export default class AvailableBaseLayers { min_zoom: props.min_zoom ?? 1, name: props.name, layer: leafletLayer, - feature: layer, - isBest: props.best ?? false, - category: props.category + feature: layer }); } return layers; @@ -197,16 +152,15 @@ export default class AvailableBaseLayers { function l(id: string, name: string): BaseLayer { try { const layer: any = () => L.tileLayer.provider(id, undefined); - return { + const baseLayer: BaseLayer = { feature: null, id: id, name: name, layer: layer, min_zoom: layer.minzoom, - max_zoom: layer.maxzoom, - category: "osmbasedmap", - isBest: false + max_zoom: layer.maxzoom } + return baseLayer } catch (e) { console.error("Could not find provided layer", name, e); return null; diff --git a/Logic/Actors/GeoLocationHandler.ts b/Logic/Actors/GeoLocationHandler.ts index 687c0e1e91..8ea2886b42 100644 --- a/Logic/Actors/GeoLocationHandler.ts +++ b/Logic/Actors/GeoLocationHandler.ts @@ -1,271 +1,265 @@ import * as L from "leaflet"; -import {UIEventSource} from "../UIEventSource"; +import { UIEventSource } from "../UIEventSource"; +import { Utils } from "../../Utils"; import Svg from "../../Svg"; import Img from "../../UI/Base/Img"; -import {LocalStorageSource} from "../Web/LocalStorageSource"; +import { LocalStorageSource } from "../Web/LocalStorageSource"; import LayoutConfig from "../../Customizations/JSON/LayoutConfig"; -import {VariableUiElement} from "../../UI/Base/VariableUIElement"; -import {CenterFlexedElement} from "../../UI/Base/CenterFlexedElement"; +import { VariableUiElement } from "../../UI/Base/VariableUIElement"; +import { CenterFlexedElement } from "../../UI/Base/CenterFlexedElement"; export default class GeoLocationHandler extends VariableUiElement { - /** - * Wether or not the geolocation is active, aka the user requested the current location - * @private - */ - private readonly _isActive: UIEventSource; + /** + * Wether or not the geolocation is active, aka the user requested the current location + * @private + */ + private readonly _isActive: UIEventSource; + /** + * The callback over the permission API + * @private + */ + private readonly _permission: UIEventSource; + /*** + * The marker on the map, in order to update it + * @private + */ + private _marker: L.Marker; + /** + * Literally: _currentGPSLocation.data != undefined + * @private + */ + private readonly _hasLocation: UIEventSource; + private readonly _currentGPSLocation: UIEventSource<{ + latlng: any; + accuracy: number; + }>; + /** + * Kept in order to update the marker + * @private + */ + private readonly _leafletMap: UIEventSource; + /** + * The date when the user requested the geolocation. If we have a location, it'll autozoom to it the first 30 secs + * @private + */ + private _lastUserRequest: Date; + /** + * A small flag on localstorage. If the user previously granted the geolocation, it will be set. + * On firefox, the permissions api is broken (probably fingerprint resistiance) and "granted + don't ask again" doesn't stick between sessions. + * + * Instead, we set this flag. If this flag is set upon loading the page, we start geolocating immediately. + * If the user denies the geolocation this time, we unset this flag + * @private + */ + private readonly _previousLocationGrant: UIEventSource; + private readonly _layoutToUse: UIEventSource; - /** - * Wether or not the geolocation is locked, aka the user requested the current location and wants the crosshair to follow the user - * @private - */ - private readonly _isLocked: UIEventSource; + constructor( + currentGPSLocation: UIEventSource<{ latlng: any; accuracy: number }>, + leafletMap: UIEventSource, + layoutToUse: UIEventSource + ) { + const hasLocation = currentGPSLocation.map( + (location) => location !== undefined + ); + const previousLocationGrant = LocalStorageSource.Get( + "geolocation-permissions" + ); + const isActive = new UIEventSource(false); - /** - * The callback over the permission API - * @private - */ - private readonly _permission: UIEventSource; + super( + hasLocation.map( + (hasLocation) => { + if (hasLocation) { + return new CenterFlexedElement( + Img.AsImageElement(Svg.location, "", "width:1.25rem;height:1.25rem") + ); // crosshair_blue_ui() + } + if (isActive.data) { + return new CenterFlexedElement( + Img.AsImageElement(Svg.location, "", "width:1.25rem;height:1.25rem") + ); // crosshair_blue_center_ui + } + return new CenterFlexedElement( + Img.AsImageElement(Svg.location, "", "width:1.25rem;height:1.25rem") + ); //crosshair_ui + }, + [isActive] + ) + ); + this._isActive = isActive; + this._permission = new UIEventSource(""); + this._previousLocationGrant = previousLocationGrant; + this._currentGPSLocation = currentGPSLocation; + this._leafletMap = leafletMap; + this._layoutToUse = layoutToUse; + this._hasLocation = hasLocation; + const self = this; - /*** - * The marker on the map, in order to update it - * @private - */ - private _marker: L.Marker; - /** - * Literally: _currentGPSLocation.data != undefined - * @private - */ - private readonly _hasLocation: UIEventSource; - private readonly _currentGPSLocation: UIEventSource<{ - latlng: any; - accuracy: number; - }>; - /** - * Kept in order to update the marker - * @private - */ - private readonly _leafletMap: UIEventSource; - - - /** - * The date when the user requested the geolocation. If we have a location, it'll autozoom to it the first 30 secs - * @private - */ - private _lastUserRequest: Date; - - - /** - * A small flag on localstorage. If the user previously granted the geolocation, it will be set. - * On firefox, the permissions api is broken (probably fingerprint resistiance) and "granted + don't ask again" doesn't stick between sessions. - * - * Instead, we set this flag. If this flag is set upon loading the page, we start geolocating immediately. - * If the user denies the geolocation this time, we unset this flag - * @private - */ - private readonly _previousLocationGrant: UIEventSource; - private readonly _layoutToUse: UIEventSource; - - constructor( - currentGPSLocation: UIEventSource<{ latlng: any; accuracy: number }>, - leafletMap: UIEventSource, - layoutToUse: UIEventSource - ) { - const hasLocation = currentGPSLocation.map( - (location) => location !== undefined - ); - const previousLocationGrant = LocalStorageSource.Get( - "geolocation-permissions" - ); - const isActive = new UIEventSource(false); - const isLocked = new UIEventSource(false); - super( - hasLocation.map( - (hasLocationData) => { - let icon: string; - - if (isLocked.data) { - icon = Svg.crosshair_locked; - } else if (hasLocationData) { - icon = Svg.crosshair_blue; - } else if (isActive.data) { - icon = Svg.crosshair_blue_center; - } else { - icon = Svg.crosshair; - } - - return new CenterFlexedElement( - Img.AsImageElement(icon, "", "width:1.25rem;height:1.25rem") - ); - - }, - [isActive, isLocked] - ) - ); - this._isActive = isActive; - this._isLocked = isLocked; - this._permission = new UIEventSource(""); - this._previousLocationGrant = previousLocationGrant; - this._currentGPSLocation = currentGPSLocation; - this._leafletMap = leafletMap; - this._layoutToUse = layoutToUse; - this._hasLocation = hasLocation; - const self = this; - - const currentPointer = this._isActive.map( - (isActive) => { - if (isActive && !self._hasLocation.data) { - return "cursor-wait"; - } - return "cursor-pointer"; - }, - [this._hasLocation] - ); - currentPointer.addCallbackAndRun((pointerClass) => { - self.SetClass(pointerClass); - }); - - this.onClick(() => { - if (self._hasLocation.data) { - self._isLocked.setData(!self._isLocked.data); - } - self.init(true); - }); - this.init(false); - - - this._currentGPSLocation.addCallback((location) => { - self._previousLocationGrant.setData("granted"); - - const timeSinceRequest = - (new Date().getTime() - (self._lastUserRequest?.getTime() ?? 0)) / 1000; - if (timeSinceRequest < 30) { - self.MoveToCurrentLoction(16); - } else if (self._isLocked.data) { - self.MoveToCurrentLoction(); - } - - let color = "#1111cc"; - try { - color = getComputedStyle(document.body).getPropertyValue( - "--catch-detail-color" - ); - } catch (e) { - console.error(e); - } - const icon = L.icon({ - iconUrl: Img.AsData(Svg.crosshair.replace(/#000000/g, color)), - iconSize: [40, 40], // size of the icon - iconAnchor: [20, 20], // point of the icon which will correspond to marker's location - }); - - const map = self._leafletMap.data; - - const newMarker = L.marker(location.latlng, {icon: icon}); - newMarker.addTo(map); - - if (self._marker !== undefined) { - map.removeLayer(self._marker); - } - self._marker = newMarker; - }); - } - - private init(askPermission: boolean) { - const self = this; - if (self._isActive.data) { - self.MoveToCurrentLoction(16); - return; + const currentPointer = this._isActive.map( + (isActive) => { + if (isActive && !self._hasLocation.data) { + return "cursor-wait"; } - try { - navigator?.permissions - ?.query({name: "geolocation"}) - ?.then(function (status) { - console.log("Geolocation is already", status); - if (status.state === "granted") { - self.StartGeolocating(false); - } - self._permission.setData(status.state); - status.onchange = function () { - self._permission.setData(status.state); - }; - }); - } catch (e) { - console.error(e); - } - if (askPermission) { - self.StartGeolocating(true); - } else if (this._previousLocationGrant.data === "granted") { - this._previousLocationGrant.setData(""); + return "cursor-pointer"; + }, + [this._hasLocation] + ); + currentPointer.addCallbackAndRun((pointerClass) => { + self.SetClass(pointerClass); + }); + + this.onClick(() => self.init(true)); + this.init(false); + } + + private init(askPermission: boolean) { + const self = this; + const map = this._leafletMap.data; + + this._currentGPSLocation.addCallback((location) => { + self._previousLocationGrant.setData("granted"); + + const timeSinceRequest = + (new Date().getTime() - (self._lastUserRequest?.getTime() ?? 0)) / 1000; + if (timeSinceRequest < 30) { + self.MoveToCurrentLoction(16); + } + + let color = "#1111cc"; + try { + color = getComputedStyle(document.body).getPropertyValue( + "--catch-detail-color" + ); + } catch (e) { + console.error(e); + } + const icon = L.icon({ + iconUrl: Img.AsData(Svg.crosshair.replace(/#000000/g, color)), + iconSize: [40, 40], // size of the icon + iconAnchor: [20, 20], // point of the icon which will correspond to marker's location + }); + + const newMarker = L.marker(location.latlng, { icon: icon }); + newMarker.addTo(map); + + if (self._marker !== undefined) { + map.removeLayer(self._marker); + } + self._marker = newMarker; + }); + + try { + navigator?.permissions + ?.query({ name: "geolocation" }) + ?.then(function (status) { + console.log("Geolocation is already", status); + if (status.state === "granted") { self.StartGeolocating(false); + } + self._permission.setData(status.state); + status.onchange = function () { + self._permission.setData(status.state); + }; + }); + } catch (e) { + console.error(e); + } + if (askPermission) { + self.StartGeolocating(true); + } else if (this._previousLocationGrant.data === "granted") { + this._previousLocationGrant.setData(""); + self.StartGeolocating(false); + } + } + + private locate() { + const self = this; + const map: any = this._leafletMap.data; + + if (navigator.geolocation) { + navigator.geolocation.getCurrentPosition( + function (position) { + self._currentGPSLocation.setData({ + latlng: [position.coords.latitude, position.coords.longitude], + accuracy: position.coords.accuracy, + }); + }, + function () { + console.warn("Could not get location with navigator.geolocation"); } + ); + return; + } else { + map.findAccuratePosition({ + maxWait: 10000, // defaults to 10000 + desiredAccuracy: 50, // defaults to 20 + }); + } + } + + private MoveToCurrentLoction(targetZoom = 16) { + const location = this._currentGPSLocation.data; + this._lastUserRequest = undefined; + + if ( + this._currentGPSLocation.data.latlng[0] === 0 && + this._currentGPSLocation.data.latlng[1] === 0 + ) { + console.debug("Not moving to GPS-location: it is null island"); + return; } - private MoveToCurrentLoction(targetZoom = 16) { - const location = this._currentGPSLocation.data; - this._lastUserRequest = undefined; + // We check that the GPS location is not out of bounds + const b = this._layoutToUse.data.lockLocation; + let inRange = true; + if (b) { + if (b !== true) { + // B is an array with our locklocation + inRange = + b[0][0] <= location.latlng[0] && + location.latlng[0] <= b[1][0] && + b[0][1] <= location.latlng[1] && + location.latlng[1] <= b[1][1]; + } + } + if (!inRange) { + console.log( + "Not zooming to GPS location: out of bounds", + b, + location.latlng + ); + } else { + this._leafletMap.data.setView(location.latlng, targetZoom); + } + } - if ( - this._currentGPSLocation.data.latlng[0] === 0 && - this._currentGPSLocation.data.latlng[1] === 0 - ) { - console.debug("Not moving to GPS-location: it is null island"); - return; - } + private StartGeolocating(zoomToGPS = true) { + const self = this; + console.log("Starting geolocation"); - // We check that the GPS location is not out of bounds - const b = this._layoutToUse.data.lockLocation; - let inRange = true; - if (b) { - if (b !== true) { - // B is an array with our locklocation - inRange = - b[0][0] <= location.latlng[0] && - location.latlng[0] <= b[1][0] && - b[0][1] <= location.latlng[1] && - location.latlng[1] <= b[1][1]; - } - } - if (!inRange) { - console.log( - "Not zooming to GPS location: out of bounds", - b, - location.latlng - ); - } else { - this._leafletMap.data.setView(location.latlng, targetZoom); - } + this._lastUserRequest = zoomToGPS ? new Date() : new Date(0); + if (self._permission.data === "denied") { + self._previousLocationGrant.setData(""); + return ""; + } + if (this._currentGPSLocation.data !== undefined) { + this.MoveToCurrentLoction(16); } - private StartGeolocating(zoomToGPS = true) { - const self = this; - console.log("Starting geolocation"); + console.log("Searching location using GPS"); + this.locate(); - this._lastUserRequest = zoomToGPS ? new Date() : new Date(0); - if (self._permission.data === "denied") { - self._previousLocationGrant.setData(""); - return ""; + if (!self._isActive.data) { + self._isActive.setData(true); + Utils.DoEvery(60000, () => { + if (document.visibilityState !== "visible") { + console.log("Not starting gps: document not visible"); + return; } - if (this._currentGPSLocation.data !== undefined) { - this.MoveToCurrentLoction(16); - } - - console.log("Searching location using GPS"); - - if (self._isActive.data) { - return; - } - self._isActive.setData(true); - navigator.geolocation.watchPosition( - function (position) { - self._currentGPSLocation.setData({ - latlng: [position.coords.latitude, position.coords.longitude], - accuracy: position.coords.accuracy, - }); - }, - function () { - console.warn("Could not get location with navigator.geolocation"); - } - ); + this.locate(); + }); } + } } diff --git a/Logic/Actors/StrayClickHandler.ts b/Logic/Actors/StrayClickHandler.ts index 3e7609fd41..b4d6300700 100644 --- a/Logic/Actors/StrayClickHandler.ts +++ b/Logic/Actors/StrayClickHandler.ts @@ -47,12 +47,7 @@ export default class StrayClickHandler { popupAnchor: [0, -45] }) }); - const popup = L.popup({ - autoPan: true, - autoPanPaddingTopLeft: [15,15], - closeOnEscapeKey: true, - autoClose: true - }).setContent("
"); + const popup = L.popup().setContent("
"); self._lastMarker.addTo(leafletMap.data); self._lastMarker.bindPopup(popup); diff --git a/Logic/FeatureSource/FeaturePipeline.ts b/Logic/FeatureSource/FeaturePipeline.ts index 351ccdc3e2..3407105034 100644 --- a/Logic/FeatureSource/FeaturePipeline.ts +++ b/Logic/FeatureSource/FeaturePipeline.ts @@ -16,7 +16,7 @@ import RegisteringFeatureSource from "./RegisteringFeatureSource"; export default class FeaturePipeline implements FeatureSource { - public features: UIEventSource<{ feature: any; freshness: Date }[]>; + public features: UIEventSource<{ feature: any; freshness: Date }[]> ; public readonly name = "FeaturePipeline" @@ -29,7 +29,7 @@ export default class FeaturePipeline implements FeatureSource { selectedElement: UIEventSource) { const allLoadedFeatures = new UIEventSource<{ feature: any; freshness: Date }[]>([]) - + // first we metatag, then we save to get the metatags into storage too // Note that we need to register before we do metatagging (as it expects the event sources) @@ -46,11 +46,8 @@ export default class FeaturePipeline implements FeatureSource { const geojsonSources: FeatureSource [] = GeoJsonSource .ConstructMultiSource(flayers.data, locationControl) .map(geojsonSource => { - let source = new RegisteringFeatureSource( - new FeatureDuplicatorPerLayer(flayers, - geojsonSource - )); - if (!geojsonSource.isOsmCache) { + let source = new RegisteringFeatureSource(new FeatureDuplicatorPerLayer(flayers, geojsonSource)); + if(!geojsonSource.isOsmCache){ source = new MetaTaggingFeatureSource(allLoadedFeatures, source, updater.features); } return source diff --git a/Logic/FeatureSource/FeatureSource.ts b/Logic/FeatureSource/FeatureSource.ts index 171db39f6a..ba568271ea 100644 --- a/Logic/FeatureSource/FeatureSource.ts +++ b/Logic/FeatureSource/FeatureSource.ts @@ -1,45 +1,9 @@ import {UIEventSource} from "../UIEventSource"; -import {Utils} from "../../Utils"; export default interface FeatureSource { - features: UIEventSource<{ feature: any, freshness: Date }[]>; + features: UIEventSource<{feature: any, freshness: Date}[]>; /** * Mainly used for debuging */ name: string; -} - -export class FeatureSourceUtils { - - /** - * Exports given featurePipeline as a geojson FeatureLists (downloads as a json) - * @param featurePipeline The FeaturePipeline you want to export - * @param options The options object - * @param options.metadata True if you want to include the MapComplete metadata, false otherwise - */ - public static extractGeoJson(featurePipeline: FeatureSource, options: { metadata?: boolean } = {}) { - let defaults = { - metadata: false, - } - options = Utils.setDefaults(options, defaults); - - // Select all features, ignore the freshness and other data - let featureList: any[] = featurePipeline.features.data.map((feature) => feature.feature); - - if (!options.metadata) { - for (let i = 0; i < featureList.length; i++) { - let feature = featureList[i]; - for (let property in feature.properties) { - if (property[0] == "_") { - delete featureList[i]["properties"][property]; - } - } - } - } - return {type: "FeatureCollection", features: featureList} - - - } - - } \ No newline at end of file diff --git a/Logic/FeatureSource/GeoJsonSource.ts b/Logic/FeatureSource/GeoJsonSource.ts index c684151c6f..b462c87da0 100644 --- a/Logic/FeatureSource/GeoJsonSource.ts +++ b/Logic/FeatureSource/GeoJsonSource.ts @@ -175,7 +175,7 @@ export default class GeoJsonSource implements FeatureSource { let freshness: Date = time; if (feature.properties["_last_edit:timestamp"] !== undefined) { - freshness = new Date(feature.properties["_last_edit:timestamp"]) + freshness = new Date(feature["_last_edit:timestamp"]) } newFeatures.push({feature: feature, freshness: freshness}) diff --git a/Logic/GeoOperations.ts b/Logic/GeoOperations.ts index f6fbd31568..31cb88ad2d 100644 --- a/Logic/GeoOperations.ts +++ b/Logic/GeoOperations.ts @@ -6,14 +6,11 @@ export class GeoOperations { return turf.area(feature); } - /** - * Converts a GeoJSon feature to a point feature - * @param feature - */ static centerpoint(feature: any) { const newFeature = turf.center(feature); newFeature.properties = feature.properties; newFeature.id = feature.id; + return newFeature; } @@ -276,14 +273,6 @@ export class GeoOperations { } return undefined; } - /** - * Generates the closest point on a way from a given point - * @param way The road on which you want to find a point - * @param point Point defined as [lon, lat] - */ - public static nearestPoint(way, point: [number, number]){ - return turf.nearestPointOnLine(way, point, {units: "kilometers"}); - } } diff --git a/Logic/Osm/Changes.ts b/Logic/Osm/Changes.ts index ff50cc022b..c9efc979b2 100644 --- a/Logic/Osm/Changes.ts +++ b/Logic/Osm/Changes.ts @@ -6,38 +6,31 @@ import Constants from "../../Models/Constants"; import FeatureSource from "../FeatureSource/FeatureSource"; import {TagsFilter} from "../Tags/TagsFilter"; import {Tag} from "../Tags/Tag"; -import {OsmConnection} from "./OsmConnection"; -import {LocalStorageSource} from "../Web/LocalStorageSource"; /** * Handles all changes made to OSM. * Needs an authenticator via OsmConnection */ -export class Changes implements FeatureSource { +export class Changes implements FeatureSource{ - - private static _nextId = -1; // Newly assigned ID's are negative + public readonly name = "Newly added features" /** * The newly created points, as a FeatureSource */ - public features = new UIEventSource<{ feature: any, freshness: Date }[]>([]); + public features = new UIEventSource<{feature: any, freshness: Date}[]>([]); + + private static _nextId = -1; // Newly assigned ID's are negative /** * All the pending changes */ - public readonly pending = LocalStorageSource.GetParsed<{ elementId: string, key: string, value: string }[]>("pending-changes", []) - - /** - * All the pending new objects to upload - */ - private readonly newObjects = LocalStorageSource.GetParsed<{ id: number, lat: number, lon: number }[]>("newObjects", []) - - private readonly isUploading = new UIEventSource(false); + public readonly pending: UIEventSource<{ elementId: string, key: string, value: string }[]> = + new UIEventSource<{elementId: string; key: string; value: string}[]>([]); /** * Adds a change to the pending changes */ - private static checkChange(kv: { k: string, v: string }): { k: string, v: string } { + private static checkChange(kv: {k: string, v: string}): { k: string, v: string } { const key = kv.k; const value = kv.v; if (key === undefined || key === null) { @@ -56,7 +49,8 @@ export class Changes implements FeatureSource { return {k: key.trim(), v: value.trim()}; } - + + addTag(elementId: string, tagsFilter: TagsFilter, tags?: UIEventSource) { const eventSource = tags ?? State.state?.allElements.getEventSourceById(elementId); @@ -65,7 +59,7 @@ export class Changes implements FeatureSource { if (changes.length == 0) { return; } - + for (const change of changes) { if (elementTags[change.k] !== change.v) { elementTags[change.k] = change.v; @@ -82,16 +76,16 @@ export class Changes implements FeatureSource { * Uploads all the pending changes in one go. * Triggered by the 'PendingChangeUploader'-actor in Actors */ - public flushChanges(flushreason: string = undefined) { - if (this.pending.data.length === 0) { + public flushChanges(flushreason: string = undefined){ + if(this.pending.data.length === 0){ return; } - if (flushreason !== undefined) { + if(flushreason !== undefined){ console.log(flushreason) } - this.uploadAll(); + this.uploadAll([], this.pending.data); + this.pending.setData([]); } - /** * Create a new node element at the given lat/long. * An internal OsmObject is created to upload later on, a geojson represention is returned. @@ -99,12 +93,12 @@ export class Changes implements FeatureSource { */ public createElement(basicTags: Tag[], lat: number, lon: number) { console.log("Creating a new element with ", basicTags) - const newId = Changes._nextId; + const osmNode = new OsmNode(Changes._nextId); Changes._nextId--; - const id = "node/" + newId; - - + const id = "node/" + osmNode.id; + osmNode.lat = lat; + osmNode.lon = lon; const properties = {id: id}; const geojson = { @@ -124,49 +118,35 @@ export class Changes implements FeatureSource { // The tags are not yet written into the OsmObject, but this is applied onto a const changes = []; for (const kv of basicTags) { + properties[kv.key] = kv.value; if (typeof kv.value !== "string") { throw "Invalid value: don't use a regex in a preset" } - properties[kv.key] = kv.value; changes.push({elementId: id, key: kv.key, value: kv.value}) } - + console.log("New feature added and pinged") - this.features.data.push({feature: geojson, freshness: new Date()}); + this.features.data.push({feature:geojson, freshness: new Date()}); this.features.ping(); - + State.state.allElements.addOrGetElement(geojson).ping(); - if (State.state.osmConnection.userDetails.data.backend !== OsmConnection.oauth_configs.osm.url) { - properties["_backend"] = State.state.osmConnection.userDetails.data.backend - } - - - this.newObjects.data.push({id: newId, lat: lat, lon: lon}) - this.pending.data.push(...changes) - this.pending.ping(); - this.newObjects.ping(); + this.uploadAll([osmNode], changes); return geojson; } private uploadChangesWithLatestVersions( - knownElements: OsmObject[]) { + knownElements: OsmObject[], newElements: OsmObject[], pending: { elementId: string; key: string; value: string }[]) { const knownById = new Map(); + knownElements.forEach(knownElement => { knownById.set(knownElement.type + "/" + knownElement.id, knownElement) }) - - const newElements: OsmNode [] = this.newObjects.data.map(spec => { - const newElement = new OsmNode(spec.id); - newElement.lat = spec.lat; - newElement.lon = spec.lon; - return newElement - }) - - + + // Here, inside the continuation, we know that all 'neededIds' are loaded in 'knownElements', which maps the ids onto the elements // We apply the changes on them - for (const change of this.pending.data) { + for (const change of pending) { if (parseInt(change.elementId.split("/")[1]) < 0) { // This is a new element - we should apply this on one of the new elements for (const newElement of newElements) { @@ -188,17 +168,9 @@ export class Changes implements FeatureSource { } } if (changedElements.length == 0 && newElements.length == 0) { - console.log("No changes in any object - clearing"); - this.pending.setData([]) - this.newObjects.setData([]) + console.log("No changes in any object"); return; } - const self = this; - - if (this.isUploading.data) { - return; - } - this.isUploading.setData(true) console.log("Beginning upload..."); // At last, we build the changeset and upload @@ -241,22 +213,17 @@ export class Changes implements FeatureSource { changes += ""; return changes; - }, - () => { - console.log("Upload successfull!") - self.newObjects.setData([]) - self.pending.setData([]); - self.isUploading.setData(false) - }, - () => self.isUploading.setData(false) - ); + }); }; - private uploadAll() { + private uploadAll( + newElements: OsmObject[], + pending: { elementId: string; key: string; value: string }[] + ) { const self = this; - const pending = this.pending.data; + let neededIds: string[] = []; for (const change of pending) { const id = change.elementId; @@ -269,7 +236,8 @@ export class Changes implements FeatureSource { neededIds = Utils.Dedup(neededIds); OsmObject.DownloadAll(neededIds).addCallbackAndRunD(knownElements => { - self.uploadChangesWithLatestVersions(knownElements) + console.log("KnownElements:", knownElements) + self.uploadChangesWithLatestVersions(knownElements, newElements, pending) }) } diff --git a/Logic/Osm/ChangesetHandler.ts b/Logic/Osm/ChangesetHandler.ts index 8fba438037..ef9f5f717c 100644 --- a/Logic/Osm/ChangesetHandler.ts +++ b/Logic/Osm/ChangesetHandler.ts @@ -27,7 +27,7 @@ export class ChangesetHandler { } } - private static parseUploadChangesetResponse(response: XMLDocument, allElements: ElementStorage): void { + private static parseUploadChangesetResponse(response: XMLDocument, allElements: ElementStorage) { const nodes = response.getElementsByTagName("node"); // @ts-ignore for (const node of nodes) { @@ -69,9 +69,7 @@ export class ChangesetHandler { public UploadChangeset( layout: LayoutConfig, allElements: ElementStorage, - generateChangeXML: (csid: string) => string, - whenDone: (csId: string) => void, - onFail: () => void) { + generateChangeXML: (csid: string) => string) { if (this.userDetails.data.csCount == 0) { // The user became a contributor! @@ -82,7 +80,6 @@ export class ChangesetHandler { if (this._dryRun) { const changesetXML = generateChangeXML("123456"); console.log(changesetXML); - whenDone("123456") return; } @@ -96,14 +93,12 @@ export class ChangesetHandler { console.log(changeset); self.AddChange(csId, changeset, allElements, - whenDone, + () => { + }, (e) => { console.error("UPLOADING FAILED!", e) - onFail() } ) - }, { - onFail: onFail }) } else { // There still exists an open changeset (or at least we hope so) @@ -112,13 +107,15 @@ export class ChangesetHandler { csId, generateChangeXML(csId), allElements, - whenDone, + () => { + }, (e) => { console.warn("Could not upload, changeset is probably closed: ", e); // Mark the CS as closed... this.currentChangeset.setData(""); // ... and try again. As the cs is closed, no recursive loop can exist - self.UploadChangeset(layout, allElements, generateChangeXML, whenDone, onFail); + self.UploadChangeset(layout, allElements, generateChangeXML); + } ) @@ -164,22 +161,18 @@ export class ChangesetHandler { const self = this; this.OpenChangeset(layout, (csId: string) => { - // The cs is open - let us actually upload! - const changes = generateChangeXML(csId) + // The cs is open - let us actually upload! + const changes = generateChangeXML(csId) - self.AddChange(csId, changes, allElements, (csId) => { - console.log("Successfully deleted ", object.id) - self.CloseChangeset(csId, continuation) - }, (csId) => { - alert("Deletion failed... Should not happend") - // FAILED - self.CloseChangeset(csId, continuation) - }) - }, { - isDeletionCS: true, - deletionReason: reason - } - ) + self.AddChange(csId, changes, allElements, (csId) => { + console.log("Successfully deleted ", object.id) + self.CloseChangeset(csId, continuation) + }, (csId) => { + alert("Deletion failed... Should not happend") + // FAILED + self.CloseChangeset(csId, continuation) + }) + }, true, reason) } private CloseChangeset(changesetId: string = undefined, continuation: (() => void) = () => { @@ -211,20 +204,15 @@ export class ChangesetHandler { private OpenChangeset( layout: LayoutConfig, continuation: (changesetId: string) => void, - options?: { - isDeletionCS?: boolean, - deletionReason?: string, - onFail?: () => void - } - ) { - options = options ?? {} - options.isDeletionCS = options.isDeletionCS ?? false + isDeletionCS: boolean = false, + deletionReason: string = undefined) { + const commentExtra = layout.changesetmessage !== undefined ? " - " + layout.changesetmessage : ""; let comment = `Adding data with #MapComplete for theme #${layout.id}${commentExtra}` - if (options.isDeletionCS) { + if (isDeletionCS) { comment = `Deleting a point with #MapComplete for theme #${layout.id}${commentExtra}` - if (options.deletionReason) { - comment += ": " + options.deletionReason; + if (deletionReason) { + comment += ": " + deletionReason; } } @@ -233,7 +221,7 @@ export class ChangesetHandler { const metadata = [ ["created_by", `MapComplete ${Constants.vNumber}`], ["comment", comment], - ["deletion", options.isDeletionCS ? "yes" : undefined], + ["deletion", isDeletionCS ? "yes" : undefined], ["theme", layout.id], ["language", Locale.language.data], ["host", window.location.host], @@ -256,9 +244,7 @@ export class ChangesetHandler { }, function (err, response) { if (response === undefined) { console.log("err", err); - if(options.onFail){ - options.onFail() - } + alert("Could not upload change (opening failed). Please file a bug report") return; } else { continuation(response); @@ -279,7 +265,7 @@ export class ChangesetHandler { private AddChange(changesetId: string, changesetXML: string, allElements: ElementStorage, - continuation: ((changesetId: string) => void), + continuation: ((changesetId: string, idMapping: any) => void), onFail: ((changesetId: string, reason: string) => void) = undefined) { this.auth.xhr({ method: 'POST', @@ -294,9 +280,9 @@ export class ChangesetHandler { } return; } - ChangesetHandler.parseUploadChangesetResponse(response, allElements); + const mapping = ChangesetHandler.parseUploadChangesetResponse(response, allElements); console.log("Uploaded changeset ", changesetId); - continuation(changesetId); + continuation(changesetId, mapping); }); } diff --git a/Logic/Osm/OsmConnection.ts b/Logic/Osm/OsmConnection.ts index 92a0823f65..a3df9be9fd 100644 --- a/Logic/Osm/OsmConnection.ts +++ b/Logic/Osm/OsmConnection.ts @@ -30,7 +30,7 @@ export default class UserDetails { export class OsmConnection { - public static readonly oauth_configs = { + public static readonly _oauth_configs = { "osm": { oauth_consumer_key: 'hivV7ec2o49Two8g9h8Is1VIiVOgxQ1iYexCbvem', oauth_secret: 'wDBRTCem0vxD7txrg1y6p5r8nvmz8tAhET7zDASI', @@ -47,7 +47,6 @@ export class OsmConnection { public auth; public userDetails: UIEventSource; public isLoggedIn: UIEventSource - private fakeUser: boolean; _dryRun: boolean; public preferencesHandler: OsmPreferences; public changesetHandler: ChangesetHandler; @@ -60,31 +59,20 @@ export class OsmConnection { url: string }; - constructor(dryRun: boolean, - fakeUser: boolean, - oauth_token: UIEventSource, + constructor(dryRun: boolean, oauth_token: UIEventSource, // Used to keep multiple changesets open and to write to the correct changeset layoutName: string, singlePage: boolean = true, osmConfiguration: "osm" | "osm-test" = 'osm' ) { - this.fakeUser = fakeUser; this._singlePage = singlePage; - this._oauth_config = OsmConnection.oauth_configs[osmConfiguration] ?? OsmConnection.oauth_configs.osm; + this._oauth_config = OsmConnection._oauth_configs[osmConfiguration] ?? OsmConnection._oauth_configs.osm; console.debug("Using backend", this._oauth_config.url) OsmObject.SetBackendUrl(this._oauth_config.url + "/") this._iframeMode = Utils.runningFromConsole ? false : window !== window.top; this.userDetails = new UIEventSource(new UserDetails(this._oauth_config.url), "userDetails"); - this.userDetails.data.dryRun = dryRun || fakeUser; - if(fakeUser){ - const ud = this.userDetails.data; - ud.csCount = 5678 - ud.loggedIn= true; - ud.unreadMessages = 0 - ud.name = "Fake user" - ud.totalMessages = 42; - } + this.userDetails.data.dryRun = dryRun; const self =this; this.isLoggedIn = this.userDetails.map(user => user.loggedIn).addCallback(isLoggedIn => { if(self.userDetails.data.loggedIn == false && isLoggedIn == true){ @@ -122,10 +110,8 @@ export class OsmConnection { public UploadChangeset( layout: LayoutConfig, allElements: ElementStorage, - generateChangeXML: (csid: string) => string, - whenDone: (csId: string) => void, - onFail: () => {}) { - this.changesetHandler.UploadChangeset(layout, allElements, generateChangeXML, whenDone, onFail); + generateChangeXML: (csid: string) => string) { + this.changesetHandler.UploadChangeset(layout, allElements, generateChangeXML); } public GetPreference(key: string, prefix: string = "mapcomplete-"): UIEventSource { @@ -150,10 +136,6 @@ export class OsmConnection { } public AttemptLogin() { - if(this.fakeUser){ - console.log("AttemptLogin called, but ignored as fakeUser is set") - return; - } const self = this; console.log("Trying to log in..."); this.updateAuthObject(); diff --git a/Logic/Osm/OsmObject.ts b/Logic/Osm/OsmObject.ts index 09ee7137c7..e8f2047591 100644 --- a/Logic/Osm/OsmObject.ts +++ b/Logic/Osm/OsmObject.ts @@ -5,8 +5,7 @@ import {UIEventSource} from "../UIEventSource"; export abstract class OsmObject { - private static defaultBackend = "https://www.openstreetmap.org/" - protected static backendURL = OsmObject.defaultBackend; + protected static backendURL = "https://www.openstreetmap.org/" private static polygonFeatures = OsmObject.constructPolygonFeatures() private static objectCache = new Map>(); private static referencingWaysCache = new Map>(); @@ -38,15 +37,15 @@ export abstract class OsmObject { } static DownloadObject(id: string, forceRefresh: boolean = false): UIEventSource { - let src: UIEventSource; + let src : UIEventSource; if (OsmObject.objectCache.has(id)) { src = OsmObject.objectCache.get(id) - if (forceRefresh) { + if(forceRefresh){ src.setData(undefined) - } else { + }else{ return src; } - } else { + }else{ src = new UIEventSource(undefined) } const splitted = id.split("/"); @@ -158,7 +157,7 @@ export abstract class OsmObject { const minlat = bounds[1][0] const maxlat = bounds[0][0]; const url = `${OsmObject.backendURL}api/0.6/map.json?bbox=${minlon},${minlat},${maxlon},${maxlat}` - Utils.downloadJson(url).then(data => { + Utils.downloadJson(url).then( data => { const elements: any[] = data.elements; const objects = OsmObject.ParseObjects(elements) callback(objects); @@ -292,7 +291,6 @@ export abstract class OsmObject { self.LoadData(element) self.SaveExtraData(element, nodes); - const meta = { "_last_edit:contributor": element.user, "_last_edit:contributor:uid": element.uid, @@ -301,11 +299,6 @@ export abstract class OsmObject { "_version_number": element.version } - if (OsmObject.backendURL !== OsmObject.defaultBackend) { - self.tags["_backend"] = OsmObject.backendURL - meta["_backend"] = OsmObject.backendURL; - } - continuation(self, meta); } ); diff --git a/Logic/SimpleMetaTagger.ts b/Logic/SimpleMetaTagger.ts index 2337c97f3f..c6269e222a 100644 --- a/Logic/SimpleMetaTagger.ts +++ b/Logic/SimpleMetaTagger.ts @@ -83,8 +83,7 @@ export default class SimpleMetaTagger { }, (feature => { - const units = State.state?.layoutToUse?.data?.units ?? []; - let rewritten = false; + const units = State.state.layoutToUse.data.units ?? []; for (const key in feature.properties) { if (!feature.properties.hasOwnProperty(key)) { continue; @@ -96,23 +95,16 @@ export default class SimpleMetaTagger { const value = feature.properties[key] const [, denomination] = unit.findDenomination(value) let canonical = denomination?.canonicalValue(value) ?? undefined; - if(canonical === value){ + console.log("Rewritten ", key, " from", value, "into", canonical) + if(canonical === undefined && !unit.eraseInvalid) { break; } - console.log("Rewritten ", key, ` from '${value}' into '${canonical}'`) - if (canonical === undefined && !unit.eraseInvalid) { - break; - } - + feature.properties[key] = canonical; - rewritten = true; break; } } - if(rewritten){ - State.state.allElements.getEventSourceById(feature.id).ping(); - } }) ) diff --git a/Logic/Web/LocalStorageSource.ts b/Logic/Web/LocalStorageSource.ts index 61009114af..050b124591 100644 --- a/Logic/Web/LocalStorageSource.ts +++ b/Logic/Web/LocalStorageSource.ts @@ -4,22 +4,6 @@ import {UIEventSource} from "../UIEventSource"; * UIEventsource-wrapper around localStorage */ export class LocalStorageSource { - - static GetParsed(key: string, defaultValue : T) : UIEventSource{ - return LocalStorageSource.Get(key).map( - str => { - if(str === undefined){ - return defaultValue - } - try{ - return JSON.parse(str) - }catch{ - return defaultValue - } - }, [], - value => JSON.stringify(value) - ) - } static Get(key: string, defaultValue: string = undefined): UIEventSource { try { diff --git a/Models/BaseLayer.ts b/Models/BaseLayer.ts index 84556fc690..01eb8e9d75 100644 --- a/Models/BaseLayer.ts +++ b/Models/BaseLayer.ts @@ -7,6 +7,4 @@ export default interface BaseLayer { max_zoom: number, min_zoom: number; feature: any, - isBest?: boolean, - category?: "map" | "osmbasedmap" | "photo" | "historicphoto" | string } \ No newline at end of file diff --git a/Models/Constants.ts b/Models/Constants.ts index 75423405e4..6747a64a3d 100644 --- a/Models/Constants.ts +++ b/Models/Constants.ts @@ -2,7 +2,7 @@ import { Utils } from "../Utils"; export default class Constants { - public static vNumber = "0.8.4-rc3"; + public static vNumber = "0.8.3f"; // The user journey states thresholds when a new feature gets unlocked public static userJourney = { diff --git a/Models/TileRange.ts b/Models/TileRange.ts deleted file mode 100644 index e1dba5532f..0000000000 --- a/Models/TileRange.ts +++ /dev/null @@ -1,8 +0,0 @@ -export interface TileRange { - xstart: number, - ystart: number, - xend: number, - yend: number, - total: number, - zoomlevel: number -} \ No newline at end of file diff --git a/State.ts b/State.ts index ca7fbff29b..c4169d50ff 100644 --- a/State.ts +++ b/State.ts @@ -1,13 +1,13 @@ -import {Utils} from "./Utils"; -import {ElementStorage} from "./Logic/ElementStorage"; -import {Changes} from "./Logic/Osm/Changes"; -import {OsmConnection} from "./Logic/Osm/OsmConnection"; +import { Utils } from "./Utils"; +import { ElementStorage } from "./Logic/ElementStorage"; +import { Changes } from "./Logic/Osm/Changes"; +import { OsmConnection } from "./Logic/Osm/OsmConnection"; import Locale from "./UI/i18n/Locale"; -import {UIEventSource} from "./Logic/UIEventSource"; -import {LocalStorageSource} from "./Logic/Web/LocalStorageSource"; -import {QueryParameters} from "./Logic/Web/QueryParameters"; +import { UIEventSource } from "./Logic/UIEventSource"; +import { LocalStorageSource } from "./Logic/Web/LocalStorageSource"; +import { QueryParameters } from "./Logic/Web/QueryParameters"; import LayoutConfig from "./Customizations/JSON/LayoutConfig"; -import {MangroveIdentity} from "./Logic/Web/MangroveReviews"; +import { MangroveIdentity } from "./Logic/Web/MangroveReviews"; import InstalledThemes from "./Logic/Actors/InstalledThemes"; import BaseLayer from "./Models/BaseLayer"; import Loc from "./Models/Loc"; @@ -17,423 +17,410 @@ import OverpassFeatureSource from "./Logic/Actors/OverpassFeatureSource"; import LayerConfig from "./Customizations/JSON/LayerConfig"; import TitleHandler from "./Logic/Actors/TitleHandler"; import PendingChangesUploader from "./Logic/Actors/PendingChangesUploader"; -import {Relation} from "./Logic/Osm/ExtractRelations"; +import { Relation } from "./Logic/Osm/ExtractRelations"; import OsmApiFeatureSource from "./Logic/FeatureSource/OsmApiFeatureSource"; -import FeaturePipeline from "./Logic/FeatureSource/FeaturePipeline"; /** * Contains the global state: a bunch of UI-event sources */ export default class State { - // The singleton of the global state - public static state: State; + // The singleton of the global state + public static state: State; - public readonly layoutToUse = new UIEventSource(undefined); + public readonly layoutToUse = new UIEventSource(undefined); - /** + /** The mapping from id -> UIEventSource */ - public allElements: ElementStorage; - /** + public allElements: ElementStorage; + /** THe change handler */ - public changes: Changes; - /** + public changes: Changes; + /** The leaflet instance of the big basemap */ - public leafletMap = new UIEventSource(undefined); - /** - * Background layer id - */ - public availableBackgroundLayers: UIEventSource; - /** + public leafletMap = new UIEventSource(undefined); + /** + * Background layer id + */ + public availableBackgroundLayers: UIEventSource; + /** The user credentials */ - public osmConnection: OsmConnection; + public osmConnection: OsmConnection; - public mangroveIdentity: MangroveIdentity; + public mangroveIdentity: MangroveIdentity; - public favouriteLayers: UIEventSource; + public favouriteLayers: UIEventSource; - public layerUpdater: OverpassFeatureSource; + public layerUpdater: OverpassFeatureSource; - public osmApiFeatureSource: OsmApiFeatureSource; + public osmApiFeatureSource: OsmApiFeatureSource; - public filteredLayers: UIEventSource<{ - readonly isDisplayed: UIEventSource; - readonly layerDef: LayerConfig; - }[]> = new UIEventSource<{ - readonly isDisplayed: UIEventSource; - readonly layerDef: LayerConfig; - }[]>([]); + public filteredLayers: UIEventSource< + { + readonly isDisplayed: UIEventSource; + readonly layerDef: LayerConfig; + }[] + > = new UIEventSource< + { + readonly isDisplayed: UIEventSource; + readonly layerDef: LayerConfig; + }[] + >([]); - /** + /** The latest element that was selected */ - public readonly selectedElement = new UIEventSource( + public readonly selectedElement = new UIEventSource( + undefined, + "Selected element" + ); + + /** + * Keeps track of relations: which way is part of which other way? + * Set by the overpass-updater; used in the metatagging + */ + public readonly knownRelations = new UIEventSource< + Map + >(undefined, "Relation memberships"); + + public readonly featureSwitchUserbadge: UIEventSource; + public readonly featureSwitchSearch: UIEventSource; + public readonly featureSwitchLayers: UIEventSource; + public readonly featureSwitchAddNew: UIEventSource; + public readonly featureSwitchWelcomeMessage: UIEventSource; + public readonly featureSwitchIframe: UIEventSource; + public readonly featureSwitchMoreQuests: UIEventSource; + public readonly featureSwitchShareScreen: UIEventSource; + public readonly featureSwitchGeolocation: UIEventSource; + public readonly featureSwitchIsTesting: UIEventSource; + public readonly featureSwitchIsDebugging: UIEventSource; + public readonly featureSwitchShowAllQuestions: UIEventSource; + public readonly featureSwitchApiURL: UIEventSource; + public readonly featureSwitchFilter: UIEventSource; + + /** + * The map location: currently centered lat, lon and zoom + */ + public readonly locationControl = new UIEventSource(undefined); + public backgroundLayer; + public readonly backgroundLayerId: UIEventSource; + + /* Last location where a click was registered + */ + public readonly LastClickLocation: UIEventSource<{ + lat: number; + lon: number; + }> = new UIEventSource<{ lat: number; lon: number }>(undefined); + + /** + * The location as delivered by the GPS + */ + public currentGPSLocation: UIEventSource<{ + latlng: { lat: number; lng: number }; + accuracy: number; + }> = new UIEventSource<{ + latlng: { lat: number; lng: number }; + accuracy: number; + }>(undefined); + public layoutDefinition: string; + public installedThemes: UIEventSource< + { layout: LayoutConfig; definition: string }[] + >; + + public layerControlIsOpened: UIEventSource = + QueryParameters.GetQueryParameter( + "layer-control-toggle", + "false", + "Whether or not the layer control is shown" + ).map( + (str) => str !== "false", + [], + (b) => "" + b + ); + + public FilterIsOpened: UIEventSource = + QueryParameters.GetQueryParameter( + "filter-toggle", + "false", + "Whether or not the filter is shown" + ).map( + (str) => str !== "false", + [], + (b) => "" + b + ); + + public welcomeMessageOpenedTab = QueryParameters.GetQueryParameter( + "tab", + "0", + `The tab that is shown in the welcome-message. 0 = the explanation of the theme,1 = OSM-credits, 2 = sharescreen, 3 = more themes, 4 = about mapcomplete (user must be logged in and have >${Constants.userJourney.mapCompleteHelpUnlock} changesets)` + ).map( + (str) => (isNaN(Number(str)) ? 0 : Number(str)), + [], + (n) => "" + n + ); + + constructor(layoutToUse: LayoutConfig) { + const self = this; + + this.layoutToUse.setData(layoutToUse); + + // -- Location control initialization + { + const zoom = State.asFloat( + QueryParameters.GetQueryParameter( + "z", + "" + (layoutToUse?.startZoom ?? 1), + "The initial/current zoom level" + ).syncWith(LocalStorageSource.Get("zoom")) + ); + const lat = State.asFloat( + QueryParameters.GetQueryParameter( + "lat", + "" + (layoutToUse?.startLat ?? 0), + "The initial/current latitude" + ).syncWith(LocalStorageSource.Get("lat")) + ); + const lon = State.asFloat( + QueryParameters.GetQueryParameter( + "lon", + "" + (layoutToUse?.startLon ?? 0), + "The initial/current longitude of the app" + ).syncWith(LocalStorageSource.Get("lon")) + ); + + this.locationControl = new UIEventSource({ + zoom: Utils.asFloat(zoom.data), + lat: Utils.asFloat(lat.data), + lon: Utils.asFloat(lon.data), + }).addCallback((latlonz) => { + zoom.setData(latlonz.zoom); + lat.setData(latlonz.lat); + lon.setData(latlonz.lon); + }); + + this.layoutToUse.addCallback((layoutToUse) => { + const lcd = self.locationControl.data; + lcd.zoom = lcd.zoom ?? layoutToUse?.startZoom; + lcd.lat = lcd.lat ?? layoutToUse?.startLat; + lcd.lon = lcd.lon ?? layoutToUse?.startLon; + self.locationControl.ping(); + }); + } + + // Helper function to initialize feature switches + function featSw( + key: string, + deflt: (layout: LayoutConfig) => boolean, + documentation: string + ): UIEventSource { + const queryParameterSource = QueryParameters.GetQueryParameter( + key, undefined, - "Selected element" - ); + documentation + ); + // I'm so sorry about someone trying to decipher this - /** - * Keeps track of relations: which way is part of which other way? - * Set by the overpass-updater; used in the metatagging - */ - public readonly knownRelations = new UIEventSource>(undefined, "Relation memberships"); + // It takes the current layout, extracts the default value for this query parameter. A query parameter event source is then retrieved and flattened + return UIEventSource.flatten( + self.layoutToUse.map((layout) => { + const defaultValue = deflt(layout); + const queryParam = QueryParameters.GetQueryParameter( + key, + "" + defaultValue, + documentation + ); + return queryParam.map((str) => + str === undefined ? defaultValue : str !== "false" + ); + }), + [queryParameterSource] + ); + } - public readonly featureSwitchUserbadge: UIEventSource; - public readonly featureSwitchSearch: UIEventSource; - public readonly featureSwitchLayers: UIEventSource; - public readonly featureSwitchAddNew: UIEventSource; - public readonly featureSwitchWelcomeMessage: UIEventSource; - public readonly featureSwitchIframe: UIEventSource; - public readonly featureSwitchMoreQuests: UIEventSource; - public readonly featureSwitchShareScreen: UIEventSource; - public readonly featureSwitchGeolocation: UIEventSource; - public readonly featureSwitchIsTesting: UIEventSource; - public readonly featureSwitchIsDebugging: UIEventSource; - public readonly featureSwitchShowAllQuestions: UIEventSource; - public readonly featureSwitchApiURL: UIEventSource; - public readonly featureSwitchFilter: UIEventSource; - public readonly featureSwitchEnableExport: UIEventSource; - public readonly featureSwitchFakeUser: UIEventSource; + // Feature switch initialization - not as a function as the UIEventSources are readonly + { + this.featureSwitchUserbadge = featSw( + "fs-userbadge", + (layoutToUse) => layoutToUse?.enableUserBadge ?? true, + "Disables/Enables the user information pill (userbadge) at the top left. Disabling this disables logging in and thus disables editing all together, effectively putting MapComplete into read-only mode." + ); + this.featureSwitchSearch = featSw( + "fs-search", + (layoutToUse) => layoutToUse?.enableSearch ?? true, + "Disables/Enables the search bar" + ); + this.featureSwitchLayers = featSw( + "fs-layers", + (layoutToUse) => layoutToUse?.enableLayers ?? true, + "Disables/Enables the layer control" + ); + this.featureSwitchFilter = featSw( + "fs-filter", + (layoutToUse) => layoutToUse?.enableLayers ?? true, + "Disables/Enables the filter" + ); + this.featureSwitchAddNew = featSw( + "fs-add-new", + (layoutToUse) => layoutToUse?.enableAddNewPoints ?? true, + "Disables/Enables the 'add new feature'-popup. (A theme without presets might not have it in the first place)" + ); + this.featureSwitchWelcomeMessage = featSw( + "fs-welcome-message", + () => true, + "Disables/enables the help menu or welcome message" + ); + this.featureSwitchIframe = featSw( + "fs-iframe", + () => false, + "Disables/Enables the iframe-popup" + ); + this.featureSwitchMoreQuests = featSw( + "fs-more-quests", + (layoutToUse) => layoutToUse?.enableMoreQuests ?? true, + "Disables/Enables the 'More Quests'-tab in the welcome message" + ); + this.featureSwitchShareScreen = featSw( + "fs-share-screen", + (layoutToUse) => layoutToUse?.enableShareScreen ?? true, + "Disables/Enables the 'Share-screen'-tab in the welcome message" + ); + this.featureSwitchGeolocation = featSw( + "fs-geolocation", + (layoutToUse) => layoutToUse?.enableGeolocation ?? true, + "Disables/Enables the geolocation button" + ); + this.featureSwitchShowAllQuestions = featSw( + "fs-all-questions", + (layoutToUse) => layoutToUse?.enableShowAllQuestions ?? false, + "Always show all questions" + ); - - public featurePipeline: FeaturePipeline; - - - /** - * The map location: currently centered lat, lon and zoom - */ - public readonly locationControl = new UIEventSource(undefined); - public backgroundLayer; - public readonly backgroundLayerId: UIEventSource; - - /* Last location where a click was registered - */ - public readonly LastClickLocation: UIEventSource<{ - lat: number; - lon: number; - }> = new UIEventSource<{ lat: number; lon: number }>(undefined); - - /** - * The location as delivered by the GPS - */ - public currentGPSLocation: UIEventSource<{ - latlng: { lat: number; lng: number }; - accuracy: number; - }> = new UIEventSource<{ - latlng: { lat: number; lng: number }; - accuracy: number; - }>(undefined); - public layoutDefinition: string; - public installedThemes: UIEventSource<{ layout: LayoutConfig; definition: string }[]>; - - public layerControlIsOpened: UIEventSource = - QueryParameters.GetQueryParameter( - "layer-control-toggle", - "false", - "Whether or not the layer control is shown" - ).map( - (str) => str !== "false", - [], - (b) => "" + b - ); - - public FilterIsOpened: UIEventSource = - QueryParameters.GetQueryParameter( - "filter-toggle", - "false", - "Whether or not the filter is shown" - ).map( - (str) => str !== "false", - [], - (b) => "" + b - ); - - public welcomeMessageOpenedTab = QueryParameters.GetQueryParameter( - "tab", - "0", - `The tab that is shown in the welcome-message. 0 = the explanation of the theme,1 = OSM-credits, 2 = sharescreen, 3 = more themes, 4 = about mapcomplete (user must be logged in and have >${Constants.userJourney.mapCompleteHelpUnlock} changesets)` - ).map( - (str) => (isNaN(Number(str)) ? 0 : Number(str)), + this.featureSwitchIsTesting = QueryParameters.GetQueryParameter( + "test", + "false", + "If true, 'dryrun' mode is activated. The app will behave as normal, except that changes to OSM will be printed onto the console instead of actually uploaded to osm.org" + ).map( + (str) => str === "true", [], - (n) => "" + n + (b) => "" + b + ); + + this.featureSwitchIsDebugging = QueryParameters.GetQueryParameter( + "debug", + "false", + "If true, shows some extra debugging help such as all the available tags on every object" + ).map( + (str) => str === "true", + [], + (b) => "" + b + ); + + this.featureSwitchApiURL = QueryParameters.GetQueryParameter( + "backend", + "osm", + "The OSM backend to use - can be used to redirect mapcomplete to the testing backend when using 'osm-test'" + ); + } + { + // Some other feature switches + const customCssQP = QueryParameters.GetQueryParameter( + "custom-css", + "", + "If specified, the custom css from the given link will be loaded additionaly" + ); + if (customCssQP.data !== undefined && customCssQP.data !== "") { + Utils.LoadCustomCss(customCssQP.data); + } + + this.backgroundLayerId = QueryParameters.GetQueryParameter( + "background", + layoutToUse?.defaultBackgroundId ?? "osm", + "The id of the background layer to start with" + ); + } + + if (Utils.runningFromConsole) { + return; + } + + this.osmConnection = new OsmConnection( + this.featureSwitchIsTesting.data, + QueryParameters.GetQueryParameter( + "oauth_token", + undefined, + "Used to complete the login" + ), + layoutToUse?.id, + true, + // @ts-ignore + this.featureSwitchApiURL.data ); - constructor(layoutToUse: LayoutConfig) { - const self = this; + this.allElements = new ElementStorage(); + this.changes = new Changes(); + this.osmApiFeatureSource = new OsmApiFeatureSource(); - this.layoutToUse.setData(layoutToUse); + new PendingChangesUploader(this.changes, this.selectedElement); - // -- Location control initialization - { - const zoom = State.asFloat( - QueryParameters.GetQueryParameter( - "z", - "" + (layoutToUse?.startZoom ?? 1), - "The initial/current zoom level" - ).syncWith(LocalStorageSource.Get("zoom")) - ); - const lat = State.asFloat( - QueryParameters.GetQueryParameter( - "lat", - "" + (layoutToUse?.startLat ?? 0), - "The initial/current latitude" - ).syncWith(LocalStorageSource.Get("lat")) - ); - const lon = State.asFloat( - QueryParameters.GetQueryParameter( - "lon", - "" + (layoutToUse?.startLon ?? 0), - "The initial/current longitude of the app" - ).syncWith(LocalStorageSource.Get("lon")) - ); + this.mangroveIdentity = new MangroveIdentity( + this.osmConnection.GetLongPreference("identity", "mangrove") + ); - this.locationControl = new UIEventSource({ - zoom: Utils.asFloat(zoom.data), - lat: Utils.asFloat(lat.data), - lon: Utils.asFloat(lon.data), - }).addCallback((latlonz) => { - zoom.setData(latlonz.zoom); - lat.setData(latlonz.lat); - lon.setData(latlonz.lon); - }); + this.installedThemes = new InstalledThemes( + this.osmConnection + ).installedThemes; - this.layoutToUse.addCallback((layoutToUse) => { - const lcd = self.locationControl.data; - lcd.zoom = lcd.zoom ?? layoutToUse?.startZoom; - lcd.lat = lcd.lat ?? layoutToUse?.startLat; - lcd.lon = lcd.lon ?? layoutToUse?.startLon; - self.locationControl.ping(); - }); + // Important: the favourite layers are initialized _after_ the installed themes, as these might contain an installedTheme + this.favouriteLayers = LocalStorageSource.Get("favouriteLayers") + .syncWith(this.osmConnection.GetLongPreference("favouriteLayers")) + .map( + (str) => Utils.Dedup(str?.split(";")) ?? [], + [], + (layers) => Utils.Dedup(layers)?.join(";") + ); + + Locale.language.syncWith(this.osmConnection.GetPreference("language")); + + Locale.language + .addCallback((currentLanguage) => { + const layoutToUse = self.layoutToUse.data; + if (layoutToUse === undefined) { + return; } - - // Helper function to initialize feature switches - function featSw( - key: string, - deflt: (layout: LayoutConfig) => boolean, - documentation: string - ): UIEventSource { - const queryParameterSource = QueryParameters.GetQueryParameter( - key, - undefined, - documentation - ); - // I'm so sorry about someone trying to decipher this - - // It takes the current layout, extracts the default value for this query parameter. A query parameter event source is then retrieved and flattened - return UIEventSource.flatten( - self.layoutToUse.map((layout) => { - const defaultValue = deflt(layout); - const queryParam = QueryParameters.GetQueryParameter( - key, - "" + defaultValue, - documentation - ); - return queryParam.map((str) => - str === undefined ? defaultValue : str !== "false" - ); - }), - [queryParameterSource] - ); + if (this.layoutToUse.data.language.indexOf(currentLanguage) < 0) { + console.log( + "Resetting language to", + layoutToUse.language[0], + "as", + currentLanguage, + " is unsupported" + ); + // The current language is not supported -> switch to a supported one + Locale.language.setData(layoutToUse.language[0]); } + }) + .ping(); - // Feature switch initialization - not as a function as the UIEventSources are readonly - { - this.featureSwitchUserbadge = featSw( - "fs-userbadge", - (layoutToUse) => layoutToUse?.enableUserBadge ?? true, - "Disables/Enables the user information pill (userbadge) at the top left. Disabling this disables logging in and thus disables editing all together, effectively putting MapComplete into read-only mode." - ); - this.featureSwitchSearch = featSw( - "fs-search", - (layoutToUse) => layoutToUse?.enableSearch ?? true, - "Disables/Enables the search bar" - ); - this.featureSwitchLayers = featSw( - "fs-layers", - (layoutToUse) => layoutToUse?.enableLayers ?? true, - "Disables/Enables the layer control" - ); - this.featureSwitchFilter = featSw( - "fs-filter", - (layoutToUse) => layoutToUse?.enableLayers ?? true, - "Disables/Enables the filter" - ); - this.featureSwitchAddNew = featSw( - "fs-add-new", - (layoutToUse) => layoutToUse?.enableAddNewPoints ?? true, - "Disables/Enables the 'add new feature'-popup. (A theme without presets might not have it in the first place)" - ); - this.featureSwitchWelcomeMessage = featSw( - "fs-welcome-message", - () => true, - "Disables/enables the help menu or welcome message" - ); - this.featureSwitchIframe = featSw( - "fs-iframe", - () => false, - "Disables/Enables the iframe-popup" - ); - this.featureSwitchMoreQuests = featSw( - "fs-more-quests", - (layoutToUse) => layoutToUse?.enableMoreQuests ?? true, - "Disables/Enables the 'More Quests'-tab in the welcome message" - ); - this.featureSwitchShareScreen = featSw( - "fs-share-screen", - (layoutToUse) => layoutToUse?.enableShareScreen ?? true, - "Disables/Enables the 'Share-screen'-tab in the welcome message" - ); - this.featureSwitchGeolocation = featSw( - "fs-geolocation", - (layoutToUse) => layoutToUse?.enableGeolocation ?? true, - "Disables/Enables the geolocation button" - ); - this.featureSwitchShowAllQuestions = featSw( - "fs-all-questions", - (layoutToUse) => layoutToUse?.enableShowAllQuestions ?? false, - "Always show all questions" - ); + new TitleHandler(this.layoutToUse, this.selectedElement, this.allElements); + } - this.featureSwitchIsTesting = QueryParameters.GetQueryParameter( - "test", - "false", - "If true, 'dryrun' mode is activated. The app will behave as normal, except that changes to OSM will be printed onto the console instead of actually uploaded to osm.org" - ).map( - (str) => str === "true", - [], - (b) => "" + b - ); - - this.featureSwitchIsDebugging = QueryParameters.GetQueryParameter( - "debug", - "false", - "If true, shows some extra debugging help such as all the available tags on every object" - ).map( - (str) => str === "true", - [], - (b) => "" + b - ); - - this.featureSwitchFakeUser = QueryParameters.GetQueryParameter("fake-user", "false", - "If true, 'dryrun' mode is activated and a fake user account is loaded") - .map(str => str === "true", [], b => "" + b); - - - this.featureSwitchApiURL = QueryParameters.GetQueryParameter( - "backend", - "osm", - "The OSM backend to use - can be used to redirect mapcomplete to the testing backend when using 'osm-test'" - ); - - - - this.featureSwitchUserbadge.addCallbackAndRun(userbadge => { - if (!userbadge) { - this.featureSwitchAddNew.setData(false) - } - }) + private static asFloat(source: UIEventSource): UIEventSource { + return source.map( + (str) => { + let parsed = parseFloat(str); + return isNaN(parsed) ? undefined : parsed; + }, + [], + (fl) => { + if (fl === undefined || isNaN(fl)) { + return undefined; } - { - // Some other feature switches - const customCssQP = QueryParameters.GetQueryParameter( - "custom-css", - "", - "If specified, the custom css from the given link will be loaded additionaly" - ); - if (customCssQP.data !== undefined && customCssQP.data !== "") { - Utils.LoadCustomCss(customCssQP.data); - } - - this.backgroundLayerId = QueryParameters.GetQueryParameter( - "background", - layoutToUse?.defaultBackgroundId ?? "osm", - "The id of the background layer to start with" - ); - } - - if (Utils.runningFromConsole) { - return; - } - - this.osmConnection = new OsmConnection( - this.featureSwitchIsTesting.data, - this.featureSwitchFakeUser.data, - QueryParameters.GetQueryParameter( - "oauth_token", - undefined, - "Used to complete the login" - ), - layoutToUse?.id, - true, - // @ts-ignore - this.featureSwitchApiURL.data - ); - - this.allElements = new ElementStorage(); - this.changes = new Changes(); - this.osmApiFeatureSource = new OsmApiFeatureSource(); - - new PendingChangesUploader(this.changes, this.selectedElement); - - this.mangroveIdentity = new MangroveIdentity( - this.osmConnection.GetLongPreference("identity", "mangrove") - ); - - this.installedThemes = new InstalledThemes( - this.osmConnection - ).installedThemes; - - // Important: the favourite layers are initialized _after_ the installed themes, as these might contain an installedTheme - this.favouriteLayers = LocalStorageSource.Get("favouriteLayers") - .syncWith(this.osmConnection.GetLongPreference("favouriteLayers")) - .map( - (str) => Utils.Dedup(str?.split(";")) ?? [], - [], - (layers) => Utils.Dedup(layers)?.join(";") - ); - - Locale.language.syncWith(this.osmConnection.GetPreference("language")); - - Locale.language - .addCallback((currentLanguage) => { - const layoutToUse = self.layoutToUse.data; - if (layoutToUse === undefined) { - return; - } - if (this.layoutToUse.data.language.indexOf(currentLanguage) < 0) { - console.log( - "Resetting language to", - layoutToUse.language[0], - "as", - currentLanguage, - " is unsupported" - ); - // The current language is not supported -> switch to a supported one - Locale.language.setData(layoutToUse.language[0]); - } - }) - .ping(); - - new TitleHandler(this.layoutToUse, this.selectedElement, this.allElements); - } - - private static asFloat(source: UIEventSource): UIEventSource { - return source.map( - (str) => { - let parsed = parseFloat(str); - return isNaN(parsed) ? undefined : parsed; - }, - [], - (fl) => { - if (fl === undefined || isNaN(fl)) { - return undefined; - } - return ("" + fl).substr(0, 8); - } - ); - } + return ("" + fl).substr(0, 8); + } + ); + } } diff --git a/Svg.ts b/Svg.ts index b3d31f0a87..22ea0e1158 100644 --- a/Svg.ts +++ b/Svg.ts @@ -54,16 +54,6 @@ export default class Svg { public static camera_plus_svg() { return new Img(Svg.camera_plus, true);} public static camera_plus_ui() { return new FixedUiElement(Svg.camera_plus_img);} - public static checkbox_empty = " " - public static checkbox_empty_img = Img.AsImageElement(Svg.checkbox_empty) - public static checkbox_empty_svg() { return new Img(Svg.checkbox_empty, true);} - public static checkbox_empty_ui() { return new FixedUiElement(Svg.checkbox_empty_img);} - - public static checkbox_filled = " " - public static checkbox_filled_img = Img.AsImageElement(Svg.checkbox_filled) - public static checkbox_filled_svg() { return new Img(Svg.checkbox_filled, true);} - public static checkbox_filled_ui() { return new FixedUiElement(Svg.checkbox_filled_img);} - public static checkmark = "" public static checkmark_img = Img.AsImageElement(Svg.checkmark) public static checkmark_svg() { return new Img(Svg.checkmark, true);} @@ -104,16 +94,6 @@ export default class Svg { public static crosshair_blue_svg() { return new Img(Svg.crosshair_blue, true);} public static crosshair_blue_ui() { return new FixedUiElement(Svg.crosshair_blue_img);} - public static crosshair_empty = " image/svg+xml " - public static crosshair_empty_img = Img.AsImageElement(Svg.crosshair_empty) - public static crosshair_empty_svg() { return new Img(Svg.crosshair_empty, true);} - public static crosshair_empty_ui() { return new FixedUiElement(Svg.crosshair_empty_img);} - - public static crosshair_locked = " image/svg+xml " - public static crosshair_locked_img = Img.AsImageElement(Svg.crosshair_locked) - public static crosshair_locked_svg() { return new Img(Svg.crosshair_locked, true);} - public static crosshair_locked_ui() { return new FixedUiElement(Svg.crosshair_locked_img);} - public static crosshair = " image/svg+xml " public static crosshair_img = Img.AsImageElement(Svg.crosshair) public static crosshair_svg() { return new Img(Svg.crosshair, true);} @@ -154,11 +134,6 @@ export default class Svg { public static down_svg() { return new Img(Svg.down, true);} public static down_ui() { return new FixedUiElement(Svg.down_img);} - public static download = " " - public static download_img = Img.AsImageElement(Svg.download) - public static download_svg() { return new Img(Svg.download, true);} - public static download_ui() { return new FixedUiElement(Svg.download_img);} - public static envelope = " image/svg+xml " public static envelope_img = Img.AsImageElement(Svg.envelope) public static envelope_svg() { return new Img(Svg.envelope, true);} @@ -209,11 +184,6 @@ export default class Svg { public static layersAdd_svg() { return new Img(Svg.layersAdd, true);} public static layersAdd_ui() { return new FixedUiElement(Svg.layersAdd_img);} - public static length_crosshair = " Created by potrace 1.15, written by Peter Selinger 2001-2017 image/svg+xml " - public static length_crosshair_img = Img.AsImageElement(Svg.length_crosshair) - public static length_crosshair_svg() { return new Img(Svg.length_crosshair, true);} - public static length_crosshair_ui() { return new FixedUiElement(Svg.length_crosshair_img);} - public static location = " " public static location_img = Img.AsImageElement(Svg.location) public static location_svg() { return new Img(Svg.location, true);} @@ -389,4 +359,4 @@ export default class Svg { public static wikipedia_svg() { return new Img(Svg.wikipedia, true);} public static wikipedia_ui() { return new FixedUiElement(Svg.wikipedia_img);} -public static All = {"SocialImageForeground.svg": Svg.SocialImageForeground,"add.svg": Svg.add,"addSmall.svg": Svg.addSmall,"ampersand.svg": Svg.ampersand,"arrow-left-smooth.svg": Svg.arrow_left_smooth,"arrow-left-thin.svg": Svg.arrow_left_thin,"arrow-right-smooth.svg": Svg.arrow_right_smooth,"back.svg": Svg.back,"bug.svg": Svg.bug,"camera-plus.svg": Svg.camera_plus,"checkbox-empty.svg": Svg.checkbox_empty,"checkbox-filled.svg": Svg.checkbox_filled,"checkmark.svg": Svg.checkmark,"circle.svg": Svg.circle,"clock.svg": Svg.clock,"close.svg": Svg.close,"compass.svg": Svg.compass,"cross_bottom_right.svg": Svg.cross_bottom_right,"crosshair-blue-center.svg": Svg.crosshair_blue_center,"crosshair-blue.svg": Svg.crosshair_blue,"crosshair-empty.svg": Svg.crosshair_empty,"crosshair-locked.svg": Svg.crosshair_locked,"crosshair.svg": Svg.crosshair,"delete_icon.svg": Svg.delete_icon,"direction.svg": Svg.direction,"direction_gradient.svg": Svg.direction_gradient,"direction_masked.svg": Svg.direction_masked,"direction_outline.svg": Svg.direction_outline,"direction_stroke.svg": Svg.direction_stroke,"down.svg": Svg.down,"download.svg": Svg.download,"envelope.svg": Svg.envelope,"filter.svg": Svg.filter,"floppy.svg": Svg.floppy,"gear.svg": Svg.gear,"help.svg": Svg.help,"home.svg": Svg.home,"home_white_bg.svg": Svg.home_white_bg,"josm_logo.svg": Svg.josm_logo,"layers.svg": Svg.layers,"layersAdd.svg": Svg.layersAdd,"length-crosshair.svg": Svg.length_crosshair,"location.svg": Svg.location,"logo.svg": Svg.logo,"logout.svg": Svg.logout,"mapcomplete_logo.svg": Svg.mapcomplete_logo,"mapillary.svg": Svg.mapillary,"mapillary_black.svg": Svg.mapillary_black,"min-zoom.svg": Svg.min_zoom,"min.svg": Svg.min,"no_checkmark.svg": Svg.no_checkmark,"or.svg": Svg.or,"osm-copyright.svg": Svg.osm_copyright,"osm-logo-us.svg": Svg.osm_logo_us,"osm-logo.svg": Svg.osm_logo,"pencil.svg": Svg.pencil,"phone.svg": Svg.phone,"pin.svg": Svg.pin,"plus-zoom.svg": Svg.plus_zoom,"plus.svg": Svg.plus,"pop-out.svg": Svg.pop_out,"reload.svg": Svg.reload,"ring.svg": Svg.ring,"search.svg": Svg.search,"send_email.svg": Svg.send_email,"share.svg": Svg.share,"square.svg": Svg.square,"star.svg": Svg.star,"star_half.svg": Svg.star_half,"star_outline.svg": Svg.star_outline,"star_outline_half.svg": Svg.star_outline_half,"statistics.svg": Svg.statistics,"translate.svg": Svg.translate,"up.svg": Svg.up,"wikidata.svg": Svg.wikidata,"wikimedia-commons-white.svg": Svg.wikimedia_commons_white,"wikipedia.svg": Svg.wikipedia};} +public static All = {"SocialImageForeground.svg": Svg.SocialImageForeground,"add.svg": Svg.add,"addSmall.svg": Svg.addSmall,"ampersand.svg": Svg.ampersand,"arrow-left-smooth.svg": Svg.arrow_left_smooth,"arrow-left-thin.svg": Svg.arrow_left_thin,"arrow-right-smooth.svg": Svg.arrow_right_smooth,"back.svg": Svg.back,"bug.svg": Svg.bug,"camera-plus.svg": Svg.camera_plus,"checkmark.svg": Svg.checkmark,"circle.svg": Svg.circle,"clock.svg": Svg.clock,"close.svg": Svg.close,"compass.svg": Svg.compass,"cross_bottom_right.svg": Svg.cross_bottom_right,"crosshair-blue-center.svg": Svg.crosshair_blue_center,"crosshair-blue.svg": Svg.crosshair_blue,"crosshair.svg": Svg.crosshair,"delete_icon.svg": Svg.delete_icon,"direction.svg": Svg.direction,"direction_gradient.svg": Svg.direction_gradient,"direction_masked.svg": Svg.direction_masked,"direction_outline.svg": Svg.direction_outline,"direction_stroke.svg": Svg.direction_stroke,"down.svg": Svg.down,"envelope.svg": Svg.envelope,"filter.svg": Svg.filter,"floppy.svg": Svg.floppy,"gear.svg": Svg.gear,"help.svg": Svg.help,"home.svg": Svg.home,"home_white_bg.svg": Svg.home_white_bg,"josm_logo.svg": Svg.josm_logo,"layers.svg": Svg.layers,"layersAdd.svg": Svg.layersAdd,"location.svg": Svg.location,"logo.svg": Svg.logo,"logout.svg": Svg.logout,"mapcomplete_logo.svg": Svg.mapcomplete_logo,"mapillary.svg": Svg.mapillary,"mapillary_black.svg": Svg.mapillary_black,"min-zoom.svg": Svg.min_zoom,"min.svg": Svg.min,"no_checkmark.svg": Svg.no_checkmark,"or.svg": Svg.or,"osm-copyright.svg": Svg.osm_copyright,"osm-logo-us.svg": Svg.osm_logo_us,"osm-logo.svg": Svg.osm_logo,"pencil.svg": Svg.pencil,"phone.svg": Svg.phone,"pin.svg": Svg.pin,"plus-zoom.svg": Svg.plus_zoom,"plus.svg": Svg.plus,"pop-out.svg": Svg.pop_out,"reload.svg": Svg.reload,"ring.svg": Svg.ring,"search.svg": Svg.search,"send_email.svg": Svg.send_email,"share.svg": Svg.share,"square.svg": Svg.square,"star.svg": Svg.star,"star_half.svg": Svg.star_half,"star_outline.svg": Svg.star_outline,"star_outline_half.svg": Svg.star_outline_half,"statistics.svg": Svg.statistics,"translate.svg": Svg.translate,"up.svg": Svg.up,"wikidata.svg": Svg.wikidata,"wikimedia-commons-white.svg": Svg.wikimedia_commons_white,"wikipedia.svg": Svg.wikipedia};} diff --git a/UI/Base/Minimap.ts b/UI/Base/Minimap.ts index 6ebf37a756..647fade477 100644 --- a/UI/Base/Minimap.ts +++ b/UI/Base/Minimap.ts @@ -5,7 +5,6 @@ import Loc from "../../Models/Loc"; import BaseLayer from "../../Models/BaseLayer"; import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers"; import {Map} from "leaflet"; -import {Utils} from "../../Utils"; export default class Minimap extends BaseUIElement { @@ -16,13 +15,11 @@ export default class Minimap extends BaseUIElement { private readonly _location: UIEventSource; private _isInited = false; private _allowMoving: boolean; - private readonly _leafletoptions: any; constructor(options?: { background?: UIEventSource, location?: UIEventSource, - allowMoving?: boolean, - leafletOptions?: any + allowMoving?: boolean } ) { super() @@ -31,11 +28,10 @@ export default class Minimap extends BaseUIElement { this._location = options?.location ?? new UIEventSource(undefined) this._id = "minimap" + Minimap._nextId; this._allowMoving = options.allowMoving ?? true; - this._leafletoptions = options.leafletOptions ?? {} Minimap._nextId++ } - + protected InnerConstructElement(): HTMLElement { const div = document.createElement("div") div.id = this._id; @@ -48,6 +44,7 @@ export default class Minimap extends BaseUIElement { const self = this; // @ts-ignore const resizeObserver = new ResizeObserver(_ => { + console.log("Change in size detected!") self.InitMap(); self.leafletMap?.data?.invalidateSize() }); @@ -75,8 +72,8 @@ export default class Minimap extends BaseUIElement { const location = this._location; let currentLayer = this._background.data.layer() - const options = { - center: <[number, number]> [location.data?.lat ?? 0, location.data?.lon ?? 0], + const map = L.map(this._id, { + center: [location.data?.lat ?? 0, location.data?.lon ?? 0], zoom: location.data?.zoom ?? 2, layers: [currentLayer], zoomControl: false, @@ -85,14 +82,8 @@ export default class Minimap extends BaseUIElement { scrollWheelZoom: this._allowMoving, doubleClickZoom: this._allowMoving, keyboard: this._allowMoving, - touchZoom: this._allowMoving, - // Disabling this breaks the geojson layer - don't ask me why! zoomAnimation: this._allowMoving, - fadeAnimation: this._allowMoving - } - - Utils.Merge(this._leafletoptions, options) - - const map = L.map(this._id, options); + touchZoom: this._allowMoving + }); map.setMaxBounds( [[-100, -200], [100, 200]] diff --git a/UI/BigComponents/Basemap.ts b/UI/BigComponents/Basemap.ts index a4afd6ec8b..2da6415b6d 100644 --- a/UI/BigComponents/Basemap.ts +++ b/UI/BigComponents/Basemap.ts @@ -3,7 +3,6 @@ import {UIEventSource} from "../../Logic/UIEventSource"; import Loc from "../../Models/Loc"; import BaseLayer from "../../Models/BaseLayer"; import BaseUIElement from "../BaseUIElement"; -import {FixedUiElement} from "../Base/FixedUiElement"; export class Basemap { @@ -36,8 +35,9 @@ export class Basemap { ); this.map.attributionControl.setPrefix( - "A"); + " | OpenStreetMap"); + extraAttribution.AttachTo('leaflet-attribution') const self = this; currentLayer.addCallbackAndRun(layer => { @@ -77,7 +77,6 @@ export class Basemap { lastClickLocation?.setData({lat: e.latlng.lat, lon: e.latlng.lng}); }); - extraAttribution.AttachTo('leaflet-attribution') } diff --git a/UI/BigComponents/ExportDataButton.ts b/UI/BigComponents/ExportDataButton.ts deleted file mode 100644 index 9a161de9fd..0000000000 --- a/UI/BigComponents/ExportDataButton.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {SubtleButton} from "../Base/SubtleButton"; -import Svg from "../../Svg"; -import Translations from "../i18n/Translations"; -import State from "../../State"; -import {FeatureSourceUtils} from "../../Logic/FeatureSource/FeatureSource"; -import {Utils} from "../../Utils"; -import Combine from "../Base/Combine"; - -export class ExportDataButton extends Combine { - constructor() { - const t = Translations.t.general.download - const button = new SubtleButton(Svg.floppy_ui(), t.downloadGeojson.Clone().SetClass("font-bold")) - .onClick(() => { - const geojson = FeatureSourceUtils.extractGeoJson(State.state.featurePipeline) - const name = State.state.layoutToUse.data.id; - Utils.offerContentsAsDownloadableFile(JSON.stringify(geojson), `MapComplete_${name}_export_${new Date().toISOString().substr(0,19)}.geojson`); - }) - - super([button, t.licenseInfo.Clone().SetClass("link-underline")]) - } -} \ No newline at end of file diff --git a/UI/BigComponents/FilterView.ts b/UI/BigComponents/FilterView.ts index 9b5b79386c..b2ee12c16b 100644 --- a/UI/BigComponents/FilterView.ts +++ b/UI/BigComponents/FilterView.ts @@ -10,7 +10,6 @@ import LayerConfig from "../../Customizations/JSON/LayerConfig"; import BaseUIElement from "../BaseUIElement"; import { Translation } from "../i18n/Translation"; import ScrollableFullScreen from "../Base/ScrollableFullScreen"; -import Svg from "../../Svg"; /** * Shows the filter @@ -27,63 +26,14 @@ export default class FilterView extends ScrollableFullScreen { } private static Generatecontent(): BaseUIElement { - let filterPanel: BaseUIElement = new FixedUiElement(""); + let filterPanel: BaseUIElement = new FixedUiElement("more stuff"); if (State.state.filteredLayers.data.length > 1) { - let activeLayers = State.state.filteredLayers; - - if (activeLayers === undefined) { - throw "ActiveLayers should be defined..."; - } - - const checkboxes: BaseUIElement[] = []; - - for (const layer of activeLayers.data) { - const iconStyle = "width:1.5rem;height:1.5rem;margin-left:1.25rem"; - - const icon = new Combine([Svg.checkbox_filled]).SetStyle(iconStyle); - const iconUnselected = new Combine([Svg.checkbox_empty]).SetStyle( - iconStyle - ); - - if (layer.layerDef.name === undefined) { - continue; - } - - const style = "display:flex;align-items:center;color:#007759"; - - const name: Translation = Translations.WT(layer.layerDef.name)?.Clone(); - - const styledNameChecked = name - .Clone() - .SetStyle("font-size:large;padding-left:1.25rem"); - - const styledNameUnChecked = name - .Clone() - .SetStyle("font-size:large;padding-left:1.25rem"); - - const layerChecked = new Combine([icon, styledNameChecked]).SetStyle( - style - ); - - const layerNotChecked = new Combine([ - iconUnselected, - styledNameUnChecked, - ]).SetStyle(style); - - checkboxes.push( - new Toggle(layerChecked, layerNotChecked, layer.isDisplayed) - .ToggleOnClick() - .SetStyle("margin:0.3em;") - ); - } - - let combinedCheckboxes = new Combine(checkboxes); - combinedCheckboxes.SetStyle("display:flex;flex-direction:column;"); - - filterPanel = new Combine([combinedCheckboxes]); - - return filterPanel; + let layers = State.state.filteredLayers; + console.log(layers); + filterPanel = new Combine(["layerssss", "
", filterPanel]); } + + return filterPanel; } } diff --git a/UI/BigComponents/LayerControlPanel.ts b/UI/BigComponents/LayerControlPanel.ts index c8837fbccc..42a3eda125 100644 --- a/UI/BigComponents/LayerControlPanel.ts +++ b/UI/BigComponents/LayerControlPanel.ts @@ -2,12 +2,11 @@ import State from "../../State"; import BackgroundSelector from "./BackgroundSelector"; import LayerSelection from "./LayerSelection"; import Combine from "../Base/Combine"; +import {FixedUiElement} from "../Base/FixedUiElement"; import ScrollableFullScreen from "../Base/ScrollableFullScreen"; import Translations from "../i18n/Translations"; import {UIEventSource} from "../../Logic/UIEventSource"; import BaseUIElement from "../BaseUIElement"; -import Toggle from "../Input/Toggle"; -import {ExportDataButton} from "./ExportDataButton"; export default class LayerControlPanel extends ScrollableFullScreen { @@ -15,34 +14,27 @@ export default class LayerControlPanel extends ScrollableFullScreen { super(LayerControlPanel.GenTitle, LayerControlPanel.GeneratePanel, "layers", isShown); } - private static GenTitle(): BaseUIElement { + private static GenTitle():BaseUIElement { return Translations.t.general.layerSelection.title.Clone().SetClass("text-2xl break-words font-bold p-2") } - private static GeneratePanel(): BaseUIElement { - const elements: BaseUIElement[] = [] - + private static GeneratePanel() : BaseUIElement { + let layerControlPanel: BaseUIElement = new FixedUiElement(""); if (State.state.layoutToUse.data.enableBackgroundLayerSelection) { - const backgroundSelector = new BackgroundSelector(); - backgroundSelector.SetStyle("margin:1em"); - backgroundSelector.onClick(() => { + layerControlPanel = new BackgroundSelector(); + layerControlPanel.SetStyle("margin:1em"); + layerControlPanel.onClick(() => { }); - elements.push(backgroundSelector) } - elements.push(new Toggle( - new LayerSelection(State.state.filteredLayers), - undefined, - State.state.filteredLayers.map(layers => layers.length > 1) - )) + if (State.state.filteredLayers.data.length > 1) { + const layerSelection = new LayerSelection(State.state.filteredLayers); + layerSelection.onClick(() => { + }); + layerControlPanel = new Combine([layerSelection, "
", layerControlPanel]); + } - elements.push(new Toggle( - new ExportDataButton(), - undefined, - State.state.featureSwitchEnableExport - )) - - return new Combine(elements).SetClass("flex flex-col") + return layerControlPanel; } } \ No newline at end of file diff --git a/UI/BigComponents/LayerSelection.ts b/UI/BigComponents/LayerSelection.ts index 3c7f108e8c..e282947096 100644 --- a/UI/BigComponents/LayerSelection.ts +++ b/UI/BigComponents/LayerSelection.ts @@ -74,6 +74,7 @@ export default class LayerSelection extends Combine { ); } + super(checkboxes) this.SetStyle("display:flex;flex-direction:column;") diff --git a/UI/BigComponents/MoreScreen.ts b/UI/BigComponents/MoreScreen.ts index 91ab436b28..bfab0567d6 100644 --- a/UI/BigComponents/MoreScreen.ts +++ b/UI/BigComponents/MoreScreen.ts @@ -62,10 +62,6 @@ export default class MoreScreen extends Combine { let officialThemes = AllKnownLayouts.layoutsList let buttons = officialThemes.map((layout) => { - if(layout === undefined){ - console.trace("Layout is undefined") - return undefined - } const button = MoreScreen.createLinkButton(layout)?.SetClass(buttonClass); if(layout.id === personal.id){ return new VariableUiElement( diff --git a/UI/BigComponents/SimpleAddUI.ts b/UI/BigComponents/SimpleAddUI.ts index 9d1fd1475e..75dd3e4037 100644 --- a/UI/BigComponents/SimpleAddUI.ts +++ b/UI/BigComponents/SimpleAddUI.ts @@ -16,10 +16,6 @@ import {VariableUiElement} from "../Base/VariableUIElement"; import Toggle from "../Input/Toggle"; import UserDetails from "../../Logic/Osm/OsmConnection"; import {Translation} from "../i18n/Translation"; -import LocationInput from "../Input/LocationInput"; -import {InputElement} from "../Input/InputElement"; -import Loc from "../../Models/Loc"; -import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers"; /* * The SimpleAddUI is a single panel, which can have multiple states: @@ -29,18 +25,14 @@ import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers"; * - A 'read your unread messages before adding a point' */ -/*private*/ interface PresetInfo { description: string | Translation, name: string | BaseUIElement, - icon: () => BaseUIElement, + icon: BaseUIElement, tags: Tag[], layerToAddTo: { layerDef: LayerConfig, isDisplayed: UIEventSource - }, - preciseInput?: { - preferredBackground?: string } } @@ -56,16 +48,18 @@ export default class SimpleAddUI extends Toggle { new SubtleButton(Svg.envelope_ui(), Translations.t.general.goToInbox, {url: "https://www.openstreetmap.org/messages/inbox", newTab: false}) ]); - - + + + const selectedPreset = new UIEventSource(undefined); isShown.addCallback(_ => selectedPreset.setData(undefined)) // Clear preset selection when the UI is closed/opened - - function createNewPoint(tags: any[], location: { lat: number, lon: number }) { - let feature = State.state.changes.createElement(tags, location.lat, location.lon); + + function createNewPoint(tags: any[]){ + const loc = State.state.LastClickLocation.data; + let feature = State.state.changes.createElement(tags, loc.lat, loc.lon); State.state.selectedElement.setData(feature); } - + const presetsOverview = SimpleAddUI.CreateAllPresetsPanel(selectedPreset) const addUi = new VariableUiElement( @@ -74,8 +68,8 @@ export default class SimpleAddUI extends Toggle { return presetsOverview } return SimpleAddUI.CreateConfirmButton(preset, - (tags, location) => { - createNewPoint(tags, location) + tags => { + createNewPoint(tags) selectedPreset.setData(undefined) }, () => { selectedPreset.setData(undefined) @@ -92,7 +86,7 @@ export default class SimpleAddUI extends Toggle { addUi, State.state.layerUpdater.runningQuery ), - Translations.t.general.add.zoomInFurther.Clone().SetClass("alert"), + Translations.t.general.add.zoomInFurther.Clone().SetClass("alert") , State.state.locationControl.map(loc => loc.zoom >= Constants.userJourney.minZoomLevelToAddNewPoints) ), readYourMessages, @@ -109,48 +103,22 @@ export default class SimpleAddUI extends Toggle { } + private static CreateConfirmButton(preset: PresetInfo, - confirm: (tags: any[], location: { lat: number, lon: number }) => void, + confirm: (tags: any[]) => void, cancel: () => void): BaseUIElement { - let location = State.state.LastClickLocation; - let preciseInput: InputElement = undefined - if (preset.preciseInput !== undefined) { - const locationSrc = new UIEventSource({ - lat: location.data.lat, - lon: location.data.lon, - zoom: 19 - }); - - let backgroundLayer = undefined; - if(preset.preciseInput.preferredBackground){ - backgroundLayer= AvailableBaseLayers.SelectBestLayerAccordingTo(locationSrc, new UIEventSource(preset.preciseInput.preferredBackground)) - } - - preciseInput = new LocationInput({ - mapBackground: backgroundLayer, - centerLocation:locationSrc - - }) - preciseInput.SetClass("h-32 rounded-xl overflow-hidden border border-gray").SetStyle("height: 12rem;") - } - - let confirmButton: BaseUIElement = new SubtleButton(preset.icon(), + const confirmButton = new SubtleButton(preset.icon, new Combine([ Translations.t.general.add.addNew.Subs({category: preset.name}), Translations.t.general.add.warnVisibleForEveryone.Clone().SetClass("alert") ]).SetClass("flex flex-col") ).SetClass("font-bold break-words") - .onClick(() => { - confirm(preset.tags, (preciseInput?.GetValue() ?? location).data); - }); - - if (preciseInput !== undefined) { - confirmButton = new Combine([preciseInput, confirmButton]) - } + .onClick(() => confirm(preset.tags)); - const openLayerControl = + + const openLayerControl = new SubtleButton( Svg.layers_ui(), new Combine([ @@ -160,9 +128,9 @@ export default class SimpleAddUI extends Toggle { Translations.t.general.add.openLayerControl ]) ) - - .onClick(() => State.state.layerControlIsOpened.setData(true)) - + + .onClick(() => State.state.layerControlIsOpened.setData(true)) + const openLayerOrConfirm = new Toggle( confirmButton, openLayerControl, @@ -172,12 +140,12 @@ export default class SimpleAddUI extends Toggle { const cancelButton = new SubtleButton(Svg.close_ui(), Translations.t.general.cancel - ).onClick(cancel) + ).onClick(cancel ) return new Combine([ Translations.t.general.add.confirmIntro.Subs({title: preset.name}), - State.state.osmConnection.userDetails.data.dryRun ? - Translations.t.general.testing.Clone().SetClass("alert") : undefined, + State.state.osmConnection.userDetails.data.dryRun ? + Translations.t.general.testing.Clone().SetClass("alert") : undefined , openLayerOrConfirm, cancelButton, preset.description, @@ -212,11 +180,11 @@ export default class SimpleAddUI extends Toggle { } - private static CreatePresetSelectButton(preset: PresetInfo) { + private static CreatePresetSelectButton(preset: PresetInfo){ - const tagInfo = SimpleAddUI.CreateTagInfoFor(preset, false); + const tagInfo =SimpleAddUI.CreateTagInfoFor(preset, false); return new SubtleButton( - preset.icon(), + preset.icon, new Combine([ Translations.t.general.add.addNew.Subs({ category: preset.name @@ -226,30 +194,29 @@ export default class SimpleAddUI extends Toggle { ]).SetClass("flex flex-col") ) } - - /* - * Generates the list with all the buttons.*/ + +/* +* Generates the list with all the buttons.*/ private static CreatePresetButtons(selectedPreset: UIEventSource): BaseUIElement { const allButtons = []; for (const layer of State.state.filteredLayers.data) { - - if (layer.isDisplayed.data === false && State.state.featureSwitchLayers) { + + if(layer.isDisplayed.data === false && State.state.featureSwitchLayers){ continue; } - + const presets = layer.layerDef.presets; for (const preset of presets) { const tags = TagUtils.KVtoProperties(preset.tags ?? []); - let icon:() => BaseUIElement = () => layer.layerDef.GenerateLeafletStyle(new UIEventSource(tags), false).icon.html + let icon: BaseUIElement = layer.layerDef.GenerateLeafletStyle(new UIEventSource(tags), false).icon.html .SetClass("w-12 h-12 block relative"); const presetInfo: PresetInfo = { tags: preset.tags, layerToAddTo: layer, name: preset.title, description: preset.description, - icon: icon, - preciseInput: preset.preciseInput + icon: icon } const button = SimpleAddUI.CreatePresetSelectButton(presetInfo); diff --git a/UI/Input/DirectionInput.ts b/UI/Input/DirectionInput.ts index 93d932c667..12689d5d4d 100644 --- a/UI/Input/DirectionInput.ts +++ b/UI/Input/DirectionInput.ts @@ -66,7 +66,6 @@ export default class DirectionInput extends InputElement { }) this.RegisterTriggers(element) - element.style.overflow = "hidden" return element; } diff --git a/UI/Input/InputElementWrapper.ts b/UI/Input/InputElementWrapper.ts deleted file mode 100644 index 765a0d3b4b..0000000000 --- a/UI/Input/InputElementWrapper.ts +++ /dev/null @@ -1,35 +0,0 @@ -import {InputElement} from "./InputElement"; -import {UIEventSource} from "../../Logic/UIEventSource"; -import BaseUIElement from "../BaseUIElement"; -import {Translation} from "../i18n/Translation"; -import {SubstitutedTranslation} from "../SubstitutedTranslation"; - -export default class InputElementWrapper extends InputElement { - public readonly IsSelected: UIEventSource; - private readonly _inputElement: InputElement; - private readonly _renderElement: BaseUIElement - - constructor(inputElement: InputElement, translation: Translation, key: string, tags: UIEventSource) { - super() - this._inputElement = inputElement; - this.IsSelected = inputElement.IsSelected - const mapping = new Map() - - mapping.set(key, inputElement) - - this._renderElement = new SubstitutedTranslation(translation, tags, mapping) - } - - GetValue(): UIEventSource { - return this._inputElement.GetValue(); - } - - IsValid(t: T): boolean { - return this._inputElement.IsValid(t); - } - - protected InnerConstructElement(): HTMLElement { - return this._renderElement.ConstructElement(); - } - -} \ No newline at end of file diff --git a/UI/Input/LengthInput.ts b/UI/Input/LengthInput.ts deleted file mode 100644 index 0558069b29..0000000000 --- a/UI/Input/LengthInput.ts +++ /dev/null @@ -1,185 +0,0 @@ -import {InputElement} from "./InputElement"; -import {UIEventSource} from "../../Logic/UIEventSource"; -import Combine from "../Base/Combine"; -import Svg from "../../Svg"; -import {Utils} from "../../Utils"; -import Loc from "../../Models/Loc"; -import {GeoOperations} from "../../Logic/GeoOperations"; -import DirectionInput from "./DirectionInput"; -import {RadioButton} from "./RadioButton"; -import {FixedInputElement} from "./FixedInputElement"; - - -/** - * Selects a length after clicking on the minimap, in meters - */ -export default class LengthInput extends InputElement { - private readonly _location: UIEventSource; - - public readonly IsSelected: UIEventSource = new UIEventSource(false); - private readonly value: UIEventSource; - private background; - - constructor(mapBackground: UIEventSource, - location: UIEventSource, - value?: UIEventSource) { - super(); - this._location = location; - this.value = value ?? new UIEventSource(undefined); - this.background = mapBackground; - this.SetClass("block") - - } - - GetValue(): UIEventSource { - return this.value; - } - - IsValid(str: string): boolean { - const t = Number(str) - return !isNaN(t) && t >= 0 && t <= 360; - } - - protected InnerConstructElement(): HTMLElement { - const modeElement = new RadioButton([ - new FixedInputElement("Measure", "measure"), - new FixedInputElement("Move", "move") - ]) - // @ts-ignore - let map = undefined - if (!Utils.runningFromConsole) { - map = DirectionInput.constructMinimap({ - background: this.background, - allowMoving: false, - location: this._location, - leafletOptions: { - tap: true - } - }) - } - const element = new Combine([ - new Combine([Svg.length_crosshair_svg().SetStyle( - `position: absolute;top: 0;left: 0;transform:rotate(${this.value.data ?? 0}deg);`) - ]) - .SetClass("block length-crosshair-svg relative") - .SetStyle("z-index: 1000; visibility: hidden"), - map?.SetClass("w-full h-full block absolute top-0 left-O overflow-hidden"), - ]) - .SetClass("relative block bg-white border border-black rounded-3xl overflow-hidden") - .ConstructElement() - - - this.RegisterTriggers(element, map?.leafletMap) - element.style.overflow = "hidden" - element.style.display = "block" - - return element - } - - private RegisterTriggers(htmlElement: HTMLElement, leafletMap: UIEventSource) { - - let firstClickXY: [number, number] = undefined - let lastClickXY: [number, number] = undefined - const self = this; - - - function onPosChange(x: number, y: number, isDown: boolean, isUp?: boolean) { - if (x === undefined || y === undefined) { - // Touch end - firstClickXY = undefined; - lastClickXY = undefined; - return; - } - - const rect = htmlElement.getBoundingClientRect(); - // From the central part of location - const dx = x - rect.left; - const dy = y - rect.top; - if (isDown) { - if (lastClickXY === undefined && firstClickXY === undefined) { - firstClickXY = [dx, dy]; - } else if (firstClickXY !== undefined && lastClickXY === undefined) { - lastClickXY = [dx, dy] - } else if (firstClickXY !== undefined && lastClickXY !== undefined) { - // we measure again - firstClickXY = [dx, dy] - lastClickXY = undefined; - } - } - if (isUp) { - const distance = Math.sqrt((dy - firstClickXY[1]) * (dy - firstClickXY[1]) + (dx - firstClickXY[0]) * (dx - firstClickXY[0])) - if (distance > 15) { - lastClickXY = [dx, dy] - } - - - } else if (lastClickXY !== undefined) { - return; - } - - - const measurementCrosshair = htmlElement.getElementsByClassName("length-crosshair-svg")[0] as HTMLElement - - const measurementCrosshairInner: HTMLElement = measurementCrosshair.firstChild - if (firstClickXY === undefined) { - measurementCrosshair.style.visibility = "hidden" - } else { - measurementCrosshair.style.visibility = "unset" - measurementCrosshair.style.left = firstClickXY[0] + "px"; - measurementCrosshair.style.top = firstClickXY[1] + "px" - - const angle = 180 * Math.atan2(firstClickXY[1] - dy, firstClickXY[0] - dx) / Math.PI; - const angleGeo = (angle + 270) % 360 - measurementCrosshairInner.style.transform = `rotate(${angleGeo}deg)`; - - const distance = Math.sqrt((dy - firstClickXY[1]) * (dy - firstClickXY[1]) + (dx - firstClickXY[0]) * (dx - firstClickXY[0])) - measurementCrosshairInner.style.width = (distance * 2) + "px" - measurementCrosshairInner.style.marginLeft = -distance + "px" - measurementCrosshairInner.style.marginTop = -distance + "px" - - - const leaflet = leafletMap?.data - if (leaflet) { - const first = leaflet.layerPointToLatLng(firstClickXY) - const last = leaflet.layerPointToLatLng([dx, dy]) - const geoDist = Math.floor(GeoOperations.distanceBetween([first.lng, first.lat], [last.lng, last.lat]) * 100000) / 100 - self.value.setData("" + geoDist) - } - - } - - } - - - htmlElement.ontouchstart = (ev: TouchEvent) => { - onPosChange(ev.touches[0].clientX, ev.touches[0].clientY, true); - ev.preventDefault(); - } - - htmlElement.ontouchmove = (ev: TouchEvent) => { - onPosChange(ev.touches[0].clientX, ev.touches[0].clientY, false); - ev.preventDefault(); - } - - htmlElement.ontouchend = (ev: TouchEvent) => { - onPosChange(undefined, undefined, false, true); - ev.preventDefault(); - } - - htmlElement.onmousedown = (ev: MouseEvent) => { - onPosChange(ev.clientX, ev.clientY, true); - ev.preventDefault(); - } - - htmlElement.onmouseup = (ev) => { - onPosChange(ev.clientX, ev.clientY, false, true); - ev.preventDefault(); - } - - htmlElement.onmousemove = (ev: MouseEvent) => { - onPosChange(ev.clientX, ev.clientY, false); - ev.preventDefault(); - } - } - -} \ No newline at end of file diff --git a/UI/Input/LocationInput.ts b/UI/Input/LocationInput.ts deleted file mode 100644 index d568e4443f..0000000000 --- a/UI/Input/LocationInput.ts +++ /dev/null @@ -1,76 +0,0 @@ -import {InputElement} from "./InputElement"; -import Loc from "../../Models/Loc"; -import {UIEventSource} from "../../Logic/UIEventSource"; -import Minimap from "../Base/Minimap"; -import BaseLayer from "../../Models/BaseLayer"; -import Combine from "../Base/Combine"; -import Svg from "../../Svg"; -import State from "../../State"; - -export default class LocationInput extends InputElement { - - IsSelected: UIEventSource = new UIEventSource(false); - private _centerLocation: UIEventSource; - private readonly mapBackground : UIEventSource; - - constructor(options?: { - mapBackground?: UIEventSource, - centerLocation?: UIEventSource, - }) { - super(); - options = options ?? {} - options.centerLocation = options.centerLocation ?? new UIEventSource({lat: 0, lon: 0, zoom: 1}) - this._centerLocation = options.centerLocation; - - this.mapBackground = options.mapBackground ?? State.state.backgroundLayer - this.SetClass("block h-full") - } - - GetValue(): UIEventSource { - return this._centerLocation; - } - - IsValid(t: Loc): boolean { - return t !== undefined; - } - - protected InnerConstructElement(): HTMLElement { - const map = new Minimap( - { - location: this._centerLocation, - background: this.mapBackground - } - ) - map.leafletMap.addCallbackAndRunD(leaflet => { - console.log(leaflet.getBounds(), leaflet.getBounds().pad(0.15)) - leaflet.setMaxBounds( - leaflet.getBounds().pad(0.15) - ) - }) - - this.mapBackground.map(layer => { - - const leaflet = map.leafletMap.data - if (leaflet === undefined || layer === undefined) { - return; - } - - leaflet.setMaxZoom(layer.max_zoom) - leaflet.setMinZoom(layer.max_zoom - 3) - leaflet.setZoom(layer.max_zoom - 1) - - }, [map.leafletMap]) - return new Combine([ - new Combine([ - Svg.crosshair_empty_ui() - .SetClass("block relative") - .SetStyle("left: -1.25rem; top: -1.25rem; width: 2.5rem; height: 2.5rem") - ]).SetClass("block w-0 h-0 z-10 relative") - .SetStyle("background: rgba(255, 128, 128, 0.21); left: 50%; top: 50%"), - map - .SetClass("z-0 relative block w-full h-full bg-gray-100") - - ]).ConstructElement(); - } - -} \ No newline at end of file diff --git a/UI/Input/RadioButton.ts b/UI/Input/RadioButton.ts index 2822b2166f..fd5c006c2e 100644 --- a/UI/Input/RadioButton.ts +++ b/UI/Input/RadioButton.ts @@ -103,7 +103,7 @@ export class RadioButton extends InputElement { const block = document.createElement("div") block.appendChild(input) block.appendChild(label) - block.classList.add("flex","w-full","border", "rounded-3xl", "border-gray-400","m-1") + block.classList.add("flex","w-full","border", "rounded-full", "border-gray-400","m-1") wrappers.push(block) form.appendChild(block) diff --git a/UI/Input/TextField.ts b/UI/Input/TextField.ts index da30733236..8f7d6ac442 100644 --- a/UI/Input/TextField.ts +++ b/UI/Input/TextField.ts @@ -36,11 +36,11 @@ export class TextField extends InputElement { this.SetClass("form-text-field") let inputEl: HTMLElement if (options.htmlType === "area") { - this.SetClass("w-full box-border max-w-full") const el = document.createElement("textarea") el.placeholder = placeholder el.rows = options.textAreaRows el.cols = 50 + el.style.cssText = "max-width: 100%; width: 100%; box-sizing: border-box" inputEl = el; } else { const el = document.createElement("input") diff --git a/UI/Input/ValidatedTextField.ts b/UI/Input/ValidatedTextField.ts index ec3aa62ce4..8ea3fb948b 100644 --- a/UI/Input/ValidatedTextField.ts +++ b/UI/Input/ValidatedTextField.ts @@ -13,8 +13,6 @@ import {Utils} from "../../Utils"; import Loc from "../../Models/Loc"; import {Unit} from "../../Customizations/JSON/Denomination"; import BaseUIElement from "../BaseUIElement"; -import LengthInput from "./LengthInput"; -import {GeoOperations} from "../../Logic/GeoOperations"; interface TextFieldDef { name: string, @@ -23,16 +21,14 @@ interface TextFieldDef { reformat?: ((s: string, country?: () => string) => string), inputHelper?: (value: UIEventSource, options?: { location: [number, number], - mapBackgroundLayer?: UIEventSource, - args: (string | number | boolean)[] - feature?: any + mapBackgroundLayer?: UIEventSource }) => InputElement, + inputmode?: string } export default class ValidatedTextField { - public static bestLayerAt: (location: UIEventSource, preferences: UIEventSource) => any public static tpList: TextFieldDef[] = [ ValidatedTextField.tp( @@ -67,83 +63,6 @@ export default class ValidatedTextField { return [year, month, day].join('-'); }, (value) => new SimpleDatePicker(value)), - ValidatedTextField.tp( - "direction", - "A geographical direction, in degrees. 0° is north, 90° is east, ... Will return a value between 0 (incl) and 360 (excl)", - (str) => { - str = "" + str; - return str !== undefined && str.indexOf(".") < 0 && !isNaN(Number(str)) && Number(str) >= 0 && Number(str) <= 360 - }, str => str, - (value, options) => { - const args = options.args ?? [] - let zoom = 19 - if (args[0]) { - zoom = Number(args[0]) - if (isNaN(zoom)) { - throw "Invalid zoom level for argument at 'length'-input" - } - } - const location = new UIEventSource({ - lat: options.location[0], - lon: options.location[1], - zoom: zoom - }) - if (args[1]) { - // We have a prefered map! - options.mapBackgroundLayer = ValidatedTextField.bestLayerAt( - location, new UIEventSource(args[1].split(",")) - ) - } - const di = new DirectionInput(options.mapBackgroundLayer, location, value) - di.SetStyle("height: 20rem;"); - - return di; - }, - "numeric" - ), - ValidatedTextField.tp( - "length", - "A geographical length in meters (rounded at two points). Will give an extra minimap with a measurement tool. Arguments: [ zoomlevel, preferredBackgroundMapType (comma seperated) ], e.g. `[\"21\", \"map,photo\"]", - (str) => { - const t = Number(str) - return !isNaN(t) - }, - str => str, - (value, options) => { - const args = options.args ?? [] - let zoom = 19 - if (args[0]) { - zoom = Number(args[0]) - if (isNaN(zoom)) { - throw "Invalid zoom level for argument at 'length'-input" - } - } - - // Bit of a hack: we project the centerpoint to the closes point on the road - if available - if(options.feature){ - const lonlat: [number, number] = [...options.location] - lonlat.reverse() - options.location = <[number,number]> GeoOperations.nearestPoint(options.feature, lonlat).geometry.coordinates - options.location.reverse() - } - options.feature - - const location = new UIEventSource({ - lat: options.location[0], - lon: options.location[1], - zoom: zoom - }) - if (args[1]) { - // We have a prefered map! - options.mapBackgroundLayer = ValidatedTextField.bestLayerAt( - location, new UIEventSource(args[1].split(",")) - ) - } - const li = new LengthInput(options.mapBackgroundLayer, location, value) - li.SetStyle("height: 20rem;") - return li; - } - ), ValidatedTextField.tp( "wikidata", "A wikidata identifier, e.g. Q42", @@ -194,6 +113,22 @@ export default class ValidatedTextField { undefined, undefined, "numeric"), + ValidatedTextField.tp( + "direction", + "A geographical direction, in degrees. 0° is north, 90° is east, ... Will return a value between 0 (incl) and 360 (excl)", + (str) => { + str = "" + str; + return str !== undefined && str.indexOf(".") < 0 && !isNaN(Number(str)) && Number(str) >= 0 && Number(str) <= 360 + }, str => str, + (value, options) => { + return new DirectionInput(options.mapBackgroundLayer , new UIEventSource({ + lat: options.location[0], + lon: options.location[1], + zoom: 19 + }),value); + }, + "numeric" + ), ValidatedTextField.tp( "float", "A decimal", @@ -287,7 +222,6 @@ export default class ValidatedTextField { * {string (typename) --> TextFieldDef} */ public static AllTypes = ValidatedTextField.allTypesDict(); - public static InputForType(type: string, options?: { placeholder?: string | BaseUIElement, value?: UIEventSource, @@ -299,9 +233,7 @@ export default class ValidatedTextField { country?: () => string, location?: [number /*lat*/, number /*lon*/], mapBackgroundLayer?: UIEventSource, - unit?: Unit, - args?: (string | number | boolean)[] // Extra arguments for the inputHelper, - feature?: any + unit?: Unit }): InputElement { options = options ?? {}; options.placeholder = options.placeholder ?? type; @@ -315,7 +247,7 @@ export default class ValidatedTextField { if (str === undefined) { return false; } - if (options.unit) { + if(options.unit) { str = options.unit.stripUnitParts(str) } return isValidTp(str, country ?? options.country) && optValid(str, country ?? options.country); @@ -336,7 +268,7 @@ export default class ValidatedTextField { }) } - if (options.unit) { + if(options.unit) { // We need to apply a unit. // This implies: // We have to create a dropdown with applicable denominations, and fuse those values @@ -350,22 +282,23 @@ export default class ValidatedTextField { }) ) unitDropDown.GetValue().setData(unit.defaultDenom) - unitDropDown.SetClass("w-min") + unitDropDown.SetStyle("width: min-content") input = new CombinedInputElement( input, unitDropDown, // combine the value from the textfield and the dropdown into the resulting value that should go into OSM - (text, denom) => denom?.canonicalValue(text, true) ?? undefined, + (text, denom) => denom?.canonicalValue(text, true) ?? undefined, (valueWithDenom: string) => { // Take the value from OSM and feed it into the textfield and the dropdown const withDenom = unit.findDenomination(valueWithDenom); - if (withDenom === undefined) { + if(withDenom === undefined) + { // Not a valid value at all - we give it undefined and leave the details up to the other elements return [undefined, undefined] } const [strippedText, denom] = withDenom - if (strippedText === undefined) { + if(strippedText === undefined){ return [undefined, undefined] } return [strippedText, denom] @@ -373,20 +306,18 @@ export default class ValidatedTextField { ).SetClass("flex") } if (tp.inputHelper) { - const helper = tp.inputHelper(input.GetValue(), { + const helper = tp.inputHelper(input.GetValue(), { location: options.location, - mapBackgroundLayer: options.mapBackgroundLayer, - args: options.args, - feature: options.feature + mapBackgroundLayer: options.mapBackgroundLayer + }) input = new CombinedInputElement(input, helper, (a, _) => a, // We can ignore b, as they are linked earlier a => [a, a] - ); + ); } return input; } - public static HelpText(): string { const explanations = ValidatedTextField.tpList.map(type => ["## " + type.name, "", type.explanation].join("\n")).join("\n\n") return "# Available types for text fields\n\nThe listed types here trigger a special input element. Use them in `tagrendering.freeform.type` of your tagrendering to activate them\n\n" + explanations @@ -398,9 +329,7 @@ export default class ValidatedTextField { reformat?: ((s: string, country?: () => string) => string), inputHelper?: (value: UIEventSource, options?: { location: [number, number], - mapBackgroundLayer: UIEventSource, - args: string[], - feature: any + mapBackgroundLayer: UIEventSource }) => InputElement, inputmode?: string): TextFieldDef { diff --git a/UI/Popup/FeatureInfoBox.ts b/UI/Popup/FeatureInfoBox.ts index b456c0ab96..f35f73ceb8 100644 --- a/UI/Popup/FeatureInfoBox.ts +++ b/UI/Popup/FeatureInfoBox.ts @@ -36,7 +36,7 @@ export default class FeatureInfoBox extends ScrollableFullScreen { .SetClass("break-words font-bold sm:p-0.5 md:p-1 sm:p-1.5 md:p-2"); const titleIcons = new Combine( layerConfig.titleIcons.map(icon => new TagRenderingAnswer(tags, icon, - "block w-8 h-8 align-baseline box-content sm:p-0.5", "width: 2rem;") + "block w-8 h-8 align-baseline box-content sm:p-0.5") )) .SetClass("flex flex-row flex-wrap pt-0.5 sm:pt-1 items-center mr-2") diff --git a/UI/Popup/TagRenderingAnswer.ts b/UI/Popup/TagRenderingAnswer.ts index c8953dd011..6c8fd257ec 100644 --- a/UI/Popup/TagRenderingAnswer.ts +++ b/UI/Popup/TagRenderingAnswer.ts @@ -16,31 +16,31 @@ export default class TagRenderingAnswer extends VariableUiElement { throw "Trying to generate a tagRenderingAnswer without configuration..." } super(tagsSource.map(tags => { - if (tags === undefined) { + if(tags === undefined){ return undefined; } - - if (configuration.condition) { - if (!configuration.condition.matchesProperties(tags)) { + + if(configuration.condition){ + if(!configuration.condition.matchesProperties(tags)){ return undefined; } } - + const trs = Utils.NoNull(configuration.GetRenderValues(tags)); - if (trs.length === 0) { - return undefined; + if(trs.length === 0){ + return undefined; } + + const valuesToRender: BaseUIElement[] = trs.map(tr => new SubstitutedTranslation(tr, tagsSource)) + if(valuesToRender.length === 1){ + return valuesToRender[0]; + }else if(valuesToRender.length > 1){ + return new List(valuesToRender) + } + return undefined; + }).map((element : BaseUIElement) => element?.SetClass(contentClasses)?.SetStyle(contentStyle))) - const valuesToRender: BaseUIElement[] = trs.map(tr => new SubstitutedTranslation(tr, tagsSource)) - if (valuesToRender.length === 1) { - return valuesToRender[0]; - } else if (valuesToRender.length > 1) { - return new List(valuesToRender) - } - return undefined; - }).map((element: BaseUIElement) => element?.SetClass(contentClasses)?.SetStyle(contentStyle))) - - this.SetClass("flex items-center flex-row text-lg link-underline") + this.SetClass("flex items-center flex-row text-lg link-underline tag-renering-answer") this.SetStyle("word-wrap: anywhere;"); } diff --git a/UI/Popup/TagRenderingQuestion.ts b/UI/Popup/TagRenderingQuestion.ts index c723759590..52b2962d81 100644 --- a/UI/Popup/TagRenderingQuestion.ts +++ b/UI/Popup/TagRenderingQuestion.ts @@ -24,7 +24,6 @@ import {TagUtils} from "../../Logic/Tags/TagUtils"; import BaseUIElement from "../BaseUIElement"; import {DropDown} from "../Input/DropDown"; import {Unit} from "../../Customizations/JSON/Denomination"; -import InputElementWrapper from "../Input/InputElementWrapper"; /** * Shows the question element. @@ -129,7 +128,7 @@ export default class TagRenderingQuestion extends Combine { } return Utils.NoNull(configuration.mappings?.map((m,i) => excludeIndex === i ? undefined: m.ifnot)) } - const ff = TagRenderingQuestion.GenerateFreeform(configuration, applicableUnit, tagsSource); + const ff = TagRenderingQuestion.GenerateFreeform(configuration, applicableUnit, tagsSource.data); const hasImages = mappings.filter(mapping => mapping.then.ExtractImages().length > 0).length > 0 if (mappings.length < 8 || configuration.multiAnswer || hasImages) { @@ -290,7 +289,7 @@ export default class TagRenderingQuestion extends Combine { (t0, t1) => t1.isEquivalent(t0)); } - private static GenerateFreeform(configuration: TagRenderingConfig, applicableUnit: Unit, tags: UIEventSource): InputElement { + private static GenerateFreeform(configuration: TagRenderingConfig, applicableUnit: Unit, tagsData: any): InputElement { const freeform = configuration.freeform; if (freeform === undefined) { return undefined; @@ -329,34 +328,20 @@ export default class TagRenderingQuestion extends Combine { return undefined; } - const tagsData = tags.data; - const feature = State.state.allElements.ContainingFeatures.get(tagsData.id) - const input: InputElement = ValidatedTextField.InputForType(configuration.freeform.type, { + let input: InputElement = ValidatedTextField.InputForType(configuration.freeform.type, { isValid: (str) => (str.length <= 255), country: () => tagsData._country, location: [tagsData._lat, tagsData._lon], mapBackgroundLayer: State.state.backgroundLayer, - unit: applicableUnit, - args: configuration.freeform.helperArgs, - feature: feature + unit: applicableUnit }); - input.GetValue().setData(tagsData[freeform.key] ?? freeform.default); + input.GetValue().setData(tagsData[configuration.freeform.key]); - let inputTagsFilter : InputElement = new InputElementMap( + return new InputElementMap( input, (a, b) => a === b || (a?.isEquivalent(b) ?? false), pickString, toString ); - - if(freeform.inline){ - - inputTagsFilter.SetClass("w-16-imp") - inputTagsFilter = new InputElementWrapper(inputTagsFilter, configuration.render, freeform.key, tags) - inputTagsFilter.SetClass("block") - - } - - return inputTagsFilter; } diff --git a/UI/ShowDataLayer.ts b/UI/ShowDataLayer.ts index 59225640f2..711f6b1c58 100644 --- a/UI/ShowDataLayer.ts +++ b/UI/ShowDataLayer.ts @@ -80,7 +80,9 @@ export default class ShowDataLayer { if (zoomToFeatures) { try { - mp.fitBounds(geoLayer.getBounds(), {animate: false}) + + mp.fitBounds(geoLayer.getBounds()) + } catch (e) { console.error(e) } @@ -146,9 +148,7 @@ export default class ShowDataLayer { const popup = L.popup({ autoPan: true, closeOnEscapeKey: true, - closeButton: false, - autoPanPaddingTopLeft: [15,15], - + closeButton: false }, leafletLayer); leafletLayer.bindPopup(popup); diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts index 5a38e8184a..346e71e47a 100644 --- a/UI/SpecialVisualizations.ts +++ b/UI/SpecialVisualizations.ts @@ -39,8 +39,7 @@ export default class SpecialVisualizations { static constructMiniMap: (options?: { background?: UIEventSource, location?: UIEventSource, - allowMoving?: boolean, - leafletOptions?: any + allowMoving?: boolean }) => BaseUIElement; static constructShowDataLayer: (features: UIEventSource<{ feature: any; freshness: Date }[]>, leafletMap: UIEventSource, layoutToUse: UIEventSource, enablePopups?: boolean, zoomToFeatures?: boolean) => any; public static specialVisualizations: SpecialVisualization[] = @@ -370,6 +369,7 @@ export default class SpecialVisualizations { if (unit === undefined) { return value; } + return unit.asHumanLongValue(value); }, @@ -379,7 +379,6 @@ export default class SpecialVisualizations { } ] - static HelpMessage: BaseUIElement = SpecialVisualizations.GenHelpMessage(); private static GenHelpMessage() { diff --git a/UI/SubstitutedTranslation.ts b/UI/SubstitutedTranslation.ts index 43352aa5b4..03c7eb0747 100644 --- a/UI/SubstitutedTranslation.ts +++ b/UI/SubstitutedTranslation.ts @@ -7,43 +7,19 @@ import SpecialVisualizations, {SpecialVisualization} from "./SpecialVisualizatio import {Utils} from "../Utils"; import {VariableUiElement} from "./Base/VariableUIElement"; import Combine from "./Base/Combine"; -import BaseUIElement from "./BaseUIElement"; export class SubstitutedTranslation extends VariableUiElement { public constructor( translation: Translation, - tagsSource: UIEventSource, - mapping: Map = undefined) { - - const extraMappings: SpecialVisualization[] = []; - - mapping?.forEach((value, key) => { - console.log("KV:", key, value) - extraMappings.push( - { - funcName: key, - constr: (() => { - return value - }), - docs: "Dynamically injected input element", - args: [], - example: "" - } - ) - }) - + tagsSource: UIEventSource) { super( Locale.language.map(language => { - let txt = translation.textFor(language); + const txt = translation.textFor(language) if (txt === undefined) { return undefined } - mapping?.forEach((_, key) => { - txt = txt.replace(new RegExp(`{${key}}`, "g"), `{${key}()}`) - }) - - return new Combine(SubstitutedTranslation.ExtractSpecialComponents(txt, extraMappings).map( + return new Combine(SubstitutedTranslation.ExtractSpecialComponents(txt).map( proto => { if (proto.fixed !== undefined) { return new VariableUiElement(tagsSource.map(tags => Utils.SubstituteKeys(proto.fixed, tags))); @@ -60,35 +36,30 @@ export class SubstitutedTranslation extends VariableUiElement { }) ) + this.SetClass("w-full") } - public static ExtractSpecialComponents(template: string, extraMappings: SpecialVisualization[] = []): { - fixed?: string, - special?: { + public static ExtractSpecialComponents(template: string): { + fixed?: string, special?: { func: SpecialVisualization, args: string[], style: string } }[] { - if (extraMappings.length > 0) { - - console.log("Extra mappings are", extraMappings) - } - - for (const knownSpecial of SpecialVisualizations.specialVisualizations.concat(extraMappings)) { + for (const knownSpecial of SpecialVisualizations.specialVisualizations) { // Note: the '.*?' in the regex reads as 'any character, but in a non-greedy way' const matched = template.match(`(.*){${knownSpecial.funcName}\\((.*?)\\)(:.*)?}(.*)`); if (matched != null) { // We found a special component that should be brought to live - const partBefore = SubstitutedTranslation.ExtractSpecialComponents(matched[1], extraMappings); + const partBefore = SubstitutedTranslation.ExtractSpecialComponents(matched[1]); const argument = matched[2].trim(); const style = matched[3]?.substring(1) ?? "" - const partAfter = SubstitutedTranslation.ExtractSpecialComponents(matched[4], extraMappings); + const partAfter = SubstitutedTranslation.ExtractSpecialComponents(matched[4]); const args = knownSpecial.args.map(arg => arg.defaultValue ?? ""); if (argument.length > 0) { const realArgs = argument.split(",").map(str => str.trim()); @@ -102,13 +73,11 @@ export class SubstitutedTranslation extends VariableUiElement { } let element; - element = { - special: { - args: args, - style: style, - func: knownSpecial - } - } + element = {special:{ + args: args, + style: style, + func: knownSpecial + }} return [...partBefore, element, ...partAfter] } } diff --git a/Utils.ts b/Utils.ts index 8cc3158dca..05ebcbaabc 100644 --- a/Utils.ts +++ b/Utils.ts @@ -1,5 +1,4 @@ import * as colors from "./assets/colors.json" -import {TileRange} from "./Models/TileRange"; export class Utils { @@ -135,7 +134,7 @@ export class Utils { } return newArr; } - + public static MergeTags(a: any, b: any) { const t = {}; for (const k in a) { @@ -359,12 +358,9 @@ export class Utils { * @param contents * @param fileName */ - public static offerContentsAsDownloadableFile(contents: string | Blob, fileName: string = "download.txt") { + public static offerContentsAsDownloadableFile(contents: string, fileName: string = "download.txt") { const element = document.createElement("a"); - let file; - if(typeof(contents) === "string"){ - file = new Blob([contents], {type: 'text/plain'}); - }else {file = contents;} + const file = new Blob([contents], {type: 'text/plain'}); element.href = URL.createObjectURL(file); element.download = fileName; document.body.appendChild(element); // Required for this to work in FireFox @@ -451,12 +447,14 @@ export class Utils { b: parseInt(hex.substr(5, 2), 16), } } - - public static setDefaults(options, defaults){ - for (let key in defaults){ - if (!(key in options)) options[key] = defaults[key]; - } - return options; - } } +export interface TileRange { + xstart: number, + ystart: number, + xend: number, + yend: number, + total: number, + zoomlevel: number + +} \ No newline at end of file diff --git a/assets/layers/parking/parking.json b/assets/layers/parking/parking.json index 56da2650a2..d4d1cab73e 100644 --- a/assets/layers/parking/parking.json +++ b/assets/layers/parking/parking.json @@ -1,7 +1,7 @@ { "id": "parking", "name": { - "nl": "Parking" + "nl": "parking" }, "minzoom": 12, "source": { @@ -25,13 +25,13 @@ { "if": "amenity=parking", "then": { - "nl": "Auto Parking" + "nl": "{name:nl}" } }, { "if": "amenity=motorcycle_parking", "then": { - "nl": "Motorfiets Parking" + "nl": "{name}" } }, { diff --git a/assets/layers/public_bookcase/public_bookcase.json b/assets/layers/public_bookcase/public_bookcase.json index 1efe04b3a9..b2a03e4c86 100644 --- a/assets/layers/public_bookcase/public_bookcase.json +++ b/assets/layers/public_bookcase/public_bookcase.json @@ -73,10 +73,7 @@ }, "tags": [ "amenity=public_bookcase" - ], - "preciseInput": { - "preferredBackground": "photo" - } + ] } ], "tagRenderings": [ @@ -142,8 +139,7 @@ }, "freeform": { "key": "capacity", - "type": "nat", - "inline": true + "type": "nat" } }, { diff --git a/assets/layers/watermill/watermill.json b/assets/layers/watermill/watermill.json index 1ff9b488df..ffa2f0dc9a 100644 --- a/assets/layers/watermill/watermill.json +++ b/assets/layers/watermill/watermill.json @@ -1,7 +1,7 @@ { "id": "watermill", "name": { - "nl": "Watermolens" + "nl": "watermolens" }, "minzoom": 12, "source": { diff --git a/assets/svg/checkbox-empty.svg b/assets/svg/checkbox-empty.svg deleted file mode 100644 index e4a9dc8663..0000000000 --- a/assets/svg/checkbox-empty.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/assets/svg/checkbox-filled.svg b/assets/svg/checkbox-filled.svg deleted file mode 100644 index 166f917855..0000000000 --- a/assets/svg/checkbox-filled.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/assets/svg/crosshair-empty.svg b/assets/svg/crosshair-empty.svg deleted file mode 100644 index 36a6e18f8a..0000000000 --- a/assets/svg/crosshair-empty.svg +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - diff --git a/assets/svg/crosshair-locked.svg b/assets/svg/crosshair-locked.svg deleted file mode 100644 index d8d04340cd..0000000000 --- a/assets/svg/crosshair-locked.svg +++ /dev/null @@ -1,106 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - diff --git a/assets/svg/length-crosshair.svg b/assets/svg/length-crosshair.svg deleted file mode 100644 index 0446f22c4a..0000000000 --- a/assets/svg/length-crosshair.svg +++ /dev/null @@ -1,115 +0,0 @@ - - - - - -Created by potrace 1.15, written by Peter Selinger 2001-2017 - - - image/svg+xml - - - - - - - - - - - - - - diff --git a/assets/svg/license_info.json b/assets/svg/license_info.json index 2ae0adf2b0..2f85396c2b 100644 --- a/assets/svg/license_info.json +++ b/assets/svg/license_info.json @@ -614,635 +614,5 @@ "path": "filter.svg", "license": "CC0", "sources": [] - }, - { - "authors": [ - "Hannah Declerck" - ], - "path": "checkbox-empty.svg", - "license": "CC0", - "sources": [] - }, - { - "authors": [ - "Hannah Declerck" - ], - "path": "checkbox-filled.svg", - "license": "CC0", - "sources": [] - }, - { - "authors": [ - "Hannah Declerck" - ], - "path": "arrow-left-thin.svg", - "license": "CC0", - "sources": [] - }, - { - "authors": [ - "Pieter Vander Vennet" - ], - "path": "direction_masked.svg", - "license": "CC0", - "sources": [] - }, - { - "authors": [ - "Pieter Vander Vennet" - ], - "path": "direction_outline.svg", - "license": "CC0", - "sources": [] - }, - { - "authors": [ - "Pieter Vander Vennet" - ], - "path": "direction_stroke.svg", - "license": "CC0", - "sources": [] - }, - { - "authors": [ - "Pieter Vander Vennet" - ], - "path": "SocialImageForeground.svg", - "license": "CC-BY-SA", - "sources": [ - "https://mapcomplete.osm.be" - ] - }, - { - "authors": [ - "Pieter Vander Vennet" - ], - "path": "add.svg", - "license": "CC0", - "sources": [] - }, - { - "authors": [ - "Pieter Vander Vennet" - ], - "path": "addSmall.svg", - "license": "CC0", - "sources": [] - }, - { - "authors": [], - "path": "ampersand.svg", - "license": "CC0; trivial", - "sources": [] - }, - { - "authors": [ - "Pieter Vander Vennet" - ], - "path": "arrow-left-smooth.svg", - "license": "CC0", - "sources": [] - }, - { - "authors": [ - "Pieter Vander Vennet" - ], - "path": "arrow-right-smooth.svg", - "license": "CC0", - "sources": [] - }, - { - "authors": [ - "Pieter Vander Vennet" - ], - "path": "back.svg", - "license": "CC0", - "sources": [] - }, - { - "authors": [ - "Github" - ], - "path": "bug.svg", - "license": "MIT", - "sources": [ - "https://commons.wikimedia.org/wiki/File:Octicons-bug.svg", - " https://github.com/primer/octicons" - ] - }, - { - "path": "camera-plus.svg", - "license": "CC-BY-SA 3.0", - "authors": [ - "Dave Gandy", - "Pieter Vander Vennet" - ], - "sources": [ - "https://fontawesome.com/", - "https://commons.wikimedia.org/wiki/File:Camera_font_awesome.svg" - ] - }, - { - "authors": [ - "Pieter Vander Vennet" - ], - "path": "checkmark.svg", - "license": "CC0", - "sources": [] - }, - { - "authors": [ - "Pieter Vander Vennet" - ], - "path": "circle.svg", - "license": "CC0", - "sources": [] - }, - { - "authors": [ - "Pieter Vander Vennet" - ], - "path": "clock.svg", - "license": "CC0", - "sources": [] - }, - { - "authors": [], - "path": "close.svg", - "license": "CC0; trivial", - "sources": [] - }, - { - "authors": [], - "path": "compass.svg", - "license": "CC0; trivial", - "sources": [] - }, - { - "authors": [], - "path": "cross_bottom_right.svg", - "license": "CC0; trivial", - "sources": [] - }, - { - "authors": [], - "path": "crosshair-blue-center.svg", - "license": "CC0; trivial", - "sources": [] - }, - { - "authors": [], - "path": "crosshair-blue.svg", - "license": "CC0; trivial", - "sources": [] - }, - { - "authors": [], - "path": "crosshair.svg", - "license": "CC0; trivial", - "sources": [] - }, - { - "authors": [], - "path": "crosshair-empty.svg", - "license": "CC0; trivial", - "sources": [] - }, - { - "authors": [], - "path": "crosshair-locked.svg", - "license": "CC0; trivial", - "sources": [] - }, - { - "authors": [ - "Dave Gandy" - ], - "path": "delete_icon.svg", - "license": "CC-BY-SA", - "sources": [ - "https://commons.wikimedia.org/wiki/File:Trash_font_awesome.svg\rT" - ] - }, - { - "authors": [], - "path": "direction.svg", - "license": "CC0; trivial", - "sources": [] - }, - { - "authors": [], - "path": "direction_gradient.svg", - "license": "CC0; trivial", - "sources": [] - }, - { - "authors": [], - "path": "down.svg", - "license": "CC0; trivial", - "sources": [] - }, - { - "authors": [], - "path": "envelope.svg", - "license": "CC0; trivial", - "sources": [] - }, - { - "authors": [ - "The Tango Desktop Project" - ], - "path": "floppy.svg", - "license": "CC0", - "sources": [ - "https://commons.wikimedia.org/wiki/File:Media-floppy.svg", - "http://tango.freedesktop.org/Tango_Desktop_Project" - ] - }, - { - "authors": [], - "path": "gear.svg", - "license": "CC0; trivial", - "sources": [] - }, - { - "authors": [], - "path": "help.svg", - "license": "CC0; trivial", - "sources": [] - }, - { - "authors": [ - "Timothy Miller" - ], - "path": "home.svg", - "license": "CC-BY-SA 3.0", - "sources": [ - "https://commons.wikimedia.org/wiki/File:Home-icon.svg" - ] - }, - { - "authors": [ - "Timothy Miller" - ], - "path": "home_white_bg.svg", - "license": "CC-BY-SA 3.0", - "sources": [ - "https://commons.wikimedia.org/wiki/File:Home-icon.svg" - ] - }, - { - "authors": [ - "JOSM Team" - ], - "path": "josm_logo.svg", - "license": "CC0", - "sources": [ - "https://wiki.openstreetmap.org/wiki/File:JOSM_Logotype_2019.svg", - " https://josm.openstreetmap.de/" - ] - }, - { - "authors": [], - "path": "layers.svg", - "license": "CC0; trivial", - "sources": [] - }, - { - "authors": [], - "path": "layersAdd.svg", - "license": "CC0; trivial", - "sources": [] - }, - { - "path": "Ornament-Horiz-0.svg", - "license": "CC-BY", - "authors": [ - "Nightwolfdezines" - ], - "sources": [ - "https://www.vecteezy.com/vector-art/226361-ornaments-and-flourishes" - ] - }, - { - "path": "Ornament-Horiz-1.svg", - "license": "CC-BY", - "authors": [ - "Nightwolfdezines" - ], - "sources": [ - "https://www.vecteezy.com/vector-art/226361-ornaments-and-flourishes" - ] - }, - { - "path": "Ornament-Horiz-2.svg", - "license": "CC-BY", - "authors": [ - "Nightwolfdezines" - ], - "sources": [ - "https://www.vecteezy.com/vector-art/226361-ornaments-and-flourishes" - ] - }, - { - "path": "Ornament-Horiz-3.svg", - "license": "CC-BY", - "authors": [ - "Nightwolfdezines" - ], - "sources": [ - "https://www.vecteezy.com/vector-art/226361-ornaments-and-flourishes" - ] - }, - { - "path": "Ornament-Horiz-4.svg", - "license": "CC-BY", - "authors": [ - "Nightwolfdezines" - ], - "sources": [ - "https://www.vecteezy.com/vector-art/226361-ornaments-and-flourishes" - ] - }, - { - "path": "Ornament-Horiz-5.svg", - "license": "CC-BY", - "authors": [ - "Nightwolfdezines" - ], - "sources": [ - "https://www.vecteezy.com/vector-art/226361-ornaments-and-flourishes" - ] - }, - { - "path": "Ornament-Horiz-6.svg", - "license": "CC-BY", - "authors": [ - "Nightwolfdezines" - ], - "sources": [ - "https://www.vecteezy.com/vector-art/226361-ornaments-and-flourishes" - ] - }, - { - "authors": [], - "path": "star.svg", - "license": "CC0; trivial", - "sources": [] - }, - { - "authors": [], - "path": "star_outline.svg", - "license": "CC0; trivial", - "sources": [] - }, - { - "authors": [], - "path": "star_half.svg", - "license": "CC0; trivial", - "sources": [] - }, - { - "authors": [], - "path": "star_outline_half.svg", - "license": "CC0; trivial", - "sources": [] - }, - { - "authors": [], - "path": "logo.svg", - "license": "CC0; trivial", - "sources": [] - }, - { - "authors": [], - "path": "logout.svg", - "license": "CC0; trivial", - "sources": [] - }, - { - "authors": [ - "Pieter Vander Vennet", - " OSM" - ], - "path": "mapcomplete_logo.svg", - "license": "Logo; CC-BY-SA", - "sources": [ - "https://mapcomplete.osm.be" - ] - }, - { - "authors": [ - "Mapillary" - ], - "path": "mapillary.svg", - "license": "Logo; All rights reserved", - "sources": [ - "https://mapillary.com/" - ] - }, - { - "authors": [], - "path": "min.svg", - "license": "CC0; trivial", - "sources": [] - }, - { - "authors": [], - "path": "no_checkmark.svg", - "license": "CC0; trivial", - "sources": [] - }, - { - "authors": [], - "path": "or.svg", - "license": "CC0; trivial", - "sources": [] - }, - { - "authors": [], - "path": "osm-copyright.svg", - "license": "logo; all rights reserved", - "sources": [ - "https://www.OpenStreetMap.org" - ] - }, - { - "authors": [ - "OpenStreetMap U.S. Chapter" - ], - "path": "osm-logo-us.svg", - "license": "Logo", - "sources": [ - "https://www.openstreetmap.us/" - ] - }, - { - "authors": [], - "path": "osm-logo.svg", - "license": "logo; all rights reserved", - "sources": [ - "https://www.OpenStreetMap.org" - ] - }, - { - "authors": [ - "GitHub Octicons" - ], - "path": "pencil.svg", - "license": "MIT", - "sources": [ - "https://github.com/primer/octicons", - " https://commons.wikimedia.org/wiki/File:Octicons-pencil.svg" - ] - }, - { - "authors": [ - "@ tyskrat" - ], - "path": "phone.svg", - "license": "CC-BY 3.0", - "sources": [ - "https://www.onlinewebfonts.com/icon/1059" - ] - }, - { - "authors": [], - "path": "pin.svg", - "license": "CC0; trivial", - "sources": [] - }, - { - "authors": [], - "path": "plus.svg", - "license": "CC0; trivial", - "sources": [] - }, - { - "authors": [ - "@fatih" - ], - "path": "pop-out.svg", - "license": "CC-BY 3.0", - "sources": [ - "https://www.onlinewebfonts.com/icon/2151" - ] - }, - { - "authors": [], - "path": "reload.svg", - "license": "CC0; trivial", - "sources": [] - }, - { - "authors": [], - "path": "ring.svg", - "license": "CC0; trivial", - "sources": [] - }, - { - "authors": [ - "OOjs UI Team and other contributors" - ], - "path": "search.svg", - "license": "MIT", - "sources": [ - "https://commons.wikimedia.org/wiki/File:OOjs_UI_indicator_search-rtl.svg", - "https://phabricator.wikimedia.org/diffusion/GOJU/browse/master/AUTHORS.txt" - ] - }, - { - "authors": [], - "path": "send_email.svg", - "license": "CC0; trivial", - "sources": [] - }, - { - "authors": [], - "path": "share.svg", - "license": "CC0; trivial", - "sources": [] - }, - { - "authors": [], - "path": "square.svg", - "license": "CC0; trivial", - "sources": [] - }, - { - "authors": [ - "@felpgrc" - ], - "path": "statistics.svg", - "license": "CC-BY 3.0", - "sources": [ - "https://www.onlinewebfonts.com/icon/197818" - ] - }, - { - "authors": [ - "MGalloway (WMF)" - ], - "path": "translate.svg", - "license": "CC-BY-SA 3.0", - "sources": [ - "https://commons.wikimedia.org/wiki/File:OOjs_UI_icon_language-ltr.svg" - ] - }, - { - "authors": [], - "path": "up.svg", - "license": "CC0; trivial", - "sources": [] - }, - { - "authors": [ - "Wikidata" - ], - "path": "wikidata.svg", - "license": "Logo; All rights reserved", - "sources": [ - "https://www.wikidata.org" - ] - }, - { - "authors": [ - "Wikimedia" - ], - "path": "wikimedia-commons-white.svg", - "license": "Logo; All rights reserved", - "sources": [ - "https://commons.wikimedia.org" - ] - }, - { - "authors": [ - "Wikipedia" - ], - "path": "wikipedia.svg", - "license": "Logo; All rights reserved", - "sources": [ - "https://www.wikipedia.org/" - ] - }, - { - "authors": [ - "Mapillary" - ], - "path": "mapillary_black.svg", - "license": "Logo; All rights reserved", - "sources": [ - "https://www.mapillary.com/" - ] - }, - { - "authors": [ - "The Tango! Desktop Project" - ], - "path": "floppy.svg", - "license": "CC0", - "sources": [ - "https://commons.wikimedia.org/wiki/File:Media-floppy.svg" - ] } ] \ No newline at end of file diff --git a/assets/tagRenderings/icons.json b/assets/tagRenderings/icons.json index c1b9d5e273..e0bde24dad 100644 --- a/assets/tagRenderings/icons.json +++ b/assets/tagRenderings/icons.json @@ -1,15 +1,15 @@ { "wikipedialink": { "render": "WP", - "condition": { - "or": [ - "wikipedia~*", - "wikidata~*" - ] - }, + "condition": "wikipedia~*", "mappings": [ { - "if": "wikipedia=", + "if": { + "and": [ + "wikipedia=", + "wikidata~*" + ] + }, "then": "WD" } ] @@ -59,12 +59,8 @@ "render": "", "mappings": [ { - "if": "id~.*/-.*", - "then": "" - }, - { - "if": "_backend~*", - "then": "" + "if": "id~=-", + "then": "Uploading..." } ], "condition": "id~(node|way|relation)/[0-9]*" diff --git a/assets/themes/.DS_Store b/assets/themes/.DS_Store index 7d02d7d4e6..0648f38aed 100644 Binary files a/assets/themes/.DS_Store and b/assets/themes/.DS_Store differ diff --git a/assets/themes/climbing/climbing.json b/assets/themes/climbing/climbing.json index fbf13310c3..71a841e54d 100644 --- a/assets/themes/climbing/climbing.json +++ b/assets/themes/climbing/climbing.json @@ -736,7 +736,7 @@ "_contained_climbing_route_ids=JSON.parse(feat.properties._contained_climbing_routes_properties ?? '[]').map(p => p.id)", "_difficulty_hist=JSON.parse(feat.properties._contained_climbing_routes_properties ?? '[]').map(p => p['climbing:grade:french'])", "_length_hist=JSON.parse(feat.properties._contained_climbing_routes_properties ?? '[]').map(p => p['climbing:length'])", - "_contained_climbing_routes_count=JSON.parse(feat.properties._contained_climbing_routes_properties ?? '[]').length" + "_contained_climbing_routes_count=JSON.parse(_contained_climbing_routes).length" ] }, { @@ -1412,8 +1412,8 @@ "_embedding_feature_properties=feat.overlapWith('climbing').map(f => f.feat.properties).filter(p => p !== undefined).map(p => {return{access: p.access, id: p.id, name: p.name, climbing: p.climbing, 'access:description': p['access:description']}})", "_embedding_features_with_access=JSON.parse(feat.properties._embedding_feature_properties ?? '[]').filter(p => p.access !== undefined)[0]", "_embedding_feature_with_rock=JSON.parse(feat.properties._embedding_feature_properties ?? '[]').filter(p => p.rock !== undefined)[0] ?? '{}'", - "_embedding_features_with_rock:rock=JSON.parse(feat.properties._embedding_feature_with_rock ?? '{}')?.rock", - "_embedding_features_with_rock:id=JSON.parse(feat.properties._embedding_feature_with_rock ?? '{}')?.id", + "_embedding_features_with_rock:rock=JSON.parse(_embedding_feature_with_rock)?.rock", + "_embedding_features_with_rock:id=JSON.parse(_embedding_feature_with_rock)?.id", "_embedding_feature:access=JSON.parse(feat.properties._embedding_features_with_access ?? '{}').access", "_embedding_feature:access:description=JSON.parse(feat.properties._embedding_features_with_access ?? '{}')['access:description']", "_embedding_feature:id=JSON.parse(feat.properties._embedding_features_with_access ?? '{}').id" diff --git a/assets/themes/natuurpunt/natuurpunt.json b/assets/themes/natuurpunt/natuurpunt.json index c84205e5eb..993b5e1c66 100644 --- a/assets/themes/natuurpunt/natuurpunt.json +++ b/assets/themes/natuurpunt/natuurpunt.json @@ -24,58 +24,36 @@ "startZoom": 15, "widenFactor": 0.05, "socialImage": "", - "defaultBackgroundId": "CartoDB.Positron", "layers": [ { - "#": "Nature reserve with geometry, z>=13", - "builtin": "nature_reserve", + "builtin": [ + "nature_reserve" + ], "override": { "source": { "osmTags": { "+and": [ "operator~.*[nN]atuurpunt.*" ] - }, - "geoJson": "https://pietervdvn.github.io/natuurpunt_cache/natuurpunt_{layer}_{z}_{x}_{y}.geojson", - "geoJsonZoomLevel": 12, - "isOsmCache": true + } }, - "minzoom": "13", + "minzoom": "10", "icon": { "render": "circle:#FE6F32;./assets/themes/natuurpunt/nature_reserve.svg" } } }, { - "#": "Nature reserve overview from cache, points only, z < 13", - "builtin": "nature_reserve", + "builtin": [ + "visitor_information_centre" + ], "override": { "source": { "osmTags": { "+and": [ "operator~.*[nN]atuurpunt.*" ] - }, - "geoJson": "https://pietervdvn.github.io/natuurpunt_cache/natuurpunt_nature_reserve_points.geojson" - }, - "minzoom": "0", - "icon": { - "render": "circle:#FE6F32;./assets/themes/natuurpunt/nature_reserve.svg" - } - } - }, - { - "builtin": "visitor_information_centre", - "override": { - "source": { - "osmTags": { - "+and": [ - "operator~.*[nN]atuurpunt.*" - ] - }, - "geoJson": "https://pietervdvn.github.io/natuurpunt_cache/natuurpunt_{layer}_{z}_{x}_{y}.geojson", - "geoJsonZoomLevel": 12, - "isOsmCache": true + } }, "minzoom": "10", "icon": { @@ -84,17 +62,16 @@ } }, { - "builtin": "trail", + "builtin": [ + "trail" + ], "override": { "source": { "osmTags": { "+and": [ "operator~.*[nN]atuurpunt.*" ] - }, - "geoJson": "https://pietervdvn.github.io/natuurpunt_cache/natuurpunt_{layer}_{z}_{x}_{y}.geojson", - "geoJsonZoomLevel": 12, - "isOsmCache": true + } }, "minzoom": "13", "icon": { @@ -113,14 +90,11 @@ } }, { - "builtin": "toilet", + "builtin": [ + "toilet" + ], "override": { "minzoom": "15", - "source": { - "geoJson": "https://pietervdvn.github.io/natuurpunt_cache/natuurpunt_{layer}_{z}_{x}_{y}.geojson", - "geoJsonZoomLevel": 12, - "isOsmCache": true - }, "icon": { "render": "circle:#FE6F32;./assets/themes/natuurpunt/toilets.svg", "mappings": [ @@ -137,61 +111,42 @@ } }, { - "builtin": "birdhide", + "builtin": [ + "birdhide" + ], "override": { "minzoom": "15", - "source": { - "geoJson": "https://pietervdvn.github.io/natuurpunt_cache/natuurpunt_{layer}_{z}_{x}_{y}.geojson", - "geoJsonZoomLevel": 12, - "isOsmCache": true - }, "icon": { - "render": "circle:#FE6F32;./assets/themes/natuurpunt/birdhide.svg", - "mappings": [ - { - "if": { - "or": [ - "building=yes", - "shelter=yes", - "amenity=shelter" - ] - }, - "then": "circle:#FE6F32;./assets/themes/natuurpunt/birdshelter.svg" - } - ] + "render": "circle:#FE6F32;./assets/themes/natuurpunt/birdhide.svg" } } }, { - "builtin": "picnic_table", + "builtin": [ + "picnic_table" + ], "override": { "minzoom": "16", - "source": { - "geoJson": "https://pietervdvn.github.io/natuurpunt_cache/natuurpunt_{layer}_{z}_{x}_{y}.geojson", - "geoJsonZoomLevel": 12, - "isOsmCache": true - }, "icon": { "render": "circle:#FE6F32;./assets/themes/natuurpunt/picnic_table.svg" } } }, { - "builtin": "drinking_water", + "builtin": [ + "drinking_water" + ], "override": { "minzoom": "16", - "source": { - "geoJson": "https://pietervdvn.github.io/natuurpunt_cache/natuurpunt_{layer}_{z}_{x}_{y}.geojson", - "geoJsonZoomLevel": 12, - "isOsmCache": true - }, "icon": { "render": "circle:#FE6F32;./assets/themes/natuurpunt/drips.svg" } } }, { - "builtin": "parking", + "builtin": [ + "parking" + ], "override": { "minzoom": "16", "icon": { @@ -218,42 +173,33 @@ } }, { - "builtin": "information_board", + "builtin": [ + "information_board" + ], "override": { "minzoom": "16", - "source": { - "geoJson": "https://pietervdvn.github.io/natuurpunt_cache/natuurpunt_{layer}_{z}_{x}_{y}.geojson", - "geoJsonZoomLevel": 12, - "isOsmCache": true - }, "icon": { "render": "circle:#FE6F32;./assets/themes/natuurpunt/information_board.svg" } } }, { - "builtin": "bench", + "builtin": [ + "bench" + ], "override": { "minzoom": "18", - "source": { - "geoJson": "https://pietervdvn.github.io/natuurpunt_cache/natuurpunt_{layer}_{z}_{x}_{y}.geojson", - "geoJsonZoomLevel": 12, - "isOsmCache": true - }, "icon": { "render": "circle:#FE6F32;./assets/themes/natuurpunt/bench.svg" } } }, { - "builtin": "watermill", + "builtin": [ + "watermill" + ], "override": { "minzoom": "18", - "source": { - "geoJson": "https://pietervdvn.github.io/natuurpunt_cache/natuurpunt_{layer}_{z}_{x}_{y}.geojson", - "geoJsonZoomLevel": 12, - "isOsmCache": true - }, "icon": { "render": "circle:#FE6F32;./assets/themes/natuurpunt/watermill.svg" } diff --git a/assets/themes/openwindpowermap/openwindpowermap.json b/assets/themes/openwindpowermap/openwindpowermap.json index 22bba27ef9..2330b885ec 100644 --- a/assets/themes/openwindpowermap/openwindpowermap.json +++ b/assets/themes/openwindpowermap/openwindpowermap.json @@ -62,8 +62,7 @@ "en": "What is the power output of this wind turbine? (e.g. 2.3 MW)" }, "freeform": { - "key": "generator:output:electricity", - "type": "pfloat" + "key": "generator:output:electricity" } }, { @@ -86,7 +85,7 @@ }, "freeform": { "key": "height", - "type": "pfloat" + "type": "float" } }, { @@ -180,24 +179,6 @@ } ], "eraseInvalidValues": true - }, - { - "appliesToKey": [ - "height", - "rotor:diameter" - ], - "applicableUnits": [ - { - "canonicalDenomination": "m", - "alternativeDenomination": [ - "meter" - ], - "human": { - "en": " meter", - "nl": " meter" - } - } - ] } ], "defaultBackgroundId": "CartoDB.Voyager" diff --git a/assets/themes/speelplekken/speelplekken_temp.json b/assets/themes/speelplekken/speelplekken_temp.json index 8672982153..f18fbbad10 100644 --- a/assets/themes/speelplekken/speelplekken_temp.json +++ b/assets/themes/speelplekken/speelplekken_temp.json @@ -105,31 +105,11 @@ { "builtin": "slow_roads", "override": { - "+tagRenderings": [ - { - "question": "Is dit een publiek toegankelijk pad?", - "mappings": [ - { - "if": "access=private", - "then": "Dit is een privaat pad" - }, - { - "if": "access=no", - "then": "Dit is een privaat pad", - "hideInAnswer": true - }, - { - "if": "access=permissive", - "then": "Dit pad is duidelijk in private eigendom, maar er hangen geen verbodsborden dus mag men erover" - } - ] - } - ], "calculatedTags": [ "_part_of_walking_routes=Array.from(new Set(feat.memberships().map(r => \"\" + r.relation.tags.name + \"\"))).join(', ')", "_is_shadowed=feat.overlapWith('shadow').length > 0 ? 'yes': ''" ], - "minzoom": 18, + "minzoom": 9, "source": { "geoJsonLocal": "http://127.0.0.1:8080/speelplekken_{layer}_{z}_{x}_{y}.geojson", "geoJson": "https://pietervdvn.github.io/speelplekken_cache/speelplekken_{layer}_{z}_{x}_{y}.geojson", diff --git a/assets/themes/widths/width.json b/assets/themes/widths/width.json index 298b9a1281..48a1e883a5 100644 --- a/assets/themes/widths/width.json +++ b/assets/themes/widths/width.json @@ -64,13 +64,7 @@ }, "tagRenderings": [ { - "render": "Deze straat is {width:carriageway}m breed", - "question": "Hoe breed is deze straat?", - "freeform": { - "key": "width:carriageway", - "type": "length", - "helperArgs": [21, "map"] - } + "render": "Deze straat is {width:carriageway}m breed" }, { "render": "Deze straat heeft {_width:difference}m te weinig:", diff --git a/index.css b/index.css index 2b7e866d0b..5ddc0efe6d 100644 --- a/index.css +++ b/index.css @@ -82,10 +82,6 @@ html, body { box-sizing: initial !important; } -.leaflet-control-attribution { - display: block ruby; -} - svg, img { box-sizing: content-box; width: 100%; @@ -105,10 +101,6 @@ a { width: min-content; } -.w-16-imp { - width: 4rem !important; -} - .space-between{ justify-content: space-between; } diff --git a/index.ts b/index.ts index 634ad8533f..70b06bf305 100644 --- a/index.ts +++ b/index.ts @@ -19,13 +19,10 @@ import DirectionInput from "./UI/Input/DirectionInput"; import SpecialVisualizations from "./UI/SpecialVisualizations"; import ShowDataLayer from "./UI/ShowDataLayer"; import * as L from "leaflet"; -import ValidatedTextField from "./UI/Input/ValidatedTextField"; -import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers"; // Workaround for a stupid crash: inject some functions which would give stupid circular dependencies or crash the other nodejs scripts SimpleMetaTagger.coder = new CountryCoder("https://pietervdvn.github.io/latlon2country/"); DirectionInput.constructMinimap = options => new Minimap(options) -ValidatedTextField.bestLayerAt = (location, layerPref) => AvailableBaseLayers.SelectBestLayerAccordingTo(location, layerPref) SpecialVisualizations.constructMiniMap = options => new Minimap(options) SpecialVisualizations.constructShowDataLayer = (features: UIEventSource<{ feature: any, freshness: Date }[]>, leafletMap: UIEventSource, diff --git a/langs/en.json b/langs/en.json index aac35335c1..7f327653c5 100644 --- a/langs/en.json +++ b/langs/en.json @@ -149,10 +149,6 @@ "zoomInToSeeThisLayer": "Zoom in to see this layer", "title": "Select layers" }, - "download": { - "downloadGeojson": "Download visible data as geojson", - "licenseInfo": "

Copyright notice

The provided is available under ODbL. Reusing this data is free for any purpose, but
  • the attribution © OpenStreetMap contributors
  • Any change to this data must be republished under the same license
. Please see the full copyright notice for details" - }, "weekdays": { "abbreviations": { "monday": "Mon", diff --git a/langs/layers/nl.json b/langs/layers/nl.json index 016512e6d7..033ad5a51f 100644 --- a/langs/layers/nl.json +++ b/langs/layers/nl.json @@ -1185,6 +1185,9 @@ }, "1": { "then": "{name}" + }, + "2": { + "then": "Fietsenstalling" } } }, diff --git a/langs/layers/ru.json b/langs/layers/ru.json index c0f5353343..0cd328a6ae 100644 --- a/langs/layers/ru.json +++ b/langs/layers/ru.json @@ -487,11 +487,6 @@ } } } - }, - "presets": { - "0": { - "title": "Обслуживание велосипедов/магазин" - } } }, "defibrillator": { @@ -1069,7 +1064,6 @@ "1": { "question": "Вы хотите добавить описание?" } - }, - "name": "Смотровая площадка" + } } -} +} \ No newline at end of file diff --git a/langs/pt_BR.json b/langs/pt_BR.json index 268c8e4e60..638ab0d39a 100644 --- a/langs/pt_BR.json +++ b/langs/pt_BR.json @@ -122,10 +122,8 @@ "thanksForSharing": "Obrigado por compartilhar!", "copiedToClipboard": "Link copiado para a área de transferência", "addToHomeScreen": "

Adicionar à sua tela inicial

Você pode adicionar facilmente este site à tela inicial do smartphone para uma sensação nativa. Clique no botão 'adicionar à tela inicial' na barra de URL para fazer isso.", - "intro": "

Compartilhe este mapa

Compartilhe este mapa copiando o link abaixo e enviando-o para amigos e familiares:", - "embedIntro": "

Incorpore em seu site

Por favor, incorpore este mapa em seu site.
Nós o encorajamos a fazer isso - você nem precisa pedir permissão.
É gratuito e sempre será. Quanto mais pessoas usarem isso, mais valioso se tornará." - }, - "aboutMapcomplete": "

Sobre o MapComplete

Com o MapComplete, você pode enriquecer o OpenStreetMap com informações sobre umúnico tema.Responda a algumas perguntas e, em minutos, suas contribuições estarão disponíveis em todo o mundo! Omantenedor do temadefine elementos, questões e linguagens para o tema.

Saiba mais

MapComplete sempreoferece a próxima etapapara saber mais sobre o OpenStreetMap.

  • Quando incorporado em um site, o iframe vincula-se a um MapComplete em tela inteira
  • A versão em tela inteira oferece informações sobre o OpenStreetMap
  • A visualização funciona sem login, mas a edição requer um login do OSM.
  • Se você não estiver conectado, será solicitado que você faça o login
  • Depois de responder a uma única pergunta, você pode adicionar novos aponta para o mapa
  • Depois de um tempo, as tags OSM reais são mostradas, posteriormente vinculadas ao wiki


Você percebeuum problema? Você tem umasolicitação de recurso ? Querajudar a traduzir? Acesse o código-fonteou rastreador de problemas.

Quer verseu progresso? Siga a contagem de edição emOsmCha.

" + "intro": "

Compartilhe este mapa

Compartilhe este mapa copiando o link abaixo e enviando-o para amigos e familiares:" + } }, "index": { "pickTheme": "Escolha um tema abaixo para começar.", @@ -144,13 +142,10 @@ "no_reviews_yet": "Não há comentários ainda. Seja o primeiro a escrever um e ajude a abrir os dados e os negócios!", "name_required": "É necessário um nome para exibir e criar comentários", "title_singular": "Um comentário", - "title": "{count} comentários", - "tos": "Se você criar um comentário, você concorda com o TOS e a política de privacidade de Mangrove.reviews ", - "affiliated_reviewer_warning": "(Revisão de afiliados)" + "title": "{count} comentários" }, "favourite": { "reload": "Recarregar dados", - "panelIntro": "

Seu tema pessoal

Ative suas camadas favoritas de todos os temas oficiais", - "loginNeeded": "

Entrar

Um layout pessoal está disponível apenas para usuários do OpenStreetMap" + "panelIntro": "

Seu tema pessoal

Ative suas camadas favoritas de todos os temas oficiais" } } diff --git a/langs/shared-questions/de.json b/langs/shared-questions/de.json index 6faff774e2..ff0b97af86 100644 --- a/langs/shared-questions/de.json +++ b/langs/shared-questions/de.json @@ -6,27 +6,6 @@ "opening_hours": { "question": "Was sind die Öffnungszeiten von {name}?", "render": "

Öffnungszeiten

{opening_hours_table(opening_hours)}" - }, - "level": { - "mappings": { - "2": { - "then": "Ist im ersten Stock" - }, - "1": { - "then": "Ist im Erdgeschoss" - } - }, - "render": "Befindet sich im {level}ten Stock", - "question": "In welchem Stockwerk befindet sich dieses Objekt?" - }, - "description": { - "question": "Gibt es noch etwas, das die vorhergehenden Fragen nicht abgedeckt haben? Hier wäre Platz dafür.
Bitte keine bereits erhobenen Informationen." - }, - "website": { - "question": "Was ist die Website von {name}?" - }, - "email": { - "question": "Was ist die Mail-Adresse von {name}?" } } -} +} \ No newline at end of file diff --git a/langs/shared-questions/pt_BR.json b/langs/shared-questions/pt_BR.json index 9c577c3966..0967ef424b 100644 --- a/langs/shared-questions/pt_BR.json +++ b/langs/shared-questions/pt_BR.json @@ -1,30 +1 @@ -{ - "undefined": { - "level": { - "render": "Localizado no {level}o andar", - "mappings": { - "2": { - "then": "Localizado no primeiro andar" - }, - "1": { - "then": "Localizado no térreo" - }, - "0": { - "then": "Localizado no subsolo" - } - } - }, - "opening_hours": { - "question": "Qual o horário de funcionamento de {name}?" - }, - "website": { - "question": "Qual o site de {name}?" - }, - "email": { - "question": "Qual o endereço de e-mail de {name}?" - }, - "phone": { - "question": "Qual o número de telefone de {name}?" - } - } -} +{} diff --git a/langs/shared-questions/ru.json b/langs/shared-questions/ru.json index 93c56dc441..a06bc76078 100644 --- a/langs/shared-questions/ru.json +++ b/langs/shared-questions/ru.json @@ -15,20 +15,6 @@ "opening_hours": { "question": "Какое время работы у {name}?", "render": "

Часы работы

{opening_hours_table(opening_hours)}" - }, - "level": { - "mappings": { - "2": { - "then": "Расположено на первом этаже" - }, - "1": { - "then": "Расположено на первом этаже" - }, - "0": { - "then": "Расположено под землей" - } - }, - "render": "Расположено на {level}ом этаже" } } -} +} \ No newline at end of file diff --git a/langs/themes/en.json b/langs/themes/en.json index 4342fd6dfd..1b7f373e53 100644 --- a/langs/themes/en.json +++ b/langs/themes/en.json @@ -1148,13 +1148,6 @@ "human": " gigawatts" } } - }, - "1": { - "applicableUnits": { - "0": { - "human": " meter" - } - } } } }, diff --git a/langs/themes/nl.json b/langs/themes/nl.json index 69ad6bc92d..120c58bacf 100644 --- a/langs/themes/nl.json +++ b/langs/themes/nl.json @@ -956,13 +956,6 @@ "human": " gigawatt" } } - }, - "1": { - "applicableUnits": { - "0": { - "human": " meter" - } - } } } }, diff --git a/package-lock.json b/package-lock.json index c3f2d76328..f4d16ca64a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,11 +28,9 @@ "escape-html": "^1.0.3", "i18next-client": "^1.11.4", "jquery": "^3.6.0", - "jspdf": "^2.3.1", "latlon2country": "^1.1.3", "leaflet": "^1.7.1", "leaflet-providers": "^1.10.2", - "leaflet-simple-map-screenshoter": "^0.4.4", "leaflet.markercluster": "^1.4.1", "libphonenumber": "0.0.10", "libphonenumber-js": "^1.7.55", @@ -46,6 +44,7 @@ "postcss": "^7.0.36", "prompt-sync": "^4.2.0", "sharp": "^0.27.0", + "tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.0.2", "tslint": "^6.1.3" }, "devDependencies": { @@ -55,7 +54,6 @@ "fs": "0.0.1-security", "marked": "^2.0.0", "read-file": "^0.2.0", - "tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.4", "ts-node": "^9.0.0", "ts-node-dev": "^1.0.0-pre.63", "tslint-no-circular-imports": "^0.7.0", @@ -1009,19 +1007,6 @@ "regenerator-runtime": "^0.13.4" } }, - "node_modules/@babel/runtime-corejs3": { - "version": "7.14.8", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.14.8.tgz", - "integrity": "sha512-4dMD5QRBkumn45oweR0SxoNtt15oz3BUBAQ8cIx7HJqZTtE8zjpM0My8aHJHVnyf4XfRg6DNzaE1080WLBiC1w==", - "optional": true, - "dependencies": { - "core-js-pure": "^3.15.0", - "regenerator-runtime": "^0.13.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/template": { "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", @@ -1308,6 +1293,7 @@ "version": "1.2.13", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "hasInstallScript": true, "optional": true, "os": [ "darwin" @@ -2879,12 +2865,6 @@ "integrity": "sha512-29GS75BE8asnTno3yB6ubOJOO0FboExEqNJy4bpz0GSmW/8wPTNL4h9h63c6s1uTrOopCmJYe/4yJLh5r92ZUA==", "dev": true }, - "node_modules/@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", - "dev": true - }, "node_modules/@types/prompt-sync": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/@types/prompt-sync/-/prompt-sync-4.1.0.tgz", @@ -2895,12 +2875,6 @@ "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz", "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==" }, - "node_modules/@types/raf": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.0.tgz", - "integrity": "sha512-taW5/WYqo36N7V39oYyHP9Ipfd5pNFvGTIQsNGj86xV88YQ7GnI30/yMfKDF7Zgin0m3e+ikX88FvImnK4RjGw==", - "optional": true - }, "node_modules/@types/sizzle": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz", @@ -3429,15 +3403,6 @@ "node": ">=0.10.0" } }, - "node_modules/base64-arraybuffer": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz", - "integrity": "sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ==", - "optional": true, - "engines": { - "node": ">= 0.6.0" - } - }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -3671,17 +3636,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/btoa": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", - "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==", - "bin": { - "btoa": "bin/btoa.js" - }, - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/buffer": { "version": "4.9.2", "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", @@ -3845,23 +3799,6 @@ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001223.tgz", "integrity": "sha512-k/RYs6zc/fjbxTjaWZemeSmOjO0JJV+KguOBA3NwPup8uzxM1cMhR2BD9XmO86GuqaqTCO8CgkgH9Rz//vdDiA==" }, - "node_modules/canvg": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.7.tgz", - "integrity": "sha512-4sq6iL5Q4VOXS3PL1BapiXIZItpxYyANVzsAKpTPS5oq4u3SKbGfUcbZh2gdLCQ3jWpG/y5wRkMlBBAJhXeiZA==", - "optional": true, - "dependencies": { - "@babel/runtime-corejs3": "^7.9.6", - "@types/raf": "^3.4.0", - "raf": "^3.4.1", - "rgbcolor": "^1.0.1", - "stackblur-canvas": "^2.0.0", - "svg-pathdata": "^5.0.5" - }, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/cardinal": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-0.4.4.tgz", @@ -4189,7 +4126,8 @@ "node_modules/core-js": { "version": "2.6.12", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", - "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==" + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "hasInstallScript": true }, "node_modules/core-js-compat": { "version": "3.12.0", @@ -4208,13 +4146,6 @@ "semver": "bin/semver.js" } }, - "node_modules/core-js-pure": { - "version": "3.15.2", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.15.2.tgz", - "integrity": "sha512-D42L7RYh1J2grW8ttxoY1+17Y4wXZeKe7uyplAI3FkNQyI5OgBIAjUfFiTPfL1rs0qLpxaabITNbjKl1Sp82tA==", - "hasInstallScript": true, - "optional": true - }, "node_modules/core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -4349,15 +4280,6 @@ "node": ">4" } }, - "node_modules/css-line-break": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-1.1.1.tgz", - "integrity": "sha512-1feNVaM4Fyzdj4mKPIQNL2n70MmuYzAXZ1aytlROFX1JsOo070OsugwGjj7nl6jnDJWHDM8zRZswkmeYVWZJQA==", - "optional": true, - "dependencies": { - "base64-arraybuffer": "^0.2.0" - } - }, "node_modules/css-modules-loader-core": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/css-modules-loader-core/-/css-modules-loader-core-1.1.0.tgz", @@ -4740,6 +4662,7 @@ "version": "0.1.21", "resolved": "https://registry.npmjs.org/deasync/-/deasync-0.1.21.tgz", "integrity": "sha512-kUmM8Y+PZpMpQ+B4AuOW9k2Pfx/mSupJtxOsLzmnHY2WqZUYRFccFn2RhzPAqt3Xb+sorK/badW2D4zNzqZz5w==", + "hasInstallScript": true, "dependencies": { "bindings": "^1.5.0", "node-addon-api": "^1.7.1" @@ -5006,11 +4929,6 @@ "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==" }, - "node_modules/dom-to-image-more": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/dom-to-image-more/-/dom-to-image-more-2.8.0.tgz", - "integrity": "sha512-YqlHI1i+TMuaKwkFRO5oDPjC3eWf+6Hln9rHZcnFYvmoXwCrGZmZ7BYXBJOjw5utYg2Lp+QF9YO96F7CsDC4eQ==" - }, "node_modules/domain-browser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", @@ -5041,12 +4959,6 @@ "domelementtype": "1" } }, - "node_modules/dompurify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.3.0.tgz", - "integrity": "sha512-VV5C6Kr53YVHGOBKO/F86OYX6/iLTw2yVSI721gKetxpHCK/V5TaLEf9ODjRgl1KLSWRMY6cUhAbv/c+IUnwQw==", - "optional": true - }, "node_modules/domutils": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.3.0.tgz", @@ -5595,16 +5507,6 @@ "reusify": "^1.0.4" } }, - "node_modules/fflate": { - "version": "0.4.8", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz", - "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==" - }, - "node_modules/file-saver": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", - "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==" - }, "node_modules/file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -6227,18 +6129,6 @@ "node": ">=8" } }, - "node_modules/html2canvas": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.1.4.tgz", - "integrity": "sha512-uHgQDwrXsRmFdnlOVFvHin9R7mdjjZvoBoXxicPR+NnucngkaLa5zIDW9fzMkiip0jSffyTyWedE8iVogYOeWg==", - "optional": true, - "dependencies": { - "css-line-break": "1.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/htmlnano": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/htmlnano/-/htmlnano-0.2.9.tgz", @@ -6535,18 +6425,6 @@ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, - "node_modules/import-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-3.0.0.tgz", - "integrity": "sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg==", - "dev": true, - "dependencies": { - "import-from": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/import-fresh": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", @@ -6559,27 +6437,6 @@ "node": ">=4" } }, - "node_modules/import-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/import-from/-/import-from-3.0.0.tgz", - "integrity": "sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==", - "dev": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/import-from/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/incremental-convex-hull": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/incremental-convex-hull/-/incremental-convex-hull-1.0.1.tgz", @@ -7228,12 +7085,6 @@ "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, "node_modules/json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", @@ -7323,29 +7174,6 @@ "semver": "bin/semver" } }, - "node_modules/jspdf": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.3.1.tgz", - "integrity": "sha512-1vp0USP1mQi1h7NKpwxjFgQkJ5ncZvtH858aLpycUc/M+r/RpWJT8PixAU7Cw/3fPd4fpC8eB/Bj42LnsR21YQ==", - "dependencies": { - "atob": "^2.1.2", - "btoa": "^1.2.1", - "fflate": "^0.4.8" - }, - "optionalDependencies": { - "canvg": "^3.0.6", - "core-js": "^3.6.0", - "dompurify": "^2.2.0", - "html2canvas": "^1.0.0-rc.5" - } - }, - "node_modules/jspdf/node_modules/core-js": { - "version": "3.15.2", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.15.2.tgz", - "integrity": "sha512-tKs41J7NJVuaya8DxIOCnl8QuPHx5/ZVbFo1oKgVl1qHFBBrDctzQGtuLjPpRdNTWmKPH6oEvgN/MUID+l485Q==", - "hasInstallScript": true, - "optional": true - }, "node_modules/jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -8915,15 +8743,6 @@ "resolved": "https://registry.npmjs.org/leaflet-providers/-/leaflet-providers-1.12.0.tgz", "integrity": "sha512-pU/mR4B+NbayBGCg5/88dmRq7t1EGiNPhsVGV3yqHuDn594vIwus4CiPVW0RtiKJNKg8Vf1pILAbFl0i+yk+lQ==" }, - "node_modules/leaflet-simple-map-screenshoter": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/leaflet-simple-map-screenshoter/-/leaflet-simple-map-screenshoter-0.4.4.tgz", - "integrity": "sha512-n5r04/PxXvqPQUJH+kP+vYj1Sg231YITPwoPMmdHwe+nSB+NJtQS0emEh9BaXXIbkZxubxeWQ1mKXpJYOxCAmw==", - "dependencies": { - "dom-to-image-more": "^2.8.0", - "file-saver": "^2.0.2" - } - }, "node_modules/leaflet.markercluster": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/leaflet.markercluster/-/leaflet.markercluster-1.5.0.tgz", @@ -8958,26 +8777,11 @@ "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.9.17.tgz", "integrity": "sha512-ElJki901OynMg1l+evooPH1VyHrECuLqpgc12z2BkK25dFU5lUKTuMHEYV2jXxvtns/PIuJax56cBeoSK7ANow==" }, - "node_modules/lilconfig": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.3.tgz", - "integrity": "sha512-EHKqr/+ZvdKCifpNrJCKxBTgk5XupZA3y/aCPY9mxfgBzmgh93Mt/WqjjQ38oMxXuvDokaKiM3lAgvSH2sjtHg==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/lineclip": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/lineclip/-/lineclip-1.1.5.tgz", "integrity": "sha1-K/JgZ9lDVP6r+R5CdoI221YW/RM=" }, - "node_modules/lines-and-columns": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", - "dev": true - }, "node_modules/load-json-file": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", @@ -10046,6 +9850,7 @@ "version": "1.12.4", "resolved": "https://registry.npmjs.org/parcel/-/parcel-1.12.4.tgz", "integrity": "sha512-qfc74e2/R4pCoU6L/ZZnK9k3iDS6ir4uHea0e9th9w52eehcAGf2ido/iABq9PBXdsIOe4NSY3oUm7Khe7+S3w==", + "hasInstallScript": true, "dependencies": { "@babel/code-frame": "^7.0.0", "@babel/core": "^7.4.4", @@ -10297,27 +10102,6 @@ "node": ">=0.10.0" } }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parent-module/node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/parse-asn1": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", @@ -10696,20 +10480,6 @@ "postcss": "^7.0.18" } }, - "node_modules/postcss-load-config": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.0.tgz", - "integrity": "sha512-ipM8Ds01ZUophjDTQYSVP70slFSYg3T0/zyfII5vzhN6V57YSxMgG5syXuwi5VtS8wSf3iL30v0uBdoIVx4Q0g==", - "dev": true, - "dependencies": { - "import-cwd": "^3.0.0", - "lilconfig": "^2.0.3", - "yaml": "^1.10.2" - }, - "engines": { - "node": ">= 10" - } - }, "node_modules/postcss-merge-longhand": { "version": "4.0.11", "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz", @@ -11548,15 +11318,6 @@ "quote-stream": "bin/cmd.js" } }, - "node_modules/raf": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", - "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", - "optional": true, - "dependencies": { - "performance-now": "^2.1.0" - } - }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -11962,15 +11723,6 @@ "resolved": "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz", "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=" }, - "node_modules/rgbcolor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", - "integrity": "sha1-1lBezbMEplldom+ktDMHMGd1lF0=", - "optional": true, - "engines": { - "node": ">= 0.8.15" - } - }, "node_modules/rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", @@ -12218,6 +11970,7 @@ "version": "0.27.2", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.27.2.tgz", "integrity": "sha512-w3FVoONPG/x5MXCc3wsjOS+b9h3CI60qkus6EPQU4dkT0BDm0PyGhDCK6KhtfT3/vbeOMOXAKFNSw+I3QGWkMA==", + "hasInstallScript": true, "dependencies": { "array-flatten": "^3.0.0", "color": "^3.1.3", @@ -12621,15 +12374,6 @@ "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==" }, - "node_modules/stackblur-canvas": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.5.0.tgz", - "integrity": "sha512-EeNzTVfj+1In7aSLPKDD03F/ly4RxEuF/EX0YcOG0cKoPXs+SLZxDawQbexQDBzwROs4VKLWTOaZQlZkGBFEIQ==", - "optional": true, - "engines": { - "node": ">=0.1.14" - } - }, "node_modules/static-eval": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.1.0.tgz", @@ -12934,15 +12678,6 @@ "node": ">=4" } }, - "node_modules/svg-pathdata": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-5.0.5.tgz", - "integrity": "sha512-TAAvLNSE3fEhyl/Da19JWfMAdhSXTYeviXsLSoDT1UM76ADj5ndwAPX1FKQEgB/gFMPavOy6tOqfalXKUiXrow==", - "optional": true, - "engines": { - "node": ">=6.9.5" - } - }, "node_modules/svgo": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", @@ -12976,45 +12711,39 @@ }, "node_modules/tailwindcss": { "name": "@tailwindcss/postcss7-compat", - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/postcss7-compat/-/postcss7-compat-2.2.4.tgz", - "integrity": "sha512-lFIBdD1D2w3RgHFg7kNB7U5LOlfbd+KXTzcLyC/RlQ9eVko6GjNCKpN/kdmfF9wiGxbSDT/3mousXeMZdOOuBg==", - "dev": true, + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss7-compat/-/postcss7-compat-2.1.2.tgz", + "integrity": "sha512-bH2kw6uyqLnDMP8wzDUsis5ovrsRzfHEyiL1McADvqlW54g6y0KVHX1xzO7PH8Fl5s0Sq8vDOAp4+3V8MEcZ9g==", "dependencies": { "@fullhuman/postcss-purgecss": "^3.1.3", - "arg": "^5.0.0", "autoprefixer": "^9", "bytes": "^3.0.0", - "chalk": "^4.1.1", - "chokidar": "^3.5.2", + "chalk": "^4.1.0", + "chokidar": "^3.5.1", "color": "^3.1.3", - "cosmiconfig": "^7.0.0", "detective": "^5.2.0", "didyoumean": "^1.2.1", "dlv": "^1.1.3", "fast-glob": "^3.2.5", - "fs-extra": "^10.0.0", - "glob-parent": "^6.0.0", + "fs-extra": "^9.1.0", "html-tags": "^3.1.0", - "is-glob": "^4.0.1", "lodash": "^4.17.21", "lodash.topath": "^4.5.2", - "modern-normalize": "^1.1.0", + "modern-normalize": "^1.0.0", "node-emoji": "^1.8.1", "normalize-path": "^3.0.0", - "object-hash": "^2.2.0", + "object-hash": "^2.1.1", + "parse-glob": "^3.0.4", "postcss": "^7", "postcss-functions": "^3", "postcss-js": "^2", - "postcss-load-config": "^3.1.0", "postcss-nested": "^4", - "postcss-selector-parser": "^6.0.6", + "postcss-selector-parser": "^6.0.4", "postcss-value-parser": "^4.1.0", "pretty-hrtime": "^1.0.3", "quick-lru": "^5.1.1", "reduce-css-calc": "^2.1.8", - "resolve": "^1.20.0", - "tmp": "^0.2.1" + "resolve": "^1.20.0" }, "bin": { "tailwind": "lib/cli.js", @@ -13028,7 +12757,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -13036,17 +12764,10 @@ "node": ">=8" } }, - "node_modules/tailwindcss/node_modules/arg": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.0.tgz", - "integrity": "sha512-4P8Zm2H+BRS+c/xX1LrHw0qKpEhdlZjLCgWy+d78T9vqa2Z2SiD2wMrYuWIAFy5IZUD7nnNXroRttz+0RzlrzQ==", - "dev": true - }, "node_modules/tailwindcss/node_modules/chalk": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -13055,44 +12776,10 @@ "node": ">=10" } }, - "node_modules/tailwindcss/node_modules/chokidar": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", - "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", - "dev": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/tailwindcss/node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/tailwindcss/node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -13103,145 +12790,20 @@ "node_modules/tailwindcss/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/tailwindcss/node_modules/cosmiconfig": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", - "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", - "dev": true, - "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/tailwindcss/node_modules/fs-extra": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", - "integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/tailwindcss/node_modules/glob-parent": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.1.tgz", - "integrity": "sha512-kEVjS71mQazDBHKcsq4E9u/vUzaLcw1A8EtUeydawvIWQCJM0qQ08G1H7/XTjFUulla6XQiDOG6MXSaG0HDKog==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">=10.13.0" - } + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/tailwindcss/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "engines": { "node": ">=8" } }, - "node_modules/tailwindcss/node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tailwindcss/node_modules/object-hash": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", - "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/tailwindcss/node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tailwindcss/node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/tailwindcss/node_modules/postcss-selector-parser": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz", - "integrity": "sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg==", - "dev": true, - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/tailwindcss/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/tailwindcss/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/tailwindcss/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -13366,30 +12928,6 @@ "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz", "integrity": "sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==" }, - "node_modules/tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "dev": true, - "dependencies": { - "rimraf": "^3.0.0" - }, - "engines": { - "node": ">=8.17.0" - } - }, - "node_modules/tmp/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, "node_modules/to-arraybuffer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", @@ -14709,15 +14247,6 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, - "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", @@ -15658,16 +15187,6 @@ "regenerator-runtime": "^0.13.4" } }, - "@babel/runtime-corejs3": { - "version": "7.14.8", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.14.8.tgz", - "integrity": "sha512-4dMD5QRBkumn45oweR0SxoNtt15oz3BUBAQ8cIx7HJqZTtE8zjpM0My8aHJHVnyf4XfRg6DNzaE1080WLBiC1w==", - "optional": true, - "requires": { - "core-js-pure": "^3.15.0", - "regenerator-runtime": "^0.13.4" - } - }, "@babel/template": { "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", @@ -17448,12 +16967,6 @@ "integrity": "sha512-29GS75BE8asnTno3yB6ubOJOO0FboExEqNJy4bpz0GSmW/8wPTNL4h9h63c6s1uTrOopCmJYe/4yJLh5r92ZUA==", "dev": true }, - "@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", - "dev": true - }, "@types/prompt-sync": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/@types/prompt-sync/-/prompt-sync-4.1.0.tgz", @@ -17464,12 +16977,6 @@ "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz", "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==" }, - "@types/raf": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.0.tgz", - "integrity": "sha512-taW5/WYqo36N7V39oYyHP9Ipfd5pNFvGTIQsNGj86xV88YQ7GnI30/yMfKDF7Zgin0m3e+ikX88FvImnK4RjGw==", - "optional": true - }, "@types/sizzle": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz", @@ -17918,12 +17425,6 @@ } } }, - "base64-arraybuffer": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz", - "integrity": "sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ==", - "optional": true - }, "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -18144,11 +17645,6 @@ "node-releases": "^1.1.71" } }, - "btoa": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", - "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==" - }, "buffer": { "version": "4.9.2", "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", @@ -18282,20 +17778,6 @@ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001223.tgz", "integrity": "sha512-k/RYs6zc/fjbxTjaWZemeSmOjO0JJV+KguOBA3NwPup8uzxM1cMhR2BD9XmO86GuqaqTCO8CgkgH9Rz//vdDiA==" }, - "canvg": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.7.tgz", - "integrity": "sha512-4sq6iL5Q4VOXS3PL1BapiXIZItpxYyANVzsAKpTPS5oq4u3SKbGfUcbZh2gdLCQ3jWpG/y5wRkMlBBAJhXeiZA==", - "optional": true, - "requires": { - "@babel/runtime-corejs3": "^7.9.6", - "@types/raf": "^3.4.0", - "raf": "^3.4.1", - "rgbcolor": "^1.0.1", - "stackblur-canvas": "^2.0.0", - "svg-pathdata": "^5.0.5" - } - }, "cardinal": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-0.4.4.tgz", @@ -18596,12 +18078,6 @@ } } }, - "core-js-pure": { - "version": "3.15.2", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.15.2.tgz", - "integrity": "sha512-D42L7RYh1J2grW8ttxoY1+17Y4wXZeKe7uyplAI3FkNQyI5OgBIAjUfFiTPfL1rs0qLpxaabITNbjKl1Sp82tA==", - "optional": true - }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -18717,15 +18193,6 @@ "timsort": "^0.3.0" } }, - "css-line-break": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-1.1.1.tgz", - "integrity": "sha512-1feNVaM4Fyzdj4mKPIQNL2n70MmuYzAXZ1aytlROFX1JsOo070OsugwGjj7nl6jnDJWHDM8zRZswkmeYVWZJQA==", - "optional": true, - "requires": { - "base64-arraybuffer": "^0.2.0" - } - }, "css-modules-loader-core": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/css-modules-loader-core/-/css-modules-loader-core-1.1.0.tgz", @@ -19255,11 +18722,6 @@ } } }, - "dom-to-image-more": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/dom-to-image-more/-/dom-to-image-more-2.8.0.tgz", - "integrity": "sha512-YqlHI1i+TMuaKwkFRO5oDPjC3eWf+6Hln9rHZcnFYvmoXwCrGZmZ7BYXBJOjw5utYg2Lp+QF9YO96F7CsDC4eQ==" - }, "domain-browser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", @@ -19286,12 +18748,6 @@ "domelementtype": "1" } }, - "dompurify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.3.0.tgz", - "integrity": "sha512-VV5C6Kr53YVHGOBKO/F86OYX6/iLTw2yVSI721gKetxpHCK/V5TaLEf9ODjRgl1KLSWRMY6cUhAbv/c+IUnwQw==", - "optional": true - }, "domutils": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.3.0.tgz", @@ -19741,16 +19197,6 @@ "reusify": "^1.0.4" } }, - "fflate": { - "version": "0.4.8", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz", - "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==" - }, - "file-saver": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", - "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==" - }, "file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -20264,15 +19710,6 @@ "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.1.0.tgz", "integrity": "sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==" }, - "html2canvas": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.1.4.tgz", - "integrity": "sha512-uHgQDwrXsRmFdnlOVFvHin9R7mdjjZvoBoXxicPR+NnucngkaLa5zIDW9fzMkiip0jSffyTyWedE8iVogYOeWg==", - "optional": true, - "requires": { - "css-line-break": "1.1.1" - } - }, "htmlnano": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/htmlnano/-/htmlnano-0.2.9.tgz", @@ -20528,15 +19965,6 @@ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, - "import-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-3.0.0.tgz", - "integrity": "sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg==", - "dev": true, - "requires": { - "import-from": "^3.0.0" - } - }, "import-fresh": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", @@ -20546,23 +19974,6 @@ "resolve-from": "^3.0.0" } }, - "import-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/import-from/-/import-from-3.0.0.tgz", - "integrity": "sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==", - "dev": true, - "requires": { - "resolve-from": "^5.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - } - } - }, "incremental-convex-hull": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/incremental-convex-hull/-/incremental-convex-hull-1.0.1.tgz", @@ -21058,12 +20469,6 @@ "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", @@ -21141,28 +20546,6 @@ } } }, - "jspdf": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.3.1.tgz", - "integrity": "sha512-1vp0USP1mQi1h7NKpwxjFgQkJ5ncZvtH858aLpycUc/M+r/RpWJT8PixAU7Cw/3fPd4fpC8eB/Bj42LnsR21YQ==", - "requires": { - "atob": "^2.1.2", - "btoa": "^1.2.1", - "canvg": "^3.0.6", - "core-js": "^3.6.0", - "dompurify": "^2.2.0", - "fflate": "^0.4.8", - "html2canvas": "^1.0.0-rc.5" - }, - "dependencies": { - "core-js": { - "version": "3.15.2", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.15.2.tgz", - "integrity": "sha512-tKs41J7NJVuaya8DxIOCnl8QuPHx5/ZVbFo1oKgVl1qHFBBrDctzQGtuLjPpRdNTWmKPH6oEvgN/MUID+l485Q==", - "optional": true - } - } - }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -22747,15 +22130,6 @@ "resolved": "https://registry.npmjs.org/leaflet-providers/-/leaflet-providers-1.12.0.tgz", "integrity": "sha512-pU/mR4B+NbayBGCg5/88dmRq7t1EGiNPhsVGV3yqHuDn594vIwus4CiPVW0RtiKJNKg8Vf1pILAbFl0i+yk+lQ==" }, - "leaflet-simple-map-screenshoter": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/leaflet-simple-map-screenshoter/-/leaflet-simple-map-screenshoter-0.4.4.tgz", - "integrity": "sha512-n5r04/PxXvqPQUJH+kP+vYj1Sg231YITPwoPMmdHwe+nSB+NJtQS0emEh9BaXXIbkZxubxeWQ1mKXpJYOxCAmw==", - "requires": { - "dom-to-image-more": "^2.8.0", - "file-saver": "^2.0.2" - } - }, "leaflet.markercluster": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/leaflet.markercluster/-/leaflet.markercluster-1.5.0.tgz", @@ -22784,23 +22158,11 @@ "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.9.17.tgz", "integrity": "sha512-ElJki901OynMg1l+evooPH1VyHrECuLqpgc12z2BkK25dFU5lUKTuMHEYV2jXxvtns/PIuJax56cBeoSK7ANow==" }, - "lilconfig": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.3.tgz", - "integrity": "sha512-EHKqr/+ZvdKCifpNrJCKxBTgk5XupZA3y/aCPY9mxfgBzmgh93Mt/WqjjQ38oMxXuvDokaKiM3lAgvSH2sjtHg==", - "dev": true - }, "lineclip": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/lineclip/-/lineclip-1.1.5.tgz", "integrity": "sha1-K/JgZ9lDVP6r+R5CdoI221YW/RM=" }, - "lines-and-columns": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", - "dev": true - }, "load-json-file": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", @@ -23901,23 +23263,6 @@ } } }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - }, - "dependencies": { - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - } - } - }, "parse-asn1": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", @@ -24241,17 +23586,6 @@ "postcss": "^7.0.18" } }, - "postcss-load-config": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.0.tgz", - "integrity": "sha512-ipM8Ds01ZUophjDTQYSVP70slFSYg3T0/zyfII5vzhN6V57YSxMgG5syXuwi5VtS8wSf3iL30v0uBdoIVx4Q0g==", - "dev": true, - "requires": { - "import-cwd": "^3.0.0", - "lilconfig": "^2.0.3", - "yaml": "^1.10.2" - } - }, "postcss-merge-longhand": { "version": "4.0.11", "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz", @@ -24967,15 +24301,6 @@ "through2": "^2.0.0" } }, - "raf": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", - "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", - "optional": true, - "requires": { - "performance-now": "^2.1.0" - } - }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -25314,12 +24639,6 @@ "resolved": "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz", "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=" }, - "rgbcolor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", - "integrity": "sha1-1lBezbMEplldom+ktDMHMGd1lF0=", - "optional": true - }, "rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", @@ -25888,12 +25207,6 @@ "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==" }, - "stackblur-canvas": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.5.0.tgz", - "integrity": "sha512-EeNzTVfj+1In7aSLPKDD03F/ly4RxEuF/EX0YcOG0cKoPXs+SLZxDawQbexQDBzwROs4VKLWTOaZQlZkGBFEIQ==", - "optional": true - }, "static-eval": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.1.0.tgz", @@ -26140,12 +25453,6 @@ "has-flag": "^3.0.0" } }, - "svg-pathdata": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-5.0.5.tgz", - "integrity": "sha512-TAAvLNSE3fEhyl/Da19JWfMAdhSXTYeviXsLSoDT1UM76ADj5ndwAPX1FKQEgB/gFMPavOy6tOqfalXKUiXrow==", - "optional": true - }, "svgo": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", @@ -26172,104 +25479,62 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" }, "tailwindcss": { - "version": "npm:@tailwindcss/postcss7-compat@2.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/postcss7-compat/-/postcss7-compat-2.2.4.tgz", - "integrity": "sha512-lFIBdD1D2w3RgHFg7kNB7U5LOlfbd+KXTzcLyC/RlQ9eVko6GjNCKpN/kdmfF9wiGxbSDT/3mousXeMZdOOuBg==", - "dev": true, + "version": "npm:@tailwindcss/postcss7-compat@2.1.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss7-compat/-/postcss7-compat-2.1.2.tgz", + "integrity": "sha512-bH2kw6uyqLnDMP8wzDUsis5ovrsRzfHEyiL1McADvqlW54g6y0KVHX1xzO7PH8Fl5s0Sq8vDOAp4+3V8MEcZ9g==", "requires": { "@fullhuman/postcss-purgecss": "^3.1.3", - "arg": "^5.0.0", "autoprefixer": "^9", "bytes": "^3.0.0", - "chalk": "^4.1.1", - "chokidar": "^3.5.2", + "chalk": "^4.1.0", + "chokidar": "^3.5.1", "color": "^3.1.3", - "cosmiconfig": "^7.0.0", "detective": "^5.2.0", "didyoumean": "^1.2.1", "dlv": "^1.1.3", "fast-glob": "^3.2.5", - "fs-extra": "^10.0.0", - "glob-parent": "^6.0.0", + "fs-extra": "^9.1.0", "html-tags": "^3.1.0", - "is-glob": "^4.0.1", "lodash": "^4.17.21", "lodash.topath": "^4.5.2", - "modern-normalize": "^1.1.0", + "modern-normalize": "^1.0.0", "node-emoji": "^1.8.1", "normalize-path": "^3.0.0", - "object-hash": "^2.2.0", + "object-hash": "^2.1.1", + "parse-glob": "^3.0.4", "postcss": "^7", "postcss-functions": "^3", "postcss-js": "^2", - "postcss-load-config": "^3.1.0", "postcss-nested": "^4", - "postcss-selector-parser": "^6.0.6", + "postcss-selector-parser": "^6.0.4", "postcss-value-parser": "^4.1.0", "pretty-hrtime": "^1.0.3", "quick-lru": "^5.1.1", "reduce-css-calc": "^2.1.8", - "resolve": "^1.20.0", - "tmp": "^0.2.1" + "resolve": "^1.20.0" }, "dependencies": { "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "requires": { "color-convert": "^2.0.1" } }, - "arg": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.0.tgz", - "integrity": "sha512-4P8Zm2H+BRS+c/xX1LrHw0qKpEhdlZjLCgWy+d78T9vqa2Z2SiD2wMrYuWIAFy5IZUD7nnNXroRttz+0RzlrzQ==", - "dev": true - }, "chalk": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, - "chokidar": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", - "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", - "dev": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "dependencies": { - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - } - } - }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "requires": { "color-name": "~1.1.4" } @@ -26277,112 +25542,17 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "cosmiconfig": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", - "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", - "dev": true, - "requires": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - } - }, - "fs-extra": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", - "integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "glob-parent": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.1.tgz", - "integrity": "sha512-kEVjS71mQazDBHKcsq4E9u/vUzaLcw1A8EtUeydawvIWQCJM0qQ08G1H7/XTjFUulla6XQiDOG6MXSaG0HDKog==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "object-hash": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", - "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", - "dev": true - }, - "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "postcss-selector-parser": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz", - "integrity": "sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg==", - "dev": true, - "requires": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - } - }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "requires": { "has-flag": "^4.0.0" } @@ -26492,26 +25662,6 @@ "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz", "integrity": "sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==" }, - "tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "dev": true, - "requires": { - "rimraf": "^3.0.0" - }, - "dependencies": { - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } - } - }, "to-arraybuffer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", @@ -27677,12 +26827,6 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, - "yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true - }, "yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/package.json b/package.json index 214f6091f2..70c7a6aaf9 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "main": "index.js", "scripts": { "increase-memory": "export NODE_OPTIONS=--max_old_space_size=4096", - "start": "ts-node scripts/generateLayerOverview.ts --no-fail && npm run increase-memory && parcel *.html UI/** Logic/** assets/*.json assets/generated/* assets/layers/*/*.svg assets/tagRendering/*.json assets/themes/*/*.svg assets/themes/*/*.png vendor/* vendor/*/*", + "start": "ts-node scripts/generateLayerOverview.ts --no-fail && npm run increase-memory && parcel *.html UI/** Logic/** assets/** assets/**/** assets/**/**/** vendor/* vendor/*/*", "test": "ts-node test/TestAll.ts", "init": "npm ci && npm run generate && npm run generate:editor-layer-index && npm run generate:layouts && npm run clean", "add-weblate-upstream": "git remote add weblate-layers https://hosted.weblate.org/git/mapcomplete/layer-translations/ ; git remote update weblate-layers", @@ -20,7 +20,7 @@ "generate:layouts": "ts-node scripts/generateLayouts.ts", "generate:docs": "ts-node scripts/generateDocs.ts && ts-node scripts/generateTaginfoProjectFiles.ts", "generate:cache:speelplekken": "npm run generate:layeroverview && ts-node scripts/generateCache.ts speelplekken 14 ../pietervdvn.github.io/speelplekken_cache/ 51.20 4.35 51.09 4.56", - "generate:cache:natuurpunt": "npm run generate:layeroverview && ts-node scripts/generateCache.ts natuurpunt 12 ../pietervdvn.github.io/natuurpunt_cache/ 50.40 2.1 51.54 6.4 --generate-point-overview nature_reserve,visitor_information_centre", + "generate:cache:natuurpunt": "npm run generate:layeroverview && ts-node scripts/generateCache.ts natuurpunt 12 ../pietervdvn.github.io/natuurpunt_cache/ 50.40 2.1 51.54 6.4", "generate:layeroverview": "npm run generate:licenses && echo {\\\"layers\\\":[], \\\"themes\\\":[]} > ./assets/generated/known_layers_and_themes.json && ts-node scripts/generateLayerOverview.ts --no-fail", "generate:licenses": "ts-node scripts/generateLicenseInfo.ts --no-fail", "generate:report": "cd Docs/Tools && ./compileStats.sh && git commit . -m 'New statistics ands graphs' && git push", @@ -65,11 +65,9 @@ "escape-html": "^1.0.3", "i18next-client": "^1.11.4", "jquery": "^3.6.0", - "jspdf": "^2.3.1", "latlon2country": "^1.1.3", "leaflet": "^1.7.1", "leaflet-providers": "^1.10.2", - "leaflet-simple-map-screenshoter": "^0.4.4", "leaflet.markercluster": "^1.4.1", "libphonenumber": "0.0.10", "libphonenumber-js": "^1.7.55", @@ -83,6 +81,7 @@ "postcss": "^7.0.36", "prompt-sync": "^4.2.0", "sharp": "^0.27.0", + "tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.0.2", "tslint": "^6.1.3" }, "devDependencies": { @@ -92,7 +91,6 @@ "fs": "0.0.1-security", "marked": "^2.0.0", "read-file": "^0.2.0", - "tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.4", "ts-node": "^9.0.0", "ts-node-dev": "^1.0.0-pre.63", "tslint-no-circular-imports": "^0.7.0", diff --git a/preferences.ts b/preferences.ts index a7ae07dedc..1c1773a143 100644 --- a/preferences.ts +++ b/preferences.ts @@ -12,7 +12,7 @@ import BaseUIElement from "./UI/BaseUIElement"; import Table from "./UI/Base/Table"; -const connection = new OsmConnection(false, false, new UIEventSource(undefined), ""); +const connection = new OsmConnection(false, new UIEventSource(undefined), ""); let rendered = false; diff --git a/scripts/generateCache.ts b/scripts/generateCache.ts index fb2303e713..c3d9fbcd90 100644 --- a/scripts/generateCache.ts +++ b/scripts/generateCache.ts @@ -1,7 +1,7 @@ /** * Generates a collection of geojson files based on an overpass query for a given theme */ -import {Utils} from "../Utils"; +import {TileRange, Utils} from "../Utils"; Utils.runningFromConsole = true import {Overpass} from "../Logic/Osm/Overpass"; @@ -17,8 +17,6 @@ import MetaTagging from "../Logic/MetaTagging"; import LayerConfig from "../Customizations/JSON/LayerConfig"; import {GeoOperations} from "../Logic/GeoOperations"; import {UIEventSource} from "../Logic/UIEventSource"; -import * as fs from "fs"; -import {TileRange} from "../Models/TileRange"; function createOverpassObject(theme: LayoutConfig) { @@ -141,7 +139,7 @@ async function downloadExtraData(theme: LayoutConfig)/* : any[] */ { return allFeatures; } -function postProcess(targetdir: string, r: TileRange, theme: LayoutConfig, extraFeatures: any[]) { +async function postProcess(targetdir: string, r: TileRange, theme: LayoutConfig, extraFeatures: any[]) { let processed = 0; const layerIndex = theme.LayerIndex(); for (let x = r.xstart; x <= r.xend; x++) { @@ -213,9 +211,8 @@ function postProcess(targetdir: string, r: TileRange, theme: LayoutConfig, extra } } -function splitPerLayer(targetdir: string, r: TileRange, theme: LayoutConfig) { +async function splitPerLayer(targetdir: string, r: TileRange, theme: LayoutConfig) { const z = r.zoomlevel; - const generated = {} // layer --> x --> y[] for (let x = r.xstart; x <= r.xend; x++) { for (let y = r.ystart; y <= r.yend; y++) { const file = readFileSync(geoJsonName(targetdir + ".unfiltered", x, y, z), "UTF8") @@ -230,8 +227,10 @@ function splitPerLayer(targetdir: string, r: TileRange, theme: LayoutConfig) { .filter(f => f._matching_layer_id === layer.id) .filter(f => { const isShown = layer.isShown.GetRenderValue(f.properties).txt - return isShown !== "no"; - + if (isShown === "no") { + return false; + } + return true; }) const new_path = geoJsonName(targetdir + "_" + layer.id, x, y, z); console.log(new_path, " has ", geojson.features.length, " features after filtering (dropped ", oldLength - geojson.features.length, ")") @@ -240,66 +239,18 @@ function splitPerLayer(targetdir: string, r: TileRange, theme: LayoutConfig) { continue; } writeFileSync(new_path, JSON.stringify(geojson, null, " ")) - - if (generated[layer.id] === undefined) { - generated[layer.id] = {} - } - if (generated[layer.id][x] === undefined) { - generated[layer.id][x] = [] - } - generated[layer.id][x].push(y) - } + + } } - - for (const layer of theme.layers) { - const id = layer.id - const loaded = generated[id] - if(loaded === undefined){ - console.log("No features loaded for layer ",id) - continue; - } - writeFileSync(targetdir + "_" + id + "_overview.json", JSON.stringify(loaded)) - } - } -async function createOverview(targetdir: string, r: TileRange, z: number, layername: string) { - const allFeatures = [] - for (let x = r.xstart; x <= r.xend; x++) { - for (let y = r.ystart; y <= r.yend; y++) { - const read_path = geoJsonName(targetdir + "_" + layername, x, y, z); - if (!fs.existsSync(read_path)) { - continue; - } - const features = JSON.parse(fs.readFileSync(read_path, "UTF-8")).features - const pointsOnly = features.map(f => { - - f.properties["_last_edit:timestamp"] = "1970-01-01" - - if (f.geometry.type === "Point") { - return f - } else { - return GeoOperations.centerpoint(f) - } - - }) - allFeatures.push(...pointsOnly) - } - } - - const geojson = { - "type": "FeatureCollection", - "features": allFeatures - } - writeFileSync(targetdir + "_" + layername + "_points.geojson", JSON.stringify(geojson, null, " ")) -} async function main(args: string[]) { if (args.length == 0) { - console.error("Expected arguments are: theme zoomlevel targetdirectory lat0 lon0 lat1 lon1 [--generate-point-overview layer-name]") + console.error("Expected arguments are: theme zoomlevel targetdirectory lat0 lon0 lat1 lon1") return; } const themeName = args[0] @@ -334,18 +285,8 @@ async function main(args: string[]) { } while (failed > 0) const extraFeatures = await downloadExtraData(theme); - postProcess(targetdir, tileRange, theme, extraFeatures) - splitPerLayer(targetdir, tileRange, theme) - - if (args[7] === "--generate-point-overview") { - const targetLayers = args[8].split(",") - for (const targetLayer of targetLayers) { - if (!theme.layers.some(l => l.id === targetLayer)) { - throw "Target layer " + targetLayer + " not found, did you mistype the name? Found layers are: " + theme.layers.map(l => l.id).join(",") - } - createOverview(targetdir, tileRange, zoomlevel, targetLayer) - } - } + await postProcess(targetdir, tileRange, theme, extraFeatures) + await splitPerLayer(targetdir, tileRange, theme) } diff --git a/scripts/generateLayerOverview.ts b/scripts/generateLayerOverview.ts index ba265a2025..785c858b35 100644 --- a/scripts/generateLayerOverview.ts +++ b/scripts/generateLayerOverview.ts @@ -4,6 +4,7 @@ import LayerConfig from "../Customizations/JSON/LayerConfig"; import * as licenses from "../assets/generated/license_info.json" import LayoutConfig from "../Customizations/JSON/LayoutConfig"; import {LayerConfigJson} from "../Customizations/JSON/LayerConfigJson"; +import {Translation} from "../UI/i18n/Translation"; import {LayoutConfigJson} from "../Customizations/JSON/LayoutConfigJson"; import AllKnownLayers from "../Customizations/AllKnownLayers"; @@ -76,6 +77,63 @@ class LayerOverviewUtils { return errorCount } + validateTranslationCompletenessOfObject(object: any, expectedLanguages: string[], context: string) { + const missingTranlations = [] + const translations: { tr: Translation, context: string }[] = []; + const queue: { object: any, context: string }[] = [{object: object, context: context}] + + while (queue.length > 0) { + const item = queue.pop(); + const o = item.object + for (const key in o) { + const v = o[key]; + if (v === undefined) { + continue; + } + if (v instanceof Translation || v?.translations !== undefined) { + translations.push({tr: v, context: item.context}); + } else if ( + ["string", "function", "boolean", "number"].indexOf(typeof (v)) < 0) { + queue.push({object: v, context: item.context + "." + key}) + } + } + } + + const missing = {} + const present = {} + for (const ln of expectedLanguages) { + missing[ln] = 0; + present[ln] = 0; + for (const translation of translations) { + if (translation.tr.translations["*"] !== undefined) { + continue; + } + const txt = translation.tr.translations[ln]; + const isMissing = txt === undefined || txt === "" || txt.toLowerCase().indexOf("todo") >= 0; + if (isMissing) { + missingTranlations.push(`${translation.context},${ln},${translation.tr.txt}`) + missing[ln]++ + } else { + present[ln]++; + } + } + } + + let message = `Translation completeness for ${context}` + let isComplete = true; + for (const ln of expectedLanguages) { + const amiss = missing[ln]; + const ok = present[ln]; + const total = amiss + ok; + message += ` ${ln}: ${ok}/${total}` + if (ok !== total) { + isComplete = false; + } + } + return missingTranlations + + } + main(args: string[]) { const lt = this.loadThemesAndLayers(); @@ -102,6 +160,7 @@ class LayerOverviewUtils { } let themeErrorCount = [] + let missingTranslations = [] for (const themeFile of themeFiles) { if (typeof themeFile.language === "string") { themeErrorCount.push("The theme " + themeFile.id + " has a string as language. Please use a list of strings") @@ -110,6 +169,10 @@ class LayerOverviewUtils { if (typeof layer === "string") { if (!knownLayerIds.has(layer)) { themeErrorCount.push(`Unknown layer id: ${layer} in theme ${themeFile.id}`) + } else { + const layerConfig = knownLayerIds.get(layer); + missingTranslations.push(...this.validateTranslationCompletenessOfObject(layerConfig, themeFile.language, "Layer " + layer)) + } } else if (layer.builtin !== undefined) { let names = layer.builtin; @@ -134,6 +197,7 @@ class LayerOverviewUtils { .filter(l => typeof l != "string") // We remove all the builtin layer references as they don't work with ts-node for some weird reason .filter(l => l.builtin === undefined) + missingTranslations.push(...this.validateTranslationCompletenessOfObject(themeFile, themeFile.language, "Theme " + themeFile.id)) try { const theme = new LayoutConfig(themeFile, true, "test") @@ -145,6 +209,11 @@ class LayerOverviewUtils { } } + if (missingTranslations.length > 0) { + console.log(missingTranslations.length, "missing translations") + writeFileSync("missing_translations.txt", missingTranslations.join("\n")) + } + if (layerErrorCount.length + themeErrorCount.length == 0) { console.log("All good!") diff --git a/test.ts b/test.ts index 21ca94b74b..eb29b9921d 100644 --- a/test.ts +++ b/test.ts @@ -7,11 +7,6 @@ import {UIEventSource} from "./Logic/UIEventSource"; import {Tag} from "./Logic/Tags/Tag"; import {QueryParameters} from "./Logic/Web/QueryParameters"; import {Translation} from "./UI/i18n/Translation"; -import LocationInput from "./UI/Input/LocationInput"; -import Loc from "./Models/Loc"; -import {VariableUiElement} from "./UI/Base/VariableUIElement"; -import LengthInput from "./UI/Input/LengthInput"; -import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers"; /*import ValidatedTextField from "./UI/Input/ValidatedTextField"; import Combine from "./UI/Base/Combine"; import {VariableUiElement} from "./UI/Base/VariableUIElement"; @@ -153,17 +148,19 @@ function TestMiniMap() { featureSource.ping() } //*/ - -const loc = new UIEventSource({ - zoom: 24, - lat: 51.21043, - lon: 3.21389 -}) -const li = new LengthInput( - AvailableBaseLayers.SelectBestLayerAccordingTo(loc, new UIEventSource("map","photo")), - loc -) - li.SetStyle("height: 30rem; background: aliceblue;") - .AttachTo("maindiv") - -new VariableUiElement(li.GetValue().map(v => JSON.stringify(v, null, " "))).AttachTo("extradiv") \ No newline at end of file +QueryParameters.GetQueryParameter("test", "true").setData("true") +State.state= new State(undefined) +const id = "node/5414688303" +State.state.allElements.addElementById(id, new UIEventSource({id: id})) +new Combine([ + new DeleteWizard(id, { + noDeleteOptions: [ + { + if:[ new Tag("access","private")], + then: new Translation({ + en: "Very private! Delete now or me send lawfull lawyer" + }) + } + ] + }), +]).AttachTo("maindiv") diff --git a/test/OsmConnection.spec.ts b/test/OsmConnection.spec.ts index 2253e56c38..ffcb4840ca 100644 --- a/test/OsmConnection.spec.ts +++ b/test/OsmConnection.spec.ts @@ -15,7 +15,7 @@ export default class OsmConnectionSpec extends T { super("OsmConnectionSpec-test", [ ["login on dev", () => { - const osmConn = new OsmConnection(false,false, + const osmConn = new OsmConnection(false, new UIEventSource(undefined), "Unit test", true, diff --git a/tslint.json b/tslint.json new file mode 100644 index 0000000000..6a204a045b --- /dev/null +++ b/tslint.json @@ -0,0 +1,10 @@ +{ + "defaultSeverity": "error", + "extends": [ + "tslint:recommended", + "tslint-no-circular-imports" + ], + "jsRules": {}, + "rules": {}, + "rulesDirectory": [] +} \ No newline at end of file