Feature: add button to download mangrove identity
This commit is contained in:
parent
e48571a80a
commit
02203dd05b
5 changed files with 87 additions and 56 deletions
|
@ -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])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
82
Utils.ts
82
Utils.ts
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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": [
|
||||||
|
|
|
@ -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",
|
||||||
|
|
Loading…
Reference in a new issue