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 {
public static readonly oauth_configs = {
"osm": {
oauth_consumer_key: 'hivV7ec2o49Two8g9h8Is1VIiVOgxQ1iYexCbvem',
oauth_secret: 'wDBRTCem0vxD7txrg1y6p5r8nvmz8tAhET7zDASI',
url: "https://www.openstreetmap.org"
osm: {
oauth_consumer_key: "hivV7ec2o49Two8g9h8Is1VIiVOgxQ1iYexCbvem",
oauth_secret: "wDBRTCem0vxD7txrg1y6p5r8nvmz8tAhET7zDASI",
url: "https://www.openstreetmap.org",
// OAUTH 1.0 application
// https://www.openstreetmap.org/user/Pieter%20Vander%20Vennet/oauth_clients/7404
},
@ -335,43 +335,52 @@ export class OsmConnection {
})
}
public async uploadGpxTrack(gpx: string, options: {
description: string,
visibility: "private" | "public" | "trackable" | "identifiable",
filename?: string
/**
* Some words to give some properties;
*
* 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.
*/
labels: string[]
}): Promise<{ id: number }> {
public async uploadGpxTrack(
gpx: string,
options: {
description: string
visibility: "private" | "public" | "trackable" | "identifiable"
filename?: string
/**
* Some words to give some properties;
*
* 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.
*/
labels: string[]
}
): Promise<{ id: number }> {
if (this._dryRun.data) {
console.warn("Dryrun enabled - not actually uploading GPX ", gpx)
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 = {
"file": gpx,
"description": options.description ?? "",
"tags": options.labels?.join(",") ?? "",
"visibility": options.visibility
file: gpx,
description: options.description ?? "",
tags: options.labels?.join(",") ?? "",
visibility: options.visibility,
}
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 boundary ="987654"
const auth = this.auth
const boundary = "987654"
let body = ""
for (const key in contents) {
body += "--" + boundary + "\r\n"
body += "Content-Disposition: form-data; name=\"" + key + "\""
if(extras[key] !== undefined){
body += 'Content-Disposition: form-data; name="' + key + '"'
if (extras[key] !== undefined) {
body += extras[key]
}
body += "\r\n\r\n"
@ -379,34 +388,31 @@ export class OsmConnection {
}
body += "--" + boundary + "--\r\n"
return new Promise((ok, error) => {
auth.xhr({
method: 'POST',
path: `/api/0.6/gpx/create`,
options: {
header:
{
auth.xhr(
{
method: "POST",
path: `/api/0.6/gpx/create`,
options: {
header: {
"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)
if (err !== null) {
error(err)
} else {
const parsed = JSON.parse(response)
console.log("Uploaded GPX track", parsed)
ok({id: parsed})
function (err, response: string) {
console.log("RESPONSE IS", response)
if (err !== null) {
error(err)
} else {
const parsed = JSON.parse(response)
console.log("Uploaded GPX track", parsed)
ok({ id: parsed })
}
}
})
)
})
}
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]
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 {LayerConfigJson} from "../Json/LayerConfigJson"
import { DesugaringStep, Each, Fuse, On } from "./Conversion"
import { LayerConfigJson } from "../Json/LayerConfigJson"
import LayerConfig from "../LayerConfig"
import {Utils} from "../../../Utils"
import { Utils } from "../../../Utils"
import Constants from "../../Constants"
import {Translation} from "../../../UI/i18n/Translation"
import {LayoutConfigJson} from "../Json/LayoutConfigJson"
import { Translation } from "../../../UI/i18n/Translation"
import { LayoutConfigJson } from "../Json/LayoutConfigJson"
import LayoutConfig from "../LayoutConfig"
import {TagRenderingConfigJson} from "../Json/TagRenderingConfigJson"
import {TagUtils} from "../../../Logic/Tags/TagUtils"
import {ExtractImages} from "./FixImages"
import { TagRenderingConfigJson } from "../Json/TagRenderingConfigJson"
import { TagUtils } from "../../../Logic/Tags/TagUtils"
import { ExtractImages } from "./FixImages"
import ScriptUtils from "../../../scripts/ScriptUtils"
import {And} from "../../../Logic/Tags/And"
import { And } from "../../../Logic/Tags/And"
import Translations from "../../../UI/i18n/Translations"
import Svg from "../../../Svg"
import {QuestionableTagRenderingConfigJson} from "../Json/QuestionableTagRenderingConfigJson"
import { QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson"
import FilterConfigJson from "../Json/FilterConfigJson"
import DeleteConfig from "../DeleteConfig"
@ -619,20 +619,31 @@ export class DetectMappingsWithImages extends DesugaringStep<TagRenderingConfigJ
class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> {
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[] } {
const errors = []
if(json["special"] !== undefined){
errors.push("At "+context+": detected `special` on the top level. Did you mean `{\"render\":{ \"special\": ... }}`")
convert(
json: TagRenderingConfigJson,
context: string
): {
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 {
result: json,
errors
};
errors,
}
}
}
export class ValidateTagRenderings extends Fuse<TagRenderingConfigJson> {

View file

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

View file

@ -68,7 +68,7 @@ export default class FilterConfig {
for (const field of fields) {
for (let ln in question.translations) {
const txt = question.translations[ln]
if(ln.startsWith("_")){
if (ln.startsWith("_")) {
continue
}
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}`
}
for (const ln in this.render.translations) {
if(ln.startsWith("_")){
if (ln.startsWith("_")) {
continue
}
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))
return new VariableUiElement(state.map(st => {
if(st === "idle"){
return button
}
return loading
}))
return new VariableUiElement(
state.map((st) => {
if (st === "idle") {
return button
}
return loading
})
)
}
}

View file

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

View file

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

View file

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

View file

@ -1,13 +1,13 @@
import {UIElement} from "../UIElement"
import {InputElement} from "./InputElement"
import { UIElement } from "../UIElement"
import { InputElement } from "./InputElement"
import BaseUIElement from "../BaseUIElement"
import {Store, UIEventSource} from "../../Logic/UIEventSource"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import Translations from "../i18n/Translations"
import Locale from "../i18n/Locale"
import Combine from "../Base/Combine"
import {TextField} from "./TextField"
import { TextField } from "./TextField"
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
@ -28,7 +28,7 @@ class SelfHidingToggle extends UIElement implements InputElement<boolean> {
searchTerms?: Record<string, string[]>
selected?: UIEventSource<boolean>
forceSelected?: UIEventSource<boolean>
squared?: boolean,
squared?: boolean
/* Hide, if not selected*/
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 forceSelected = (this.forceSelected =
options?.forceSelected ?? new UIEventSource<boolean>(false))
this.matchesSearchCriteria = search.map(s => {
this.matchesSearchCriteria = search.map((s) => {
if (s === undefined || s.length === 0) {
return true
}
@ -152,13 +152,13 @@ export class SearchablePillsSelector<T> extends Combine implements InputElement<
show: BaseUIElement
value: T
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? */
hasPriority?: Store<boolean>
}[],
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"
/**
@ -189,14 +189,14 @@ export class SearchablePillsSelector<T> extends Combine implements InputElement<
hideSearchBar?: false | boolean
}
) {
const search = new TextField({value: options?.searchValue})
const search = new TextField({ value: options?.searchValue })
const searchBar = options?.hideSearchBar
? undefined
: new Combine([
Svg.search_svg().SetClass("w-8 normal-background"),
search.SetClass("w-full"),
]).SetClass("flex items-center border-2 border-black m-2")
Svg.search_svg().SetClass("w-8 normal-background"),
search.SetClass("w-full"),
]).SetClass("flex items-center border-2 border-black m-2")
const searchValue = search.GetValue().map((s) => s?.trim()?.toLowerCase())
const selectedElements = options?.selectedElements ?? new UIEventSource<T[]>([])
@ -238,7 +238,10 @@ export class SearchablePillsSelector<T> extends Combine implements InputElement<
searchTerms: v.searchTerms,
selected: vIsSelected,
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 {
@ -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
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
totalShown.addCallbackAndRunD(shown => forceHide.setData(tooMuchElementsCutoff < shown))
totalShown.addCallbackAndRunD((shown) => forceHide.setData(tooMuchElementsCutoff < shown))
super([
searchBar,
new VariableUiElement(
Locale.language.map(
(lng) => {
if (
options?.onNoSearchMade !== undefined &&
(searchValue.data === undefined || searchValue.data.length === 0)
@ -275,7 +279,10 @@ export class SearchablePillsSelector<T> extends Combine implements InputElement<
.SetClass(options?.searchAreaClass ?? "")
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
},

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,10 +1,10 @@
import Translations from "../i18n/Translations";
import {SubtleButton} from "../Base/SubtleButton";
import Svg from "../../Svg";
import Combine from "../Base/Combine";
import {GeoOperations} from "../../Logic/GeoOperations";
import {Utils} from "../../Utils";
import {SpecialVisualization} from "../SpecialVisualization";
import Translations from "../i18n/Translations"
import { SubtleButton } from "../Base/SubtleButton"
import Svg from "../../Svg"
import Combine from "../Base/Combine"
import { GeoOperations } from "../../Logic/GeoOperations"
import { Utils } from "../../Utils"
import { SpecialVisualization } from "../SpecialVisualization"
export class ExportAsGpxViz implements SpecialVisualization {
funcName = "export_as_gpx"
@ -26,16 +26,10 @@ export class ExportAsGpxViz implements SpecialVisualization {
const feature = state.allElements.ContainingFeatures.get(tags.id)
const matchingLayer = state?.layoutToUse?.getMatchingLayer(tags)
const gpx = GeoOperations.AsGpx(feature, matchingLayer)
const title =
matchingLayer.title?.GetRenderValue(tags)?.Subs(tags)?.txt ??
"gpx_track"
Utils.offerContentsAsDownloadableFile(
gpx,
title + "_mapcomplete_export.gpx",
{
mimetype: "{gpx=application/gpx+xml}",
}
)
const title = matchingLayer.title?.GetRenderValue(tags)?.Subs(tags)?.txt ?? "gpx_track"
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 {FixedUiElement} from "../Base/FixedUiElement";
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import { FixedUiElement } from "../Base/FixedUiElement"
// import Histogram from "../BigComponents/Histogram";
// import {SpecialVisualization} from "../SpecialVisualization";
export class HistogramViz {
export class HistogramViz {
funcName = "histogram"
docs = "Create a histogram for a list of given values, read from the properties."
example =
@ -28,7 +28,7 @@ export class HistogramViz {
name: "colors*",
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[]) {
let assignColors = undefined
@ -39,7 +39,7 @@ export class HistogramViz {
const splitted = c.split(":")
const value = splitted.pop()
const regex = splitted.join(":")
return {regex: "^" + regex + "$", color: value}
return { regex: "^" + regex + "$", color: value }
})
assignColors = (key) => {
for (const kv of mapping) {
@ -59,10 +59,7 @@ export class HistogramViz {
}
return JSON.parse(value)
} catch (e) {
console.error(
"Could not load histogram: parsing of the list failed: ",
e
)
console.error("Could not load histogram: parsing of the list failed: ", e)
return undefined
}
})

View file

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

View file

@ -1,73 +1,68 @@
import {SpecialVisualization} from "../SpecialVisualization";
import BaseUIElement from "../BaseUIElement";
import {UIEventSource} from "../../Logic/UIEventSource";
import FeaturePipelineState from "../../Logic/State/FeaturePipelineState";
import {VariableUiElement} from "../Base/VariableUIElement";
import {OsmTags} from "../../Models/OsmFeature";
import { SpecialVisualization } from "../SpecialVisualization"
import BaseUIElement from "../BaseUIElement"
import { UIEventSource } from "../../Logic/UIEventSource"
import FeaturePipelineState from "../../Logic/State/FeaturePipelineState"
import { VariableUiElement } from "../Base/VariableUIElement"
import { OsmTags } from "../../Models/OsmFeature"
import * as all_languages from "../../assets/language_translations.json"
import {Translation} from "../i18n/Translation";
import Combine from "../Base/Combine";
import Title from "../Base/Title";
import Lazy from "../Base/Lazy";
import {SubstitutedTranslation} from "../SubstitutedTranslation";
import List from "../Base/List";
import {AllLanguagesSelector} from "./AllLanguagesSelector";
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction";
import {And} from "../../Logic/Tags/And";
import {Tag} from "../../Logic/Tags/Tag";
import {EditButton, SaveButton} from "./SaveButton";
import {FixedUiElement} from "../Base/FixedUiElement";
import Translations from "../i18n/Translations";
import Toggle from "../Input/Toggle";
import {On} from "../../Models/ThemeConfig/Conversion/Conversion";
import { Translation } from "../i18n/Translation"
import Combine from "../Base/Combine"
import Title from "../Base/Title"
import Lazy from "../Base/Lazy"
import { SubstitutedTranslation } from "../SubstitutedTranslation"
import List from "../Base/List"
import { AllLanguagesSelector } from "./AllLanguagesSelector"
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"
import { And } from "../../Logic/Tags/And"
import { Tag } from "../../Logic/Tags/Tag"
import { EditButton, SaveButton } from "./SaveButton"
import { FixedUiElement } from "../Base/FixedUiElement"
import Translations from "../i18n/Translations"
import Toggle from "../Input/Toggle"
import { On } from "../../Models/ThemeConfig/Conversion/Conversion"
export class LanguageElement implements SpecialVisualization {
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",
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",
required: true,
doc: "What to ask if no questions are known"
},
{
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).",
defaultValue: "{language()}"
},
{
name: "render_single_language",
doc: "What will be shown if the feature only supports a single language",
required: true
},
{
name: "render_all",
doc: "The full rendering. Use `{list}` to show where the list of languages must come. Optional if mode=single",
defaultValue: "{list()}"
},
{
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"
},
{
name: 'mode',
doc: "If one or many languages can be selected. Should be 'multi' or 'single'",
defaultValue: 'multi'
}
]
;
{
name: "question",
required: true,
doc: "What to ask if no questions are known",
},
{
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).",
defaultValue: "{language()}",
},
{
name: "render_single_language",
doc: "What will be shown if the feature only supports a single language",
required: true,
},
{
name: "render_all",
doc: "The full rendering. Use `{list}` to show where the list of languages must come. Optional if mode=single",
defaultValue: "{list()}",
},
{
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",
},
{
name: "mode",
doc: "If one or many languages can be selected. Should be 'multi' or 'single'",
defaultValue: "multi",
},
]
example: `
\`\`\`json
@ -83,8 +78,13 @@ export class LanguageElement implements SpecialVisualization {
\`\`\`
`
constr(state: FeaturePipelineState, tagSource: UIEventSource<OsmTags>, argument: string[]): BaseUIElement {
let [key, question, item_render, single_render, all_render, on_no_known_languages, mode] = argument
constr(
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) {
mode = "multi"
}
@ -95,7 +95,10 @@ export class LanguageElement implements SpecialVisualization {
all_render = "{list()}"
}
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) {
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 foundLanguages = tagSource
.map(tags => {
const foundLanguages: string[] = []
for (const k in tags) {
const v = tags[k]
if (v !== "yes") {
continue
}
if (k.startsWith(prefix)) {
foundLanguages.push(k.substring(prefix.length))
}
const foundLanguages = tagSource.map((tags) => {
const foundLanguages: string[] = []
for (const k in tags) {
const v = tags[k]
if (v !== "yes") {
continue
}
return foundLanguages
})
const forceInputMode = new UIEventSource(false);
if (k.startsWith(prefix)) {
foundLanguages.push(k.substring(prefix.length))
}
}
return foundLanguages
})
const forceInputMode = new UIEventSource(false)
const inputEl = new Lazy(() => {
const selector = new AllLanguagesSelector(
{
mode: mode === "single" ? "select-one" : "select-many",
currentCountry: tagSource.map(tgs => tgs["_country"])
}
)
const cancelButton = Toggle.If(forceInputMode,
() => Translations.t.general.cancel
const selector = new AllLanguagesSelector({
mode: mode === "single" ? "select-one" : "select-many",
currentCountry: tagSource.map((tgs) => tgs["_country"]),
})
const cancelButton = Toggle.If(forceInputMode, () =>
Translations.t.general.cancel
.Clone()
.SetClass("btn btn-secondary").onClick(() => forceInputMode.setData(false)))
.SetClass("btn btn-secondary")
.onClick(() => forceInputMode.setData(false))
)
const saveButton = new SaveButton(
selector.GetValue().map(lngs => lngs.length > 0 ? "true" : undefined),
state.osmConnection,
selector.GetValue().map((lngs) => (lngs.length > 0 ? "true" : undefined)),
state.osmConnection
).onClick(() => {
const selectedLanguages = selector.GetValue().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) {
if (selectedLanguages.indexOf(currentLanguage) >= 0) {
@ -148,19 +150,23 @@ export class LanguageElement implements SpecialVisualization {
selection.push(new Tag(prefix + currentLanguage, ""))
}
if (state.featureSwitchIsTesting.data) {
for (const tag of selection) {
tagSource.data[tag.key] = tag.value
}
tagSource.ping()
} else {
(state?.changes)
;(state?.changes)
.applyAction(
new ChangeTagAction(tagSource.data.id, new And(selection), tagSource.data, {
theme: state?.layoutToUse?.id ?? "unkown",
changeType: "answer",
})
new ChangeTagAction(
tagSource.data.id,
new And(selection),
tagSource.data,
{
theme: state?.layoutToUse?.id ?? "unkown",
changeType: "answer",
}
)
)
.then((_) => {
console.log("Tagchanges applied")
@ -169,60 +175,72 @@ export class LanguageElement implements SpecialVisualization {
forceInputMode.setData(false)
})
return new Combine([new Title(question), selector,
new Combine([cancelButton, saveButton]).SetClass("flex justify-end")
]).SetClass("flex flex-col question disable-links");
return new Combine([
new Title(question),
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))
return new VariableUiElement(foundLanguages
.map(foundLanguages => {
if (forceInputMode.data) {
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 new VariableUiElement(
foundLanguages.map(
(foundLanguages) => {
if (forceInputMode.data) {
return inputEl
}
return inputEl
}
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
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"
)
})
);
mapping.set("list", languagesList)
rendered = new SubstitutedTranslation(
new Translation({'*': all_render}, undefined), tagSource,
state, mapping
)
}
return new Combine([rendered, editButton]).SetClass("flex justify-between")
}
return inputEl
}
}, [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 {MapillaryLink} from "../BigComponents/MapillaryLink";
import {UIEventSource} from "../../Logic/UIEventSource";
import Loc from "../../Models/Loc";
import {SpecialVisualization} from "../SpecialVisualization";
import { GeoOperations } from "../../Logic/GeoOperations"
import { MapillaryLink } from "../BigComponents/MapillaryLink"
import { UIEventSource } from "../../Logic/UIEventSource"
import Loc from "../../Models/Loc"
import { SpecialVisualization } from "../SpecialVisualization"
export class MapillaryLinkVis implements SpecialVisualization {
funcName = "mapillary_link"

View file

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

View file

@ -1,10 +1,11 @@
import {Store} from "../../Logic/UIEventSource";
import MultiApply from "./MultiApply";
import {SpecialVisualization} from "../SpecialVisualization";
import { Store } from "../../Logic/UIEventSource"
import MultiApply from "./MultiApply"
import { SpecialVisualization } from "../SpecialVisualization"
export class MultiApplyViz implements SpecialVisualization {
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 = [
{
name: "feature_ids",
@ -52,17 +53,14 @@ export class MultiApplyViz implements SpecialVisualization {
return []
}
})
return new MultiApply(
{
featureIds,
keysToApply,
text,
autoapply,
overwrite,
tagsSource,
state
}
);
return new MultiApply({
featureIds,
keysToApply,
text,
autoapply,
overwrite,
tagsSource,
state,
})
}
}

View file

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

View file

@ -1,29 +1,30 @@
import {Store, UIEventSource} from "../../Logic/UIEventSource";
import Toggle from "../Input/Toggle";
import Lazy from "../Base/Lazy";
import {ProvidedImage} from "../../Logic/ImageProviders/ImageProvider";
import PlantNetSpeciesSearch from "../BigComponents/PlantNetSpeciesSearch";
import Wikidata from "../../Logic/Web/Wikidata";
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction";
import {And} from "../../Logic/Tags/And";
import {Tag} from "../../Logic/Tags/Tag";
import {SubtleButton} from "../Base/SubtleButton";
import Combine from "../Base/Combine";
import Svg from "../../Svg";
import Translations from "../i18n/Translations";
import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders";
import {SpecialVisualization} from "../SpecialVisualization";
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import Toggle from "../Input/Toggle"
import Lazy from "../Base/Lazy"
import { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
import PlantNetSpeciesSearch from "../BigComponents/PlantNetSpeciesSearch"
import Wikidata from "../../Logic/Web/Wikidata"
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"
import { And } from "../../Logic/Tags/And"
import { Tag } from "../../Logic/Tags/Tag"
import { SubtleButton } from "../Base/SubtleButton"
import Combine from "../Base/Combine"
import Svg from "../../Svg"
import Translations from "../i18n/Translations"
import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders"
import { SpecialVisualization } from "../SpecialVisualization"
export class PlantNetDetectionViz implements SpecialVisualization {
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 = [
{
name: "image_key",
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 ",
}
},
]
public constr(state, tags, args) {
@ -35,44 +36,40 @@ export class PlantNetDetectionViz implements SpecialVisualization {
const detect = new UIEventSource(false)
const toggle = new Toggle(
new Lazy(() => {
const allProvidedImages: Store<ProvidedImage[]> =
AllImageProviders.LoadImagesFor(tags, imagePrefixes)
const allProvidedImages: Store<ProvidedImage[]> = AllImageProviders.LoadImagesFor(
tags,
imagePrefixes
)
const allImages: Store<string[]> = allProvidedImages.map((pi) =>
pi.map((pi) => pi.url)
)
return new PlantNetSpeciesSearch(
allImages,
async (selectedWikidata) => {
selectedWikidata = Wikidata.ExtractKey(selectedWikidata)
const change = new ChangeTagAction(
tags.data.id,
new And([
new Tag("species:wikidata", selectedWikidata),
new Tag("source:species:wikidata", "PlantNet.org AI"),
]),
tags.data,
{
theme: state.layoutToUse.id,
changeType: "plantnet-ai-detection",
}
)
await state.changes.applyAction(change)
}
)
return new PlantNetSpeciesSearch(allImages, async (selectedWikidata) => {
selectedWikidata = Wikidata.ExtractKey(selectedWikidata)
const change = new ChangeTagAction(
tags.data.id,
new And([
new Tag("species:wikidata", selectedWikidata),
new Tag("source:species:wikidata", "PlantNet.org AI"),
]),
tags.data,
{
theme: state.layoutToUse.id,
changeType: "plantnet-ai-detection",
}
)
await state.changes.applyAction(change)
})
}),
new SubtleButton(
undefined,
"Detect plant species with plantnet.org"
).onClick(() => detect.setData(true)),
new SubtleButton(undefined, "Detect plant species with plantnet.org").onClick(() =>
detect.setData(true)
),
detect
)
return new Combine([
toggle,
new Combine([
Svg.plantnet_logo_svg().SetClass(
"w-10 h-10 p-1 mr-1 bg-white rounded-full"
),
Svg.plantnet_logo_svg().SetClass("w-10 h-10 p-1 mr-1 bg-white rounded-full"),
Translations.t.plantDetection.poweredByPlantnet,
]).SetClass("flex p-2 bg-gray-200 rounded-xl self-end"),
]).SetClass("flex flex-col")

View file

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

View file

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

View file

@ -1,15 +1,16 @@
import {UIEventSource} from "../../Logic/UIEventSource";
import Loc from "../../Models/Loc";
import Minimap from "../Base/Minimap";
import ShowDataLayer from "../ShowDataLayer/ShowDataLayer";
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
import * as left_right_style_json from "../../assets/layers/left_right_style/left_right_style.json";
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource";
import {SpecialVisualization} from "../SpecialVisualization";
import { UIEventSource } from "../../Logic/UIEventSource"
import Loc from "../../Models/Loc"
import Minimap from "../Base/Minimap"
import ShowDataLayer from "../ShowDataLayer/ShowDataLayer"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import * as left_right_style_json from "../../assets/layers/left_right_style/left_right_style.json"
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"
import { SpecialVisualization } from "../SpecialVisualization"
export class SidedMinimap implements SpecialVisualization {
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 = [
{
doc: "The side to show, either `left` or `right`",
@ -33,18 +34,14 @@ export class SidedMinimap implements SpecialVisualization {
})
const side = args[0]
const feature = state.allElements.ContainingFeatures.get(tagSource.data.id)
const copy = {...feature}
const copy = { ...feature }
copy.properties = {
id: side,
}
new ShowDataLayer({
leafletMap: minimap["leafletMap"],
zoomToFeatures: true,
layerToShow: new LayerConfig(
left_right_style_json,
"all_known_layers",
true
),
layerToShow: new LayerConfig(left_right_style_json, "all_known_layers", true),
features: StaticFeatureSource.fromGeojson([copy]),
state,
})

View file

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

View file

@ -14,9 +14,9 @@ import { Tag } from "../../Logic/Tags/Tag"
import FeaturePipelineState from "../../Logic/State/FeaturePipelineState"
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
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 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" +

View file

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

View file

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

32
test.ts
View file

@ -1,24 +1,26 @@
import {LanguageElement} from "./UI/Popup/LanguageElement";
import {ImmutableStore, UIEventSource} from "./Logic/UIEventSource";
import {VariableUiElement} from "./UI/Base/VariableUIElement";
import Locale from "./UI/i18n/Locale";
import {OsmConnection} from "./Logic/Osm/OsmConnection";
import { LanguageElement } from "./UI/Popup/LanguageElement"
import { ImmutableStore, UIEventSource } from "./Logic/UIEventSource"
import { VariableUiElement } from "./UI/Base/VariableUIElement"
import Locale from "./UI/i18n/Locale"
import { OsmConnection } from "./Logic/Osm/OsmConnection"
const tgs = new UIEventSource({
"name": "xyz",
"id": "node/1234",
"_country" : "BE",
name: "xyz",
id: "node/1234",
_country: "BE",
})
Locale.language.setData("nl")
console.log(tgs)
console.log("Locale", Locale.language)
const conn = new OsmConnection({})
new LanguageElement().constr(<any> {osmConnection: conn, featureSwitchIsTesting: new ImmutableStore(true)}, tgs, [
"language",
"What languages are spoken here?",
"{language()} is spoken here",
"{language()} is the only language spoken here",
"The following languages are spoken here: {list()}"
]).AttachTo("maindiv")
new LanguageElement()
.constr(<any>{ osmConnection: conn, featureSwitchIsTesting: new ImmutableStore(true) }, tgs, [
"language",
"What languages are spoken here?",
"{language()} is spoken here",
"{language()} is the only language spoken here",
"The following languages are spoken here: {list()}",
])
.AttachTo("maindiv")
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"
)
if(special.args === undefined){
throw "The field 'args' is undefined for special visualisation "+special.funcName
if (special.args === undefined) {
throw (
"The field 'args' is undefined for special visualisation " +
special.funcName
)
}
for (const arg of special.args) {