Formatting

This commit is contained in:
Pieter Vander Vennet 2022-11-02 14:44:06 +01:00
parent d5d2c08706
commit 72ca67e3ab
34 changed files with 616 additions and 566 deletions

View file

@ -27,10 +27,10 @@ export default class UserDetails {
export class OsmConnection { export class OsmConnection {
public static readonly oauth_configs = { public static readonly oauth_configs = {
"osm": { osm: {
oauth_consumer_key: 'hivV7ec2o49Two8g9h8Is1VIiVOgxQ1iYexCbvem', oauth_consumer_key: "hivV7ec2o49Two8g9h8Is1VIiVOgxQ1iYexCbvem",
oauth_secret: 'wDBRTCem0vxD7txrg1y6p5r8nvmz8tAhET7zDASI', oauth_secret: "wDBRTCem0vxD7txrg1y6p5r8nvmz8tAhET7zDASI",
url: "https://www.openstreetmap.org" url: "https://www.openstreetmap.org",
// OAUTH 1.0 application // OAUTH 1.0 application
// https://www.openstreetmap.org/user/Pieter%20Vander%20Vennet/oauth_clients/7404 // https://www.openstreetmap.org/user/Pieter%20Vander%20Vennet/oauth_clients/7404
}, },
@ -335,43 +335,52 @@ export class OsmConnection {
}) })
} }
public async uploadGpxTrack(gpx: string, options: { public async uploadGpxTrack(
description: string, gpx: string,
visibility: "private" | "public" | "trackable" | "identifiable", options: {
filename?: string description: string
/** visibility: "private" | "public" | "trackable" | "identifiable"
* Some words to give some properties; filename?: string
* /**
* Note: these are called 'tags' on the wiki, but I opted to name them 'labels' instead as they aren't "key=value" tags, but just words. * Some words to give some properties;
*/ *
labels: string[] * Note: these are called 'tags' on the wiki, but I opted to name them 'labels' instead as they aren't "key=value" tags, but just words.
}): Promise<{ id: number }> { */
labels: string[]
}
): Promise<{ id: number }> {
if (this._dryRun.data) { if (this._dryRun.data) {
console.warn("Dryrun enabled - not actually uploading GPX ", gpx) console.warn("Dryrun enabled - not actually uploading GPX ", gpx)
return new Promise<{ id: number }>((ok, error) => { return new Promise<{ id: number }>((ok, error) => {
window.setTimeout(() => ok({id: Math.floor(Math.random() * 1000)}), Math.random() * 5000) window.setTimeout(
}); () => ok({ id: Math.floor(Math.random() * 1000) }),
Math.random() * 5000
)
})
} }
const contents = { const contents = {
"file": gpx, file: gpx,
"description": options.description ?? "", description: options.description ?? "",
"tags": options.labels?.join(",") ?? "", tags: options.labels?.join(",") ?? "",
"visibility": options.visibility visibility: options.visibility,
} }
const extras = { const extras = {
"file": "; filename=\""+(options.filename ?? ("gpx_track_mapcomplete_"+(new Date().toISOString())))+"\"\r\nContent-Type: application/gpx+xml" file:
'; filename="' +
(options.filename ?? "gpx_track_mapcomplete_" + new Date().toISOString()) +
'"\r\nContent-Type: application/gpx+xml',
} }
const auth = this.auth; const auth = this.auth
const boundary ="987654" const boundary = "987654"
let body = "" let body = ""
for (const key in contents) { for (const key in contents) {
body += "--" + boundary + "\r\n" body += "--" + boundary + "\r\n"
body += "Content-Disposition: form-data; name=\"" + key + "\"" body += 'Content-Disposition: form-data; name="' + key + '"'
if(extras[key] !== undefined){ if (extras[key] !== undefined) {
body += extras[key] body += extras[key]
} }
body += "\r\n\r\n" body += "\r\n\r\n"
@ -379,34 +388,31 @@ export class OsmConnection {
} }
body += "--" + boundary + "--\r\n" body += "--" + boundary + "--\r\n"
return new Promise((ok, error) => { return new Promise((ok, error) => {
auth.xhr({ auth.xhr(
method: 'POST', {
path: `/api/0.6/gpx/create`, method: "POST",
options: { path: `/api/0.6/gpx/create`,
header: options: {
{ header: {
"Content-Type": "multipart/form-data; boundary=" + boundary, "Content-Type": "multipart/form-data; boundary=" + boundary,
"Content-Length": body.length "Content-Length": body.length,
} },
},
content: body,
}, },
content: body function (err, response: string) {
console.log("RESPONSE IS", response)
}, function ( if (err !== null) {
err, error(err)
response: string) { } else {
console.log("RESPONSE IS", response) const parsed = JSON.parse(response)
if (err !== null) { console.log("Uploaded GPX track", parsed)
error(err) ok({ id: parsed })
} else { }
const parsed = JSON.parse(response)
console.log("Uploaded GPX track", parsed)
ok({id: parsed})
} }
}) )
}) })
} }
public addCommentToNote(id: number | string, text: string): Promise<void> { public addCommentToNote(id: number | string, text: string): Promise<void> {

View file

@ -623,7 +623,9 @@ export class RewriteSpecial extends DesugaringStep<TagRenderingConfigJson> {
} }
const param = special[arg.name] const param = special[arg.name]
if (param === undefined) { if (param === undefined) {
errors.push(`At ${context}: Obligated parameter '${arg.name}' in special rendering of type ${vis.funcName} not found.\n${arg.doc}`) errors.push(
`At ${context}: Obligated parameter '${arg.name}' in special rendering of type ${vis.funcName} not found.\n${arg.doc}`
)
} }
} }

View file

@ -1,19 +1,19 @@
import {DesugaringStep, Each, Fuse, On} from "./Conversion" import { DesugaringStep, Each, Fuse, On } from "./Conversion"
import {LayerConfigJson} from "../Json/LayerConfigJson" import { LayerConfigJson } from "../Json/LayerConfigJson"
import LayerConfig from "../LayerConfig" import LayerConfig from "../LayerConfig"
import {Utils} from "../../../Utils" import { Utils } from "../../../Utils"
import Constants from "../../Constants" import Constants from "../../Constants"
import {Translation} from "../../../UI/i18n/Translation" import { Translation } from "../../../UI/i18n/Translation"
import {LayoutConfigJson} from "../Json/LayoutConfigJson" import { LayoutConfigJson } from "../Json/LayoutConfigJson"
import LayoutConfig from "../LayoutConfig" import LayoutConfig from "../LayoutConfig"
import {TagRenderingConfigJson} from "../Json/TagRenderingConfigJson" import { TagRenderingConfigJson } from "../Json/TagRenderingConfigJson"
import {TagUtils} from "../../../Logic/Tags/TagUtils" import { TagUtils } from "../../../Logic/Tags/TagUtils"
import {ExtractImages} from "./FixImages" import { ExtractImages } from "./FixImages"
import ScriptUtils from "../../../scripts/ScriptUtils" import ScriptUtils from "../../../scripts/ScriptUtils"
import {And} from "../../../Logic/Tags/And" import { And } from "../../../Logic/Tags/And"
import Translations from "../../../UI/i18n/Translations" import Translations from "../../../UI/i18n/Translations"
import Svg from "../../../Svg" import Svg from "../../../Svg"
import {QuestionableTagRenderingConfigJson} from "../Json/QuestionableTagRenderingConfigJson" import { QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson"
import FilterConfigJson from "../Json/FilterConfigJson" import FilterConfigJson from "../Json/FilterConfigJson"
import DeleteConfig from "../DeleteConfig" import DeleteConfig from "../DeleteConfig"
@ -619,20 +619,31 @@ export class DetectMappingsWithImages extends DesugaringStep<TagRenderingConfigJ
class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> { class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> {
constructor() { constructor() {
super("Miscellanious checks on the tagrendering", ["special"], "MiscTagREnderingChecksRew"); super("Miscellanious checks on the tagrendering", ["special"], "MiscTagREnderingChecksRew")
} }
convert(json: TagRenderingConfigJson, context: string): { result: TagRenderingConfigJson; errors?: string[]; warnings?: string[]; information?: string[] } { convert(
const errors = [] json: TagRenderingConfigJson,
if(json["special"] !== undefined){ context: string
errors.push("At "+context+": detected `special` on the top level. Did you mean `{\"render\":{ \"special\": ... }}`") ): {
result: TagRenderingConfigJson
errors?: string[]
warnings?: string[]
information?: string[]
} {
const errors = []
if (json["special"] !== undefined) {
errors.push(
"At " +
context +
': detected `special` on the top level. Did you mean `{"render":{ "special": ... }}`'
)
} }
return { return {
result: json, result: json,
errors errors,
}; }
} }
} }
export class ValidateTagRenderings extends Fuse<TagRenderingConfigJson> { export class ValidateTagRenderings extends Fuse<TagRenderingConfigJson> {

View file

@ -2,7 +2,7 @@ import { SubstitutedTranslation } from "../../UI/SubstitutedTranslation"
import TagRenderingConfig from "./TagRenderingConfig" import TagRenderingConfig from "./TagRenderingConfig"
import { ExtraFuncParams, ExtraFunctions } from "../../Logic/ExtraFunctions" import { ExtraFuncParams, ExtraFunctions } from "../../Logic/ExtraFunctions"
import LayerConfig from "./LayerConfig" import LayerConfig from "./LayerConfig"
import {SpecialVisualization} from "../../UI/SpecialVisualization"; import { SpecialVisualization } from "../../UI/SpecialVisualization"
export default class DependencyCalculator { export default class DependencyCalculator {
public static GetTagRenderingDependencies(tr: TagRenderingConfig): string[] { public static GetTagRenderingDependencies(tr: TagRenderingConfig): string[] {

View file

@ -68,7 +68,7 @@ export default class FilterConfig {
for (const field of fields) { for (const field of fields) {
for (let ln in question.translations) { for (let ln in question.translations) {
const txt = question.translations[ln] const txt = question.translations[ln]
if(ln.startsWith("_")){ if (ln.startsWith("_")) {
continue continue
} }
if (txt.indexOf("{" + field.name + "}") < 0) { if (txt.indexOf("{" + field.name + "}") < 0) {

View file

@ -239,7 +239,7 @@ export default class TagRenderingConfig {
throw `${context}: Detected a freeform key without rendering... Key: ${this.freeform.key} in ${context}` throw `${context}: Detected a freeform key without rendering... Key: ${this.freeform.key} in ${context}`
} }
for (const ln in this.render.translations) { for (const ln in this.render.translations) {
if(ln.startsWith("_")){ if (ln.startsWith("_")) {
continue continue
} }
const txt: string = this.render.translations[ln] const txt: string = this.render.translations[ln]

View file

@ -73,11 +73,13 @@ export class SubtleButton extends UIElement {
} }
}) })
const loading = new Lazy(() => new Loading(loadingText)) const loading = new Lazy(() => new Loading(loadingText))
return new VariableUiElement(state.map(st => { return new VariableUiElement(
if(st === "idle"){ state.map((st) => {
return button if (st === "idle") {
} return button
return loading }
})) return loading
})
)
} }
} }

View file

@ -1,20 +1,20 @@
import Combine from "../Base/Combine" import Combine from "../Base/Combine"
import {FlowPanelFactory, FlowStep} from "../ImportFlow/FlowStep" import { FlowPanelFactory, FlowStep } from "../ImportFlow/FlowStep"
import {ImmutableStore, Store, UIEventSource} from "../../Logic/UIEventSource" import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource"
import {InputElement} from "../Input/InputElement" import { InputElement } from "../Input/InputElement"
import {SvgToPdf, SvgToPdfOptions} from "../../Utils/svgToPdf" import { SvgToPdf, SvgToPdfOptions } from "../../Utils/svgToPdf"
import {FixedInputElement} from "../Input/FixedInputElement" import { FixedInputElement } from "../Input/FixedInputElement"
import {FixedUiElement} from "../Base/FixedUiElement" import { FixedUiElement } from "../Base/FixedUiElement"
import FileSelectorButton from "../Input/FileSelectorButton" import FileSelectorButton from "../Input/FileSelectorButton"
import InputElementMap from "../Input/InputElementMap" import InputElementMap from "../Input/InputElementMap"
import {RadioButton} from "../Input/RadioButton" import { RadioButton } from "../Input/RadioButton"
import {Utils} from "../../Utils" import { Utils } from "../../Utils"
import {VariableUiElement} from "../Base/VariableUIElement" import { VariableUiElement } from "../Base/VariableUIElement"
import Loading from "../Base/Loading" import Loading from "../Base/Loading"
import BaseUIElement from "../BaseUIElement" import BaseUIElement from "../BaseUIElement"
import Img from "../Base/Img" import Img from "../Base/Img"
import Title from "../Base/Title" import Title from "../Base/Title"
import {CheckBox} from "../Input/Checkboxes" import { CheckBox } from "../Input/Checkboxes"
import Minimap from "../Base/Minimap" import Minimap from "../Base/Minimap"
import SearchAndGo from "./SearchAndGo" import SearchAndGo from "./SearchAndGo"
import Toggle from "../Input/Toggle" import Toggle from "../Input/Toggle"
@ -25,7 +25,7 @@ import Toggleable from "../Base/Toggleable"
import Lazy from "../Base/Lazy" import Lazy from "../Base/Lazy"
import LinkToWeblate from "../Base/LinkToWeblate" import LinkToWeblate from "../Base/LinkToWeblate"
import Link from "../Base/Link" import Link from "../Base/Link"
import {AllLanguagesSelector} from "../Popup/AllLanguagesSelector"; import { AllLanguagesSelector } from "../Popup/AllLanguagesSelector"
class SelectTemplate extends Combine implements FlowStep<{ title: string; pages: string[] }> { class SelectTemplate extends Combine implements FlowStep<{ title: string; pages: string[] }> {
readonly IsValid: Store<boolean> readonly IsValid: Store<boolean>
@ -201,7 +201,7 @@ class PreparePdf extends Combine implements FlowStep<{ svgToPdf: SvgToPdf; langu
constructor(title: string, pages: string[], options: SvgToPdfOptions) { constructor(title: string, pages: string[], options: SvgToPdfOptions) {
const svgToPdf = new SvgToPdf(title, pages, options) const svgToPdf = new SvgToPdf(title, pages, options)
const languageSelector = new AllLanguagesSelector( ) const languageSelector = new AllLanguagesSelector()
const isPrepared = UIEventSource.FromPromiseWithErr(svgToPdf.Prepare()) const isPrepared = UIEventSource.FromPromiseWithErr(svgToPdf.Prepare())
super([ super([

View file

@ -1,25 +1,23 @@
import Toggle from "../Input/Toggle"; import Toggle from "../Input/Toggle"
import {RadioButton} from "../Input/RadioButton"; import { RadioButton } from "../Input/RadioButton"
import {FixedInputElement} from "../Input/FixedInputElement"; import { FixedInputElement } from "../Input/FixedInputElement"
import Combine from "../Base/Combine"; import Combine from "../Base/Combine"
import Translations from "../i18n/Translations"; import Translations from "../i18n/Translations"
import {TextField} from "../Input/TextField"; import { TextField } from "../Input/TextField"
import {UIEventSource} from "../../Logic/UIEventSource"; import { UIEventSource } from "../../Logic/UIEventSource"
import Title from "../Base/Title"; import Title from "../Base/Title"
import {SubtleButton} from "../Base/SubtleButton"; import { SubtleButton } from "../Base/SubtleButton"
import Svg from "../../Svg"; import Svg from "../../Svg"
import {OsmConnection} from "../../Logic/Osm/OsmConnection"; import { OsmConnection } from "../../Logic/Osm/OsmConnection"
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
import {Translation} from "../i18n/Translation"; import { Translation } from "../i18n/Translation"
export default class UploadTraceToOsmUI extends Toggle { export default class UploadTraceToOsmUI extends Toggle {
private static createDefault(s: string, defaultValue: string) {
private static createDefault(s: string, defaultValue: string){ if (defaultValue.length < 1) {
if(defaultValue.length < 1){
throw "Default value should have some characters" throw "Default value should have some characters"
} }
if(s === undefined || s === null || s === ""){ if (s === undefined || s === null || s === "") {
return defaultValue return defaultValue
} }
return s return s
@ -28,43 +26,50 @@ export default class UploadTraceToOsmUI extends Toggle {
constructor( constructor(
trace: (title: string) => string, trace: (title: string) => string,
state: { state: {
layoutToUse: LayoutConfig; layoutToUse: LayoutConfig
osmConnection: OsmConnection osmConnection: OsmConnection
}, options?: { },
options?: {
whenUploaded?: () => void | Promise<void> whenUploaded?: () => void | Promise<void>
}) { }
) {
const t = Translations.t.general.uploadGpx const t = Translations.t.general.uploadGpx
const uploadFinished = new UIEventSource(false) const uploadFinished = new UIEventSource(false)
const traceVisibilities: { const traceVisibilities: {
key: "private" | "public", key: "private" | "public"
name: Translation, name: Translation
docs: Translation docs: Translation
}[] = [ }[] = [
{ {
key: "private", key: "private",
...t.modes.private ...t.modes.private,
}, },
{ {
key: "public", key: "public",
...t.modes.public ...t.modes.public,
} },
] ]
const dropdown = new RadioButton<"private" | "public">( const dropdown = new RadioButton<"private" | "public">(
traceVisibilities.map(tv => new FixedInputElement<"private" | "public">( traceVisibilities.map(
new Combine([Translations.W( (tv) =>
tv.name new FixedInputElement<"private" | "public">(
).SetClass("font-bold"), tv.docs]).SetClass("flex flex-col") new Combine([
, tv.key)), Translations.W(tv.name).SetClass("font-bold"),
tv.docs,
]).SetClass("flex flex-col"),
tv.key
)
),
{ {
value: <any>state?.osmConnection?.GetPreference("gps.trace.visibility") value: <any>state?.osmConnection?.GetPreference("gps.trace.visibility"),
} }
) )
const description = new TextField({ const description = new TextField({
placeholder: t.meta.descriptionPlaceHolder placeholder: t.meta.descriptionPlaceHolder,
}) })
const title = new TextField({ const title = new TextField({
placeholder: t.meta.titlePlaceholder placeholder: t.meta.titlePlaceholder,
}) })
const clicked = new UIEventSource<boolean>(false) const clicked = new UIEventSource<boolean>(false)
@ -81,38 +86,48 @@ export default class UploadTraceToOsmUI extends Toggle {
t.meta.descriptionIntro, t.meta.descriptionIntro,
description, description,
new Combine([ new Combine([
new SubtleButton(Svg.close_svg(), Translations.t.general.cancel).onClick(() => { new SubtleButton(Svg.close_svg(), Translations.t.general.cancel)
clicked.setData(false) .onClick(() => {
}).SetClass(""), clicked.setData(false)
new SubtleButton(Svg.upload_svg(), t.confirm).OnClickWithLoading(t.uploading, async () => {
const titleStr = UploadTraceToOsmUI.createDefault(title.GetValue().data, "Track with mapcomplete")
const descriptionStr = UploadTraceToOsmUI.createDefault(description.GetValue().data, "Track created with MapComplete with theme "+state?.layoutToUse?.id)
await state?.osmConnection?.uploadGpxTrack(trace(title.GetValue().data), {
visibility: dropdown.GetValue().data,
description: descriptionStr,
filename: titleStr +".gpx",
labels: ["MapComplete", state?.layoutToUse?.id]
}) })
.SetClass(""),
new SubtleButton(Svg.upload_svg(), t.confirm).OnClickWithLoading(
t.uploading,
async () => {
const titleStr = UploadTraceToOsmUI.createDefault(
title.GetValue().data,
"Track with mapcomplete"
)
const descriptionStr = UploadTraceToOsmUI.createDefault(
description.GetValue().data,
"Track created with MapComplete with theme " + state?.layoutToUse?.id
)
await state?.osmConnection?.uploadGpxTrack(trace(title.GetValue().data), {
visibility: dropdown.GetValue().data,
description: descriptionStr,
filename: titleStr + ".gpx",
labels: ["MapComplete", state?.layoutToUse?.id],
})
if (options?.whenUploaded !== undefined) { if (options?.whenUploaded !== undefined) {
await options.whenUploaded() await options.whenUploaded()
}
uploadFinished.setData(true)
} }
uploadFinished.setData(true) ),
]).SetClass("flex flex-wrap flex-wrap-reverse justify-between items-stretch"),
})
]).SetClass("flex flex-wrap flex-wrap-reverse justify-between items-stretch")
]).SetClass("flex flex-col p-4 rounded border-2 m-2 border-subtle") ]).SetClass("flex flex-col p-4 rounded border-2 m-2 border-subtle")
super( super(
new Combine([Svg.confirm_svg().SetClass("w-12 h-12 mr-2"), new Combine([Svg.confirm_svg().SetClass("w-12 h-12 mr-2"), t.uploadFinished]).SetClass(
t.uploadFinished]) "flex p-2 rounded-xl border-2 subtle-border items-center"
.SetClass("flex p-2 rounded-xl border-2 subtle-border items-center"), ),
new Toggle( new Toggle(
confirmPanel, confirmPanel,
new SubtleButton(Svg.upload_svg(), t.title) new SubtleButton(Svg.upload_svg(), t.title).onClick(() => clicked.setData(true)),
.onClick(() => clicked.setData(true)),
clicked clicked
), uploadFinished) ),
uploadFinished
)
} }
} }

View file

@ -13,8 +13,8 @@ export class RadioButton<T> extends InputElement<T> {
constructor( constructor(
elements: InputElement<T>[], elements: InputElement<T>[],
options?: { options?: {
selectFirstAsDefault?: true | boolean, selectFirstAsDefault?: true | boolean
dontStyle?: boolean, dontStyle?: boolean
value?: UIEventSource<T> value?: UIEventSource<T>
} }
) { ) {

View file

@ -1,13 +1,13 @@
import {UIElement} from "../UIElement" import { UIElement } from "../UIElement"
import {InputElement} from "./InputElement" import { InputElement } from "./InputElement"
import BaseUIElement from "../BaseUIElement" import BaseUIElement from "../BaseUIElement"
import {Store, UIEventSource} from "../../Logic/UIEventSource" import { Store, UIEventSource } from "../../Logic/UIEventSource"
import Translations from "../i18n/Translations" import Translations from "../i18n/Translations"
import Locale from "../i18n/Locale" import Locale from "../i18n/Locale"
import Combine from "../Base/Combine" import Combine from "../Base/Combine"
import {TextField} from "./TextField" import { TextField } from "./TextField"
import Svg from "../../Svg" import Svg from "../../Svg"
import {VariableUiElement} from "../Base/VariableUIElement" import { VariableUiElement } from "../Base/VariableUIElement"
/** /**
* A single 'pill' which can hide itself if the search criteria is not met * A single 'pill' which can hide itself if the search criteria is not met
@ -28,7 +28,7 @@ class SelfHidingToggle extends UIElement implements InputElement<boolean> {
searchTerms?: Record<string, string[]> searchTerms?: Record<string, string[]>
selected?: UIEventSource<boolean> selected?: UIEventSource<boolean>
forceSelected?: UIEventSource<boolean> forceSelected?: UIEventSource<boolean>
squared?: boolean, squared?: boolean
/* Hide, if not selected*/ /* Hide, if not selected*/
hide?: Store<boolean> hide?: Store<boolean>
} }
@ -53,7 +53,7 @@ class SelfHidingToggle extends UIElement implements InputElement<boolean> {
const selected = (this._selected = options?.selected ?? new UIEventSource<boolean>(false)) const selected = (this._selected = options?.selected ?? new UIEventSource<boolean>(false))
const forceSelected = (this.forceSelected = const forceSelected = (this.forceSelected =
options?.forceSelected ?? new UIEventSource<boolean>(false)) options?.forceSelected ?? new UIEventSource<boolean>(false))
this.matchesSearchCriteria = search.map(s => { this.matchesSearchCriteria = search.map((s) => {
if (s === undefined || s.length === 0) { if (s === undefined || s.length === 0) {
return true return true
} }
@ -152,13 +152,13 @@ export class SearchablePillsSelector<T> extends Combine implements InputElement<
show: BaseUIElement show: BaseUIElement
value: T value: T
mainTerm: Record<string, string> mainTerm: Record<string, string>
searchTerms?: Record<string, string[]>, searchTerms?: Record<string, string[]>
/* If there are more then 200 elements, should this element still be shown? */ /* If there are more then 200 elements, should this element still be shown? */
hasPriority?: Store<boolean> hasPriority?: Store<boolean>
}[], }[],
options?: { options?: {
/* /*
* If one single value can be selected (like a radio button) or if many values can be selected (like checkboxes) * If one single value can be selected (like a radio button) or if many values can be selected (like checkboxes)
*/ */
mode?: "select-one" | "select-many" mode?: "select-one" | "select-many"
/** /**
@ -189,14 +189,14 @@ export class SearchablePillsSelector<T> extends Combine implements InputElement<
hideSearchBar?: false | boolean hideSearchBar?: false | boolean
} }
) { ) {
const search = new TextField({value: options?.searchValue}) const search = new TextField({ value: options?.searchValue })
const searchBar = options?.hideSearchBar const searchBar = options?.hideSearchBar
? undefined ? undefined
: new Combine([ : new Combine([
Svg.search_svg().SetClass("w-8 normal-background"), Svg.search_svg().SetClass("w-8 normal-background"),
search.SetClass("w-full"), search.SetClass("w-full"),
]).SetClass("flex items-center border-2 border-black m-2") ]).SetClass("flex items-center border-2 border-black m-2")
const searchValue = search.GetValue().map((s) => s?.trim()?.toLowerCase()) const searchValue = search.GetValue().map((s) => s?.trim()?.toLowerCase())
const selectedElements = options?.selectedElements ?? new UIEventSource<T[]>([]) const selectedElements = options?.selectedElements ?? new UIEventSource<T[]>([])
@ -238,7 +238,10 @@ export class SearchablePillsSelector<T> extends Combine implements InputElement<
searchTerms: v.searchTerms, searchTerms: v.searchTerms,
selected: vIsSelected, selected: vIsSelected,
squared: mode === "select-many", squared: mode === "select-many",
hide: v.hasPriority === undefined ? forceHide : forceHide.map(fh => fh && !v.hasPriority?.data, [v.hasPriority]) hide:
v.hasPriority === undefined
? forceHide
: forceHide.map((fh) => fh && !v.hasPriority?.data, [v.hasPriority]),
}) })
return { return {
@ -249,16 +252,17 @@ export class SearchablePillsSelector<T> extends Combine implements InputElement<
// The total number of elements that would be displayed based on the search criteria alone // The total number of elements that would be displayed based on the search criteria alone
let totalShown: Store<number> let totalShown: Store<number>
totalShown = searchValue.map((_) => mappedValues.filter((mv) => mv.show.matchesSearchCriteria.data).length) totalShown = searchValue.map(
(_) => mappedValues.filter((mv) => mv.show.matchesSearchCriteria.data).length
)
const tooMuchElementsCutoff = 40 const tooMuchElementsCutoff = 40
totalShown.addCallbackAndRunD(shown => forceHide.setData(tooMuchElementsCutoff < shown)) totalShown.addCallbackAndRunD((shown) => forceHide.setData(tooMuchElementsCutoff < shown))
super([ super([
searchBar, searchBar,
new VariableUiElement( new VariableUiElement(
Locale.language.map( Locale.language.map(
(lng) => { (lng) => {
if ( if (
options?.onNoSearchMade !== undefined && options?.onNoSearchMade !== undefined &&
(searchValue.data === undefined || searchValue.data.length === 0) (searchValue.data === undefined || searchValue.data.length === 0)
@ -275,7 +279,10 @@ export class SearchablePillsSelector<T> extends Combine implements InputElement<
.SetClass(options?.searchAreaClass ?? "") .SetClass(options?.searchAreaClass ?? "")
if (totalShown.data >= tooMuchElementsCutoff) { if (totalShown.data >= tooMuchElementsCutoff) {
pills = new Combine([options?.onManyElements ?? Translations.t.general.useSearch, pills]) pills = new Combine([
options?.onManyElements ?? Translations.t.general.useSearch,
pills,
])
} }
return pills return pills
}, },

View file

@ -1,14 +1,14 @@
import Translations from "../i18n/Translations"; import Translations from "../i18n/Translations"
import {TextField} from "../Input/TextField"; import { TextField } from "../Input/TextField"
import {SubtleButton} from "../Base/SubtleButton"; import { SubtleButton } from "../Base/SubtleButton"
import Svg from "../../Svg"; import Svg from "../../Svg"
import NoteCommentElement from "./NoteCommentElement"; import NoteCommentElement from "./NoteCommentElement"
import {VariableUiElement} from "../Base/VariableUIElement"; import { VariableUiElement } from "../Base/VariableUIElement"
import Toggle from "../Input/Toggle"; import Toggle from "../Input/Toggle"
import {LoginToggle} from "./LoginButton"; import { LoginToggle } from "./LoginButton"
import Combine from "../Base/Combine"; import Combine from "../Base/Combine"
import Title from "../Base/Title"; import Title from "../Base/Title"
import {SpecialVisualization} from "../SpecialVisualization"; import { SpecialVisualization } from "../SpecialVisualization"
export class AddNoteCommentViz implements SpecialVisualization { export class AddNoteCommentViz implements SpecialVisualization {
funcName = "add_note_comment" funcName = "add_note_comment"
@ -103,12 +103,8 @@ export class AddNoteCommentViz implements SpecialVisualization {
stateButtons.SetClass("sm:mr-2"), stateButtons.SetClass("sm:mr-2"),
new Toggle( new Toggle(
addCommentButton, addCommentButton,
new Combine([t.typeText]).SetClass( new Combine([t.typeText]).SetClass("flex items-center h-full subtle"),
"flex items-center h-full subtle" textField.GetValue().map((t) => t !== undefined && t.length >= 1)
),
textField
.GetValue()
.map((t) => t !== undefined && t.length >= 1)
).SetClass("sm:mr-2"), ).SetClass("sm:mr-2"),
]).SetClass("sm:flex sm:justify-between sm:items-stretch"), ]).SetClass("sm:flex sm:justify-between sm:items-stretch"),
]).SetClass("border-2 border-black rounded-xl p-4 block"), ]).SetClass("border-2 border-black rounded-xl p-4 block"),

View file

@ -1,45 +1,46 @@
import {SearchablePillsSelector} from "../Input/SearchableMappingsSelector"; import { SearchablePillsSelector } from "../Input/SearchableMappingsSelector"
import {Store} from "../../Logic/UIEventSource"; import { Store } from "../../Logic/UIEventSource"
import BaseUIElement from "../BaseUIElement"; import BaseUIElement from "../BaseUIElement"
import * as all_languages from "../../assets/language_translations.json"; import * as all_languages from "../../assets/language_translations.json"
import {Translation} from "../i18n/Translation"; import { Translation } from "../i18n/Translation"
export class AllLanguagesSelector extends SearchablePillsSelector <string> {
export class AllLanguagesSelector extends SearchablePillsSelector<string> {
constructor(options?: { constructor(options?: {
mode?: "select-many" | "select-one" mode?: "select-many" | "select-one"
currentCountry?: Store<string>, currentCountry?: Store<string>
supportedLanguages?: Record<string, string> & { _meta?: { countries?: string[] } } supportedLanguages?: Record<string, string> & { _meta?: { countries?: string[] } }
}) { }) {
const possibleValues: { const possibleValues: {
show: BaseUIElement show: BaseUIElement
value: string value: string
mainTerm: Record<string, string> mainTerm: Record<string, string>
searchTerms?: Record<string, string[]>, searchTerms?: Record<string, string[]>
hasPriority?: Store<boolean> hasPriority?: Store<boolean>
}[] = [] }[] = []
const langs = options?.supportedLanguages ?? all_languages["default"] ?? all_languages const langs = options?.supportedLanguages ?? all_languages["default"] ?? all_languages
for (const ln in langs) { for (const ln in langs) {
let languageInfo: Record<string, string> & { _meta?: { countries: string[] } } = all_languages[ln] let languageInfo: Record<string, string> & { _meta?: { countries: string[] } } =
const countries = languageInfo._meta?.countries?.map(c => c.toLowerCase()) all_languages[ln]
languageInfo = {...languageInfo} const countries = languageInfo._meta?.countries?.map((c) => c.toLowerCase())
languageInfo = { ...languageInfo }
delete languageInfo._meta delete languageInfo._meta
const term = { const term = {
show: new Translation(languageInfo), show: new Translation(languageInfo),
value: ln, value: ln,
mainTerm: languageInfo, mainTerm: languageInfo,
searchTerms: {"*": [ln]}, searchTerms: { "*": [ln] },
hasPriority: countries === undefined ? undefined : options?.currentCountry?.map(country => countries?.indexOf(country.toLowerCase()) >= 0) hasPriority:
countries === undefined
? undefined
: options?.currentCountry?.map(
(country) => countries?.indexOf(country.toLowerCase()) >= 0
),
} }
possibleValues.push(term) possibleValues.push(term)
} }
super(possibleValues, super(possibleValues, {
{ mode: options?.mode ?? "select-many",
mode: options?.mode ?? 'select-many' })
});
} }
} }

View file

@ -23,7 +23,7 @@ import FilteredLayer from "../../Models/FilteredLayer"
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig" import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
import Lazy from "../Base/Lazy" import Lazy from "../Base/Lazy"
import List from "../Base/List" import List from "../Base/List"
import {SpecialVisualization} from "../SpecialVisualization"; import { SpecialVisualization } from "../SpecialVisualization"
export interface AutoAction extends SpecialVisualization { export interface AutoAction extends SpecialVisualization {
supportsAutoAction: boolean supportsAutoAction: boolean

View file

@ -1,13 +1,13 @@
import FeaturePipelineState from "../../Logic/State/FeaturePipelineState"; import FeaturePipelineState from "../../Logic/State/FeaturePipelineState"
import BaseUIElement from "../BaseUIElement"; import BaseUIElement from "../BaseUIElement"
import Translations from "../i18n/Translations"; import Translations from "../i18n/Translations"
import {Utils} from "../../Utils"; import { Utils } from "../../Utils"
import Svg from "../../Svg"; import Svg from "../../Svg"
import Img from "../Base/Img"; import Img from "../Base/Img"
import {SubtleButton} from "../Base/SubtleButton"; import { SubtleButton } from "../Base/SubtleButton"
import Toggle from "../Input/Toggle"; import Toggle from "../Input/Toggle"
import {LoginToggle} from "./LoginButton"; import { LoginToggle } from "./LoginButton"
import {SpecialVisualization} from "../SpecialVisualization"; import { SpecialVisualization } from "../SpecialVisualization"
export class CloseNoteButton implements SpecialVisualization { export class CloseNoteButton implements SpecialVisualization {
public readonly funcName = "close_note" public readonly funcName = "close_note"

View file

@ -10,7 +10,7 @@ import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
import { Unit } from "../../Models/Unit" import { Unit } from "../../Models/Unit"
import Lazy from "../Base/Lazy" import Lazy from "../Base/Lazy"
import { FixedUiElement } from "../Base/FixedUiElement" import { FixedUiElement } from "../Base/FixedUiElement"
import {EditButton} from "./SaveButton"; import { EditButton } from "./SaveButton"
export default class EditableTagRendering extends Toggle { export default class EditableTagRendering extends Toggle {
constructor( constructor(
@ -71,7 +71,7 @@ export default class EditableTagRendering extends Toggle {
// We have a question and editing is enabled // We have a question and editing is enabled
const answerWithEditButton = new Combine([ const answerWithEditButton = new Combine([
answer, answer,
new EditButton(state.osmConnection,() => { new EditButton(state.osmConnection, () => {
editMode.setData(true) editMode.setData(true)
}), }),
]).SetClass("flex justify-between w-full") ]).SetClass("flex justify-between w-full")

View file

@ -1,10 +1,10 @@
import Translations from "../i18n/Translations"; import Translations from "../i18n/Translations"
import {SubtleButton} from "../Base/SubtleButton"; import { SubtleButton } from "../Base/SubtleButton"
import Svg from "../../Svg"; import Svg from "../../Svg"
import Combine from "../Base/Combine"; import Combine from "../Base/Combine"
import {GeoOperations} from "../../Logic/GeoOperations"; import { GeoOperations } from "../../Logic/GeoOperations"
import {Utils} from "../../Utils"; import { Utils } from "../../Utils"
import {SpecialVisualization} from "../SpecialVisualization"; import { SpecialVisualization } from "../SpecialVisualization"
export class ExportAsGpxViz implements SpecialVisualization { export class ExportAsGpxViz implements SpecialVisualization {
funcName = "export_as_gpx" funcName = "export_as_gpx"
@ -26,16 +26,10 @@ export class ExportAsGpxViz implements SpecialVisualization {
const feature = state.allElements.ContainingFeatures.get(tags.id) const feature = state.allElements.ContainingFeatures.get(tags.id)
const matchingLayer = state?.layoutToUse?.getMatchingLayer(tags) const matchingLayer = state?.layoutToUse?.getMatchingLayer(tags)
const gpx = GeoOperations.AsGpx(feature, matchingLayer) const gpx = GeoOperations.AsGpx(feature, matchingLayer)
const title = const title = matchingLayer.title?.GetRenderValue(tags)?.Subs(tags)?.txt ?? "gpx_track"
matchingLayer.title?.GetRenderValue(tags)?.Subs(tags)?.txt ?? Utils.offerContentsAsDownloadableFile(gpx, title + "_mapcomplete_export.gpx", {
"gpx_track" mimetype: "{gpx=application/gpx+xml}",
Utils.offerContentsAsDownloadableFile( })
gpx,
title + "_mapcomplete_export.gpx",
{
mimetype: "{gpx=application/gpx+xml}",
}
)
}) })
} }
} }

View file

@ -1,9 +1,9 @@
import {Store, UIEventSource} from "../../Logic/UIEventSource"; import { Store, UIEventSource } from "../../Logic/UIEventSource"
import {FixedUiElement} from "../Base/FixedUiElement"; import { FixedUiElement } from "../Base/FixedUiElement"
// import Histogram from "../BigComponents/Histogram"; // import Histogram from "../BigComponents/Histogram";
// import {SpecialVisualization} from "../SpecialVisualization"; // import {SpecialVisualization} from "../SpecialVisualization";
export class HistogramViz { export class HistogramViz {
funcName = "histogram" funcName = "histogram"
docs = "Create a histogram for a list of given values, read from the properties." docs = "Create a histogram for a list of given values, read from the properties."
example = example =
@ -28,7 +28,7 @@ export class HistogramViz {
name: "colors*", name: "colors*",
doc: "(Matches all resting arguments - optional) Matches a regex onto a color value, e.g. `3[a-zA-Z+-]*:#33cc33`", doc: "(Matches all resting arguments - optional) Matches a regex onto a color value, e.g. `3[a-zA-Z+-]*:#33cc33`",
}, },
]; ]
constr(state, tagSource: UIEventSource<any>, args: string[]) { constr(state, tagSource: UIEventSource<any>, args: string[]) {
let assignColors = undefined let assignColors = undefined
@ -39,7 +39,7 @@ export class HistogramViz {
const splitted = c.split(":") const splitted = c.split(":")
const value = splitted.pop() const value = splitted.pop()
const regex = splitted.join(":") const regex = splitted.join(":")
return {regex: "^" + regex + "$", color: value} return { regex: "^" + regex + "$", color: value }
}) })
assignColors = (key) => { assignColors = (key) => {
for (const kv of mapping) { for (const kv of mapping) {
@ -59,10 +59,7 @@ export class HistogramViz {
} }
return JSON.parse(value) return JSON.parse(value)
} catch (e) { } catch (e) {
console.error( console.error("Could not load histogram: parsing of the list failed: ", e)
"Could not load histogram: parsing of the list failed: ",
e
)
return undefined return undefined
} }
}) })

View file

@ -44,7 +44,7 @@ import { Changes } from "../../Logic/Osm/Changes"
import { ElementStorage } from "../../Logic/ElementStorage" import { ElementStorage } from "../../Logic/ElementStorage"
import Hash from "../../Logic/Web/Hash" import Hash from "../../Logic/Web/Hash"
import { PreciseInput } from "../../Models/ThemeConfig/PresetConfig" import { PreciseInput } from "../../Models/ThemeConfig/PresetConfig"
import {SpecialVisualization} from "../SpecialVisualization"; import { SpecialVisualization } from "../SpecialVisualization"
/** /**
* A helper class for the various import-flows. * A helper class for the various import-flows.

View file

@ -1,73 +1,68 @@
import {SpecialVisualization} from "../SpecialVisualization"; import { SpecialVisualization } from "../SpecialVisualization"
import BaseUIElement from "../BaseUIElement"; import BaseUIElement from "../BaseUIElement"
import {UIEventSource} from "../../Logic/UIEventSource"; import { UIEventSource } from "../../Logic/UIEventSource"
import FeaturePipelineState from "../../Logic/State/FeaturePipelineState"; import FeaturePipelineState from "../../Logic/State/FeaturePipelineState"
import {VariableUiElement} from "../Base/VariableUIElement"; import { VariableUiElement } from "../Base/VariableUIElement"
import {OsmTags} from "../../Models/OsmFeature"; import { OsmTags } from "../../Models/OsmFeature"
import * as all_languages from "../../assets/language_translations.json" import * as all_languages from "../../assets/language_translations.json"
import {Translation} from "../i18n/Translation"; import { Translation } from "../i18n/Translation"
import Combine from "../Base/Combine"; import Combine from "../Base/Combine"
import Title from "../Base/Title"; import Title from "../Base/Title"
import Lazy from "../Base/Lazy"; import Lazy from "../Base/Lazy"
import {SubstitutedTranslation} from "../SubstitutedTranslation"; import { SubstitutedTranslation } from "../SubstitutedTranslation"
import List from "../Base/List"; import List from "../Base/List"
import {AllLanguagesSelector} from "./AllLanguagesSelector"; import { AllLanguagesSelector } from "./AllLanguagesSelector"
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"; import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"
import {And} from "../../Logic/Tags/And"; import { And } from "../../Logic/Tags/And"
import {Tag} from "../../Logic/Tags/Tag"; import { Tag } from "../../Logic/Tags/Tag"
import {EditButton, SaveButton} from "./SaveButton"; import { EditButton, SaveButton } from "./SaveButton"
import {FixedUiElement} from "../Base/FixedUiElement"; import { FixedUiElement } from "../Base/FixedUiElement"
import Translations from "../i18n/Translations"; import Translations from "../i18n/Translations"
import Toggle from "../Input/Toggle"; import Toggle from "../Input/Toggle"
import {On} from "../../Models/ThemeConfig/Conversion/Conversion"; import { On } from "../../Models/ThemeConfig/Conversion/Conversion"
export class LanguageElement implements SpecialVisualization { export class LanguageElement implements SpecialVisualization {
funcName: string = "language_chooser" funcName: string = "language_chooser"
docs: string | BaseUIElement = "The language element allows to show and pick all known (modern) languages. The key can be set"; docs: string | BaseUIElement =
"The language element allows to show and pick all known (modern) languages. The key can be set"
args: { name: string; defaultValue?: string; doc: string; required?: boolean }[] = args: { name: string; defaultValue?: string; doc: string; required?: boolean }[] = [
[{ {
name: "key", name: "key",
required: true, required: true,
doc: "What key to use, e.g. `language`, `tactile_writing:braille:language`, ... If a language is supported, the language code will be appended to this key, resulting in `language:nl=yes` if nl is picked " doc: "What key to use, e.g. `language`, `tactile_writing:braille:language`, ... If a language is supported, the language code will be appended to this key, resulting in `language:nl=yes` if nl is picked ",
}, },
{ {
name: "question", name: "question",
required: true, required: true,
doc: "What to ask if no questions are known" doc: "What to ask if no questions are known",
}, },
{ {
name: "render_list_item", name: "render_list_item",
doc: "How a single language will be shown in the list of languages. Use `{language}` to indicate the language (which it must contain).", doc: "How a single language will be shown in the list of languages. Use `{language}` to indicate the language (which it must contain).",
defaultValue: "{language()}" defaultValue: "{language()}",
}, },
{ {
name: "render_single_language", name: "render_single_language",
doc: "What will be shown if the feature only supports a single language", doc: "What will be shown if the feature only supports a single language",
required: true required: true,
}, },
{ {
name: "render_all", name: "render_all",
doc: "The full rendering. Use `{list}` to show where the list of languages must come. Optional if mode=single", doc: "The full rendering. Use `{list}` to show where the list of languages must come. Optional if mode=single",
defaultValue: "{list()}" defaultValue: "{list()}",
}, },
{ {
name: "no_known_languages", name: "no_known_languages",
doc: "The text that is shown if no languages are known for this key. If this text is omitted, the languages will be prompted instead" doc: "The text that is shown if no languages are known for this key. If this text is omitted, the languages will be prompted instead",
}, },
{ {
name: 'mode', name: "mode",
doc: "If one or many languages can be selected. Should be 'multi' or 'single'", doc: "If one or many languages can be selected. Should be 'multi' or 'single'",
defaultValue: 'multi' defaultValue: "multi",
} },
]
]
;
example: ` example: `
\`\`\`json \`\`\`json
@ -83,8 +78,13 @@ export class LanguageElement implements SpecialVisualization {
\`\`\` \`\`\`
` `
constr(state: FeaturePipelineState, tagSource: UIEventSource<OsmTags>, argument: string[]): BaseUIElement { constr(
let [key, question, item_render, single_render, all_render, on_no_known_languages, mode] = argument state: FeaturePipelineState,
tagSource: UIEventSource<OsmTags>,
argument: string[]
): BaseUIElement {
let [key, question, item_render, single_render, all_render, on_no_known_languages, mode] =
argument
if (mode === undefined || mode.length == 0) { if (mode === undefined || mode.length == 0) {
mode = "multi" mode = "multi"
} }
@ -95,7 +95,10 @@ export class LanguageElement implements SpecialVisualization {
all_render = "{list()}" all_render = "{list()}"
} }
if (mode !== "single" && mode !== "multi") { if (mode !== "single" && mode !== "multi") {
throw "Error while calling language_chooser: mode must be either 'single' or 'multi' but it is " + mode throw (
"Error while calling language_chooser: mode must be either 'single' or 'multi' but it is " +
mode
)
} }
if (single_render.indexOf("{language()") < 0 || item_render.indexOf("{language()") < 0) { if (single_render.indexOf("{language()") < 0 || item_render.indexOf("{language()") < 0) {
throw "Error while calling language_chooser: render_single_language and render_list_item must contain '{language()}'" throw "Error while calling language_chooser: render_single_language and render_list_item must contain '{language()}'"
@ -105,40 +108,39 @@ export class LanguageElement implements SpecialVisualization {
} }
const prefix = key + ":" const prefix = key + ":"
const foundLanguages = tagSource const foundLanguages = tagSource.map((tags) => {
.map(tags => { const foundLanguages: string[] = []
const foundLanguages: string[] = [] for (const k in tags) {
for (const k in tags) { const v = tags[k]
const v = tags[k] if (v !== "yes") {
if (v !== "yes") { continue
continue
}
if (k.startsWith(prefix)) {
foundLanguages.push(k.substring(prefix.length))
}
} }
return foundLanguages if (k.startsWith(prefix)) {
}) foundLanguages.push(k.substring(prefix.length))
const forceInputMode = new UIEventSource(false); }
}
return foundLanguages
})
const forceInputMode = new UIEventSource(false)
const inputEl = new Lazy(() => { const inputEl = new Lazy(() => {
const selector = new AllLanguagesSelector( const selector = new AllLanguagesSelector({
{ mode: mode === "single" ? "select-one" : "select-many",
mode: mode === "single" ? "select-one" : "select-many", currentCountry: tagSource.map((tgs) => tgs["_country"]),
currentCountry: tagSource.map(tgs => tgs["_country"]) })
} const cancelButton = Toggle.If(forceInputMode, () =>
) Translations.t.general.cancel
const cancelButton = Toggle.If(forceInputMode,
() => Translations.t.general.cancel
.Clone() .Clone()
.SetClass("btn btn-secondary").onClick(() => forceInputMode.setData(false))) .SetClass("btn btn-secondary")
.onClick(() => forceInputMode.setData(false))
)
const saveButton = new SaveButton( const saveButton = new SaveButton(
selector.GetValue().map(lngs => lngs.length > 0 ? "true" : undefined), selector.GetValue().map((lngs) => (lngs.length > 0 ? "true" : undefined)),
state.osmConnection, state.osmConnection
).onClick(() => { ).onClick(() => {
const selectedLanguages = selector.GetValue().data const selectedLanguages = selector.GetValue().data
const currentLanguages = foundLanguages.data const currentLanguages = foundLanguages.data
const selection: Tag[] = selectedLanguages.map(ln => new Tag(prefix + ln, "yes")); const selection: Tag[] = selectedLanguages.map((ln) => new Tag(prefix + ln, "yes"))
for (const currentLanguage of currentLanguages) { for (const currentLanguage of currentLanguages) {
if (selectedLanguages.indexOf(currentLanguage) >= 0) { if (selectedLanguages.indexOf(currentLanguage) >= 0) {
@ -148,19 +150,23 @@ export class LanguageElement implements SpecialVisualization {
selection.push(new Tag(prefix + currentLanguage, "")) selection.push(new Tag(prefix + currentLanguage, ""))
} }
if (state.featureSwitchIsTesting.data) { if (state.featureSwitchIsTesting.data) {
for (const tag of selection) { for (const tag of selection) {
tagSource.data[tag.key] = tag.value tagSource.data[tag.key] = tag.value
} }
tagSource.ping() tagSource.ping()
} else { } else {
(state?.changes) ;(state?.changes)
.applyAction( .applyAction(
new ChangeTagAction(tagSource.data.id, new And(selection), tagSource.data, { new ChangeTagAction(
theme: state?.layoutToUse?.id ?? "unkown", tagSource.data.id,
changeType: "answer", new And(selection),
}) tagSource.data,
{
theme: state?.layoutToUse?.id ?? "unkown",
changeType: "answer",
}
)
) )
.then((_) => { .then((_) => {
console.log("Tagchanges applied") console.log("Tagchanges applied")
@ -169,60 +175,72 @@ export class LanguageElement implements SpecialVisualization {
forceInputMode.setData(false) forceInputMode.setData(false)
}) })
return new Combine([new Title(question), selector, return new Combine([
new Combine([cancelButton, saveButton]).SetClass("flex justify-end") new Title(question),
]).SetClass("flex flex-col question disable-links"); selector,
new Combine([cancelButton, saveButton]).SetClass("flex justify-end"),
]).SetClass("flex flex-col question disable-links")
}) })
const editButton = new EditButton(state.osmConnection, () => forceInputMode.setData(true)) const editButton = new EditButton(state.osmConnection, () => forceInputMode.setData(true))
return new VariableUiElement(foundLanguages return new VariableUiElement(
.map(foundLanguages => { foundLanguages.map(
(foundLanguages) => {
if (forceInputMode.data) { if (forceInputMode.data) {
return inputEl return inputEl
}
if (foundLanguages.length === 0) {
// No languages found - we show the question and the input element
if (on_no_known_languages !== undefined && on_no_known_languages.length > 0) {
return new Combine([on_no_known_languages, editButton]).SetClass("flex justify-end")
} }
return inputEl
} if (foundLanguages.length === 0) {
// No languages found - we show the question and the input element
let rendered: BaseUIElement; if (
if (foundLanguages.length === 1) { on_no_known_languages !== undefined &&
const ln = foundLanguages[0] on_no_known_languages.length > 0
let mapping = new Map<string, BaseUIElement>(); ) {
mapping.set("language", new Translation(all_languages[ln])) return new Combine([on_no_known_languages, editButton]).SetClass(
rendered = new SubstitutedTranslation( "flex justify-end"
new Translation({"*": single_render}, undefined),
tagSource, state, mapping
)
} else {
let mapping = new Map<string, BaseUIElement>();
const languagesList = new List(
foundLanguages.map(ln => {
let mappingLn = new Map<string, BaseUIElement>();
mappingLn.set("language", new Translation(all_languages[ln]))
return new SubstitutedTranslation(
new Translation({"*": item_render}, undefined),
tagSource, state, mappingLn
) )
}) }
); return inputEl
mapping.set("list", languagesList) }
rendered = new SubstitutedTranslation(
new Translation({'*': all_render}, undefined), tagSource,
state, mapping
)
}
return new Combine([rendered, editButton]).SetClass("flex justify-between")
}, [forceInputMode])); let rendered: BaseUIElement
if (foundLanguages.length === 1) {
const ln = foundLanguages[0]
let mapping = new Map<string, BaseUIElement>()
mapping.set("language", new Translation(all_languages[ln]))
rendered = new SubstitutedTranslation(
new Translation({ "*": single_render }, undefined),
tagSource,
state,
mapping
)
} else {
let mapping = new Map<string, BaseUIElement>()
const languagesList = new List(
foundLanguages.map((ln) => {
let mappingLn = new Map<string, BaseUIElement>()
mappingLn.set("language", new Translation(all_languages[ln]))
return new SubstitutedTranslation(
new Translation({ "*": item_render }, undefined),
tagSource,
state,
mappingLn
)
})
)
mapping.set("list", languagesList)
rendered = new SubstitutedTranslation(
new Translation({ "*": all_render }, undefined),
tagSource,
state,
mapping
)
}
return new Combine([rendered, editButton]).SetClass("flex justify-between")
},
[forceInputMode]
)
)
} }
} }

View file

@ -1,8 +1,8 @@
import {GeoOperations} from "../../Logic/GeoOperations"; import { GeoOperations } from "../../Logic/GeoOperations"
import {MapillaryLink} from "../BigComponents/MapillaryLink"; import { MapillaryLink } from "../BigComponents/MapillaryLink"
import {UIEventSource} from "../../Logic/UIEventSource"; import { UIEventSource } from "../../Logic/UIEventSource"
import Loc from "../../Models/Loc"; import Loc from "../../Models/Loc"
import {SpecialVisualization} from "../SpecialVisualization"; import { SpecialVisualization } from "../SpecialVisualization"
export class MapillaryLinkVis implements SpecialVisualization { export class MapillaryLinkVis implements SpecialVisualization {
funcName = "mapillary_link" funcName = "mapillary_link"

View file

@ -1,9 +1,9 @@
import {Store, UIEventSource} from "../../Logic/UIEventSource"; import { Store, UIEventSource } from "../../Logic/UIEventSource"
import Loc from "../../Models/Loc"; import Loc from "../../Models/Loc"
import Minimap from "../Base/Minimap"; import Minimap from "../Base/Minimap"
import ShowDataMultiLayer from "../ShowDataLayer/ShowDataMultiLayer"; import ShowDataMultiLayer from "../ShowDataLayer/ShowDataMultiLayer"
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"; import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"
import {SpecialVisualization} from "../SpecialVisualization"; import { SpecialVisualization } from "../SpecialVisualization"
export class MinimapViz implements SpecialVisualization { export class MinimapViz implements SpecialVisualization {
funcName = "minimap" funcName = "minimap"
@ -20,8 +20,7 @@ export class MinimapViz implements SpecialVisualization {
defaultValue: "id", defaultValue: "id",
}, },
] ]
example: example: "`{minimap()}`, `{minimap(17, id, _list_of_embedded_feature_ids_calculated_by_calculated_tag):height:10rem; border: 2px solid black}`"
"`{minimap()}`, `{minimap(17, id, _list_of_embedded_feature_ids_calculated_by_calculated_tag):height:10rem; border: 2px solid black}`"
constr(state, tagSource, args, _) { constr(state, tagSource, args, _) {
if (state === undefined) { if (state === undefined) {
@ -30,8 +29,8 @@ export class MinimapViz implements SpecialVisualization {
const keys = [...args] const keys = [...args]
keys.splice(0, 1) keys.splice(0, 1)
const featureStore = state.allElements.ContainingFeatures const featureStore = state.allElements.ContainingFeatures
const featuresToShow: Store<{ freshness: Date; feature: any }[]> = const featuresToShow: Store<{ freshness: Date; feature: any }[]> = tagSource.map(
tagSource.map((properties) => { (properties) => {
const features: { freshness: Date; feature: any }[] = [] const features: { freshness: Date; feature: any }[] = []
for (const key of keys) { for (const key of keys) {
const value = properties[key] const value = properties[key]
@ -58,7 +57,8 @@ export class MinimapViz implements SpecialVisualization {
} }
} }
return features return features
}) }
)
const properties = tagSource.data const properties = tagSource.data
let zoom = 18 let zoom = 18
if (args[0]) { if (args[0]) {

View file

@ -1,10 +1,11 @@
import {Store} from "../../Logic/UIEventSource"; import { Store } from "../../Logic/UIEventSource"
import MultiApply from "./MultiApply"; import MultiApply from "./MultiApply"
import {SpecialVisualization} from "../SpecialVisualization"; import { SpecialVisualization } from "../SpecialVisualization"
export class MultiApplyViz implements SpecialVisualization { export class MultiApplyViz implements SpecialVisualization {
funcName = "multi_apply" funcName = "multi_apply"
docs = "A button to apply the tagging of this object onto a list of other features. This is an advanced feature for which you'll need calculatedTags" docs =
"A button to apply the tagging of this object onto a list of other features. This is an advanced feature for which you'll need calculatedTags"
args = [ args = [
{ {
name: "feature_ids", name: "feature_ids",
@ -52,17 +53,14 @@ export class MultiApplyViz implements SpecialVisualization {
return [] return []
} }
}) })
return new MultiApply( return new MultiApply({
{ featureIds,
featureIds, keysToApply,
keysToApply, text,
text, autoapply,
autoapply, overwrite,
overwrite, tagsSource,
tagsSource, state,
state })
}
);
} }
} }

View file

@ -1,25 +1,25 @@
import FeaturePipelineState from "../../Logic/State/FeaturePipelineState"; import FeaturePipelineState from "../../Logic/State/FeaturePipelineState"
import {UIEventSource} from "../../Logic/UIEventSource"; import { UIEventSource } from "../../Logic/UIEventSource"
import {DefaultGuiState} from "../DefaultGuiState"; import { DefaultGuiState } from "../DefaultGuiState"
import BaseUIElement from "../BaseUIElement"; import BaseUIElement from "../BaseUIElement"
import Translations from "../i18n/Translations"; import Translations from "../i18n/Translations"
import {GeoOperations} from "../../Logic/GeoOperations"; import { GeoOperations } from "../../Logic/GeoOperations"
import NearbyImages, {NearbyImageOptions, P4CPicture, SelectOneNearbyImage} from "./NearbyImages"; import NearbyImages, { NearbyImageOptions, P4CPicture, SelectOneNearbyImage } from "./NearbyImages"
import {SubstitutedTranslation} from "../SubstitutedTranslation"; import { SubstitutedTranslation } from "../SubstitutedTranslation"
import {Tag} from "../../Logic/Tags/Tag"; import { Tag } from "../../Logic/Tags/Tag"
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"; import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"
import {And} from "../../Logic/Tags/And"; import { And } from "../../Logic/Tags/And"
import {SaveButton} from "./SaveButton"; import { SaveButton } from "./SaveButton"
import Lazy from "../Base/Lazy"; import Lazy from "../Base/Lazy"
import {CheckBox} from "../Input/Checkboxes"; import { CheckBox } from "../Input/Checkboxes"
import Slider from "../Input/Slider"; import Slider from "../Input/Slider"
import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders"; import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders"
import Combine from "../Base/Combine"; import Combine from "../Base/Combine"
import {VariableUiElement} from "../Base/VariableUIElement"; import { VariableUiElement } from "../Base/VariableUIElement"
import Toggle from "../Input/Toggle"; import Toggle from "../Input/Toggle"
import Title from "../Base/Title"; import Title from "../Base/Title"
import {MapillaryLinkVis} from "./MapillaryLinkVis"; import { MapillaryLinkVis } from "./MapillaryLinkVis"
import {SpecialVisualization} from "../SpecialVisualization"; import { SpecialVisualization } from "../SpecialVisualization"
export class NearbyImageVis implements SpecialVisualization { export class NearbyImageVis implements SpecialVisualization {
args: { name: string; defaultValue?: string; doc: string; required?: boolean }[] = [ args: { name: string; defaultValue?: string; doc: string; required?: boolean }[] = [
@ -116,7 +116,7 @@ export class NearbyImageVis implements SpecialVisualization {
towardsCenter, towardsCenter,
new Combine([ new Combine([
new VariableUiElement( new VariableUiElement(
radius.GetValue().map((radius) => t.withinRadius.Subs({radius})) radius.GetValue().map((radius) => t.withinRadius.Subs({ radius }))
), ),
radius, radius,
]).SetClass("flex justify-between"), ]).SetClass("flex justify-between"),

View file

@ -1,29 +1,30 @@
import {Store, UIEventSource} from "../../Logic/UIEventSource"; import { Store, UIEventSource } from "../../Logic/UIEventSource"
import Toggle from "../Input/Toggle"; import Toggle from "../Input/Toggle"
import Lazy from "../Base/Lazy"; import Lazy from "../Base/Lazy"
import {ProvidedImage} from "../../Logic/ImageProviders/ImageProvider"; import { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
import PlantNetSpeciesSearch from "../BigComponents/PlantNetSpeciesSearch"; import PlantNetSpeciesSearch from "../BigComponents/PlantNetSpeciesSearch"
import Wikidata from "../../Logic/Web/Wikidata"; import Wikidata from "../../Logic/Web/Wikidata"
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"; import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"
import {And} from "../../Logic/Tags/And"; import { And } from "../../Logic/Tags/And"
import {Tag} from "../../Logic/Tags/Tag"; import { Tag } from "../../Logic/Tags/Tag"
import {SubtleButton} from "../Base/SubtleButton"; import { SubtleButton } from "../Base/SubtleButton"
import Combine from "../Base/Combine"; import Combine from "../Base/Combine"
import Svg from "../../Svg"; import Svg from "../../Svg"
import Translations from "../i18n/Translations"; import Translations from "../i18n/Translations"
import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders"; import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders"
import {SpecialVisualization} from "../SpecialVisualization"; import { SpecialVisualization } from "../SpecialVisualization"
export class PlantNetDetectionViz implements SpecialVisualization { export class PlantNetDetectionViz implements SpecialVisualization {
funcName = "plantnet_detection" funcName = "plantnet_detection"
docs = "Sends the images linked to the current object to plantnet.org and asks it what plant species is shown on it. The user can then select the correct species; the corresponding wikidata-identifier will then be added to the object (together with `source:species:wikidata=plantnet.org AI`). " docs =
"Sends the images linked to the current object to plantnet.org and asks it what plant species is shown on it. The user can then select the correct species; the corresponding wikidata-identifier will then be added to the object (together with `source:species:wikidata=plantnet.org AI`). "
args = [ args = [
{ {
name: "image_key", name: "image_key",
defaultValue: AllImageProviders.defaultKeys.join(","), defaultValue: AllImageProviders.defaultKeys.join(","),
doc: "The keys given to the images, e.g. if <span class='literal-code'>image</span> is given, the first picture URL will be added as <span class='literal-code'>image</span>, the second as <span class='literal-code'>image:0</span>, the third as <span class='literal-code'>image:1</span>, etc... Multiple values are allowed if ';'-separated ", doc: "The keys given to the images, e.g. if <span class='literal-code'>image</span> is given, the first picture URL will be added as <span class='literal-code'>image</span>, the second as <span class='literal-code'>image:0</span>, the third as <span class='literal-code'>image:1</span>, etc... Multiple values are allowed if ';'-separated ",
} },
] ]
public constr(state, tags, args) { public constr(state, tags, args) {
@ -35,44 +36,40 @@ export class PlantNetDetectionViz implements SpecialVisualization {
const detect = new UIEventSource(false) const detect = new UIEventSource(false)
const toggle = new Toggle( const toggle = new Toggle(
new Lazy(() => { new Lazy(() => {
const allProvidedImages: Store<ProvidedImage[]> = const allProvidedImages: Store<ProvidedImage[]> = AllImageProviders.LoadImagesFor(
AllImageProviders.LoadImagesFor(tags, imagePrefixes) tags,
imagePrefixes
)
const allImages: Store<string[]> = allProvidedImages.map((pi) => const allImages: Store<string[]> = allProvidedImages.map((pi) =>
pi.map((pi) => pi.url) pi.map((pi) => pi.url)
) )
return new PlantNetSpeciesSearch( return new PlantNetSpeciesSearch(allImages, async (selectedWikidata) => {
allImages, selectedWikidata = Wikidata.ExtractKey(selectedWikidata)
async (selectedWikidata) => { const change = new ChangeTagAction(
selectedWikidata = Wikidata.ExtractKey(selectedWikidata) tags.data.id,
const change = new ChangeTagAction( new And([
tags.data.id, new Tag("species:wikidata", selectedWikidata),
new And([ new Tag("source:species:wikidata", "PlantNet.org AI"),
new Tag("species:wikidata", selectedWikidata), ]),
new Tag("source:species:wikidata", "PlantNet.org AI"), tags.data,
]), {
tags.data, theme: state.layoutToUse.id,
{ changeType: "plantnet-ai-detection",
theme: state.layoutToUse.id, }
changeType: "plantnet-ai-detection", )
} await state.changes.applyAction(change)
) })
await state.changes.applyAction(change)
}
)
}), }),
new SubtleButton( new SubtleButton(undefined, "Detect plant species with plantnet.org").onClick(() =>
undefined, detect.setData(true)
"Detect plant species with plantnet.org" ),
).onClick(() => detect.setData(true)),
detect detect
) )
return new Combine([ return new Combine([
toggle, toggle,
new Combine([ new Combine([
Svg.plantnet_logo_svg().SetClass( Svg.plantnet_logo_svg().SetClass("w-10 h-10 p-1 mr-1 bg-white rounded-full"),
"w-10 h-10 p-1 mr-1 bg-white rounded-full"
),
Translations.t.plantDetection.poweredByPlantnet, Translations.t.plantDetection.poweredByPlantnet,
]).SetClass("flex p-2 bg-gray-200 rounded-xl self-end"), ]).SetClass("flex p-2 bg-gray-200 rounded-xl self-end"),
]).SetClass("flex flex-col") ]).SetClass("flex flex-col")

View file

@ -3,8 +3,8 @@ import Translations from "../i18n/Translations"
import { OsmConnection } from "../../Logic/Osm/OsmConnection" import { OsmConnection } from "../../Logic/Osm/OsmConnection"
import Toggle from "../Input/Toggle" import Toggle from "../Input/Toggle"
import BaseUIElement from "../BaseUIElement" import BaseUIElement from "../BaseUIElement"
import Combine from "../Base/Combine"; import Combine from "../Base/Combine"
import Svg from "../../Svg"; import Svg from "../../Svg"
export class EditButton extends Toggle { export class EditButton extends Toggle {
constructor(osmConnection: OsmConnection, onClick: () => void) { constructor(osmConnection: OsmConnection, onClick: () => void) {

View file

@ -1,9 +1,9 @@
import {UIEventSource} from "../../Logic/UIEventSource"; import { UIEventSource } from "../../Logic/UIEventSource"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import ShareButton from "../BigComponents/ShareButton"; import ShareButton from "../BigComponents/ShareButton"
import Svg from "../../Svg"; import Svg from "../../Svg"
import {FixedUiElement} from "../Base/FixedUiElement"; import { FixedUiElement } from "../Base/FixedUiElement"
import {SpecialVisualization} from "../SpecialVisualization"; import { SpecialVisualization } from "../SpecialVisualization"
export class ShareLinkViz implements SpecialVisualization { export class ShareLinkViz implements SpecialVisualization {
funcName = "share_link" funcName = "share_link"
@ -45,10 +45,7 @@ export class ShareLinkViz implements SpecialVisualization {
} }
} }
return new ShareButton( return new ShareButton(Svg.share_svg().SetClass("w-8 h-8"), generateShareData)
Svg.share_svg().SetClass("w-8 h-8"),
generateShareData
)
} else { } else {
return new FixedUiElement("") return new FixedUiElement("")
} }

View file

@ -1,15 +1,16 @@
import {UIEventSource} from "../../Logic/UIEventSource"; import { UIEventSource } from "../../Logic/UIEventSource"
import Loc from "../../Models/Loc"; import Loc from "../../Models/Loc"
import Minimap from "../Base/Minimap"; import Minimap from "../Base/Minimap"
import ShowDataLayer from "../ShowDataLayer/ShowDataLayer"; import ShowDataLayer from "../ShowDataLayer/ShowDataLayer"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import * as left_right_style_json from "../../assets/layers/left_right_style/left_right_style.json"; import * as left_right_style_json from "../../assets/layers/left_right_style/left_right_style.json"
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"; import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"
import {SpecialVisualization} from "../SpecialVisualization"; import { SpecialVisualization } from "../SpecialVisualization"
export class SidedMinimap implements SpecialVisualization { export class SidedMinimap implements SpecialVisualization {
funcName = "sided_minimap" funcName = "sided_minimap"
docs = "A small map showing _only one side_ the selected feature. *This features requires to have linerenderings with offset* as only linerenderings with a postive or negative offset will be shown. Note: in most cases, this map will be automatically introduced" docs =
"A small map showing _only one side_ the selected feature. *This features requires to have linerenderings with offset* as only linerenderings with a postive or negative offset will be shown. Note: in most cases, this map will be automatically introduced"
args = [ args = [
{ {
doc: "The side to show, either `left` or `right`", doc: "The side to show, either `left` or `right`",
@ -33,18 +34,14 @@ export class SidedMinimap implements SpecialVisualization {
}) })
const side = args[0] const side = args[0]
const feature = state.allElements.ContainingFeatures.get(tagSource.data.id) const feature = state.allElements.ContainingFeatures.get(tagSource.data.id)
const copy = {...feature} const copy = { ...feature }
copy.properties = { copy.properties = {
id: side, id: side,
} }
new ShowDataLayer({ new ShowDataLayer({
leafletMap: minimap["leafletMap"], leafletMap: minimap["leafletMap"],
zoomToFeatures: true, zoomToFeatures: true,
layerToShow: new LayerConfig( layerToShow: new LayerConfig(left_right_style_json, "all_known_layers", true),
left_right_style_json,
"all_known_layers",
true
),
features: StaticFeatureSource.fromGeojson([copy]), features: StaticFeatureSource.fromGeojson([copy]),
state, state,
}) })

View file

@ -1,10 +1,10 @@
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"; import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
import {VariableUiElement} from "../Base/VariableUIElement"; import { VariableUiElement } from "../Base/VariableUIElement"
import BaseUIElement from "../BaseUIElement"; import BaseUIElement from "../BaseUIElement"
import EditableTagRendering from "./EditableTagRendering"; import EditableTagRendering from "./EditableTagRendering"
import Combine from "../Base/Combine"; import Combine from "../Base/Combine"
import {SpecialVisualization} from "../SpecialVisualization"; import { SpecialVisualization } from "../SpecialVisualization"
export class StealViz implements SpecialVisualization { export class StealViz implements SpecialVisualization {
funcName = "steal" funcName = "steal"
@ -27,9 +27,7 @@ export class StealViz implements SpecialVisualization {
for (const layerAndTagRenderingId of layerAndtagRenderingIds.split(";")) { for (const layerAndTagRenderingId of layerAndtagRenderingIds.split(";")) {
const [layerId, tagRenderingId] = layerAndTagRenderingId.trim().split(".") const [layerId, tagRenderingId] = layerAndTagRenderingId.trim().split(".")
const layer = state.layoutToUse.layers.find((l) => l.id === layerId) const layer = state.layoutToUse.layers.find((l) => l.id === layerId)
const tagRendering = layer.tagRenderings.find( const tagRendering = layer.tagRenderings.find((tr) => tr.id === tagRenderingId)
(tr) => tr.id === tagRenderingId
)
tagRenderings.push([layer, tagRendering]) tagRenderings.push([layer, tagRendering])
} }
if (tagRenderings.length === 0) { if (tagRenderings.length === 0) {

View file

@ -14,9 +14,9 @@ import { Tag } from "../../Logic/Tags/Tag"
import FeaturePipelineState from "../../Logic/State/FeaturePipelineState" import FeaturePipelineState from "../../Logic/State/FeaturePipelineState"
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
import { Changes } from "../../Logic/Osm/Changes" import { Changes } from "../../Logic/Osm/Changes"
import {SpecialVisualization} from "../SpecialVisualization"; import { SpecialVisualization } from "../SpecialVisualization"
export default class TagApplyButton implements AutoAction , SpecialVisualization{ export default class TagApplyButton implements AutoAction, SpecialVisualization {
public readonly funcName = "tag_apply" public readonly funcName = "tag_apply"
public readonly docs = public readonly docs =
"Shows a big button; clicking this button will apply certain tags onto the feature.\n\nThe first argument takes a specification of which tags to add.\n" + "Shows a big button; clicking this button will apply certain tags onto the feature.\n\nThe first argument takes a specification of which tags to add.\n" +

View file

@ -105,7 +105,7 @@ export default class TagRenderingQuestion extends Combine {
TagUtils.FlattenAnd(inputElement.GetValue().data, tags.data) TagUtils.FlattenAnd(inputElement.GetValue().data, tags.data)
) )
if (selection) { if (selection) {
(state?.changes) ;(state?.changes)
.applyAction( .applyAction(
new ChangeTagAction(tags.data.id, selection, tags.data, { new ChangeTagAction(tags.data.id, selection, tags.data, {
theme: state?.layoutToUse?.id ?? "unkown", theme: state?.layoutToUse?.id ?? "unkown",
@ -288,7 +288,7 @@ export default class TagRenderingQuestion extends Combine {
value: number value: number
mainTerm: Record<string, string> mainTerm: Record<string, string>
searchTerms?: Record<string, string[]> searchTerms?: Record<string, string[]>
original: Mapping, original: Mapping
hasPriority?: Store<boolean> hasPriority?: Store<boolean>
}[] { }[] {
const values: { const values: {
@ -296,7 +296,7 @@ export default class TagRenderingQuestion extends Combine {
value: number value: number
mainTerm: Record<string, string> mainTerm: Record<string, string>
searchTerms?: Record<string, string[]> searchTerms?: Record<string, string[]>
original: Mapping, original: Mapping
hasPriority?: Store<boolean> hasPriority?: Store<boolean>
}[] = [] }[] = []
const addIcons = applicableMappings.some((m) => m.icon !== undefined) const addIcons = applicableMappings.some((m) => m.icon !== undefined)
@ -319,7 +319,7 @@ export default class TagRenderingQuestion extends Combine {
mainTerm: tr.translations, mainTerm: tr.translations,
searchTerms: mapping.searchTerms, searchTerms: mapping.searchTerms,
original: mapping, original: mapping,
hasPriority: tagsSource.map(tags => mapping.priorityIf?.matchesProperties(tags)) hasPriority: tagsSource.map((tags) => mapping.priorityIf?.matchesProperties(tags)),
}) })
} }
return values return values
@ -400,7 +400,7 @@ export default class TagRenderingQuestion extends Combine {
const values = TagRenderingQuestion.MappingToPillValue( const values = TagRenderingQuestion.MappingToPillValue(
applicableMappings, applicableMappings,
tagsSource, tagsSource,
state, state
) )
const searchValue: UIEventSource<string> = const searchValue: UIEventSource<string> =
@ -419,7 +419,7 @@ export default class TagRenderingQuestion extends Combine {
mode, mode,
searchValue, searchValue,
onNoMatches: onEmpty?.SetClass(classes).SetClass("flex justify-center items-center"), onNoMatches: onEmpty?.SetClass(classes).SetClass("flex justify-center items-center"),
searchAreaClass: classes searchAreaClass: classes,
}) })
const fallbackTag = searchValue.map((s) => { const fallbackTag = searchValue.map((s) => {
if (s === undefined || ff?.key === undefined) { if (s === undefined || ff?.key === undefined) {

View file

@ -1,27 +1,28 @@
import {Utils} from "../../Utils"; import { Utils } from "../../Utils"
import {Feature} from "geojson"; import { Feature } from "geojson"
import {Point} from "@turf/turf"; import { Point } from "@turf/turf"
import {GeoLocationPointProperties} from "../../Logic/Actors/GeoLocationHandler"; import { GeoLocationPointProperties } from "../../Logic/Actors/GeoLocationHandler"
import UploadTraceToOsmUI from "../BigComponents/UploadTraceToOsmUI"; import UploadTraceToOsmUI from "../BigComponents/UploadTraceToOsmUI"
import {SpecialVisualization} from "../SpecialVisualization"; import { SpecialVisualization } from "../SpecialVisualization"
/** /**
* Wrapper around 'UploadTraceToOsmUI' * Wrapper around 'UploadTraceToOsmUI'
*/ */
export class UploadToOsmViz implements SpecialVisualization { export class UploadToOsmViz implements SpecialVisualization {
funcName = "upload_to_osm" funcName = "upload_to_osm"
docs = "Uploads the GPS-history as GPX to OpenStreetMap.org; clears the history afterwards. The actual feature is ignored." docs =
args = [] "Uploads the GPS-history as GPX to OpenStreetMap.org; clears the history afterwards. The actual feature is ignored."
args = []
constr(state, featureTags, args) { constr(state, featureTags, args) {
function getTrace(title: string) { function getTrace(title: string) {
title = title?.trim() title = title?.trim()
if (title === undefined || title === "") { if (title === undefined || title === "") {
title = "Uploaded with MapComplete" title = "Uploaded with MapComplete"
} }
title = Utils.EncodeXmlValue(title) title = Utils.EncodeXmlValue(title)
const userLocations: Feature<Point, GeoLocationPointProperties>[] = state.historicalUserLocations.features.data.map(f => f.feature) const userLocations: Feature<Point, GeoLocationPointProperties>[] =
state.historicalUserLocations.features.data.map((f) => f.feature)
const trackPoints: string[] = [] const trackPoints: string[] = []
for (const l of userLocations) { for (const l of userLocations) {
let trkpt = ` <trkpt lat="${l.geometry.coordinates[1]}" lon="${l.geometry.coordinates[0]}">` let trkpt = ` <trkpt lat="${l.geometry.coordinates[1]}" lon="${l.geometry.coordinates[0]}">`
@ -32,14 +33,22 @@ export class UploadToOsmViz implements SpecialVisualization {
trkpt += " </trkpt>" trkpt += " </trkpt>"
trackPoints.push(trkpt) trackPoints.push(trkpt)
} }
const header = '<gpx version="1.1" creator="MapComplete track uploader" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">' const header =
return header + "\n<name>" + title + "</name>\n<trk><trkseg>\n" + trackPoints.join("\n") + "\n</trkseg></trk></gpx>" '<gpx version="1.1" creator="MapComplete track uploader" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">'
return (
header +
"\n<name>" +
title +
"</name>\n<trk><trkseg>\n" +
trackPoints.join("\n") +
"\n</trkseg></trk></gpx>"
)
} }
return new UploadTraceToOsmUI(getTrace, state, { return new UploadTraceToOsmUI(getTrace, state, {
whenUploaded: async () => { whenUploaded: async () => {
state.historicalUserLocations.features.setData([]) state.historicalUserLocations.features.setData([])
} },
}) })
} }
} }

32
test.ts
View file

@ -1,24 +1,26 @@
import {LanguageElement} from "./UI/Popup/LanguageElement"; import { LanguageElement } from "./UI/Popup/LanguageElement"
import {ImmutableStore, UIEventSource} from "./Logic/UIEventSource"; import { ImmutableStore, UIEventSource } from "./Logic/UIEventSource"
import {VariableUiElement} from "./UI/Base/VariableUIElement"; import { VariableUiElement } from "./UI/Base/VariableUIElement"
import Locale from "./UI/i18n/Locale"; import Locale from "./UI/i18n/Locale"
import {OsmConnection} from "./Logic/Osm/OsmConnection"; import { OsmConnection } from "./Logic/Osm/OsmConnection"
const tgs = new UIEventSource({ const tgs = new UIEventSource({
"name": "xyz", name: "xyz",
"id": "node/1234", id: "node/1234",
"_country" : "BE", _country: "BE",
}) })
Locale.language.setData("nl") Locale.language.setData("nl")
console.log(tgs) console.log(tgs)
console.log("Locale", Locale.language) console.log("Locale", Locale.language)
const conn = new OsmConnection({}) const conn = new OsmConnection({})
new LanguageElement().constr(<any> {osmConnection: conn, featureSwitchIsTesting: new ImmutableStore(true)}, tgs, [ new LanguageElement()
"language", .constr(<any>{ osmConnection: conn, featureSwitchIsTesting: new ImmutableStore(true) }, tgs, [
"What languages are spoken here?", "language",
"{language()} is spoken here", "What languages are spoken here?",
"{language()} is the only language spoken here", "{language()} is spoken here",
"The following languages are spoken here: {list()}" "{language()} is the only language spoken here",
]).AttachTo("maindiv") "The following languages are spoken here: {list()}",
])
.AttachTo("maindiv")
new VariableUiElement(tgs.map(JSON.stringify)).AttachTo("extradiv") new VariableUiElement(tgs.map(JSON.stringify)).AttachTo("extradiv")

View file

@ -12,8 +12,11 @@ describe("SpecialVisualisations", () => {
"A special visualisation is not allowed to be named 'type', as this will conflict with the 'special'-blocks" "A special visualisation is not allowed to be named 'type', as this will conflict with the 'special'-blocks"
) )
if(special.args === undefined){ if (special.args === undefined) {
throw "The field 'args' is undefined for special visualisation "+special.funcName throw (
"The field 'args' is undefined for special visualisation " +
special.funcName
)
} }
for (const arg of special.args) { for (const arg of special.args) {