forked from MapComplete/MapComplete
Full code cleanup
This commit is contained in:
parent
3a4a2a2016
commit
fa971ffbbf
300 changed files with 16352 additions and 19284 deletions
|
@ -11,7 +11,6 @@ import FeaturedMessage from "./BigComponents/FeaturedMessage";
|
|||
import Toggle from "./Input/Toggle";
|
||||
import {SubtleButton} from "./Base/SubtleButton";
|
||||
import {VariableUiElement} from "./Base/VariableUIElement";
|
||||
import Title from "./Base/Title";
|
||||
import Svg from "../Svg";
|
||||
|
||||
export default class AllThemesGui {
|
||||
|
@ -36,12 +35,12 @@ export default class AllThemesGui {
|
|||
new SubtleButton(undefined, Translations.t.index.logIn).SetStyle("height:min-content").onClick(() => state.osmConnection.AttemptLogin()),
|
||||
state.osmConnection.isLoggedIn),
|
||||
new VariableUiElement(state.osmConnection.userDetails.map(ud => {
|
||||
if(ud.csCount < Constants.userJourney.importHelperUnlock){
|
||||
if (ud.csCount < Constants.userJourney.importHelperUnlock) {
|
||||
return undefined;
|
||||
}
|
||||
return new Combine([
|
||||
new SubtleButton( undefined, Translations.t.importHelper.title, {url: "import_helper.html"}),
|
||||
new SubtleButton( Svg.note_svg(), Translations.t.importInspector.title, {url: "import_viewer.html"})
|
||||
new SubtleButton(undefined, Translations.t.importHelper.title, {url: "import_helper.html"}),
|
||||
new SubtleButton(Svg.note_svg(), Translations.t.importInspector.title, {url: "import_viewer.html"})
|
||||
]).SetClass("p-4 border-2 border-gray-500 m-4 block")
|
||||
})),
|
||||
Translations.t.general.aboutMapcomplete
|
||||
|
|
|
@ -30,7 +30,7 @@ import DynamicGeoJsonTileSource from "../Logic/FeatureSource/TiledFeatureSource/
|
|||
import * as themeOverview from "../assets/generated/theme_overview.json"
|
||||
|
||||
|
||||
class AutomationPanel extends Combine{
|
||||
class AutomationPanel extends Combine {
|
||||
private static readonly openChangeset = new UIEventSource<number>(undefined);
|
||||
|
||||
constructor(layoutToUse: LayoutConfig, indices: number[], extraCommentText: UIEventSource<string>, tagRenderingToAutomate: { layer: LayerConfig, tagRendering: TagRenderingConfig }) {
|
||||
|
@ -39,10 +39,10 @@ class AutomationPanel extends Combine{
|
|||
const tileState = LocalStorageSource.GetParsed("automation-tile_state-" + layerId + "-" + trId, {})
|
||||
const logMessages = new UIEventSource<string[]>([])
|
||||
if (indices === undefined) {
|
||||
throw ("No tiles loaded - can not automate")
|
||||
throw ("No tiles loaded - can not automate")
|
||||
}
|
||||
const openChangeset = AutomationPanel.openChangeset;
|
||||
|
||||
|
||||
openChangeset.addCallbackAndRun(cs => console.trace("Sync current open changeset to:", cs))
|
||||
|
||||
const nextTileToHandle = tileState.map(handledTiles => {
|
||||
|
@ -62,22 +62,22 @@ class AutomationPanel extends Combine{
|
|||
if (tileIndex === undefined) {
|
||||
return new FixedUiElement("All done!").SetClass("thanks")
|
||||
}
|
||||
console.warn("Triggered map on nextTileToHandle",tileIndex)
|
||||
console.warn("Triggered map on nextTileToHandle", tileIndex)
|
||||
const start = new Date()
|
||||
return AutomationPanel.TileHandler(layoutToUse, tileIndex, layerId, tagRenderingToAutomate.tagRendering, extraCommentText,
|
||||
openChangeset,
|
||||
(result, logMessage) => {
|
||||
const end = new Date()
|
||||
const timeNeeded = (end.getTime() - start.getTime()) / 1000;
|
||||
neededTimes.data.push(timeNeeded)
|
||||
neededTimes.ping()
|
||||
tileState.data[tileIndex] = result
|
||||
tileState.ping();
|
||||
if(logMessage !== undefined){
|
||||
logMessages.data.push(logMessage)
|
||||
logMessages.ping();
|
||||
}
|
||||
});
|
||||
const end = new Date()
|
||||
const timeNeeded = (end.getTime() - start.getTime()) / 1000;
|
||||
neededTimes.data.push(timeNeeded)
|
||||
neededTimes.ping()
|
||||
tileState.data[tileIndex] = result
|
||||
tileState.ping();
|
||||
if (logMessage !== undefined) {
|
||||
logMessages.data.push(logMessage)
|
||||
logMessages.ping();
|
||||
}
|
||||
});
|
||||
}))
|
||||
|
||||
|
||||
|
@ -102,27 +102,27 @@ class AutomationPanel extends Combine{
|
|||
]).SetClass("flex flex-col")
|
||||
}))
|
||||
|
||||
super([statistics, automaton,
|
||||
new SubtleButton(undefined, "Clear fixed").onClick(() => {
|
||||
super([statistics, automaton,
|
||||
new SubtleButton(undefined, "Clear fixed").onClick(() => {
|
||||
const st = tileState.data
|
||||
for (const tileIndex in st) {
|
||||
if(st[tileIndex] === "fixed"){
|
||||
delete st[tileIndex]
|
||||
}
|
||||
}
|
||||
|
||||
tileState.ping();
|
||||
}),
|
||||
new VariableUiElement(logMessages.map(logMessages => new List(logMessages)))])
|
||||
this.SetClass("flex flex-col")
|
||||
for (const tileIndex in st) {
|
||||
if (st[tileIndex] === "fixed") {
|
||||
delete st[tileIndex]
|
||||
}
|
||||
}
|
||||
|
||||
tileState.ping();
|
||||
}),
|
||||
new VariableUiElement(logMessages.map(logMessages => new List(logMessages)))])
|
||||
this.SetClass("flex flex-col")
|
||||
}
|
||||
|
||||
private static TileHandler(layoutToUse: LayoutConfig, tileIndex: number, targetLayer: string, targetAction: TagRenderingConfig, extraCommentText: UIEventSource<string>,
|
||||
private static TileHandler(layoutToUse: LayoutConfig, tileIndex: number, targetLayer: string, targetAction: TagRenderingConfig, extraCommentText: UIEventSource<string>,
|
||||
openChangeset: UIEventSource<number>,
|
||||
whenDone: ((result: string, logMessage?: string) => void)): BaseUIElement {
|
||||
|
||||
const state = new MapState(layoutToUse, {attemptLogin: false})
|
||||
extraCommentText.syncWith( state.changes.extraComment)
|
||||
extraCommentText.syncWith(state.changes.extraComment)
|
||||
const [z, x, y] = Tiles.tile_from_index(tileIndex)
|
||||
state.locationControl.setData({
|
||||
zoom: z,
|
||||
|
@ -176,7 +176,7 @@ class AutomationPanel extends Combine{
|
|||
const feature = ffs.feature
|
||||
const renderingTr = targetAction.GetRenderValue(feature.properties)
|
||||
const rendering = renderingTr.txt
|
||||
log.push("<a href='https://openstreetmap.org/"+feature.properties.id+"' target='_blank'>"+feature.properties.id+"</a>: "+new SubstitutedTranslation(renderingTr, new UIEventSource<any>(feature.properties), undefined).ConstructElement().innerText)
|
||||
log.push("<a href='https://openstreetmap.org/" + feature.properties.id + "' target='_blank'>" + feature.properties.id + "</a>: " + new SubstitutedTranslation(renderingTr, new UIEventSource<any>(feature.properties), undefined).ConstructElement().innerText)
|
||||
const actions = Utils.NoNull(SubstitutedTranslation.ExtractSpecialComponents(rendering)
|
||||
.map(obj => obj.special))
|
||||
for (const action of actions) {
|
||||
|
@ -201,11 +201,11 @@ class AutomationPanel extends Combine{
|
|||
}
|
||||
|
||||
if (handled === 0) {
|
||||
whenDone("no-action","Inspected "+inspected+" elements: "+log.join("; "))
|
||||
}else{
|
||||
whenDone("no-action", "Inspected " + inspected + " elements: " + log.join("; "))
|
||||
} else {
|
||||
state.osmConnection.AttemptLogin()
|
||||
state.changes.flushChanges("handled tile automatically, time to flush!", openChangeset)
|
||||
whenDone("fixed", "Updated " + handled+" elements, inspected "+inspected+": "+log.join("; "))
|
||||
whenDone("fixed", "Updated " + handled + " elements, inspected " + inspected + ": " + log.join("; "))
|
||||
}
|
||||
return true;
|
||||
|
||||
|
@ -219,7 +219,6 @@ class AutomationPanel extends Combine{
|
|||
}
|
||||
|
||||
|
||||
|
||||
class AutomatonGui {
|
||||
|
||||
constructor() {
|
||||
|
@ -242,7 +241,7 @@ class AutomatonGui {
|
|||
AutomatonGui.GenerateMainPanel(),
|
||||
new SubtleButton(Svg.osm_logo_svg(), "Login to get started").onClick(() => osmConnection.AttemptLogin()),
|
||||
osmConnection.isLoggedIn
|
||||
)]) .SetClass("block p-4")
|
||||
)]).SetClass("block p-4")
|
||||
.AttachTo("main")
|
||||
}
|
||||
|
||||
|
@ -250,7 +249,7 @@ class AutomatonGui {
|
|||
private static GenerateMainPanel(): BaseUIElement {
|
||||
|
||||
const themeSelect = new DropDown<string>("Select a theme",
|
||||
Array.from(themeOverview).map(l => ({value: l.id, shown: l.id}))
|
||||
Array.from(themeOverview).map(l => ({value: l.id, shown: l.id}))
|
||||
)
|
||||
|
||||
LocalStorageSource.Get("automation-theme-id", "missing_streets").syncWith(themeSelect.GetValue())
|
||||
|
@ -262,24 +261,24 @@ class AutomatonGui {
|
|||
tilepath.SetClass("w-full")
|
||||
LocalStorageSource.Get("automation-tile_path").syncWith(tilepath.GetValue(), true)
|
||||
|
||||
|
||||
|
||||
let tilesToRunOver = tilepath.GetValue().bind(path => {
|
||||
if (path === undefined) {
|
||||
return undefined
|
||||
}
|
||||
return UIEventSource.FromPromiseWithErr(Utils.downloadJsonCached(path,1000*60*60))
|
||||
return UIEventSource.FromPromiseWithErr(Utils.downloadJsonCached(path, 1000 * 60 * 60))
|
||||
})
|
||||
|
||||
|
||||
const targetZoom = 14
|
||||
|
||||
const tilesPerIndex = tilesToRunOver.map(tiles => {
|
||||
|
||||
|
||||
if (tiles === undefined || tiles["error"] !== undefined) {
|
||||
return undefined
|
||||
}
|
||||
let indexes : number[] = [];
|
||||
let indexes: number[] = [];
|
||||
const tilesS = tiles["success"]
|
||||
DynamicGeoJsonTileSource.RegisterWhitelist(tilepath.GetValue().data , tilesS)
|
||||
DynamicGeoJsonTileSource.RegisterWhitelist(tilepath.GetValue().data, tilesS)
|
||||
const z = Number(tilesS["zoom"])
|
||||
for (const key in tilesS) {
|
||||
if (key === "zoom") {
|
||||
|
@ -315,7 +314,7 @@ class AutomatonGui {
|
|||
tilepath,
|
||||
"Add an extra comment:",
|
||||
extraComment,
|
||||
new VariableUiElement(extraComment.GetValue().map(c => "Your comment is "+(c?.length??0)+"/200 characters long")).SetClass("subtle"),
|
||||
new VariableUiElement(extraComment.GetValue().map(c => "Your comment is " + (c?.length ?? 0) + "/200 characters long")).SetClass("subtle"),
|
||||
new VariableUiElement(tilesToRunOver.map(t => {
|
||||
if (t === undefined) {
|
||||
return "No path given or still loading..."
|
||||
|
|
|
@ -31,6 +31,10 @@ export default class Combine extends BaseUIElement {
|
|||
}
|
||||
}
|
||||
|
||||
public getElements(): BaseUIElement[] {
|
||||
return this.uiElements
|
||||
}
|
||||
|
||||
protected InnerConstructElement(): HTMLElement {
|
||||
const el = document.createElement("span")
|
||||
try {
|
||||
|
@ -57,9 +61,5 @@ export default class Combine extends BaseUIElement {
|
|||
return el;
|
||||
}
|
||||
|
||||
public getElements(): BaseUIElement[] {
|
||||
return this.uiElements
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -29,6 +29,18 @@ export default class Img extends BaseUIElement {
|
|||
return `<img class="${css_class}" style="${style}" alt="" src="${Img.AsData(source)}">`;
|
||||
}
|
||||
|
||||
AsMarkdown(): string {
|
||||
if (this._rawSvg === true) {
|
||||
console.warn("Converting raw svgs to markdown is not supported");
|
||||
return undefined
|
||||
}
|
||||
let src = this._src
|
||||
if (this._src.startsWith("./")) {
|
||||
src = "https://mapcomplete.osm.be/" + src
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
protected InnerConstructElement(): HTMLElement {
|
||||
const self = this;
|
||||
if (this._rawSvg) {
|
||||
|
@ -53,17 +65,5 @@ export default class Img extends BaseUIElement {
|
|||
}
|
||||
return el;
|
||||
}
|
||||
|
||||
AsMarkdown(): string {
|
||||
if (this._rawSvg === true) {
|
||||
console.warn("Converting raw svgs to markdown is not supported");
|
||||
return undefined
|
||||
}
|
||||
let src = this._src
|
||||
if (this._src.startsWith("./")) {
|
||||
src = "https://mapcomplete.osm.be/" + src
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,15 +2,15 @@ import BaseUIElement from "../BaseUIElement";
|
|||
import Combine from "./Combine";
|
||||
import BackToIndex from "../BigComponents/BackToIndex";
|
||||
|
||||
export default class LeftIndex extends Combine{
|
||||
|
||||
|
||||
constructor(leftContents: BaseUIElement[], mainContent: BaseUIElement, options?:{
|
||||
hideBackButton : false | boolean
|
||||
} ) {
|
||||
|
||||
let back : BaseUIElement = undefined;
|
||||
if(options?.hideBackButton ?? true){
|
||||
export default class LeftIndex extends Combine {
|
||||
|
||||
|
||||
constructor(leftContents: BaseUIElement[], mainContent: BaseUIElement, options?: {
|
||||
hideBackButton: false | boolean
|
||||
}) {
|
||||
|
||||
let back: BaseUIElement = undefined;
|
||||
if (options?.hideBackButton ?? true) {
|
||||
back = new BackToIndex()
|
||||
}
|
||||
super([
|
||||
|
@ -21,5 +21,5 @@ export default class LeftIndex extends Combine{
|
|||
])
|
||||
this.SetClass("h-full block md:flex")
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -19,6 +19,17 @@ export default class Link extends BaseUIElement {
|
|||
|
||||
}
|
||||
|
||||
public static OsmWiki(key: string, value?: string, hideKey = false) {
|
||||
if (value !== undefined) {
|
||||
let k = "";
|
||||
if (!hideKey) {
|
||||
k = key + "="
|
||||
}
|
||||
return new Link(k + value, `https://wiki.openstreetmap.org/wiki/Tag:${key}%3D${value}`)
|
||||
}
|
||||
return new Link(key, "https://wiki.openstreetmap.org/wiki/Key:" + key)
|
||||
}
|
||||
|
||||
AsMarkdown(): string {
|
||||
// @ts-ignore
|
||||
return `[${this._embeddedShow.AsMarkdown()}](${this._href.data ?? this._href})`;
|
||||
|
@ -44,15 +55,4 @@ export default class Link extends BaseUIElement {
|
|||
return el;
|
||||
}
|
||||
|
||||
public static OsmWiki(key: string, value?: string, hideKey = false) {
|
||||
if (value !== undefined) {
|
||||
let k = "";
|
||||
if (!hideKey) {
|
||||
k = key + "="
|
||||
}
|
||||
return new Link(k + value, `https://wiki.openstreetmap.org/wiki/Tag:${key}%3D${value}`)
|
||||
}
|
||||
return new Link(key, "https://wiki.openstreetmap.org/wiki/Key:" + key)
|
||||
}
|
||||
|
||||
}
|
|
@ -34,6 +34,9 @@ export default class Minimap {
|
|||
* importing leaflet crashes node-ts, which is pretty annoying considering the fact that a lot of scripts use it
|
||||
*/
|
||||
|
||||
private constructor() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a minimap
|
||||
*/
|
||||
|
@ -41,8 +44,5 @@ export default class Minimap {
|
|||
throw "CreateMinimap hasn't been initialized yet. Please call MinimapImplementation.initialize()"
|
||||
}
|
||||
|
||||
private constructor() {
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -16,15 +16,15 @@ import AvailableBaseLayersImplementation from "../../Logic/Actors/AvailableBaseL
|
|||
export default class MinimapImplementation extends BaseUIElement implements MinimapObj {
|
||||
private static _nextId = 0;
|
||||
public readonly leafletMap: UIEventSource<Map>
|
||||
public readonly location: UIEventSource<Loc>;
|
||||
public readonly bounds: UIEventSource<BBox> | undefined;
|
||||
private readonly _id: string;
|
||||
private readonly _background: UIEventSource<BaseLayer>;
|
||||
public readonly location: UIEventSource<Loc>;
|
||||
private _isInited = false;
|
||||
private _allowMoving: boolean;
|
||||
private readonly _leafletoptions: any;
|
||||
private readonly _onFullyLoaded: (leaflet: L.Map) => void
|
||||
private readonly _attribution: BaseUIElement | boolean;
|
||||
public readonly bounds: UIEventSource<BBox> | undefined;
|
||||
private readonly _addLayerControl: boolean;
|
||||
private readonly _options: MinimapOptions;
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ export default class ScrollableFullScreen extends UIElement {
|
|||
|
||||
const self = this;
|
||||
Hash.hash.addCallback(h => {
|
||||
if(h === undefined){
|
||||
if (h === undefined) {
|
||||
isShown.setData(false)
|
||||
}
|
||||
})
|
||||
|
@ -55,18 +55,17 @@ export default class ScrollableFullScreen extends UIElement {
|
|||
self.Activate();
|
||||
} else {
|
||||
// Some cleanup...
|
||||
|
||||
|
||||
const fs = document.getElementById("fullscreen");
|
||||
if(fs !== null){
|
||||
if (fs !== null) {
|
||||
ScrollableFullScreen.empty.AttachTo("fullscreen")
|
||||
fs.classList.add("hidden")
|
||||
}
|
||||
|
||||
|
||||
ScrollableFullScreen._currentlyOpen?.isShown?.setData(false);
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ export default class TableOfContents extends Combine {
|
|||
} else {
|
||||
titles = elements ?? []
|
||||
}
|
||||
|
||||
|
||||
let els: { level: number, content: BaseUIElement }[] = []
|
||||
for (const title of titles) {
|
||||
let content: BaseUIElement
|
||||
|
@ -33,9 +33,9 @@ export default class TableOfContents extends Combine {
|
|||
content = new FixedUiElement(title.title.content)
|
||||
} else if (Utils.runningFromConsole) {
|
||||
content = new FixedUiElement(title.AsMarkdown())
|
||||
} else if(title["title"] !== undefined) {
|
||||
} else if (title["title"] !== undefined) {
|
||||
content = new FixedUiElement(title.title.ConstructElement().innerText)
|
||||
}else{
|
||||
} else {
|
||||
console.log("Not generating a title for ", title)
|
||||
continue
|
||||
}
|
||||
|
|
|
@ -3,12 +3,11 @@ import {FixedUiElement} from "./FixedUiElement";
|
|||
import {Utils} from "../../Utils";
|
||||
|
||||
export default class Title extends BaseUIElement {
|
||||
private static readonly defaultClassesPerLevel = ["", "text-3xl font-bold", "text-2xl font-bold", "text-xl font-bold", "text-lg font-bold"]
|
||||
public readonly title: BaseUIElement;
|
||||
public readonly level: number;
|
||||
public readonly id: string
|
||||
|
||||
private static readonly defaultClassesPerLevel = ["", "text-3xl font-bold", "text-2xl font-bold", "text-xl font-bold", "text-lg font-bold"]
|
||||
|
||||
constructor(embedded: string | BaseUIElement, level: number = 3) {
|
||||
super()
|
||||
if (embedded === undefined) {
|
||||
|
|
|
@ -33,19 +33,19 @@ export default class Toggleable extends Combine {
|
|||
title.SetClass("background-subtle rounded-lg")
|
||||
const self = this
|
||||
this.onClick(() => {
|
||||
if(self.isVisible.data){
|
||||
if(options?.closeOnClick ?? true){
|
||||
if (self.isVisible.data) {
|
||||
if (options?.closeOnClick ?? true) {
|
||||
self.isVisible.setData(false)
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
self.isVisible.setData(true)
|
||||
}
|
||||
})
|
||||
const contentElement = content.ConstructElement()
|
||||
|
||||
if(title instanceof Combine){
|
||||
for(const el of title.getElements()){
|
||||
if(el instanceof Title){
|
||||
|
||||
if (title instanceof Combine) {
|
||||
for (const el of title.getElements()) {
|
||||
if (el instanceof Title) {
|
||||
title = el;
|
||||
break;
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ export default class Toggleable extends Combine {
|
|||
|
||||
if (title instanceof Title) {
|
||||
Hash.hash.addCallbackAndRun(h => {
|
||||
if (h === (<Title> title).id) {
|
||||
if (h === (<Title>title).id) {
|
||||
self.isVisible.setData(true)
|
||||
content.RemoveClass("border-gray-300")
|
||||
content.SetClass("border-red-300")
|
||||
|
@ -82,9 +82,9 @@ export default class Toggleable extends Combine {
|
|||
})
|
||||
}
|
||||
|
||||
public Collapse() : Toggleable{
|
||||
public Collapse(): Toggleable {
|
||||
this.isVisible.setData(false)
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -15,6 +15,17 @@ export class VariableUiElement extends BaseUIElement {
|
|||
this.isDestroyed = true;
|
||||
}
|
||||
|
||||
AsMarkdown(): string {
|
||||
const d = this._contents.data;
|
||||
if (typeof d === "string") {
|
||||
return d;
|
||||
}
|
||||
if (d instanceof BaseUIElement) {
|
||||
return d.AsMarkdown()
|
||||
}
|
||||
return new Combine(<BaseUIElement[]>d).AsMarkdown()
|
||||
}
|
||||
|
||||
protected InnerConstructElement(): HTMLElement {
|
||||
const el = document.createElement("span");
|
||||
const self = this;
|
||||
|
@ -47,15 +58,4 @@ export class VariableUiElement extends BaseUIElement {
|
|||
});
|
||||
return el;
|
||||
}
|
||||
|
||||
AsMarkdown(): string {
|
||||
const d = this._contents.data;
|
||||
if (typeof d === "string") {
|
||||
return d;
|
||||
}
|
||||
if (d instanceof BaseUIElement) {
|
||||
return d.AsMarkdown()
|
||||
}
|
||||
return new Combine(<BaseUIElement[]>d).AsMarkdown()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,10 +8,10 @@ import {Utils} from "../Utils";
|
|||
export default abstract class BaseUIElement {
|
||||
|
||||
protected _constructedHtmlElement: HTMLElement;
|
||||
protected isDestroyed = false;
|
||||
private clss: Set<string> = new Set<string>();
|
||||
private style: string;
|
||||
private _onClick: () => void;
|
||||
protected isDestroyed = false;
|
||||
|
||||
public onClick(f: (() => void)) {
|
||||
this._onClick = f;
|
||||
|
@ -148,10 +148,10 @@ export default abstract class BaseUIElement {
|
|||
}
|
||||
|
||||
public AsMarkdown(): string {
|
||||
throw "AsMarkdown is not implemented by " + this.constructor.name+"; implement it in the subclass"
|
||||
throw "AsMarkdown is not implemented by " + this.constructor.name + "; implement it in the subclass"
|
||||
}
|
||||
|
||||
public Destroy(){
|
||||
|
||||
public Destroy() {
|
||||
this.isDestroyed = true;
|
||||
}
|
||||
|
||||
|
|
|
@ -27,8 +27,8 @@ export default class AddNewMarker extends Combine {
|
|||
}
|
||||
}
|
||||
}
|
||||
if(icons.length === 0){
|
||||
return undefined
|
||||
if (icons.length === 0) {
|
||||
return undefined
|
||||
}
|
||||
if (icons.length === 1) {
|
||||
return icons[0]
|
||||
|
|
|
@ -1,20 +1,19 @@
|
|||
import {SubtleButton} from "../Base/SubtleButton";
|
||||
import Combine from "../Base/Combine";
|
||||
import Svg from "../../Svg";
|
||||
import Translations from "../i18n/Translations";
|
||||
import BaseUIElement from "../BaseUIElement";
|
||||
|
||||
export default class BackToIndex extends SubtleButton {
|
||||
|
||||
constructor(message? : string | BaseUIElement) {
|
||||
|
||||
constructor(message?: string | BaseUIElement) {
|
||||
super(
|
||||
Svg.back_svg().SetStyle("height: 1.5rem;"),
|
||||
message ?? Translations.t.general.backToMapcomplete,
|
||||
message ?? Translations.t.general.backToMapcomplete,
|
||||
{
|
||||
url: "index.html"
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -84,7 +84,7 @@ class SingleLayerSelectionButton extends Toggle {
|
|||
previousLayer.setData(previousLayer.data ?? available.data)
|
||||
options.currentBackground.setData(previousLayer.data)
|
||||
})
|
||||
|
||||
|
||||
options.currentBackground.addCallbackAndRunD(background => {
|
||||
if (background.category === options.preferredType) {
|
||||
previousLayer.setData(background)
|
||||
|
@ -103,9 +103,9 @@ class SingleLayerSelectionButton extends Toggle {
|
|||
// The previously used layer doesn't match the current layer -> no need to switch
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Is the previous layer still valid? If so, we don't bother to switch
|
||||
if(previousLayer.data.feature === null || GeoOperations.inside(locationControl.data, previousLayer.data.feature)){
|
||||
if (previousLayer.data.feature === null || GeoOperations.inside(locationControl.data, previousLayer.data.feature)) {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -22,15 +22,15 @@ import Constants from "../../Models/Constants";
|
|||
import ContributorCount from "../../Logic/ContributorCount";
|
||||
|
||||
export class OpenIdEditor extends VariableUiElement {
|
||||
constructor(state : {locationControl: UIEventSource<Loc>}, iconStyle? : string, objectId?: string) {
|
||||
constructor(state: { locationControl: UIEventSource<Loc> }, iconStyle?: string, objectId?: string) {
|
||||
const t = Translations.t.general.attribution
|
||||
super(state.locationControl.map(location => {
|
||||
let elementSelect = "";
|
||||
if(objectId !== undefined){
|
||||
const parts = objectId.split("/")
|
||||
if (objectId !== undefined) {
|
||||
const parts = objectId.split("/")
|
||||
const tp = parts[0]
|
||||
if(parts.length === 2 && !isNaN(Number(parts[1])) && (tp === "node" || tp === "way" || tp === "relation")){
|
||||
elementSelect = "&"+ tp+"="+parts[1]
|
||||
if (parts.length === 2 && !isNaN(Number(parts[1])) && (tp === "node" || tp === "way" || tp === "relation")) {
|
||||
elementSelect = "&" + tp + "=" + parts[1]
|
||||
}
|
||||
}
|
||||
const idLink = `https://www.openstreetmap.org/edit?editor=id${elementSelect}#map=${location?.zoom ?? 0}/${location?.lat ?? 0}/${location?.lon ?? 0}`
|
||||
|
@ -41,9 +41,9 @@ export class OpenIdEditor extends VariableUiElement {
|
|||
}
|
||||
|
||||
export class OpenMapillary extends VariableUiElement {
|
||||
constructor(state : {locationControl: UIEventSource<Loc>}, iconStyle? : string) {
|
||||
constructor(state: { locationControl: UIEventSource<Loc> }, iconStyle?: string) {
|
||||
const t = Translations.t.general.attribution
|
||||
super( state.locationControl.map(location => {
|
||||
super(state.locationControl.map(location => {
|
||||
const mapillaryLink = `https://www.mapillary.com/app/?focus=map&lat=${location?.lat ?? 0}&lng=${location?.lon ?? 0}&z=${Math.max((location?.zoom ?? 2) - 1, 1)}`
|
||||
return new SubtleButton(Svg.mapillary_black_ui().SetStyle(iconStyle), t.openMapillary, {
|
||||
url: mapillaryLink,
|
||||
|
@ -55,13 +55,13 @@ export class OpenMapillary extends VariableUiElement {
|
|||
|
||||
export class OpenJosm extends Combine {
|
||||
|
||||
constructor(state : {osmConnection: OsmConnection, currentBounds: UIEventSource<BBox>,}, iconStyle? : string) {
|
||||
const t = Translations.t.general.attribution
|
||||
|
||||
constructor(state: { osmConnection: OsmConnection, currentBounds: UIEventSource<BBox>, }, iconStyle?: string) {
|
||||
const t = Translations.t.general.attribution
|
||||
|
||||
const josmState = new UIEventSource<string>(undefined)
|
||||
// Reset after 15s
|
||||
josmState.stabilized(15000).addCallbackD(_ => josmState.setData(undefined))
|
||||
|
||||
|
||||
const stateIndication = new VariableUiElement(josmState.map(state => {
|
||||
if (state === undefined) {
|
||||
return undefined
|
||||
|
@ -72,23 +72,23 @@ export class OpenJosm extends Combine {
|
|||
}
|
||||
return t.josmNotOpened.SetClass("alert")
|
||||
}));
|
||||
|
||||
const toggle = new Toggle(
|
||||
new SubtleButton(Svg.josm_logo_ui().SetStyle(iconStyle), t.editJosm).onClick(() => {
|
||||
const bounds: any = state.currentBounds.data;
|
||||
if (bounds === undefined) {
|
||||
return undefined
|
||||
}
|
||||
const top = bounds.getNorth();
|
||||
const bottom = bounds.getSouth();
|
||||
const right = bounds.getEast();
|
||||
const left = bounds.getWest();
|
||||
const josmLink = `http://127.0.0.1:8111/load_and_zoom?left=${left}&right=${right}&top=${top}&bottom=${bottom}`
|
||||
Utils.download(josmLink).then(answer => josmState.setData(answer.replace(/\n/g, '').trim())).catch(_ => josmState.setData("ERROR"))
|
||||
}), undefined, state.osmConnection.userDetails.map(ud => ud.loggedIn && ud.csCount >= Constants.userJourney.historyLinkVisible))
|
||||
|
||||
const toggle = new Toggle(
|
||||
new SubtleButton(Svg.josm_logo_ui().SetStyle(iconStyle), t.editJosm).onClick(() => {
|
||||
const bounds: any = state.currentBounds.data;
|
||||
if (bounds === undefined) {
|
||||
return undefined
|
||||
}
|
||||
const top = bounds.getNorth();
|
||||
const bottom = bounds.getSouth();
|
||||
const right = bounds.getEast();
|
||||
const left = bounds.getWest();
|
||||
const josmLink = `http://127.0.0.1:8111/load_and_zoom?left=${left}&right=${right}&top=${top}&bottom=${bottom}`
|
||||
Utils.download(josmLink).then(answer => josmState.setData(answer.replace(/\n/g, '').trim())).catch(_ => josmState.setData("ERROR"))
|
||||
}), undefined, state.osmConnection.userDetails.map(ud => ud.loggedIn && ud.csCount >= Constants.userJourney.historyLinkVisible))
|
||||
|
||||
super([stateIndication, toggle]);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -112,7 +112,7 @@ export default class CopyrightPanel extends Combine {
|
|||
|
||||
const t = Translations.t.general.attribution
|
||||
const layoutToUse = state.layoutToUse
|
||||
const iconStyle = "height: 1.5rem; width: auto"
|
||||
const iconStyle = "height: 1.5rem; width: auto"
|
||||
const actionButtons = [
|
||||
new SubtleButton(Svg.liberapay_ui().SetStyle(iconStyle), t.donate, {
|
||||
url: "https://liberapay.com/pietervdvn/",
|
||||
|
@ -139,11 +139,11 @@ export default class CopyrightPanel extends Combine {
|
|||
maintainer = Translations.t.general.attribution.themeBy.Subs({author: layoutToUse.maintainer})
|
||||
}
|
||||
|
||||
const contributions = new ContributorCount(state).Contributors
|
||||
|
||||
const contributions = new ContributorCount(state).Contributors
|
||||
|
||||
super([
|
||||
Translations.t.general.attribution.attributionContent,
|
||||
new FixedUiElement("MapComplete "+Constants.vNumber).SetClass("font-bold"),
|
||||
new FixedUiElement("MapComplete " + Constants.vNumber).SetClass("font-bold"),
|
||||
maintainer,
|
||||
new Combine(actionButtons).SetClass("block w-full"),
|
||||
new FixedUiElement(layoutToUse.credits),
|
||||
|
|
|
@ -33,12 +33,12 @@ export default class FeaturedMessage extends Combine {
|
|||
|
||||
public static WelcomeMessages(): { start_date: Date, end_date: Date, message: string, featured_theme?: string }[] {
|
||||
const all_messages: { start_date: Date, end_date: Date, message: string, featured_theme?: string }[] = []
|
||||
|
||||
const themesById = new Map<string, {id: string, title: any, shortDescription: any}>();
|
||||
|
||||
const themesById = new Map<string, { id: string, title: any, shortDescription: any }>();
|
||||
for (const theme of themeOverview["default"]) {
|
||||
themesById.set(theme.id, theme);
|
||||
}
|
||||
|
||||
|
||||
for (const i in welcome_messages) {
|
||||
if (isNaN(Number(i))) {
|
||||
continue
|
||||
|
@ -78,9 +78,9 @@ export default class FeaturedMessage extends Combine {
|
|||
const msg = new FixedUiElement(welcome_message.message).SetClass("link-underline font-lg")
|
||||
els.push(new Combine([title, msg]).SetClass("m-4"))
|
||||
if (welcome_message.featured_theme !== undefined) {
|
||||
|
||||
|
||||
const theme = themeOverview["default"].filter(th => th.id === welcome_message.featured_theme)[0];
|
||||
|
||||
|
||||
els.push(MoreScreen.createLinkButton({}, theme)
|
||||
.SetClass("m-4 self-center md:w-160")
|
||||
.SetStyle("height: min-content;"))
|
||||
|
|
|
@ -145,14 +145,14 @@ export default class FilterView extends VariableUiElement {
|
|||
if (layer.filters.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
const toShow : BaseUIElement [] = []
|
||||
|
||||
|
||||
const toShow: BaseUIElement [] = []
|
||||
|
||||
for (const filter of layer.filters) {
|
||||
|
||||
|
||||
const [ui, actualTags] = FilterView.createFilter(filter)
|
||||
|
||||
|
||||
ui.SetClass("mt-3")
|
||||
toShow.push(ui)
|
||||
actualTags.addCallback(tagsToFilterFor => {
|
||||
|
@ -161,15 +161,15 @@ export default class FilterView extends VariableUiElement {
|
|||
})
|
||||
flayer.appliedFilters.map(dict => dict.get(filter.id))
|
||||
.addCallbackAndRun(filters => actualTags.setData(filters))
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
return new Combine(toShow)
|
||||
.SetClass("flex flex-col ml-8 bg-gray-300 rounded-xl p-2")
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Filter which uses one or more textfields
|
||||
private static createFilterWithFields(filterConfig: FilterConfig): [BaseUIElement, UIEventSource<FilterState>] {
|
||||
|
||||
|
@ -191,7 +191,7 @@ export default class FilterView extends VariableUiElement {
|
|||
allValid = allValid.map(previous => previous && field.IsValid(stable.data) && stable.data !== "", [stable])
|
||||
}
|
||||
const tr = new SubstitutedTranslation(filter.question, new UIEventSource<any>({id: filterConfig.id}), State.state, mappings)
|
||||
const trigger : UIEventSource<FilterState>= allValid.map(isValid => {
|
||||
const trigger: UIEventSource<FilterState> = allValid.map(isValid => {
|
||||
if (!isValid) {
|
||||
return undefined
|
||||
}
|
||||
|
@ -204,9 +204,9 @@ export default class FilterView extends VariableUiElement {
|
|||
}
|
||||
|
||||
for (const key in props) {
|
||||
v = (<string>v).replace("{"+key+"}", props[key])
|
||||
v = (<string>v).replace("{" + key + "}", props[key])
|
||||
}
|
||||
|
||||
|
||||
return v
|
||||
}
|
||||
)
|
||||
|
@ -216,11 +216,11 @@ export default class FilterView extends VariableUiElement {
|
|||
state: JSON.stringify(props)
|
||||
}
|
||||
}, [properties])
|
||||
|
||||
|
||||
return [tr, trigger];
|
||||
}
|
||||
|
||||
private static createCheckboxFilter(filterConfig: FilterConfig): [BaseUIElement, UIEventSource<FilterState>] {
|
||||
|
||||
private static createCheckboxFilter(filterConfig: FilterConfig): [BaseUIElement, UIEventSource<FilterState>] {
|
||||
let option = filterConfig.options[0];
|
||||
|
||||
const icon = Svg.checkbox_filled_svg().SetClass("block mr-2 w-6");
|
||||
|
@ -233,21 +233,25 @@ export default class FilterView extends VariableUiElement {
|
|||
.ToggleOnClick()
|
||||
.SetClass("block m-1")
|
||||
|
||||
return [toggle, toggle.isEnabled.map(enabled => enabled ? {currentFilter: option.osmTags, state: "true"} : undefined, [],
|
||||
return [toggle, toggle.isEnabled.map(enabled => enabled ? {
|
||||
currentFilter: option.osmTags,
|
||||
state: "true"
|
||||
} : undefined, [],
|
||||
f => f !== undefined)
|
||||
]
|
||||
}
|
||||
|
||||
private static createMultiFilter(filterConfig: FilterConfig): [BaseUIElement, UIEventSource<FilterState>] {
|
||||
|
||||
let options = filterConfig.options;
|
||||
|
||||
const values : FilterState[] = options.map((f, i) => ({
|
||||
const values: FilterState[] = options.map((f, i) => ({
|
||||
currentFilter: f.osmTags, state: i
|
||||
}))
|
||||
let filterPicker : InputElement<number>
|
||||
|
||||
if(options.length <= 6){
|
||||
filterPicker = new RadioButton(
|
||||
let filterPicker: InputElement<number>
|
||||
|
||||
if (options.length <= 6) {
|
||||
filterPicker = new RadioButton(
|
||||
options.map(
|
||||
(option, i) =>
|
||||
new FixedInputElement(option.question.Clone().SetClass("block"), i)
|
||||
|
@ -256,25 +260,26 @@ export default class FilterView extends VariableUiElement {
|
|||
dontStyle: true
|
||||
}
|
||||
);
|
||||
}else{
|
||||
} else {
|
||||
filterPicker = new DropDown("", options.map((option, i) => ({
|
||||
value: i, shown: option.question.Clone()
|
||||
})))
|
||||
}
|
||||
|
||||
|
||||
return [filterPicker,
|
||||
filterPicker.GetValue().map(
|
||||
i => values[i],
|
||||
[],
|
||||
selected => {
|
||||
const v = selected?.state
|
||||
if(v === undefined || typeof v === "string"){
|
||||
if (v === undefined || typeof v === "string") {
|
||||
return undefined
|
||||
}
|
||||
return v
|
||||
}
|
||||
)]
|
||||
}
|
||||
|
||||
private static createFilter(filterConfig: FilterConfig): [BaseUIElement, UIEventSource<FilterState>] {
|
||||
|
||||
if (filterConfig.options[0].fields.length > 0) {
|
||||
|
@ -283,7 +288,7 @@ export default class FilterView extends VariableUiElement {
|
|||
|
||||
|
||||
if (filterConfig.options.length === 1) {
|
||||
return FilterView.createCheckboxFilter(filterConfig)
|
||||
return FilterView.createCheckboxFilter(filterConfig)
|
||||
}
|
||||
|
||||
return FilterView.createMultiFilter(filterConfig)
|
||||
|
|
|
@ -92,7 +92,7 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
|
|||
)
|
||||
}
|
||||
tabs.push(copyright)
|
||||
|
||||
|
||||
const privacy = {
|
||||
header: Svg.eye_svg(),
|
||||
content: new PrivacyPolicy()
|
||||
|
|
|
@ -22,12 +22,12 @@ export default class Histogram<T> extends VariableUiElement {
|
|||
constructor(values: UIEventSource<string[]>,
|
||||
title: string | BaseUIElement,
|
||||
countTitle: string | BaseUIElement,
|
||||
options?:{
|
||||
assignColor?: (t0: string) => string,
|
||||
options?: {
|
||||
assignColor?: (t0: string) => string,
|
||||
sortMode?: "name" | "name-rev" | "count" | "count-rev"
|
||||
}
|
||||
) {
|
||||
const sortMode = new UIEventSource<"name" | "name-rev" | "count" | "count-rev">(options?.sortMode ??"name")
|
||||
const sortMode = new UIEventSource<"name" | "name-rev" | "count" | "count-rev">(options?.sortMode ?? "name")
|
||||
const sortName = new VariableUiElement(sortMode.map(m => {
|
||||
switch (m) {
|
||||
case "name":
|
||||
|
|
|
@ -50,7 +50,7 @@ export default class LeftControls extends Combine {
|
|||
}
|
||||
return new Lazy(() => {
|
||||
const tagsSource = state.allElements.getEventSourceById(feature.properties.id)
|
||||
return new FeatureInfoBox(tagsSource, currentViewFL.layerDef,state, "currentview", guiState.currentViewControlIsOpened)
|
||||
return new FeatureInfoBox(tagsSource, currentViewFL.layerDef, state, "currentview", guiState.currentViewControlIsOpened)
|
||||
.SetClass("md:floating-element-width")
|
||||
})
|
||||
}))
|
||||
|
|
|
@ -10,26 +10,26 @@ export default class LicensePicker extends DropDown<string> {
|
|||
private static readonly ccbysa = "CC-BY-SA 4.0"
|
||||
private static readonly ccby = "CC-BY 4.0"
|
||||
|
||||
constructor(state: {osmConnection: OsmConnection}) {
|
||||
constructor(state: { osmConnection: OsmConnection }) {
|
||||
super(Translations.t.image.willBePublished.Clone(),
|
||||
[
|
||||
{value:LicensePicker. cc0, shown: Translations.t.image.cco.Clone()},
|
||||
{value:LicensePicker. ccbysa, shown: Translations.t.image.ccbs.Clone()},
|
||||
{value: LicensePicker. ccby, shown: Translations.t.image.ccb.Clone()}
|
||||
{value: LicensePicker.cc0, shown: Translations.t.image.cco.Clone()},
|
||||
{value: LicensePicker.ccbysa, shown: Translations.t.image.ccbs.Clone()},
|
||||
{value: LicensePicker.ccby, shown: Translations.t.image.ccb.Clone()}
|
||||
],
|
||||
state?.osmConnection?.GetPreference("pictures-license") ?? new UIEventSource<string>("CC0")
|
||||
)
|
||||
this.SetClass("flex flex-col sm:flex-row").SetStyle("float:left");
|
||||
}
|
||||
|
||||
public static LicenseExplanations() : Map<string, Translation>{
|
||||
public static LicenseExplanations(): Map<string, Translation> {
|
||||
let dict = new Map<string, Translation>();
|
||||
|
||||
dict.set(LicensePicker. cc0, Translations.t.image.ccoExplanation)
|
||||
dict.set(LicensePicker. ccby, Translations.t.image.ccbExplanation)
|
||||
dict.set(LicensePicker. ccbysa, Translations.t.image.ccbsExplanation)
|
||||
|
||||
dict.set(LicensePicker.cc0, Translations.t.image.ccoExplanation)
|
||||
dict.set(LicensePicker.ccby, Translations.t.image.ccbExplanation)
|
||||
dict.set(LicensePicker.ccbysa, Translations.t.image.ccbsExplanation)
|
||||
|
||||
return dict
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -198,7 +198,7 @@ export default class MoreScreen extends Combine {
|
|||
}
|
||||
return button;
|
||||
})
|
||||
|
||||
|
||||
const professional = MoreScreen.CreateProffessionalSerivesButton();
|
||||
const customGeneratorLink = MoreScreen.createCustomGeneratorButton(state)
|
||||
buttons.splice(0, 0, customGeneratorLink, professional);
|
||||
|
|
|
@ -94,7 +94,7 @@ export default class SimpleAddUI extends Toggle {
|
|||
return presetsOverview
|
||||
}
|
||||
|
||||
function confirm(tags:any[], location: {lat: number, lon:number}, snapOntoWayId?: string) {
|
||||
function confirm(tags: any[], location: { lat: number, lon: number }, snapOntoWayId?: string) {
|
||||
if (snapOntoWayId === undefined) {
|
||||
createNewPoint(tags, location, undefined)
|
||||
} else {
|
||||
|
|
|
@ -93,11 +93,11 @@ export default class DefaultGUI {
|
|||
state.LastClickLocation.setData(undefined);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
let noteMarker = undefined;
|
||||
if(!hasPresets && addNewNoteDialog !== undefined){
|
||||
if (!hasPresets && addNewNoteDialog !== undefined) {
|
||||
noteMarker = new Combine(
|
||||
[Svg.note_svg().SetClass("absolute bottom-0").SetStyle("height: 40px"),
|
||||
[Svg.note_svg().SetClass("absolute bottom-0").SetStyle("height: 40px"),
|
||||
Svg.addSmall_svg().SetClass("absolute w-6 animate-pulse")
|
||||
.SetStyle("right: 10px; bottom: -8px;")
|
||||
])
|
||||
|
|
|
@ -56,7 +56,7 @@ export class DefaultGuiState {
|
|||
hash = hash.toLowerCase()
|
||||
states[hash]?.setData(true)
|
||||
})
|
||||
|
||||
|
||||
if (Hash.hash.data === "" || Hash.hash.data === undefined) {
|
||||
this.welcomeMessageIsOpened.setData(true)
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
|
|||
|
||||
export default class DeleteImage extends Toggle {
|
||||
|
||||
constructor(key: string, tags: UIEventSource<any>, state: {layoutToUse: LayoutConfig, changes?: Changes, osmConnection?: OsmConnection}) {
|
||||
constructor(key: string, tags: UIEventSource<any>, state: { layoutToUse: LayoutConfig, changes?: Changes, osmConnection?: OsmConnection }) {
|
||||
const oldValue = tags.data[key]
|
||||
const isDeletedBadge = Translations.t.image.isDeleted.Clone()
|
||||
.SetClass("rounded-full p-1")
|
||||
|
|
|
@ -12,9 +12,9 @@ import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
|
|||
|
||||
export class ImageCarousel extends Toggle {
|
||||
|
||||
constructor(images: UIEventSource<{ key: string, url: string, provider: ImageProvider}[]>,
|
||||
constructor(images: UIEventSource<{ key: string, url: string, provider: ImageProvider }[]>,
|
||||
tags: UIEventSource<any>,
|
||||
state: {osmConnection?: OsmConnection, changes?: Changes, layoutToUse: LayoutConfig }) {
|
||||
state: { osmConnection?: OsmConnection, changes?: Changes, layoutToUse: LayoutConfig }) {
|
||||
const uiElements = images.map((imageURLS: { key: string, url: string, provider: ImageProvider }[]) => {
|
||||
const uiElements: BaseUIElement[] = [];
|
||||
for (const url of imageURLS) {
|
||||
|
|
|
@ -21,7 +21,7 @@ export class ImageUploadFlow extends Toggle {
|
|||
|
||||
private static readonly uploadCountsPerId = new Map<string, UIEventSource<number>>()
|
||||
|
||||
constructor(tagsSource: UIEventSource<any>,
|
||||
constructor(tagsSource: UIEventSource<any>,
|
||||
state: {
|
||||
osmConnection: OsmConnection;
|
||||
layoutToUse: LayoutConfig;
|
||||
|
|
|
@ -44,22 +44,22 @@ export class AskMetadata extends Combine implements FlowStep<{
|
|||
inputStyle: "width: 100%"
|
||||
})
|
||||
|
||||
let options : {value: string, shown: BaseUIElement}[]= AllKnownLayouts.layoutsList
|
||||
let options: { value: string, shown: BaseUIElement }[] = AllKnownLayouts.layoutsList
|
||||
.filter(th => th.layers.some(l => l.id === params.layer.id))
|
||||
.filter(th => th.id !== "personal")
|
||||
.map(th => ({
|
||||
value: th.id,
|
||||
shown: th.title
|
||||
}))
|
||||
|
||||
options.splice(0,0, {
|
||||
|
||||
options.splice(0, 0, {
|
||||
shown: new FixedUiElement("Select a theme"),
|
||||
value: undefined
|
||||
value: undefined
|
||||
})
|
||||
|
||||
const theme = new DropDown("Which theme should be linked in the note?",options)
|
||||
|
||||
ValidatedTextField.InputForType("string", {
|
||||
|
||||
const theme = new DropDown("Which theme should be linked in the note?", options)
|
||||
|
||||
ValidatedTextField.InputForType("string", {
|
||||
value: LocalStorageSource.Get("import-helper-theme-text"),
|
||||
inputStyle: "width: 100%"
|
||||
})
|
||||
|
@ -89,7 +89,7 @@ export class AskMetadata extends Combine implements FlowStep<{
|
|||
}, [wikilink.GetValue(), source.GetValue(), theme.GetValue()])
|
||||
|
||||
this.IsValid = this.Value.map(obj => {
|
||||
if(obj === undefined){
|
||||
if (obj === undefined) {
|
||||
return false;
|
||||
}
|
||||
return obj.theme !== undefined && obj.features !== undefined && obj.wikilink !== undefined && obj.intro !== undefined && obj.source !== undefined;
|
||||
|
|
|
@ -40,12 +40,12 @@ export class CompareToAlreadyExistingNotes extends Combine implements FlowStep<{
|
|||
tagRenderings: new Map()
|
||||
}
|
||||
|
||||
|
||||
|
||||
const layerConfig = known_layers.layers.filter(l => l.id === params.layer.id)[0]
|
||||
if(layerConfig === undefined){
|
||||
if (layerConfig === undefined) {
|
||||
console.error("WEIRD: layer not found in the builtin layer overview")
|
||||
}
|
||||
const importLayerJson = new CreateNoteImportLayer(365).convertStrict(convertState, <LayerConfigJson> layerConfig, "CompareToAlreadyExistingNotes")
|
||||
const importLayerJson = new CreateNoteImportLayer(365).convertStrict(convertState, <LayerConfigJson>layerConfig, "CompareToAlreadyExistingNotes")
|
||||
const importLayer = new LayerConfig(importLayerJson, "import-layer-dynamic")
|
||||
const flayer: FilteredLayer = {
|
||||
appliedFilters: new UIEventSource<Map<string, FilterState>>(new Map<string, FilterState>()),
|
||||
|
@ -108,11 +108,11 @@ export class CompareToAlreadyExistingNotes extends Combine implements FlowStep<{
|
|||
super([
|
||||
new Title("Compare with already existing 'to-import'-notes"),
|
||||
new VariableUiElement(
|
||||
alreadyOpenImportNotes.features.map(notesWithImport => {
|
||||
if(allNotesWithinBbox.features.data === undefined || allNotesWithinBbox.features.data.length === 0){
|
||||
return new Loading("Fetching notes from OSM")
|
||||
alreadyOpenImportNotes.features.map(notesWithImport => {
|
||||
if (allNotesWithinBbox.features.data === undefined || allNotesWithinBbox.features.data.length === 0) {
|
||||
return new Loading("Fetching notes from OSM")
|
||||
}
|
||||
if(notesWithImport.length === 0){
|
||||
if (notesWithImport.length === 0) {
|
||||
return new FixedUiElement("No previous note to import found").SetClass("thanks")
|
||||
}
|
||||
return new Combine([
|
||||
|
@ -126,7 +126,7 @@ export class CompareToAlreadyExistingNotes extends Combine implements FlowStep<{
|
|||
).SetClass("w-full"),
|
||||
comparisonMap,
|
||||
]).SetClass("flex flex-col")
|
||||
|
||||
|
||||
}, [allNotesWithinBbox.features])
|
||||
),
|
||||
|
||||
|
@ -140,15 +140,15 @@ export class CompareToAlreadyExistingNotes extends Combine implements FlowStep<{
|
|||
}))
|
||||
|
||||
this.IsValid = alreadyOpenImportNotes.features.map(ff => {
|
||||
if(allNotesWithinBbox.features.data.length === 0){
|
||||
if (allNotesWithinBbox.features.data.length === 0) {
|
||||
// Not yet loaded
|
||||
return false
|
||||
}
|
||||
if(ff.length == 0){
|
||||
if (ff.length == 0) {
|
||||
// No import notes at all
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return partitionedImportPoints.data.noNearby.length > 0; // at least _something_ can be imported
|
||||
}, [partitionedImportPoints, allNotesWithinBbox.features])
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ import {ImportUtils} from "./ImportUtils";
|
|||
/**
|
||||
* Given the data to import, the bbox and the layer, will query overpass for similar items
|
||||
*/
|
||||
export default class ConflationChecker extends Combine implements FlowStep<{features: any[], layer: LayerConfig}> {
|
||||
export default class ConflationChecker extends Combine implements FlowStep<{ features: any[], layer: LayerConfig }> {
|
||||
|
||||
public readonly IsValid
|
||||
public readonly Value
|
||||
|
@ -40,12 +40,12 @@ export default class ConflationChecker extends Combine implements FlowStep<{feat
|
|||
constructor(
|
||||
state,
|
||||
params: { bbox: BBox, layer: LayerConfig, geojson: any }) {
|
||||
|
||||
|
||||
|
||||
|
||||
const bbox = params.bbox.padAbsolute(0.0001)
|
||||
const layer = params.layer;
|
||||
const toImport = params.geojson;
|
||||
let overpassStatus = new UIEventSource<{ error: string } | "running" | "success" | "idle" | "cached" >("idle")
|
||||
let overpassStatus = new UIEventSource<{ error: string } | "running" | "success" | "idle" | "cached">("idle")
|
||||
const cacheAge = new UIEventSource<number>(undefined);
|
||||
const fromLocalStorage = IdbLocalStorage.Get<[any, Date]>("importer-overpass-cache-" + layer.id, {
|
||||
whenLoaded: (v) => {
|
||||
|
@ -53,7 +53,7 @@ export default class ConflationChecker extends Combine implements FlowStep<{feat
|
|||
console.log("Loaded from local storage:", v)
|
||||
const [geojson, date] = v;
|
||||
const timeDiff = (new Date().getTime() - date.getTime()) / 1000;
|
||||
console.log("Loaded ", geojson.features.length," features; cache is ", timeDiff, "seconds old")
|
||||
console.log("Loaded ", geojson.features.length, " features; cache is ", timeDiff, "seconds old")
|
||||
cacheAge.setData(timeDiff)
|
||||
if (timeDiff < 24 * 60 * 60) {
|
||||
// Recently cached!
|
||||
|
@ -69,18 +69,20 @@ export default class ConflationChecker extends Combine implements FlowStep<{feat
|
|||
console.log("Loading from overpass!")
|
||||
overpassStatus.setData("running")
|
||||
overpass.queryGeoJson(bbox).then(
|
||||
([data, date] ) => {
|
||||
console.log("Received overpass-data: ", data.features.length,"features are loaded at ", date);
|
||||
([data, date]) => {
|
||||
console.log("Received overpass-data: ", data.features.length, "features are loaded at ", date);
|
||||
overpassStatus.setData("success")
|
||||
fromLocalStorage.setData([data, date])
|
||||
},
|
||||
(error) => {overpassStatus.setData({error})})
|
||||
},
|
||||
(error) => {
|
||||
overpassStatus.setData({error})
|
||||
})
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const geojson : UIEventSource<any> = fromLocalStorage.map(d => {
|
||||
const geojson: UIEventSource<any> = fromLocalStorage.map(d => {
|
||||
if (d === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
@ -102,21 +104,20 @@ export default class ConflationChecker extends Combine implements FlowStep<{feat
|
|||
})
|
||||
osmLiveData.SetClass("w-full").SetStyle("height: 500px")
|
||||
const preview = new StaticFeatureSource(geojson.map(geojson => {
|
||||
if(geojson?.features === undefined){
|
||||
if (geojson?.features === undefined) {
|
||||
return []
|
||||
}
|
||||
const zoomedEnough: boolean = osmLiveData.location.data.zoom >= Number(zoomLevel.GetValue().data)
|
||||
if(!zoomedEnough){
|
||||
if (!zoomedEnough) {
|
||||
return []
|
||||
}
|
||||
const bounds = osmLiveData.bounds.data
|
||||
return geojson.features.filter(f => BBox.get(f).overlapsWith(bounds))
|
||||
}, [osmLiveData.bounds, zoomLevel.GetValue()]), false);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
new ShowDataLayer({
|
||||
layerToShow:new LayerConfig(currentview),
|
||||
layerToShow: new LayerConfig(currentview),
|
||||
state,
|
||||
leafletMap: osmLiveData.leafletMap,
|
||||
popup: undefined,
|
||||
|
@ -128,7 +129,7 @@ export default class ConflationChecker extends Combine implements FlowStep<{feat
|
|||
|
||||
|
||||
new ShowDataLayer({
|
||||
layerToShow:layer,
|
||||
layerToShow: layer,
|
||||
state,
|
||||
leafletMap: osmLiveData.leafletMap,
|
||||
popup: (tags, layer) => new FeatureInfoBox(tags, layer, state),
|
||||
|
@ -137,18 +138,18 @@ export default class ConflationChecker extends Combine implements FlowStep<{feat
|
|||
})
|
||||
|
||||
new ShowDataLayer({
|
||||
layerToShow:new LayerConfig(import_candidate),
|
||||
layerToShow: new LayerConfig(import_candidate),
|
||||
state,
|
||||
leafletMap: osmLiveData.leafletMap,
|
||||
popup: (tags, layer) => new FeatureInfoBox(tags, layer, state),
|
||||
zoomToFeatures: false,
|
||||
features: new StaticFeatureSource(toImport.features, false)
|
||||
})
|
||||
|
||||
|
||||
const nearbyCutoff = ValidatedTextField.InputForType("pnat")
|
||||
nearbyCutoff.SetClass("ml-1 border border-black")
|
||||
nearbyCutoff.GetValue().syncWith(LocalStorageSource.Get("importer-cutoff", "25"), true)
|
||||
|
||||
|
||||
const matchedFeaturesMap = Minimap.createMiniMap({
|
||||
allowMoving: true,
|
||||
background
|
||||
|
@ -157,21 +158,21 @@ export default class ConflationChecker extends Combine implements FlowStep<{feat
|
|||
|
||||
// Featuresource showing OSM-features which are nearby a toImport-feature
|
||||
const nearbyFeatures = new StaticFeatureSource(geojson.map(osmData => {
|
||||
if(osmData?.features === undefined){
|
||||
if (osmData?.features === undefined) {
|
||||
return []
|
||||
}
|
||||
const maxDist = Number(nearbyCutoff.GetValue().data)
|
||||
return osmData.features.filter(f =>
|
||||
toImport.features.some(imp =>
|
||||
maxDist >= GeoOperations.distanceBetween(imp.geometry.coordinates, GeoOperations.centerpointCoordinates(f))) )
|
||||
return osmData.features.filter(f =>
|
||||
toImport.features.some(imp =>
|
||||
maxDist >= GeoOperations.distanceBetween(imp.geometry.coordinates, GeoOperations.centerpointCoordinates(f))))
|
||||
}, [nearbyCutoff.GetValue()]), false);
|
||||
const paritionedImport = ImportUtils.partitionFeaturesIfNearby(toImport, geojson, nearbyCutoff.GetValue().map(Number));
|
||||
|
||||
// Featuresource showing OSM-features which are nearby a toImport-feature
|
||||
const toImportWithNearby = new StaticFeatureSource(paritionedImport.map(els =>els?.hasNearby ?? []), false);
|
||||
const toImportWithNearby = new StaticFeatureSource(paritionedImport.map(els => els?.hasNearby ?? []), false);
|
||||
|
||||
new ShowDataLayer({
|
||||
layerToShow:layer,
|
||||
layerToShow: layer,
|
||||
state,
|
||||
leafletMap: matchedFeaturesMap.leafletMap,
|
||||
popup: (tags, layer) => new FeatureInfoBox(tags, layer, state),
|
||||
|
@ -180,47 +181,47 @@ export default class ConflationChecker extends Combine implements FlowStep<{feat
|
|||
})
|
||||
|
||||
new ShowDataLayer({
|
||||
layerToShow:new LayerConfig(import_candidate),
|
||||
layerToShow: new LayerConfig(import_candidate),
|
||||
state,
|
||||
leafletMap: matchedFeaturesMap.leafletMap,
|
||||
popup: (tags, layer) => new FeatureInfoBox(tags, layer, state),
|
||||
zoomToFeatures: false,
|
||||
features: toImportWithNearby
|
||||
})
|
||||
|
||||
|
||||
const conflationMaps = new Combine([
|
||||
|
||||
|
||||
const conflationMaps = new Combine([
|
||||
new VariableUiElement(
|
||||
geojson.map(geojson => {
|
||||
if (geojson === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return new SubtleButton(Svg.download_svg(), "Download the loaded geojson from overpass").onClick(() => {
|
||||
Utils.offerContentsAsDownloadableFile(JSON.stringify(geojson, null, " "), "mapcomplete-" + layer.id + ".geojson", {
|
||||
mimetype: "application/json+geo"
|
||||
})
|
||||
});
|
||||
})),
|
||||
geojson.map(geojson => {
|
||||
if (geojson === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return new SubtleButton(Svg.download_svg(), "Download the loaded geojson from overpass").onClick(() => {
|
||||
Utils.offerContentsAsDownloadableFile(JSON.stringify(geojson, null, " "), "mapcomplete-" + layer.id + ".geojson", {
|
||||
mimetype: "application/json+geo"
|
||||
})
|
||||
});
|
||||
})),
|
||||
new VariableUiElement(cacheAge.map(age => {
|
||||
if(age === undefined){
|
||||
if (age === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if(age < 0){
|
||||
if (age < 0) {
|
||||
return new FixedUiElement("Cache was expired")
|
||||
}
|
||||
return new FixedUiElement("Loaded data is from the cache and is "+Utils.toHumanTime(age)+" old")
|
||||
return new FixedUiElement("Loaded data is from the cache and is " + Utils.toHumanTime(age) + " old")
|
||||
})),
|
||||
|
||||
new Title("Live data on OSM"),
|
||||
osmLiveData,
|
||||
new Combine(["The live data is shown if the zoomlevel is at least ", zoomLevel, ". The current zoom level is ", new VariableUiElement(osmLiveData.location.map(l => ""+l.zoom))]).SetClass("flex"),
|
||||
new Combine(["The live data is shown if the zoomlevel is at least ", zoomLevel, ". The current zoom level is ", new VariableUiElement(osmLiveData.location.map(l => "" + l.zoom))]).SetClass("flex"),
|
||||
|
||||
new Title("Nearby features"),
|
||||
new Combine([ "The following map shows features to import which have an OSM-feature within ", nearbyCutoff, "meter"]).SetClass("flex"),
|
||||
new Combine(["The following map shows features to import which have an OSM-feature within ", nearbyCutoff, "meter"]).SetClass("flex"),
|
||||
new FixedUiElement("The red elements on the following map will <b>not</b> be imported!").SetClass("alert"),
|
||||
"Set the range to 0 or 1 if you want to import them all",
|
||||
matchedFeaturesMap]).SetClass("flex flex-col")
|
||||
|
||||
|
||||
super([
|
||||
new Title("Comparison with existing data"),
|
||||
new VariableUiElement(overpassStatus.map(d => {
|
||||
|
@ -230,16 +231,16 @@ export default class ConflationChecker extends Combine implements FlowStep<{feat
|
|||
if (d["error"] !== undefined) {
|
||||
return new FixedUiElement("Could not load latest data from overpass: " + d["error"]).SetClass("alert")
|
||||
}
|
||||
if(d === "running"){
|
||||
if (d === "running") {
|
||||
return new Loading("Querying overpass...")
|
||||
}
|
||||
if(d === "cached"){
|
||||
if (d === "cached") {
|
||||
return conflationMaps
|
||||
}
|
||||
if(d === "success"){
|
||||
if (d === "success") {
|
||||
return conflationMaps
|
||||
}
|
||||
return new FixedUiElement("Unexpected state "+d).SetClass("alert")
|
||||
return new FixedUiElement("Unexpected state " + d).SetClass("alert")
|
||||
}))
|
||||
|
||||
])
|
||||
|
|
|
@ -23,8 +23,8 @@ export class CreateNotes extends Combine {
|
|||
delete f.properties["source"]
|
||||
delete f.properties["src"]
|
||||
let extraNote = ""
|
||||
if(f.properties["note"]){
|
||||
extraNote = f.properties["note"]+"\n"
|
||||
if (f.properties["note"]) {
|
||||
extraNote = f.properties["note"] + "\n"
|
||||
delete f.properties["note"]
|
||||
}
|
||||
|
||||
|
|
|
@ -101,7 +101,7 @@ export class FlowPanel<T> extends Toggle {
|
|||
// Startup the flow
|
||||
elements = [
|
||||
initial,
|
||||
|
||||
|
||||
new Combine([
|
||||
backbutton,
|
||||
new Toggle(
|
||||
|
|
|
@ -27,7 +27,7 @@ export default class ImportHelperGui extends LeftIndex {
|
|||
constructor() {
|
||||
const state = new UserRelatedState(undefined)
|
||||
|
||||
const {flow, furthestStep, titles} =
|
||||
const {flow, furthestStep, titles} =
|
||||
FlowPanelFactory
|
||||
.start("Introduction", new Introdution())
|
||||
.then("Login", _ => new LoginToImport(state))
|
||||
|
@ -37,28 +37,28 @@ export default class ImportHelperGui extends LeftIndex {
|
|||
.then("Compare with open notes", v => new CompareToAlreadyExistingNotes(state, v))
|
||||
.then("Compare with existing data", v => new ConflationChecker(state, v))
|
||||
.then("License and community check", v => new ConfirmProcess(v))
|
||||
.then("Metadata", (v:{features:any[], layer: LayerConfig}) => new AskMetadata(v))
|
||||
.then("Metadata", (v: { features: any[], layer: LayerConfig }) => new AskMetadata(v))
|
||||
.finish("Note creation", v => new CreateNotes(state, v));
|
||||
|
||||
|
||||
const toc = new List(
|
||||
titles.map((title, i) => new VariableUiElement(furthestStep.map(currentStep => {
|
||||
if(i > currentStep){
|
||||
if (i > currentStep) {
|
||||
return new Combine([title]).SetClass("subtle");
|
||||
}
|
||||
if(i == currentStep){
|
||||
if (i == currentStep) {
|
||||
return new Combine([title]).SetClass("font-bold");
|
||||
}
|
||||
if(i < currentStep){
|
||||
if (i < currentStep) {
|
||||
return title
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
})))
|
||||
, true)
|
||||
|
||||
|
||||
const leftContents: BaseUIElement[] = [
|
||||
new SubtleButton(undefined,"Inspect your preview imports", {
|
||||
url:"import_viewer.html"
|
||||
new SubtleButton(undefined, "Inspect your preview imports", {
|
||||
url: "import_viewer.html"
|
||||
}),
|
||||
toc,
|
||||
new Toggle(new FixedUiElement("Testmode - won't actually import notes").SetClass("alert"), undefined, state.featureSwitchIsTesting),
|
||||
|
|
|
@ -7,7 +7,7 @@ export class ImportUtils {
|
|||
if (osmData?.features === undefined) {
|
||||
return undefined
|
||||
}
|
||||
if(osmData.features.length === 0){
|
||||
if (osmData.features.length === 0) {
|
||||
return {noNearby: toPartitionFeatureCollection.features, hasNearby: []}
|
||||
}
|
||||
const maxDist = cutoffDistanceInMeters.data
|
||||
|
|
|
@ -7,14 +7,14 @@ import Title from "../Base/Title";
|
|||
export default class Introdution extends Combine implements FlowStep<void> {
|
||||
readonly IsValid: UIEventSource<boolean> = new UIEventSource<boolean>(true);
|
||||
readonly Value: UIEventSource<void> = new UIEventSource<void>(undefined);
|
||||
|
||||
|
||||
constructor() {
|
||||
super([
|
||||
new Title( Translations.t.importHelper.title),
|
||||
new Title(Translations.t.importHelper.title),
|
||||
Translations.t.importHelper.description,
|
||||
Translations.t.importHelper.importFormat,
|
||||
]);
|
||||
this.SetClass("flex flex-col")
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -26,26 +26,26 @@ import Title from "../Base/Title";
|
|||
import CheckBoxes from "../Input/Checkboxes";
|
||||
|
||||
class PreviewPanel extends ScrollableFullScreen {
|
||||
|
||||
|
||||
constructor(tags, layer) {
|
||||
super(
|
||||
_ => new FixedUiElement("Element to import"),
|
||||
_ => new Combine(["The tags are:",
|
||||
_ => new Combine(["The tags are:",
|
||||
new AllTagsPanel(tags)
|
||||
]).SetClass("flex flex-col"),
|
||||
"element"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the data to import on a map, asks for the correct layer to be selected
|
||||
*/
|
||||
export class MapPreview extends Combine implements FlowStep<{ bbox: BBox, layer: LayerConfig, geojson: any }>{
|
||||
export class MapPreview extends Combine implements FlowStep<{ bbox: BBox, layer: LayerConfig, geojson: any }> {
|
||||
public readonly IsValid: UIEventSource<boolean>;
|
||||
public readonly Value: UIEventSource<{ bbox: BBox, layer: LayerConfig, geojson: any }>
|
||||
|
||||
|
||||
constructor(
|
||||
state: UserRelatedState,
|
||||
geojson: { features: { properties: any, geometry: { coordinates: [number, number] } }[] }) {
|
||||
|
@ -115,10 +115,10 @@ export class MapPreview extends Combine implements FlowStep<{ bbox: BBox, layer:
|
|||
layers: new UIEventSource<FilteredLayer[]>(AllKnownLayouts.AllPublicLayers()
|
||||
.filter(l => l.source.geojsonSource === undefined)
|
||||
.map(l => ({
|
||||
layerDef: l,
|
||||
isDisplayed: new UIEventSource<boolean>(true),
|
||||
appliedFilters: new UIEventSource<Map<string, FilterState>>(undefined)
|
||||
}))),
|
||||
layerDef: l,
|
||||
isDisplayed: new UIEventSource<boolean>(true),
|
||||
appliedFilters: new UIEventSource<Map<string, FilterState>>(undefined)
|
||||
}))),
|
||||
zoomToFeatures: true,
|
||||
features: new StaticFeatureSource(matching, false),
|
||||
leafletMap: map.leafletMap,
|
||||
|
@ -126,8 +126,8 @@ export class MapPreview extends Combine implements FlowStep<{ bbox: BBox, layer:
|
|||
})
|
||||
var bbox = matching.map(feats => BBox.bboxAroundAll(feats.map(f => new BBox([f.geometry.coordinates]))))
|
||||
|
||||
|
||||
const mismatchIndicator = new VariableUiElement(matching.map(matching => {
|
||||
|
||||
const mismatchIndicator = new VariableUiElement(matching.map(matching => {
|
||||
if (matching === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
@ -138,14 +138,14 @@ export class MapPreview extends Combine implements FlowStep<{ bbox: BBox, layer:
|
|||
const obligatory = layerPicker.GetValue().data?.source?.osmTags?.asHumanString(false, false, {});
|
||||
return t.mismatch.Subs({count: diff, tags: obligatory}).SetClass("alert")
|
||||
}))
|
||||
|
||||
|
||||
const confirm = new CheckBoxes([t.confirm]);
|
||||
super([
|
||||
new Title(t.title, 1),
|
||||
layerPicker,
|
||||
new Toggle(t.autodetected.SetClass("thank"), undefined, autodetected),
|
||||
|
||||
mismatchIndicator ,
|
||||
|
||||
mismatchIndicator,
|
||||
map,
|
||||
confirm
|
||||
]);
|
||||
|
@ -156,17 +156,17 @@ export class MapPreview extends Combine implements FlowStep<{ bbox: BBox, layer:
|
|||
geojson,
|
||||
layer: layerPicker.GetValue().data
|
||||
}), [layerPicker.GetValue()])
|
||||
|
||||
|
||||
this.IsValid = matching.map(matching => {
|
||||
if (matching === undefined) {
|
||||
return false
|
||||
}
|
||||
if(confirm.GetValue().data.length !== 1){
|
||||
if (confirm.GetValue().data.length !== 1) {
|
||||
return false
|
||||
}
|
||||
const diff = geojson.features.length - matching.length;
|
||||
return diff === 0;
|
||||
}, [confirm.GetValue()])
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -36,7 +36,7 @@ export class PreviewPanel extends Combine implements FlowStep<{ features: { prop
|
|||
for (const key of Array.from(propertyKeys)) {
|
||||
|
||||
const values = Utils.NoNull(geojson.features.map(f => f.properties[key]))
|
||||
console.log("There are ",values.length,"features with attribute",key, "namely",values)
|
||||
console.log("There are ", values.length, "features with attribute", key, "namely", values)
|
||||
const allSame = !values.some(v => v !== values[0])
|
||||
let countSummary: BaseUIElement
|
||||
if (values.length === n) {
|
||||
|
|
|
@ -3,13 +3,12 @@ import {UIEventSource} from "../../Logic/UIEventSource";
|
|||
import Translations from "../i18n/Translations";
|
||||
import {SubtleButton} from "../Base/SubtleButton";
|
||||
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||
import {FixedUiElement} from "../Base/FixedUiElement";
|
||||
import Title from "../Base/Title";
|
||||
import InputElementMap from "../Input/InputElementMap";
|
||||
import BaseUIElement from "../BaseUIElement";
|
||||
import FileSelectorButton from "../Input/FileSelectorButton";
|
||||
import {FlowStep} from "./FlowStep";
|
||||
import { parse } from "papaparse";
|
||||
import {parse} from "papaparse";
|
||||
|
||||
class FileSelector extends InputElementMap<FileList, { name: string, contents: Promise<string> }> {
|
||||
constructor(label: BaseUIElement) {
|
||||
|
@ -76,7 +75,7 @@ export class RequestFile extends Combine implements FlowStep<any> {
|
|||
|
||||
} catch (e) {
|
||||
// Loading as CSV
|
||||
var lines : string[][] = <any> parse(src).data;
|
||||
var lines: string[][] = <any>parse(src).data;
|
||||
const header = lines[0]
|
||||
lines.splice(0, 1)
|
||||
if (header.indexOf("lat") < 0 || header.indexOf("lon") < 0) {
|
||||
|
@ -84,26 +83,26 @@ export class RequestFile extends Combine implements FlowStep<any> {
|
|||
}
|
||||
|
||||
if (header.some(h => h.trim() == "")) {
|
||||
return {error:t.errNoName}
|
||||
return {error: t.errNoName}
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (new Set(header).size !== header.length) {
|
||||
return {error:t.errDuplicate}
|
||||
return {error: t.errDuplicate}
|
||||
}
|
||||
|
||||
|
||||
|
||||
const features = []
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const attrs = lines[i];
|
||||
if(attrs.length == 0 || (attrs.length == 1 && attrs[0] == "")){
|
||||
if (attrs.length == 0 || (attrs.length == 1 && attrs[0] == "")) {
|
||||
// empty line
|
||||
continue
|
||||
}
|
||||
const properties = {}
|
||||
for (let i = 0; i < header.length; i++) {
|
||||
const v = attrs[i]
|
||||
if(v === undefined || v === ""){
|
||||
const v = attrs[i]
|
||||
if (v === undefined || v === "") {
|
||||
continue
|
||||
}
|
||||
properties[header[i]] = v;
|
||||
|
|
|
@ -12,8 +12,10 @@ export default class FileSelectorButton extends InputElement<FileList> {
|
|||
private readonly allowMultiple: boolean;
|
||||
|
||||
constructor(label: BaseUIElement, options?:
|
||||
{ acceptType: "image/*" | string,
|
||||
allowMultiple: true | boolean}) {
|
||||
{
|
||||
acceptType: "image/*" | string,
|
||||
allowMultiple: true | boolean
|
||||
}) {
|
||||
super();
|
||||
this._label = label;
|
||||
this._acceptType = options?.acceptType ?? "image/*";
|
||||
|
|
|
@ -17,9 +17,9 @@ export default class InputElementWrapper<T> extends InputElement<T> {
|
|||
mapping.set(key, inputElement)
|
||||
|
||||
// Bit of a hack: the SubstitutedTranslation expects a special rendering, but those are formatted '{key()}' instead of '{key}', so we substitute it first
|
||||
const newTranslations ={}
|
||||
const newTranslations = {}
|
||||
for (const lang in translation.translations) {
|
||||
newTranslations[lang] = translation.translations[lang].replace("{"+key+"}", "{"+key+"()}")
|
||||
newTranslations[lang] = translation.translations[lang].replace("{" + key + "}", "{" + key + "()}")
|
||||
}
|
||||
this._renderElement = new SubstitutedTranslation(new Translation(newTranslations), tags, state, mapping)
|
||||
}
|
||||
|
|
|
@ -25,6 +25,8 @@ export default class LocationInput extends InputElement<Loc> implements MinimapO
|
|||
public readonly snappedOnto: UIEventSource<any> = new UIEventSource<any>(undefined)
|
||||
public readonly _matching_layer: LayerConfig;
|
||||
public readonly leafletMap: UIEventSource<any>
|
||||
public readonly bounds;
|
||||
public readonly location;
|
||||
private _centerLocation: UIEventSource<Loc>;
|
||||
private readonly mapBackground: UIEventSource<BaseLayer>;
|
||||
/**
|
||||
|
@ -38,8 +40,6 @@ export default class LocationInput extends InputElement<Loc> implements MinimapO
|
|||
private readonly _snappedPointTags: any;
|
||||
private readonly _bounds: UIEventSource<BBox>;
|
||||
private readonly map: BaseUIElement & MinimapObj;
|
||||
public readonly bounds;
|
||||
public readonly location;
|
||||
private readonly clickLocation: UIEventSource<Loc>;
|
||||
private readonly _minZoom: number;
|
||||
|
||||
|
|
|
@ -73,7 +73,7 @@ export class RadioButton<T> extends InputElement<T> {
|
|||
elements[i]?.onClick(() => {
|
||||
selectedElementIndex.setData(i);
|
||||
});
|
||||
|
||||
|
||||
elements[i].GetValue().addCallback(() => {
|
||||
selectedElementIndex.setData(i);
|
||||
});
|
||||
|
|
|
@ -236,9 +236,9 @@ class UrlTextfieldDef implements TextFieldDef {
|
|||
"fbclid",// Oh god, how I hate the fbclid. Let it burn, burn in hell!
|
||||
"gclid",
|
||||
"cmpid", "agid", "utm", "utm_source", "utm_medium",
|
||||
"campaignid","campaign","AdGroupId","AdGroup","TargetId","msclkid"]
|
||||
"campaignid", "campaign", "AdGroupId", "AdGroup", "TargetId", "msclkid"]
|
||||
for (const dontLike of blacklistedTrackingParams) {
|
||||
url.searchParams.delete(dontLike.toLowerCase() )
|
||||
url.searchParams.delete(dontLike.toLowerCase())
|
||||
}
|
||||
let cleaned = url.toString();
|
||||
if (cleaned.endsWith("/") && !str.endsWith("/")) {
|
||||
|
@ -265,7 +265,7 @@ class UrlTextfieldDef implements TextFieldDef {
|
|||
}
|
||||
const url = new URL(str);
|
||||
const dotIndex = url.host.indexOf(".")
|
||||
return dotIndex > 0 && url.host[url.host.length - 1 ] !== ".";
|
||||
return dotIndex > 0 && url.host[url.host.length - 1] !== ".";
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -78,7 +78,7 @@ export default class ConfirmLocationOfPoint extends Combine {
|
|||
// return;
|
||||
}
|
||||
|
||||
bbox = bbox.pad(Math.max(preset.boundsFactor , 2), Math.max(preset.boundsFactor , 2));
|
||||
bbox = bbox.pad(Math.max(preset.boundsFactor, 2), Math.max(preset.boundsFactor, 2));
|
||||
loadedBbox = bbox;
|
||||
const allFeatures: { feature: any }[] = []
|
||||
preset.preciseInput.snapToLayers.forEach(layerId => {
|
||||
|
@ -139,7 +139,7 @@ export default class ConfirmLocationOfPoint extends Combine {
|
|||
]
|
||||
).SetClass("flex flex-col")
|
||||
).onClick(() => {
|
||||
|
||||
|
||||
const appliedFilters = preset.layerToAddTo.appliedFilters;
|
||||
appliedFilters.data.forEach((_, k) => appliedFilters.data.set(k, undefined))
|
||||
appliedFilters.ping()
|
||||
|
@ -150,15 +150,14 @@ export default class ConfirmLocationOfPoint extends Combine {
|
|||
const hasActiveFilter = preset.layerToAddTo.appliedFilters
|
||||
.map(appliedFilters => {
|
||||
const activeFilters = Array.from(appliedFilters.values()).filter(f => f?.currentFilter !== undefined);
|
||||
return activeFilters.length === 0;
|
||||
return activeFilters.length === 0;
|
||||
})
|
||||
|
||||
|
||||
// If at least one filter is active which _might_ hide a newly added item, this blocks the preset and requests the filter to be disabled
|
||||
const disableFiltersOrConfirm = new Toggle(
|
||||
openLayerOrConfirm,
|
||||
disableFilter,
|
||||
disableFilter,
|
||||
hasActiveFilter)
|
||||
|
||||
|
||||
|
||||
const tagInfo = SimpleAddUI.CreateTagInfoFor(preset, state.osmConnection);
|
||||
|
|
|
@ -298,16 +298,16 @@ export class OH {
|
|||
}
|
||||
}
|
||||
|
||||
public static simplify(str: string): string{
|
||||
public static simplify(str: string): string {
|
||||
return OH.ToString(OH.MergeTimes(OH.Parse(str)))
|
||||
}
|
||||
|
||||
public static Parse(rules: string) : OpeningHour[] {
|
||||
|
||||
public static Parse(rules: string): OpeningHour[] {
|
||||
if (rules === undefined || rules === "") {
|
||||
return []
|
||||
}
|
||||
|
||||
const ohs : OpeningHour[] = []
|
||||
const ohs: OpeningHour[] = []
|
||||
|
||||
const split = rules.split(";");
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ export default class OpeningHoursVisualization extends Toggle {
|
|||
Translations.t.general.weekdays.abbreviations.sunday,
|
||||
]
|
||||
|
||||
constructor(tags: UIEventSource<any>, state:{osmConnection?: OsmConnection}, key: string, prefix = "", postfix = "") {
|
||||
constructor(tags: UIEventSource<any>, state: { osmConnection?: OsmConnection }, key: string, prefix = "", postfix = "") {
|
||||
const tagsDirect = tags.data;
|
||||
const ohTable = new VariableUiElement(tags
|
||||
.map(tags => {
|
||||
|
|
|
@ -60,13 +60,24 @@ export default class AutoApplyButton implements SpecialVisualization {
|
|||
this.docs = AutoApplyButton.generateDocs(allSpecialVisualisations.filter(sv => sv["supportsAutoAction"] === true).map(sv => sv.funcName))
|
||||
}
|
||||
|
||||
private static generateDocs(supportedActions: string[]) {
|
||||
return [
|
||||
"A button to run many actions for many features at once.\n",
|
||||
"To effectively use this button, you'll need some ingredients:\n" +
|
||||
"- A target layer with features for which an action is defined in a tag rendering. The following special visualisations support an autoAction: " + supportedActions.join(", "),
|
||||
"- A host feature to place the auto-action on. This can be a big outline (such as a city). Another good option for this is the [current_view](./BuiltinLayers.md#current_view)",
|
||||
"- Then, use a calculated tag on the host feature to determine the overlapping object ids",
|
||||
"- At last, add this component"
|
||||
].join("\n")
|
||||
}
|
||||
|
||||
constr(state: FeaturePipelineState, tagSource: UIEventSource<any>, argument: string[], guistate: DefaultGuiState): BaseUIElement {
|
||||
|
||||
if (!state.layoutToUse.official && !(state.featureSwitchIsTesting.data || state.osmConnection._oauth_config.url === OsmConnection.oauth_configs["osm-test"].url)) {
|
||||
const t = Translations.t.general.add.import;
|
||||
return new Combine([new FixedUiElement("The auto-apply button is only available in official themes (or in testing mode)").SetClass("alert"), t.howToTest])
|
||||
}
|
||||
|
||||
|
||||
const to_parse = tagSource.data[argument[1]]
|
||||
if (to_parse === undefined) {
|
||||
return new Loading("Gathering which elements support auto-apply... ")
|
||||
|
@ -76,11 +87,11 @@ export default class AutoApplyButton implements SpecialVisualization {
|
|||
|
||||
const target_layer_id = argument[0]
|
||||
const target_feature_ids = <string[]>JSON.parse(to_parse)
|
||||
|
||||
if(target_feature_ids.length === 0){
|
||||
|
||||
if (target_feature_ids.length === 0) {
|
||||
return new FixedUiElement("No elements found to perform action")
|
||||
}
|
||||
|
||||
|
||||
const targetTagRendering = argument[2]
|
||||
const text = argument[3]
|
||||
const icon = argument[4]
|
||||
|
@ -93,7 +104,7 @@ export default class AutoApplyButton implements SpecialVisualization {
|
|||
return new FixedUiElement("Target tagrendering " + targetTagRendering + " not found").SetClass("alert")
|
||||
}
|
||||
|
||||
const buttonState = new UIEventSource<"idle" | "running" | "done" | {error: string}>("idle")
|
||||
const buttonState = new UIEventSource<"idle" | "running" | "done" | { error: string }>("idle")
|
||||
|
||||
const button = new SubtleButton(
|
||||
new Img(icon),
|
||||
|
@ -154,10 +165,10 @@ export default class AutoApplyButton implements SpecialVisualization {
|
|||
return new FixedUiElement("All done!").SetClass("thanks")
|
||||
}
|
||||
if (st === "running") {
|
||||
return new Loading("Applying changes...")
|
||||
return new Loading("Applying changes...")
|
||||
}
|
||||
const error =st.error
|
||||
return new Combine([new FixedUiElement("Something went wrong...").SetClass("alert"), new FixedUiElement(error).SetClass("subtle")]).SetClass("flex flex-col")
|
||||
const error = st.error
|
||||
return new Combine([new FixedUiElement("Something went wrong...").SetClass("alert"), new FixedUiElement(error).SetClass("subtle")]).SetClass("flex flex-col")
|
||||
}
|
||||
))
|
||||
|
||||
|
@ -172,16 +183,5 @@ export default class AutoApplyButton implements SpecialVisualization {
|
|||
return [args[0]]
|
||||
}
|
||||
|
||||
private static generateDocs(supportedActions: string[]) {
|
||||
return [
|
||||
"A button to run many actions for many features at once.\n",
|
||||
"To effectively use this button, you'll need some ingredients:\n" +
|
||||
"- A target layer with features for which an action is defined in a tag rendering. The following special visualisations support an autoAction: " + supportedActions.join(", "),
|
||||
"- A host feature to place the auto-action on. This can be a big outline (such as a city). Another good option for this is the [current_view](./BuiltinLayers.md#current_view)",
|
||||
"- Then, use a calculated tag on the host feature to determine the overlapping object ids",
|
||||
"- At last, add this component"
|
||||
].join("\n")
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -50,7 +50,7 @@ export default class DeleteWizard extends Toggle {
|
|||
},
|
||||
options: DeleteConfig) {
|
||||
|
||||
|
||||
|
||||
const deleteAbility = new DeleteabilityChecker(id, state, options.neededChangesets)
|
||||
const tagsSource = state.allElements.getEventSourceById(id)
|
||||
|
||||
|
@ -293,7 +293,7 @@ class DeleteabilityChecker {
|
|||
|
||||
|
||||
constructor(id: string,
|
||||
state: {osmConnection: OsmConnection},
|
||||
state: { osmConnection: OsmConnection },
|
||||
allowDeletionAtChangesetCount?: number) {
|
||||
this._id = id;
|
||||
this._state = state;
|
||||
|
@ -316,7 +316,7 @@ class DeleteabilityChecker {
|
|||
const t = Translations.t.delete;
|
||||
const id = this._id;
|
||||
const state = this.canBeDeleted
|
||||
const self = this;
|
||||
const self = this;
|
||||
if (!id.startsWith("node")) {
|
||||
this.canBeDeleted.setData({
|
||||
canBeDeleted: false,
|
||||
|
|
|
@ -42,7 +42,7 @@ export default class EditableTagRendering extends Toggle {
|
|||
)
|
||||
}
|
||||
|
||||
private static CreateRendering(state: {featureSwitchUserbadge?: UIEventSource<boolean>, osmConnection: OsmConnection}, tags: UIEventSource<any>, configuration: TagRenderingConfig, units: Unit[], editMode: UIEventSource<boolean>): BaseUIElement {
|
||||
private static CreateRendering(state: { featureSwitchUserbadge?: UIEventSource<boolean>, osmConnection: OsmConnection }, tags: UIEventSource<any>, configuration: TagRenderingConfig, units: Unit[], editMode: UIEventSource<boolean>): BaseUIElement {
|
||||
const answer: BaseUIElement = new TagRenderingAnswer(tags, configuration, state)
|
||||
answer.SetClass("w-full")
|
||||
let rendering = answer;
|
||||
|
@ -60,7 +60,7 @@ export default class EditableTagRendering extends Toggle {
|
|||
|
||||
|
||||
const question = new Lazy(() =>
|
||||
new TagRenderingQuestion(tags, configuration,state,
|
||||
new TagRenderingQuestion(tags, configuration, state,
|
||||
{
|
||||
units: units,
|
||||
cancelButton: Translations.t.general.cancel.Clone()
|
||||
|
|
|
@ -139,7 +139,7 @@ export default class MoveWizard extends Toggle {
|
|||
minZoom: reason.minZoom,
|
||||
centerLocation: loc,
|
||||
mapBackground: new UIEventSource<BaseLayer>(preferredBackground) // We detach the layer
|
||||
|
||||
|
||||
})
|
||||
|
||||
if (reason.lockBounds) {
|
||||
|
|
|
@ -60,7 +60,6 @@ export default class NewNoteUi extends Toggle {
|
|||
|
||||
super(
|
||||
new Toggle(
|
||||
|
||||
new Combine(
|
||||
[
|
||||
t.noteLayerHasFilters.SetClass("alert"),
|
||||
|
|
|
@ -70,15 +70,15 @@ export default class NoteCommentElement extends Combine {
|
|||
|
||||
}
|
||||
|
||||
public static addCommentTo(txt: string, tags: UIEventSource<any>, state: {osmConnection: OsmConnection}){
|
||||
public static addCommentTo(txt: string, tags: UIEventSource<any>, state: { osmConnection: OsmConnection }) {
|
||||
const comments: any[] = JSON.parse(tags.data["comments"])
|
||||
const username = state.osmConnection.userDetails.data.name
|
||||
|
||||
var urlRegex = /(https?:\/\/[^\s]+)/g;
|
||||
const html = txt.replace(urlRegex, function(url) {
|
||||
const html = txt.replace(urlRegex, function (url) {
|
||||
return '<a href="' + url + '">' + url + '</a>';
|
||||
})
|
||||
|
||||
|
||||
comments.push({
|
||||
"date": new Date().toISOString(),
|
||||
"uid": state.osmConnection.userDetails.data.uid,
|
||||
|
@ -91,5 +91,5 @@ export default class NoteCommentElement extends Combine {
|
|||
tags.data["comments"] = JSON.stringify(comments)
|
||||
tags.ping()
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -45,7 +45,8 @@ export default class SplitRoadWizard extends Toggle {
|
|||
featureSwitchUserbadge: UIEventSource<boolean>,
|
||||
changes: Changes,
|
||||
layoutToUse: LayoutConfig,
|
||||
allElements: ElementStorage}) {
|
||||
allElements: ElementStorage
|
||||
}) {
|
||||
|
||||
const t = Translations.t.split;
|
||||
|
||||
|
@ -84,7 +85,7 @@ export default class SplitRoadWizard extends Toggle {
|
|||
zoomToFeatures: true,
|
||||
state
|
||||
})
|
||||
|
||||
|
||||
new ShowDataLayer({
|
||||
features: new StaticFeatureSource(splitPoints, true),
|
||||
leafletMap: miniMap.leafletMap,
|
||||
|
@ -94,7 +95,6 @@ export default class SplitRoadWizard extends Toggle {
|
|||
state
|
||||
})
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Handles a click on the overleaf map.
|
||||
|
|
|
@ -38,6 +38,7 @@ export default class TagApplyButton implements AutoAction {
|
|||
doc: "If specified, applies the the tags onto _another_ object. The id will be read from properties[id_of_object_to_apply_this_one] of the selected object. The tags are still calculated based on the tags of the _selected_ element"
|
||||
}
|
||||
];
|
||||
public readonly example = "`{tag_apply(survey_date=$_now:date, Surveyed today!)}`, `{tag_apply(addr:street=$addr:street, Apply the address, apply_icon.svg, _closest_osm_id)";
|
||||
|
||||
public static generateTagsToApply(spec: string, tagSource: UIEventSource<any>): UIEventSource<Tag[]> {
|
||||
|
||||
|
@ -79,12 +80,10 @@ export default class TagApplyButton implements AutoAction {
|
|||
|
||||
}
|
||||
|
||||
public readonly example = "`{tag_apply(survey_date=$_now:date, Surveyed today!)}`, `{tag_apply(addr:street=$addr:street, Apply the address, apply_icon.svg, _closest_osm_id)";
|
||||
|
||||
async applyActionOn(state: {
|
||||
layoutToUse: LayoutConfig,
|
||||
changes: Changes
|
||||
}, tags: UIEventSource<any>, args: string[]) : Promise<void>{
|
||||
}, tags: UIEventSource<any>, args: string[]): Promise<void> {
|
||||
const tagsToApply = TagApplyButton.generateTagsToApply(args[0], tags)
|
||||
const targetIdKey = args[3]
|
||||
|
||||
|
|
|
@ -13,9 +13,9 @@ export default class TagRenderingAnswer extends VariableUiElement {
|
|||
|
||||
constructor(tagsSource: UIEventSource<any>, configuration: TagRenderingConfig,
|
||||
state: any,
|
||||
contentClasses: string = "", contentStyle: string = "", options?:{
|
||||
specialViz: Map<string, BaseUIElement>
|
||||
}) {
|
||||
contentClasses: string = "", contentStyle: string = "", options?: {
|
||||
specialViz: Map<string, BaseUIElement>
|
||||
}) {
|
||||
if (configuration === undefined) {
|
||||
throw "Trying to generate a tagRenderingAnswer without configuration..."
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import List from "./Base/List";
|
|||
import BaseUIElement from "./BaseUIElement";
|
||||
import LanguagePicker from "./LanguagePicker";
|
||||
import TableOfContents from "./Base/TableOfContents";
|
||||
import BackToIndex from "./BigComponents/BackToIndex";
|
||||
import LeftIndex from "./Base/LeftIndex";
|
||||
|
||||
class Snippet extends Toggleable {
|
||||
|
@ -21,26 +20,26 @@ class Snippet extends Toggleable {
|
|||
|
||||
|
||||
class SnippetContent extends Combine {
|
||||
constructor(translations:any, ...extras: BaseUIElement[]) {
|
||||
super([
|
||||
translations.intro,
|
||||
new List([
|
||||
translations.li0,
|
||||
translations.li1,
|
||||
translations.li2,
|
||||
translations.li3,
|
||||
translations.li4,
|
||||
translations.li5,
|
||||
translations.li6,
|
||||
]),
|
||||
translations.outro,
|
||||
...extras
|
||||
])
|
||||
this.SetClass("flex flex-col")
|
||||
constructor(translations: any, ...extras: BaseUIElement[]) {
|
||||
super([
|
||||
translations.intro,
|
||||
new List([
|
||||
translations.li0,
|
||||
translations.li1,
|
||||
translations.li2,
|
||||
translations.li3,
|
||||
translations.li4,
|
||||
translations.li5,
|
||||
translations.li6,
|
||||
]),
|
||||
translations.outro,
|
||||
...extras
|
||||
])
|
||||
this.SetClass("flex flex-col")
|
||||
}
|
||||
}
|
||||
|
||||
class ProfessionalGui extends LeftIndex{
|
||||
class ProfessionalGui extends LeftIndex {
|
||||
|
||||
|
||||
constructor() {
|
||||
|
@ -83,11 +82,11 @@ class ProfessionalGui extends LeftIndex{
|
|||
new Accordeon([
|
||||
new Snippet(t.drawbacks.unsuitedData),
|
||||
new Snippet(t.drawbacks.licenseNuances,
|
||||
new Title( t.drawbacks.licenseNuances.usecaseMapDifferentSources.title, 4),
|
||||
new Title(t.drawbacks.licenseNuances.usecaseMapDifferentSources.title, 4),
|
||||
new SnippetContent(t.drawbacks.licenseNuances.usecaseMapDifferentSources),
|
||||
new Title( t.drawbacks.licenseNuances.usecaseGatheringOpenData.title, 4),
|
||||
new Title(t.drawbacks.licenseNuances.usecaseGatheringOpenData.title, 4),
|
||||
new SnippetContent(t.drawbacks.licenseNuances.usecaseGatheringOpenData)
|
||||
)
|
||||
)
|
||||
]),
|
||||
|
||||
]).SetClass("flex flex-col pb-12 m-3 lg:w-3/4 lg:ml-10 link-underline")
|
||||
|
@ -98,10 +97,10 @@ class ProfessionalGui extends LeftIndex{
|
|||
noTopLevel: true,
|
||||
maxDepth: 2
|
||||
}).SetClass("subtle"),
|
||||
|
||||
|
||||
LanguagePicker.CreateLanguagePicker(Translations.t.professional.title.SupportedLanguages())?.SetClass("mt-4 self-end flex-col"),
|
||||
].map(el => el?.SetClass("pl-4"))
|
||||
|
||||
|
||||
super(leftContents, content)
|
||||
|
||||
}
|
||||
|
|
|
@ -23,9 +23,9 @@ export default class QueryParameterDocumentation {
|
|||
),
|
||||
"Finally, the URL-hash is the part after the `#`. It is `node/1234` in this case."
|
||||
])
|
||||
|
||||
|
||||
public static GenerateQueryParameterDocs(): BaseUIElement {
|
||||
const docs : (string | BaseUIElement)[] = [...QueryParameterDocumentation.QueryParamDocsIntro];
|
||||
const docs: (string | BaseUIElement)[] = [...QueryParameterDocumentation.QueryParamDocsIntro];
|
||||
for (const key in QueryParameters.documentation) {
|
||||
const c = new Combine([
|
||||
new Title(key, 2),
|
||||
|
|
|
@ -44,8 +44,8 @@ export default class ShowDataLayer {
|
|||
*/
|
||||
private readonly leafletLayersPerId = new Map<string, { feature: any, leafletlayer: any }>()
|
||||
private readonly showDataLayerid: number;
|
||||
private readonly createPopup : (tags: any, layer: LayerConfig) => ScrollableFullScreen
|
||||
|
||||
private readonly createPopup: (tags: any, layer: LayerConfig) => ScrollableFullScreen
|
||||
|
||||
constructor(options: ShowDataLayerOptions & { layerToShow: LayerConfig }) {
|
||||
this._leafletMap = options.leafletMap;
|
||||
this.showDataLayerid = ShowDataLayer.dataLayerIds;
|
||||
|
@ -60,7 +60,7 @@ export default class ShowDataLayer {
|
|||
this.allElements = options.state?.allElements;
|
||||
this.createPopup = undefined;
|
||||
this._enablePopups = options.popup !== undefined;
|
||||
if(options.popup !== undefined){
|
||||
if (options.popup !== undefined) {
|
||||
this.createPopup = options.popup
|
||||
}
|
||||
const self = this;
|
||||
|
@ -73,7 +73,7 @@ export default class ShowDataLayer {
|
|||
this._features.features.addCallback(_ => self.update(options));
|
||||
options.doShowLayer?.addCallback(doShow => {
|
||||
const mp = options.leafletMap.data;
|
||||
if(mp === null){
|
||||
if (mp === null) {
|
||||
self.Destroy()
|
||||
return true;
|
||||
}
|
||||
|
@ -142,7 +142,7 @@ export default class ShowDataLayer {
|
|||
}
|
||||
}
|
||||
|
||||
private update(options: ShowDataLayerOptions) : boolean{
|
||||
private update(options: ShowDataLayerOptions): boolean {
|
||||
if (this._features.features.data === undefined) {
|
||||
return;
|
||||
}
|
||||
|
@ -152,13 +152,13 @@ export default class ShowDataLayer {
|
|||
}
|
||||
const mp = options.leafletMap.data;
|
||||
|
||||
if(mp === null){
|
||||
if (mp === null) {
|
||||
return true; // Unregister as the map has been destroyed
|
||||
}
|
||||
if (mp === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
this._cleanCount++
|
||||
// clean all the old stuff away, if any
|
||||
if (this.geoLayer !== undefined) {
|
||||
|
@ -312,7 +312,7 @@ export default class ShowDataLayer {
|
|||
leafletLayer.on("popupopen", () => {
|
||||
if (infobox === undefined) {
|
||||
const tags = this.allElements?.getEventSourceById(feature.properties.id) ?? new UIEventSource<any>(feature.properties);
|
||||
infobox = createpopup(tags, layer );
|
||||
infobox = createpopup(tags, layer);
|
||||
|
||||
infobox.isShown.addCallback(isShown => {
|
||||
if (!isShown) {
|
||||
|
|
|
@ -1,11 +1,6 @@
|
|||
import FeatureSource from "../../Logic/FeatureSource/FeatureSource";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import {ElementStorage} from "../../Logic/ElementStorage";
|
||||
import {OsmConnection} from "../../Logic/Osm/OsmConnection";
|
||||
import {Changes} from "../../Logic/Osm/Changes";
|
||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
|
||||
import FilteredLayer from "../../Models/FilteredLayer";
|
||||
import BaseLayer from "../../Models/BaseLayer";
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
|
||||
import ScrollableFullScreen from "../Base/ScrollableFullScreen";
|
||||
|
||||
|
@ -16,5 +11,5 @@ export interface ShowDataLayerOptions {
|
|||
popup?: undefined | ((tags: any, layer: LayerConfig) => ScrollableFullScreen),
|
||||
zoomToFeatures?: false | boolean,
|
||||
doShowLayer?: UIEventSource<boolean>,
|
||||
state?: {allElements?: ElementStorage}
|
||||
state?: { allElements?: ElementStorage }
|
||||
}
|
|
@ -7,7 +7,6 @@ import {GeoOperations} from "../../Logic/GeoOperations";
|
|||
import {Tiles} from "../../Models/TileRange";
|
||||
import * as clusterstyle from "../../assets/layers/cluster_style/cluster_style.json"
|
||||
import State from "../../State";
|
||||
import FeatureInfoBox from "../Popup/FeatureInfoBox";
|
||||
|
||||
export default class ShowTileInfo {
|
||||
public static readonly styling = new LayerConfig(clusterstyle, "ShowTileInfo", true)
|
||||
|
|
|
@ -58,12 +58,12 @@ export interface SpecialVisualization {
|
|||
export class AllTagsPanel extends VariableUiElement {
|
||||
|
||||
constructor(tags: UIEventSource<any>, state?) {
|
||||
|
||||
|
||||
const calculatedTags = [].concat(
|
||||
SimpleMetaTagger.lazyTags,
|
||||
...(state?.layoutToUse?.layers?.map(l => l.calculatedTags?.map(c => c[0]) ?? []) ?? []))
|
||||
|
||||
|
||||
|
||||
|
||||
super(tags.map(tags => {
|
||||
const parts = [];
|
||||
for (const key in tags) {
|
||||
|
@ -76,7 +76,7 @@ export class AllTagsPanel extends VariableUiElement {
|
|||
}
|
||||
parts.push([key, v ?? "<b>undefined</b>"]);
|
||||
}
|
||||
|
||||
|
||||
for (const key of calculatedTags) {
|
||||
const value = tags[key]
|
||||
if (value === undefined) {
|
||||
|
@ -84,12 +84,12 @@ export class AllTagsPanel extends VariableUiElement {
|
|||
}
|
||||
parts.push(["<i>" + key + "</i>", value])
|
||||
}
|
||||
|
||||
|
||||
return new Table(
|
||||
["key", "value"],
|
||||
parts
|
||||
)
|
||||
.SetStyle("border: 1px solid black; border-radius: 1em;padding:1em;display:block;").SetClass("zebra-table")
|
||||
.SetStyle("border: 1px solid black; border-radius: 1em;padding:1em;display:block;").SetClass("zebra-table")
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
@ -697,11 +697,11 @@ export default class SpecialVisualizations {
|
|||
const id = tags.data[args[2] ?? "id"]
|
||||
state.osmConnection.closeNote(id, args[3])
|
||||
?.then(_ => {
|
||||
tags.data["closed_at"] = new Date().toISOString();
|
||||
tags.ping()
|
||||
})
|
||||
tags.data["closed_at"] = new Date().toISOString();
|
||||
tags.ping()
|
||||
})
|
||||
})
|
||||
return new LoginToggle( new Toggle(
|
||||
return new LoginToggle(new Toggle(
|
||||
t.isClosed.SetClass("thanks"),
|
||||
closeButton,
|
||||
isClosed
|
||||
|
@ -794,7 +794,7 @@ export default class SpecialVisualizations {
|
|||
},
|
||||
{
|
||||
name: "start",
|
||||
doc:"Drop the first 'start' comments",
|
||||
doc: "Drop the first 'start' comments",
|
||||
defaultValue: "0"
|
||||
}
|
||||
]
|
||||
|
@ -804,7 +804,7 @@ export default class SpecialVisualizations {
|
|||
.map(commentsStr => {
|
||||
const comments: any[] = JSON.parse(commentsStr)
|
||||
const startLoc = Number(args[1] ?? 0)
|
||||
if(!isNaN(startLoc) && startLoc > 0){
|
||||
if (!isNaN(startLoc) && startLoc > 0) {
|
||||
comments.splice(0, startLoc)
|
||||
}
|
||||
return new Combine(comments
|
||||
|
@ -822,9 +822,9 @@ export default class SpecialVisualizations {
|
|||
defaultValue: "id"
|
||||
}],
|
||||
constr: (state, tags, args) => {
|
||||
const isUploading = new UIEventSource(false);
|
||||
const t = Translations.t.notes;
|
||||
const id = tags.data[args[0] ?? "id"]
|
||||
const isUploading = new UIEventSource(false);
|
||||
const t = Translations.t.notes;
|
||||
const id = tags.data[args[0] ?? "id"]
|
||||
|
||||
const uploader = new ImgurUploader(url => {
|
||||
isUploading.setData(false)
|
||||
|
@ -842,7 +842,7 @@ export default class SpecialVisualizations {
|
|||
fileSelector.GetValue().addCallback(filelist => {
|
||||
isUploading.setData(true)
|
||||
uploader.uploadMany("Image for osm.org/note/" + id, "CC0", filelist)
|
||||
|
||||
|
||||
})
|
||||
const ti = Translations.t.image
|
||||
const uploadPanel = new Combine([
|
||||
|
@ -851,7 +851,7 @@ export default class SpecialVisualizations {
|
|||
ti.ccoExplanation.SetClass("subtle text-sm"),
|
||||
ti.respectPrivacy.SetClass("text-sm")
|
||||
]).SetClass("flex flex-col")
|
||||
return new LoginToggle( new Toggle(
|
||||
return new LoginToggle(new Toggle(
|
||||
Translations.t.image.uploadingPicture.SetClass("alert"),
|
||||
uploadPanel,
|
||||
isUploading), t.loginToAddPicture, state)
|
||||
|
|
|
@ -72,7 +72,7 @@ export class SubstitutedTranslation extends VariableUiElement {
|
|||
style: string
|
||||
}
|
||||
}[] {
|
||||
|
||||
|
||||
for (const knownSpecial of SpecialVisualizations.specialVisualizations.concat(extraMappings)) {
|
||||
|
||||
// Note: the '.*?' in the regex reads as 'any character, but in a non-greedy way'
|
||||
|
|
|
@ -81,6 +81,7 @@ export default class WikidataPreviewBox extends VariableUiElement {
|
|||
}))
|
||||
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
|
||||
public static WikidataResponsePreview(wikidata: WikidataResponse): BaseUIElement {
|
||||
|
@ -142,7 +143,7 @@ export default class WikidataPreviewBox extends VariableUiElement {
|
|||
continue
|
||||
}
|
||||
const value: string[] = Array.from(wikidata.claims.get(key))
|
||||
|
||||
|
||||
if (display instanceof Translation) {
|
||||
els.push(display.Subs({value: value.join(", ")}).SetClass("m-2"))
|
||||
continue
|
||||
|
|
|
@ -7,13 +7,13 @@ export class Translation extends BaseUIElement {
|
|||
public static forcedLanguage = undefined;
|
||||
|
||||
public readonly translations: object
|
||||
|
||||
|
||||
constructor(translations: object, context?: string) {
|
||||
super()
|
||||
if (translations === undefined) {
|
||||
throw `Translation without content (${context})`
|
||||
}
|
||||
if(typeof translations === "string"){
|
||||
if (typeof translations === "string") {
|
||||
translations = {"*": translations};
|
||||
}
|
||||
let count = 0;
|
||||
|
@ -36,11 +36,6 @@ export class Translation extends BaseUIElement {
|
|||
get txt(): string {
|
||||
return this.textFor(Translation.forcedLanguage ?? Locale.language.data)
|
||||
}
|
||||
|
||||
Destroy() {
|
||||
super.Destroy();
|
||||
this.isDestroyed = true;
|
||||
}
|
||||
|
||||
static ExtractAllTranslationsFrom(object: any, context = ""): { context: string, tr: Translation }[] {
|
||||
const allTranslations: { context: string, tr: Translation }[] = []
|
||||
|
@ -74,6 +69,11 @@ export class Translation extends BaseUIElement {
|
|||
return new Translation(translations);
|
||||
}
|
||||
|
||||
Destroy() {
|
||||
super.Destroy();
|
||||
this.isDestroyed = true;
|
||||
}
|
||||
|
||||
public textFor(language: string): string {
|
||||
if (this.translations["*"]) {
|
||||
return this.translations["*"];
|
||||
|
@ -100,7 +100,7 @@ export class Translation extends BaseUIElement {
|
|||
const el = document.createElement("span")
|
||||
const self = this
|
||||
Locale.language.addCallbackAndRun(_ => {
|
||||
if(self.isDestroyed){
|
||||
if (self.isDestroyed) {
|
||||
return true
|
||||
}
|
||||
el.innerHTML = this.txt
|
||||
|
@ -124,8 +124,8 @@ export class Translation extends BaseUIElement {
|
|||
}
|
||||
return langs;
|
||||
}
|
||||
|
||||
public AllValues(): string[]{
|
||||
|
||||
public AllValues(): string[] {
|
||||
return this.SupportedLanguages().map(lng => this.translations[lng]);
|
||||
}
|
||||
|
||||
|
@ -205,7 +205,7 @@ export class Translation extends BaseUIElement {
|
|||
}
|
||||
return allIcons.filter(icon => icon != undefined)
|
||||
}
|
||||
|
||||
|
||||
AsMarkdown(): string {
|
||||
return this.txt
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue