Feature: add button to download mangrove identity

This commit is contained in:
Pieter Vander Vennet 2023-07-17 01:07:01 +02:00
parent e48571a80a
commit 02203dd05b
5 changed files with 87 additions and 56 deletions

View file

@ -24,6 +24,7 @@ export default class UserRelatedState {
public static readonly usersettingsConfig = UserRelatedState.initUserRelatedState() public static readonly usersettingsConfig = UserRelatedState.initUserRelatedState()
public static readonly availableUserSettingsIds: string[] = public static readonly availableUserSettingsIds: string[] =
UserRelatedState.usersettingsConfig?.tagRenderings?.map((tr) => tr.id) ?? [] UserRelatedState.usersettingsConfig?.tagRenderings?.map((tr) => tr.id) ?? []
public static readonly SHOW_TAGS_VALUES = ["always", "yes", "full"] as const
/** /**
The user credentials The user credentials
*/ */
@ -34,7 +35,6 @@ export default class UserRelatedState {
public readonly mangroveIdentity: MangroveIdentity public readonly mangroveIdentity: MangroveIdentity
public readonly installedUserThemes: Store<string[]> public readonly installedUserThemes: Store<string[]>
public readonly showAllQuestionsAtOnce: UIEventSource<boolean> public readonly showAllQuestionsAtOnce: UIEventSource<boolean>
public static readonly SHOW_TAGS_VALUES = ["always", "yes", "full"] as const
public readonly showTags: UIEventSource<"no" | undefined | "always" | "yes" | "full"> public readonly showTags: UIEventSource<"no" | undefined | "always" | "yes" | "full">
public readonly homeLocation: FeatureSource public readonly homeLocation: FeatureSource
public readonly language: UIEventSource<string> public readonly language: UIEventSource<string>
@ -111,13 +111,13 @@ export default class UserRelatedState {
public GetUnofficialTheme(id: string): public GetUnofficialTheme(id: string):
| { | {
id: string id: string
icon: string icon: string
title: any title: any
shortDescription: any shortDescription: any
definition?: any definition?: any
isOfficial: boolean isOfficial: boolean
} }
| undefined { | undefined {
console.log("GETTING UNOFFICIAL THEME") console.log("GETTING UNOFFICIAL THEME")
const pref = this.osmConnection.GetLongPreference("unofficial-theme-" + id) const pref = this.osmConnection.GetLongPreference("unofficial-theme-" + id)
@ -142,8 +142,8 @@ export default class UserRelatedState {
} catch (e) { } catch (e) {
console.warn( console.warn(
"Removing theme " + "Removing theme " +
id + id +
" as it could not be parsed from the preferences; the content is:", " as it could not be parsed from the preferences; the content is:",
str str
) )
pref.setData(null) pref.setData(null)
@ -178,6 +178,7 @@ export default class UserRelatedState {
) )
} }
} }
private InitInstalledUserThemes(): Store<string[]> { private InitInstalledUserThemes(): Store<string[]> {
const prefix = "mapcomplete-unofficial-theme-" const prefix = "mapcomplete-unofficial-theme-"
const postfix = "-combined-length" const postfix = "-combined-length"
@ -247,9 +248,23 @@ export default class UserRelatedState {
const osmConnection = this.osmConnection const osmConnection = this.osmConnection
osmConnection.preferencesHandler.preferences.addCallback((newPrefs) => { osmConnection.preferencesHandler.preferences.addCallback((newPrefs) => {
for (const k in newPrefs) { for (const k in newPrefs) {
amendedPrefs.data[k] = newPrefs[k] const v = newPrefs[k]
if (k.endsWith("-combined-length")) {
const l = Number(v)
const key = k.substring(0, k.length - "length".length)
let combined = ""
for (let i = 0; i < l; i++) {
combined += newPrefs[key + i]
}
amendedPrefs.data[key.substring(0, key.length - "-combined-".length)] = combined
} else {
amendedPrefs.data[k] = newPrefs[k]
}
} }
amendedPrefs.ping() amendedPrefs.ping()
console.log("Amended prefs are:", amendedPrefs.data)
}) })
const usersettingsConfig = UserRelatedState.usersettingsConfig const usersettingsConfig = UserRelatedState.usersettingsConfig
const translationMode = osmConnection.GetPreference("translation-mode") const translationMode = osmConnection.GetPreference("translation-mode")
@ -272,13 +287,13 @@ export default class UserRelatedState {
const zenLinks: { link: string; id: string }[] = Utils.NoNull([ const zenLinks: { link: string; id: string }[] = Utils.NoNull([
hasMissingTheme hasMissingTheme
? { ? {
id: "theme:" + layout.id, id: "theme:" + layout.id,
link: LinkToWeblate.hrefToWeblateZen( link: LinkToWeblate.hrefToWeblateZen(
language, language,
"themes", "themes",
layout.id layout.id
), ),
} }
: undefined, : undefined,
...missingLayers.map((id) => ({ ...missingLayers.map((id) => ({
id: "layer:" + id, id: "layer:" + id,
@ -350,7 +365,7 @@ export default class UserRelatedState {
// Language is managed seperately // Language is managed seperately
continue continue
} }
this.osmConnection.GetPreference(key, undefined, { prefix: "" }).setData(tags[key]) this.osmConnection.GetPreference(key, undefined, {prefix: ""}).setData(tags[key])
} }
}) })

View file

@ -2,7 +2,6 @@ import { UIEventSource } from "../Logic/UIEventSource"
import { Translation } from "./i18n/Translation" import { Translation } from "./i18n/Translation"
import Locale from "./i18n/Locale" import Locale from "./i18n/Locale"
import { FixedUiElement } from "./Base/FixedUiElement" import { FixedUiElement } from "./Base/FixedUiElement"
// import SpecialVisualizations from "./SpecialVisualizations"
import { Utils } from "../Utils" import { Utils } from "../Utils"
import { VariableUiElement } from "./Base/VariableUIElement" import { VariableUiElement } from "./Base/VariableUIElement"
import Combine from "./Base/Combine" import Combine from "./Base/Combine"

View file

@ -1,5 +1,5 @@
import colors from "./assets/colors.json" import colors from "./assets/colors.json"
import { HTMLElement } from "node-html-parser" import {HTMLElement} from "node-html-parser"
export class Utils { export class Utils {
/** /**
@ -437,6 +437,11 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
/** /**
* Given a piece of text, will replace any key occuring in 'tags' by the corresponding value * Given a piece of text, will replace any key occuring in 'tags' by the corresponding value
*
* Utils.SubstituteKeys("abc{def}ghi", {def: 'XYZ'}) // => "abcXYZghi"
* Utils.SubstituteKeys("abc{def}{def}ghi", {def: 'XYZ'}) // => "abcXYZXYZghi"
* Utils.SubstituteKeys("abc{def}ghi", {def: '{XYZ}'}) // => "abc{XYZ}ghi"
*
* @param txt * @param txt
* @param tags * @param tags
* @param useLang * @param useLang
@ -450,12 +455,16 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
if (txt === undefined) { if (txt === undefined) {
return undefined return undefined
} }
const regex = /.*{([^}]*)}.*/ const regex = /(.*?){([^}]*)}(.*)/
let match = txt.match(regex) let match = txt.match(regex)
if(!match){
return txt
}
let result = ""
while (match) { while (match) {
const key = match[1] const [_, normal, key, leftover] = match
let v = tags === undefined ? undefined : tags[key] let v = tags === undefined ? undefined : tags[key]
if (v !== undefined && v !== null) { if (v !== undefined && v !== null) {
if (v["toISOString"] != undefined) { if (v["toISOString"] != undefined) {
@ -490,11 +499,14 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
// v === undefined // v === undefined
v = "" v = ""
} }
txt = txt.replace("{" + key + "}", v)
match = txt.match(regex)
}
return txt result += normal + v
match = leftover.match(regex)
if(!match){
result += leftover
}
}
return result
} }
public static LoadCustomCss(location: string) { public static LoadCustomCss(location: string) {
@ -687,10 +699,10 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
if (Array.isArray(leaf)) { if (Array.isArray(leaf)) {
for (let i = 0; i < (<any[]>leaf).length; i++) { for (let i = 0; i < (<any[]>leaf).length; i++) {
const l = (<any[]>leaf)[i] const l = (<any[]>leaf)[i]
collectedList.push({ leaf: l, path: [...travelledPath, "" + i] }) collectedList.push({leaf: l, path: [...travelledPath, "" + i]})
} }
} else { } else {
collectedList.push({ leaf, path: travelledPath }) collectedList.push({leaf, path: travelledPath})
} }
return collectedList return collectedList
} }
@ -768,7 +780,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
}) })
} }
const cp = { ...json } const cp = {...json}
for (const key in json) { for (const key in json) {
cp[key] = Utils.WalkJson(json[key], f, isLeaf, [...path, key]) cp[key] = Utils.WalkJson(json[key], f, isLeaf, [...path, key])
} }
@ -898,11 +910,11 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
const xhr = new XMLHttpRequest() const xhr = new XMLHttpRequest()
xhr.onload = () => { xhr.onload = () => {
if (xhr.status == 200) { if (xhr.status == 200) {
resolve({ content: xhr.response }) resolve({content: xhr.response})
} else if (xhr.status === 302) { } else if (xhr.status === 302) {
resolve({ redirect: xhr.getResponseHeader("location") }) resolve({redirect: xhr.getResponseHeader("location")})
} else if (xhr.status === 509 || xhr.status === 429) { } else if (xhr.status === 509 || xhr.status === 429) {
resolve({ error: "rate limited", url, statuscode: xhr.status }) resolve({error: "rate limited", url, statuscode: xhr.status})
} else { } else {
resolve({ resolve({
error: "other error: " + xhr.statusText, error: "other error: " + xhr.statusText,
@ -972,10 +984,10 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
} }
const promise = const promise =
/*NO AWAIT as we work with the promise directly */ Utils.downloadJsonAdvanced( /*NO AWAIT as we work with the promise directly */ Utils.downloadJsonAdvanced(
url, url,
headers headers
) )
Utils._download_cache.set(url, { promise, timestamp: new Date().getTime() }) Utils._download_cache.set(url, {promise, timestamp: new Date().getTime()})
return await promise return await promise
} }
@ -994,11 +1006,11 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
const injected = Utils.injectedDownloads[url] const injected = Utils.injectedDownloads[url]
if (injected !== undefined) { if (injected !== undefined) {
console.log("Using injected resource for test for URL", url) console.log("Using injected resource for test for URL", url)
return new Promise((resolve, _) => resolve({ content: injected })) return new Promise((resolve, _) => resolve({content: injected}))
} }
const result = await Utils.downloadAdvanced( const result = await Utils.downloadAdvanced(
url, url,
Utils.Merge({ accept: "application/json" }, headers ?? {}) Utils.Merge({accept: "application/json"}, headers ?? {})
) )
if (result["error"] !== undefined) { if (result["error"] !== undefined) {
return <{ error: string; url: string; statuscode?: number }>result return <{ error: string; url: string; statuscode?: number }>result
@ -1006,12 +1018,12 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
const data = result["content"] const data = result["content"]
try { try {
if (typeof data === "string") { if (typeof data === "string") {
return { content: JSON.parse(data) } return {content: JSON.parse(data)}
} }
return { content: data } return {content: data}
} catch (e) { } catch (e) {
console.error("Could not parse ", data, "due to", e, "\n", e.stack) console.error("Could not parse ", data, "due to", e, "\n", e.stack)
return { error: "malformed", url } return {error: "malformed", url}
} }
} }
@ -1035,7 +1047,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
const element = document.createElement("a") const element = document.createElement("a")
let file let file
if (typeof contents === "string") { if (typeof contents === "string") {
file = new Blob([contents], { type: options?.mimetype ?? "text/plain" }) file = new Blob([contents], {type: options?.mimetype ?? "text/plain"})
} else { } else {
file = contents file = contents
} }
@ -1306,7 +1318,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
if (match == undefined) { if (match == undefined) {
return undefined return undefined
} }
return { r: Number(match[1]), g: Number(match[2]), b: Number(match[3]) } return {r: Number(match[1]), g: Number(match[2]), b: Number(match[3])}
} }
if (!hex.startsWith("#")) { if (!hex.startsWith("#")) {
@ -1366,7 +1378,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
if (inView) { if (inView) {
return return
} }
element.scrollIntoView({ behavior: "smooth", block: "nearest" }) element.scrollIntoView({behavior: "smooth", block: "nearest"})
} }
public static findParentWithScrolling(element: HTMLBaseElement): HTMLBaseElement { public static findParentWithScrolling(element: HTMLBaseElement): HTMLBaseElement {
@ -1443,13 +1455,6 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
return false return false
} }
private static colorDiff(
c0: { r: number; g: number; b: number },
c1: { r: number; g: number; b: number }
) {
return Math.abs(c0.r - c1.r) + Math.abs(c0.g - c1.g) + Math.abs(c0.b - c1.b)
}
/** /**
* *
* Utils.splitIntoSubstitutionParts("abc") // => [{message: "abc"}] * Utils.splitIntoSubstitutionParts("abc") // => [{message: "abc"}]
@ -1465,15 +1470,22 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
const postParts = prepart.split("}") const postParts = prepart.split("}")
if (postParts.length === 1) { if (postParts.length === 1) {
// This was a normal part // This was a normal part
spec.push({ message: postParts[0] }) spec.push({message: postParts[0]})
} else { } else {
const [subs, message] = postParts const [subs, message] = postParts
spec.push({ subs }) spec.push({subs})
if (message !== "") { if (message !== "") {
spec.push({ message }) spec.push({message})
} }
} }
} }
return spec return spec
} }
private static colorDiff(
c0: { r: number; g: number; b: number },
c1: { r: number; g: number; b: number }
) {
return Math.abs(c0.r - c1.r) + Math.abs(c0.g - c1.g) + Math.abs(c0.b - c1.b)
}
} }

View file

@ -221,6 +221,11 @@
} }
] ]
}, },
{"id": "mangrove-keys",
"render": {
"en": "<a href='data:application/json,{mangroveidentity}' download='mangrove_private_key_{_name}'>Download the private key for your Mangrove Account</a> <p>Anyone possessing this file can make reviews with your identity</p>"
}
},
{ {
"id": "translations-title", "id": "translations-title",
"label": [ "label": [

View file

@ -1,6 +1,6 @@
{ {
"name": "mapcomplete", "name": "mapcomplete",
"version": "0.30.9", "version": "0.31.0",
"repository": "https://github.com/pietervdvn/MapComplete", "repository": "https://github.com/pietervdvn/MapComplete",
"description": "A small website to edit OSM easily", "description": "A small website to edit OSM easily",
"bugs": "https://github.com/pietervdvn/MapComplete/issues", "bugs": "https://github.com/pietervdvn/MapComplete/issues",