chore: automated housekeeping...

This commit is contained in:
Pieter Vander Vennet 2024-12-17 04:23:24 +01:00
parent 047d741b1d
commit 39a98ed4a1
30 changed files with 1362 additions and 1690 deletions

View file

@ -273,7 +273,7 @@ This tagrendering is only visible in the popup if the following condition is met
### max_bolts ### max_bolts
The question is `How many bolts do routes in {title()} have at most?` The question is `How many bolts do routes in {title()} have at most?`
*The sport climbing routes here have at most {climbing:bolts:max} bolts.<div class='subtle'>This is without relays and indicates how much quickdraws a climber needs</div>* is shown if `climbing:bolts:max` is set *The sport climbing routes here have at most {climbing:bolts:max} bolts. <div class='subtle'>This is without belay stations and indicates how much quickdraws a climber needs.</div>* is shown if `climbing:bolts:max` is set
### Speed climbing? ### Speed climbing?

View file

@ -80,7 +80,7 @@ The question is `What is the grade of this climbing route according to the frenc
### bolts ### bolts
The question is `How many bolts does this route have before reaching the anchor?` The question is `How many bolts does this route have before reaching the anchor?`
*This route has {climbing:bolts} bolts <div class='subtle'>This is without relays and indicates how much quickdraws a climber needs</div>* is shown if `climbing:bolts` is set *This route has {climbing:bolts} bolts. <div class='subtle'>This is without belay stations and indicates how much quickdraws a climber needs.</div>* is shown if `climbing:bolts` is set
- *This route is not bolted* is shown if with <a href='https://wiki.openstreetmap.org/wiki/Key:climbing:bolted' target='_blank'>climbing:bolted</a>=<a href='https://wiki.openstreetmap.org/wiki/Tag:climbing:bolted%3Dno' target='_blank'>no</a> - *This route is not bolted* is shown if with <a href='https://wiki.openstreetmap.org/wiki/Key:climbing:bolted' target='_blank'>climbing:bolted</a>=<a href='https://wiki.openstreetmap.org/wiki/Tag:climbing:bolted%3Dno' target='_blank'>no</a>

View file

@ -426,10 +426,11 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
| max_snap_distance | 5 | The maximum distance that the imported point will be moved to snap onto a way in an already existing layer (in meters). This is previewed to the contributor, similar to the 'add new point'-action of MapComplete | | max_snap_distance | 5 | The maximum distance that the imported point will be moved to snap onto a way in an already existing layer (in meters). This is previewed to the contributor, similar to the 'add new point'-action of MapComplete |
| note_id | _undefined_ | If given, this key will be read. The corresponding note on OSM will be closed, stating 'imported' | | note_id | _undefined_ | If given, this key will be read. The corresponding note on OSM will be closed, stating 'imported' |
| maproulette_id | _undefined_ | The property name of the maproulette_id - this is probably `mr_taskId`. If given, the maproulette challenge will be marked as fixed. Only use this if part of a maproulette-layer. | | maproulette_id | _undefined_ | The property name of the maproulette_id - this is probably `mr_taskId`. If given, the maproulette challenge will be marked as fixed. Only use this if part of a maproulette-layer. |
| to_point | _undefined_ | If set, a feature will be converted to a centerpoint |
#### Example usage of import_button #### Example usage of import_button
<code>`{import_button(,,Import this data into OpenStreetMap,./assets/svg/addSmall.svg,,5,,)}`</code> <code>`{import_button(,,Import this data into OpenStreetMap,./assets/svg/addSmall.svg,,5,,,)}`</code>
### import_way_button ### import_way_button

View file

@ -125,7 +125,7 @@ This tagrendering is only visible in the popup if the following condition is met
### uk_addresses_import_button ### uk_addresses_import_button
_This tagrendering has no question and is thus read-only_ _This tagrendering has no question and is thus read-only_
*{import_button(address,urpn_count=$urpn_count;ref:GB:uprn=$ref:GB:uprn$,Add this address,./assets/themes/uk_addresses/housenumber_add.svg,,,,)}* *{import_button(address,urpn_count=$urpn_count;ref:GB:uprn=$ref:GB:uprn$,Add this address,./assets/themes/uk_addresses/housenumber_add.svg,,,,,)}*
### leftover-questions ### leftover-questions

View file

@ -98,7 +98,7 @@ Maproulette challenge containing velopark data
This layer is loaded from an external source, namely This layer is loaded from an external source, namely
`https://maproulette.org/api/v2/challenge/view/43282` `https://maproulette.org/api/v2/challenge/view/50552`
No themes use this layer No themes use this layer
@ -176,7 +176,7 @@ This tagrendering is only visible in the popup if the following condition is met
### import_point ### import_point
_This tagrendering has no question and is thus read-only_ _This tagrendering has no question and is thus read-only_
*{import_button(bike_parking_with_velopark_ref bike_parking,amenity=bicycle_parking;ref:velopark=$ref:velopark,Create a new bicycle parking in OSM. This parking will have the link&COMMA you'll be able to copy the attributes in the next step,,,,,mr_taskId)}* *{import_button(bike_parking_with_velopark_ref bike_parking,amenity=bicycle_parking;ref:velopark=$ref:velopark,Create a new bicycle parking in OSM. This parking will have the link&COMMA you'll be able to copy the attributes in the next step,,,,,mr_taskId,yes)}*
This tagrendering is only visible in the popup if the following condition is met: <a href='https://wiki.openstreetmap.org/wiki/Key:mr_taskStatus' target='_blank'>mr_taskStatus</a>=<a href='https://wiki.openstreetmap.org/wiki/Tag:mr_taskStatus%3DCreated' target='_blank'>Created</a> This tagrendering is only visible in the popup if the following condition is met: <a href='https://wiki.openstreetmap.org/wiki/Key:mr_taskStatus' target='_blank'>mr_taskStatus</a>=<a href='https://wiki.openstreetmap.org/wiki/Tag:mr_taskStatus%3DCreated' target='_blank'>Created</a>

View file

@ -35,10 +35,10 @@ This document gives an overview of which URL-parameters can be used to influence
26. [background](#background) 26. [background](#background)
+ [Selecting a category](#selecting-a-category) + [Selecting a category](#selecting-a-category)
+ [Selecting a specific layer](#selecting-a-specific-layer) + [Selecting a specific layer](#selecting-a-specific-layer)
27. [z](#z) 27. [oauth_token](#oauth_token)
28. [lat](#lat) 28. [z](#z)
29. [lon](#lon) 29. [lat](#lat)
30. [oauth_token](#oauth_token) 30. [lon](#lon)
31. [layer-public_bookcase](#layer-public_bookcase) 31. [layer-public_bookcase](#layer-public_bookcase)
32. [filter-public_bookcase-kid-books](#filter-public_bookcase-kid-books) 32. [filter-public_bookcase-kid-books](#filter-public_bookcase-kid-books)
33. [filter-public_bookcase-adult-books](#filter-public_bookcase-adult-books) 33. [filter-public_bookcase-adult-books](#filter-public_bookcase-adult-books)
@ -334,11 +334,19 @@ This documentation is defined in the source code at [FeatureSwitchState.ts](/src
No default value set No default value set
## oauth_token
Used to complete the login
This documentation is defined in the source code at [ThemeViewState.ts](/src/Models/ThemeViewState.ts#L177)
No default value set
## z ## z
The initial/current zoom level The initial/current zoom level
This documentation is defined in the source code at [InitialMapPositioning.ts](/src/Logic/Actors/InitialMapPositioning.ts#L37) This documentation is defined in the source code at [InitialMapPositioning.ts](/src/Logic/Actors/InitialMapPositioning.ts#L39)
The default value is _1_ The default value is _1_
@ -346,7 +354,7 @@ The default value is _1_
The initial/current latitude The initial/current latitude
This documentation is defined in the source code at [InitialMapPositioning.ts](/src/Logic/Actors/InitialMapPositioning.ts#L37) This documentation is defined in the source code at [InitialMapPositioning.ts](/src/Logic/Actors/InitialMapPositioning.ts#L39)
The default value is _0_ The default value is _0_
@ -354,18 +362,10 @@ The default value is _0_
The initial/current longitude of the app The initial/current longitude of the app
This documentation is defined in the source code at [InitialMapPositioning.ts](/src/Logic/Actors/InitialMapPositioning.ts#L37) This documentation is defined in the source code at [InitialMapPositioning.ts](/src/Logic/Actors/InitialMapPositioning.ts#L39)
The default value is _0_ The default value is _0_
## oauth_token
Used to complete the login
This documentation is defined in the source code at [ThemeViewState.ts](/src/Models/ThemeViewState.ts#L189)
No default value set
## layer-public_bookcase ## layer-public_bookcase
Whether or not layer public_bookcase is shown Whether or not layer public_bookcase is shown
@ -410,7 +410,7 @@ The default value is _0_
The mode the application starts in, e.g. 'map', 'dashboard' or 'statistics' The mode the application starts in, e.g. 'map', 'dashboard' or 'statistics'
This documentation is defined in the source code at [generateDocs.ts](ervdvn/git2/MapComplete/scripts/generateDocs.ts#L436) This documentation is defined in the source code at [generateDocs.ts](ervdvn/git/MapComplete/scripts/generateDocs.ts#L436)
The default value is _map_ The default value is _map_

View file

@ -282,11 +282,11 @@
"logout": "登出", "logout": "登出",
"mappingsAreHidden": "有些選項已經隱藏,搜尋來顯示更多選項。", "mappingsAreHidden": "有些選項已經隱藏,搜尋來顯示更多選項。",
"menu": { "menu": {
"aboutCurrentThemeTitle": "關於地圖",
"aboutMapComplete": "關於 MapComplete", "aboutMapComplete": "關於 MapComplete",
"filter": "篩選資料", "filter": "篩選資料",
"aboutCurrentThemeTitle": "關於地圖",
"openHereDifferentApp": "在其他應用程式開啟目前位置",
"moreUtilsTitle": "探索更多", "moreUtilsTitle": "探索更多",
"openHereDifferentApp": "在其他應用程式開啟目前位置",
"showIntroduction": "顯示指引", "showIntroduction": "顯示指引",
"title": "選單" "title": "選單"
}, },
@ -354,6 +354,12 @@
"skippedMultiple": "你跳過 {skipped} 問題", "skippedMultiple": "你跳過 {skipped} 問題",
"skippedOne": "你跳過一個問題" "skippedOne": "你跳過一個問題"
}, },
"questions": {
"disable": "不要再問這個問題",
"disabledIntro": "你關閉一些類型的問題,要再次啟用問題,請在這邊點一下",
"disabledTitle": "關閉問題",
"enable": "針對所有問題啟用"
},
"removeLocationHistory": "刪除位置歷史", "removeLocationHistory": "刪除位置歷史",
"retry": "重試", "retry": "重試",
"returnToTheMap": "回到地圖", "returnToTheMap": "回到地圖",
@ -362,9 +368,9 @@
"search": { "search": {
"error": "有狀況發生了…", "error": "有狀況發生了…",
"nothing": "沒有找到…", "nothing": "沒有找到…",
"recents": "最近看到的地方",
"search": "搜尋地點", "search": "搜尋地點",
"searching": "搜尋中…", "searching": "搜尋中…"
"recents": "最近看到的地方"
}, },
"searchAnswer": "搜尋選項", "searchAnswer": "搜尋選項",
"seeIndex": "查看所有專題地圖的概覽", "seeIndex": "查看所有專題地圖的概覽",
@ -494,12 +500,6 @@
"readMore": "閱讀剩下的條目內容", "readMore": "閱讀剩下的條目內容",
"searchToShort": "你的搜尋檢索太短了,請輸入長一點的文字", "searchToShort": "你的搜尋檢索太短了,請輸入長一點的文字",
"searchWikidata": "在 Wikidata 搜尋" "searchWikidata": "在 Wikidata 搜尋"
},
"questions": {
"disable": "不要再問這個問題",
"disabledIntro": "你關閉一些類型的問題,要再次啟用問題,請在這邊點一下",
"disabledTitle": "關閉問題",
"enable": "針對所有問題啟用"
} }
}, },
"hotkeyDocumentation": { "hotkeyDocumentation": {
@ -536,9 +536,30 @@
"seeNearby": "瀏覽與連結附近圖片", "seeNearby": "瀏覽與連結附近圖片",
"title": "附近的街景影像" "title": "附近的街景影像"
}, },
"panoramax": {
"deletionRequested": "報告已經送出,管理員不久會觀看",
"freeform": "還有其他相關資訊嗎?",
"otherFreeform": "請指明為何需要移除這一圖片:",
"placeholder": "請解釋為何這圖片需要刪除",
"report": {
"copyright": "圖片內含有版權內容",
"inappropriate": "這圖片不洽當(有裸露、仇恨內容或是並非街景)",
"other": "如果是其他原因請指明",
"privacy": "圖片顯示私人產權"
},
"requestDeletion": "請求刪除圖片",
"title": "為什麼要永久刪除圖片?"
},
"pleaseLogin": "請登入以新增圖片", "pleaseLogin": "請登入以新增圖片",
"processing": "伺服器正在處理你的圖片",
"respectPrivacy": "請別照人像或是車牌,不要上傳 Google 地圖、Google 街景或其他受版權保護的資料來源。", "respectPrivacy": "請別照人像或是車牌,不要上傳 Google 地圖、Google 街景或其他受版權保護的資料來源。",
"selectFile": "從你的裝置選取圖片",
"toBig": "{actual_size} 因此照片太大,請使用最大 {max_size} 的照片", "toBig": "{actual_size} 因此照片太大,請使用最大 {max_size} 的照片",
"unlink": {
"button": "解除連結圖片",
"explanation": "圖片解除連結之後,這個圖片不會與這個物件一同顯示。但仍會在附近圖片或其他物件時顯示。",
"title": "解除連結這一圖片?"
},
"upload": { "upload": {
"failReasons": "你也許已經失去網路連線", "failReasons": "你也許已經失去網路連線",
"failReasonsAdvanced": "另一個方式,請確認瀏覽器與外掛沒有擋掉第三方 API。", "failReasonsAdvanced": "另一個方式,請確認瀏覽器與外掛沒有擋掉第三方 API。",
@ -548,36 +569,15 @@
"someFailed": "抱歉,我們無法上傳 {count} 影像", "someFailed": "抱歉,我們無法上傳 {count} 影像",
"uploading": "{count} 影像已經上傳…" "uploading": "{count} 影像已經上傳…"
}, },
"noBlur": "圖片不會模糊化,請不要照人",
"one": { "one": {
"done": "你的影像已經成功上傳,謝謝你!", "done": "你的影像已經成功上傳,謝謝你!",
"failed": "抱歉,我們無法上傳你的影像", "failed": "抱歉,我們無法上傳你的影像",
"retrying": "再次上傳你的影像中 …", "retrying": "再次上傳你的影像中 …",
"uploading": "你的影像已經上傳了…" "uploading": "你的影像已經上傳了…"
}
}, },
"noBlur": "圖片不會模糊化,請不要照人" "uploadFailed": "無法上傳您的圖片。您是否已連線至網際網路,並允許第三方 APIBrave 瀏覽器或 uMatrix 外掛程式都可能會封鎖它們。"
},
"uploadFailed": "無法上傳您的圖片。您是否已連線至網際網路,並允許第三方 APIBrave 瀏覽器或 uMatrix 外掛程式都可能會封鎖它們。",
"panoramax": {
"title": "為什麼要永久刪除圖片?",
"deletionRequested": "報告已經送出,管理員不久會觀看",
"otherFreeform": "請指明為何需要移除這一圖片:",
"report": {
"copyright": "圖片內含有版權內容",
"inappropriate": "這圖片不洽當(有裸露、仇恨內容或是並非街景)",
"other": "如果是其他原因請指明",
"privacy": "圖片顯示私人產權"
},
"freeform": "還有其他相關資訊嗎?",
"placeholder": "請解釋為何這圖片需要刪除",
"requestDeletion": "請求刪除圖片"
},
"unlink": {
"button": "解除連結圖片",
"explanation": "圖片解除連結之後,這個圖片不會與這個物件一同顯示。但仍會在附近圖片或其他物件時顯示。",
"title": "解除連結這一圖片?"
},
"processing": "伺服器正在處理你的圖片",
"selectFile": "從你的裝置選取圖片"
}, },
"importInspector": { "importInspector": {
"title": "檢視與管理匯入註解" "title": "檢視與管理匯入註解"
@ -595,6 +595,9 @@
"logIn": "登入來看其他你先前查看的主題", "logIn": "登入來看其他你先前查看的主題",
"title": "MapComplete" "title": "MapComplete"
}, },
"inspector": {
"menu": "檢核貢獻者"
},
"move": { "move": {
"cancel": "選擇不同的原因", "cancel": "選擇不同的原因",
"cannotBeMoved": "這個圖徵無法移動。", "cannotBeMoved": "這個圖徵無法移動。",
@ -669,6 +672,11 @@
"takeImages": "拍攝樹木照片來自動偵測樹木類型", "takeImages": "拍攝樹木照片來自動偵測樹木類型",
"tryAgain": "選擇不同物種" "tryAgain": "選擇不同物種"
}, },
"preset_type": {
"question": "這個物件屬於什麼類型?",
"typeDescription": "這是 <b>{title}</b>. <div class='subtle'>{description}</div>",
"typeTitle": "這是 <b>{title}</b>"
},
"privacy": { "privacy": {
"editingIntro": "當你對地圖變動時,這些變動會存在開放街圖並且是公開給所有人。採用 MapComplete 的編輯變動包括以下資料:", "editingIntro": "當你對地圖變動時,這些變動會存在開放街圖並且是公開給所有人。採用 MapComplete 的編輯變動包括以下資料:",
"editingOutro": "請參考<a href='https://wiki.osmfoundation.org/wiki/Privacy_Policy' target='_blank'>OpenStreetMap.org的隱私政策</a>來取得更多資訊。我們也提醒你註冊帳號時能夠採用假名。", "editingOutro": "請參考<a href='https://wiki.osmfoundation.org/wiki/Privacy_Policy' target='_blank'>OpenStreetMap.org的隱私政策</a>來取得更多資訊。我們也提醒你註冊帳號時能夠採用假名。",
@ -731,6 +739,9 @@
"activateButton": "協助翻譯 MapComplete", "activateButton": "協助翻譯 MapComplete",
"missing": "{count} 未翻譯字串" "missing": "{count} 未翻譯字串"
}, },
"unknown": {
"clear": "清除答案"
},
"userinfo": { "userinfo": {
"notLoggedIn": "你已經登出了" "notLoggedIn": "你已經登出了"
}, },
@ -810,16 +821,5 @@
"empty": "請輸入一些 Wikidata 項目", "empty": "請輸入一些 Wikidata 項目",
"startsWithQ": "維基數據編號以 Q 開頭後面接數字" "startsWithQ": "維基數據編號以 Q 開頭後面接數字"
} }
},
"preset_type": {
"typeDescription": "這是 <b>{title}</b>. <div class='subtle'>{description}</div>",
"question": "這個物件屬於什麼類型?",
"typeTitle": "這是 <b>{title}</b>"
},
"unknown": {
"clear": "清除答案"
},
"inspector": {
"menu": "檢核貢獻者"
} }
} }

View file

@ -8,11 +8,13 @@ import { FeatureSource, WritableFeatureSource } from "../FeatureSource/FeatureSo
import { LocalStorageSource } from "../Web/LocalStorageSource" import { LocalStorageSource } from "../Web/LocalStorageSource"
import { GeoOperations } from "../GeoOperations" import { GeoOperations } from "../GeoOperations"
import { OsmTags } from "../../Models/OsmFeature" import { OsmTags } from "../../Models/OsmFeature"
import StaticFeatureSource, { WritableStaticFeatureSource } from "../FeatureSource/Sources/StaticFeatureSource" import StaticFeatureSource, {
WritableStaticFeatureSource,
} from "../FeatureSource/Sources/StaticFeatureSource"
import { MapProperties } from "../../Models/MapProperties" import { MapProperties } from "../../Models/MapProperties"
import { Orientation } from "../../Sensors/Orientation" import { Orientation } from "../../Sensors/Orientation"
"use strict" ;("use strict")
/** /**
* The geolocation-handler takes a map-location and a geolocation state. * The geolocation-handler takes a map-location and a geolocation state.
* It'll move the map as appropriate given the state of the geolocation-API * It'll move the map as appropriate given the state of the geolocation-API

View file

@ -9,7 +9,7 @@ import { Utils } from "../../Utils"
import { GeoLocationState } from "../State/GeoLocationState" import { GeoLocationState } from "../State/GeoLocationState"
import { OsmConnection } from "../Osm/OsmConnection" import { OsmConnection } from "../Osm/OsmConnection"
"use strict" ;("use strict")
/** /**
* This actor is responsible to set the map location. * This actor is responsible to set the map location.
@ -27,7 +27,11 @@ export default class InitialMapPositioning {
public location: UIEventSource<{ lon: number; lat: number }> public location: UIEventSource<{ lon: number; lat: number }>
public useTerrain: Store<boolean> public useTerrain: Store<boolean>
constructor(layoutToUse: ThemeConfig, geolocationState: GeoLocationState, osmConnection: OsmConnection) { constructor(
layoutToUse: ThemeConfig,
geolocationState: GeoLocationState,
osmConnection: OsmConnection
) {
function localStorageSynced( function localStorageSynced(
key: string, key: string,
deflt: number, deflt: number,
@ -49,7 +53,6 @@ export default class InitialMapPositioning {
return src return src
} }
// -- Location control initialization // -- Location control initialization
this.zoom = localStorageSynced( this.zoom = localStorageSynced(
"z", "z",
@ -94,12 +97,11 @@ export default class InitialMapPositioning {
console.log("Loading note", initialHash) console.log("Loading note", initialHash)
const noteId = Number(initialHash) const noteId = Number(initialHash)
if (osmConnection.isLoggedIn.data) { if (osmConnection.isLoggedIn.data) {
osmConnection.getNote(noteId).then(note => { osmConnection.getNote(noteId).then((note) => {
const [lon, lat] = note.geometry.coordinates const [lon, lat] = note.geometry.coordinates
console.log("Got note:", note) console.log("Got note:", note)
this.location.set({ lon, lat }) this.location.set({ lon, lat })
} })
)
} }
} else if ( } else if (
Constants.GeoIpServer && Constants.GeoIpServer &&

View file

@ -2,7 +2,7 @@ import { FeatureSource, WritableFeatureSource } from "../FeatureSource"
import { ImmutableStore, Store, UIEventSource } from "../../UIEventSource" import { ImmutableStore, Store, UIEventSource } from "../../UIEventSource"
import { Feature } from "geojson" import { Feature } from "geojson"
"use strict" ;("use strict")
/** /**
* A simple, read only feature store. * A simple, read only feature store.
*/ */
@ -32,7 +32,9 @@ export default class StaticFeatureSource<T extends Feature = Feature> implements
} }
} }
export class WritableStaticFeatureSource<T extends Feature = Feature> implements WritableFeatureSource<T> { export class WritableStaticFeatureSource<T extends Feature = Feature>
implements WritableFeatureSource<T>
{
public readonly features: UIEventSource<T[]> = undefined public readonly features: UIEventSource<T[]> = undefined
constructor(features: UIEventSource<T[]> | T[] | { features: T[] } | { features: Store<T[]> }) { constructor(features: UIEventSource<T[]> | T[] | { features: T[] } | { features: Store<T[]> }) {
@ -53,6 +55,5 @@ export class WritableStaticFeatureSource<T extends Feature = Feature> implements
} else { } else {
this.features = feats this.features = feats
} }
} }
} }

View file

@ -10,13 +10,13 @@ import {
MultiPolygon, MultiPolygon,
Point, Point,
Polygon, Polygon,
Position Position,
} from "geojson" } from "geojson"
import { Tiles } from "../Models/TileRange" import { Tiles } from "../Models/TileRange"
import { Utils } from "../Utils" import { Utils } from "../Utils"
import { NearestPointOnLine } from "@turf/nearest-point-on-line" import { NearestPointOnLine } from "@turf/nearest-point-on-line"
("use strict") ;("use strict")
export class GeoOperations { export class GeoOperations {
private static readonly _earthRadius = 6378137 private static readonly _earthRadius = 6378137

View file

@ -190,7 +190,7 @@ export class ImageUploadManager {
} }
} }
try { try {
({ key, value, absoluteUrl } = await this._uploader.uploadImage( ;({ key, value, absoluteUrl } = await this._uploader.uploadImage(
blob, blob,
location, location,
author, author,
@ -200,7 +200,7 @@ export class ImageUploadManager {
this.increaseCountFor(this._uploadRetried, featureId) this.increaseCountFor(this._uploadRetried, featureId)
console.error("Could not upload image, trying again:", e) console.error("Could not upload image, trying again:", e)
try { try {
({ key, value, absoluteUrl } = await this._uploader.uploadImage( ;({ key, value, absoluteUrl } = await this._uploader.uploadImage(
blob, blob,
location, location,
author, author,

View file

@ -6,7 +6,8 @@ export interface MaprouletteTask {
description: string description: string
instruction: string instruction: string
} }
export const maprouletteStatus = ["Open", export const maprouletteStatus = [
"Open",
"Fixed", "Fixed",
"False_positive", "False_positive",
"Skipped", "Skipped",
@ -16,7 +17,7 @@ export const maprouletteStatus = ["Open",
"Disabled", "Disabled",
] as const ] as const
export type MaprouletteStatus = typeof maprouletteStatus[number] export type MaprouletteStatus = (typeof maprouletteStatus)[number]
export default class Maproulette { export default class Maproulette {
public static readonly defaultEndpoint = "https://maproulette.org/api/v2" public static readonly defaultEndpoint = "https://maproulette.org/api/v2"
@ -30,7 +31,6 @@ export default class Maproulette {
public static readonly STATUS_TOO_HARD = 6 public static readonly STATUS_TOO_HARD = 6
public static readonly STATUS_DISABLED = 9 public static readonly STATUS_DISABLED = 9
public static singleton = new Maproulette() public static singleton = new Maproulette()
/* /*
* The API endpoint to use * The API endpoint to use
@ -88,7 +88,7 @@ export default class Maproulette {
tags?: string tags?: string
requestReview?: boolean requestReview?: boolean
completionResponses?: Record<string, string> completionResponses?: Record<string, string>
}, }
): Promise<void> { ): Promise<void> {
console.log("Maproulette: setting", `${this.endpoint}/task/${taskId}/${status}`, options) console.log("Maproulette: setting", `${this.endpoint}/task/${taskId}/${status}`, options)
options ??= {} options ??= {}

View file

@ -42,31 +42,34 @@ export default class UserDetails {
export type OsmServiceState = "online" | "readonly" | "offline" | "unknown" | "unreachable" export type OsmServiceState = "online" | "readonly" | "offline" | "unknown" | "unreachable"
interface CapabilityResult { interface CapabilityResult {
"version": "0.6" | string, version: "0.6" | string
"generator": "OpenStreetMap server" | string, generator: "OpenStreetMap server" | string
"copyright": "OpenStreetMap and contributors" | string, copyright: "OpenStreetMap and contributors" | string
"attribution": "http://www.openstreetmap.org/copyright" | string, attribution: "http://www.openstreetmap.org/copyright" | string
"license": "http://opendatacommons.org/licenses/odbl/1-0/" | string, license: "http://opendatacommons.org/licenses/odbl/1-0/" | string
"api": { api: {
"version": { "minimum": "0.6", "maximum": "0.6" }, version: { minimum: "0.6"; maximum: "0.6" }
"area": { "maximum": 0.25 | number }, area: { maximum: 0.25 | number }
"note_area": { "maximum": 25 | number }, note_area: { maximum: 25 | number }
"tracepoints": { "per_page": 5000 | number }, tracepoints: { per_page: 5000 | number }
"waynodes": { "maximum": 2000 | number }, waynodes: { maximum: 2000 | number }
"relationmembers": { "maximum": 32000 | number }, relationmembers: { maximum: 32000 | number }
"changesets": { "maximum_elements": 10000 | number, changesets: {
"default_query_limit": 100 | number, maximum_elements: 10000 | number
"maximum_query_limit": 100 |number}, default_query_limit: 100 | number
"notes": { "default_query_limit": 100 | number, "maximum_query_limit": 10000 |number}, maximum_query_limit: 100 | number
"timeout": { "seconds": 300 |number}, }
"status": { notes: { default_query_limit: 100 | number; maximum_query_limit: 10000 | number }
"database": OsmServiceState, timeout: { seconds: 300 | number }
"api": OsmServiceState, status: {
"gpx": OsmServiceState } database: OsmServiceState
}, api: OsmServiceState
"policy": { gpx: OsmServiceState
"imagery": { }
"blacklist":{regex: string}[] }
policy: {
imagery: {
blacklist: { regex: string }[]
} }
} }
} }
@ -76,14 +79,14 @@ export class OsmConnection {
public userDetails: UIEventSource<UserDetails> public userDetails: UIEventSource<UserDetails>
public isLoggedIn: Store<boolean> public isLoggedIn: Store<boolean>
public gpxServiceIsOnline: UIEventSource<OsmServiceState> = new UIEventSource<OsmServiceState>( public gpxServiceIsOnline: UIEventSource<OsmServiceState> = new UIEventSource<OsmServiceState>(
"unknown", "unknown"
) )
public apiIsOnline: UIEventSource<OsmServiceState> = new UIEventSource<OsmServiceState>( public apiIsOnline: UIEventSource<OsmServiceState> = new UIEventSource<OsmServiceState>(
"unknown", "unknown"
) )
public loadingStatus = new UIEventSource<"not-attempted" | "loading" | "error" | "logged-in">( public loadingStatus = new UIEventSource<"not-attempted" | "loading" | "error" | "logged-in">(
"not-attempted", "not-attempted"
) )
public preferencesHandler: OsmPreferences public preferencesHandler: OsmPreferences
public readonly _oauth_config: AuthConfig public readonly _oauth_config: AuthConfig
@ -127,7 +130,7 @@ export class OsmConnection {
this.userDetails = new UIEventSource<UserDetails>( this.userDetails = new UIEventSource<UserDetails>(
new UserDetails(this._oauth_config.url), new UserDetails(this._oauth_config.url),
"userDetails", "userDetails"
) )
if (options.fakeUser) { if (options.fakeUser) {
const ud = this.userDetails.data const ud = this.userDetails.data
@ -148,7 +151,7 @@ export class OsmConnection {
(user) => (user) =>
user.loggedIn && user.loggedIn &&
(this.apiIsOnline.data === "unknown" || this.apiIsOnline.data === "online"), (this.apiIsOnline.data === "unknown" || this.apiIsOnline.data === "online"),
[this.apiIsOnline], [this.apiIsOnline]
) )
this.isLoggedIn.addCallback((isLoggedIn) => { this.isLoggedIn.addCallback((isLoggedIn) => {
if (this.userDetails.data.loggedIn == false && isLoggedIn == true) { if (this.userDetails.data.loggedIn == false && isLoggedIn == true) {
@ -191,7 +194,7 @@ export class OsmConnection {
defaultValue: string = undefined, defaultValue: string = undefined,
options?: { options?: {
prefix?: string prefix?: string
}, }
): UIEventSource<T | undefined> { ): UIEventSource<T | undefined> {
const prefix = options?.prefix ?? "mapcomplete-" const prefix = options?.prefix ?? "mapcomplete-"
return <UIEventSource<T>>this.preferencesHandler.getPreference(key, defaultValue, prefix) return <UIEventSource<T>>this.preferencesHandler.getPreference(key, defaultValue, prefix)
@ -200,7 +203,7 @@ export class OsmConnection {
public getPreference<T extends string = string>( public getPreference<T extends string = string>(
key: string, key: string,
defaultValue: string = undefined, defaultValue: string = undefined,
prefix: string = "mapcomplete-", prefix: string = "mapcomplete-"
): UIEventSource<T | undefined> { ): UIEventSource<T | undefined> {
return <UIEventSource<T>>this.preferencesHandler.getPreference(key, defaultValue, prefix) return <UIEventSource<T>>this.preferencesHandler.getPreference(key, defaultValue, prefix)
} }
@ -244,12 +247,12 @@ export class OsmConnection {
this.updateAuthObject() this.updateAuthObject()
LocalStorageSource.get("location_before_login").setData( LocalStorageSource.get("location_before_login").setData(
Utils.runningFromConsole ? undefined : window.location.href, Utils.runningFromConsole ? undefined : window.location.href
) )
this.auth.xhr( this.auth.xhr(
{ {
method: "GET", method: "GET",
path: "/api/0.6/user/details" path: "/api/0.6/user/details",
}, },
(err, details: XMLDocument) => { (err, details: XMLDocument) => {
if (err != null) { if (err != null) {
@ -282,13 +285,13 @@ export class OsmConnection {
data.account_created = userInfo.getAttribute("account_created") data.account_created = userInfo.getAttribute("account_created")
data.uid = Number(userInfo.getAttribute("id")) data.uid = Number(userInfo.getAttribute("id"))
data.languages = Array.from( data.languages = Array.from(
userInfo.getElementsByTagName("languages")[0].getElementsByTagName("lang"), userInfo.getElementsByTagName("languages")[0].getElementsByTagName("lang")
).map((l) => l.textContent) ).map((l) => l.textContent)
data.csCount = Number.parseInt( data.csCount = Number.parseInt(
userInfo.getElementsByTagName("changesets")[0].getAttribute("count") ?? "0", userInfo.getElementsByTagName("changesets")[0].getAttribute("count") ?? "0"
) )
data.tracesCount = Number.parseInt( data.tracesCount = Number.parseInt(
userInfo.getElementsByTagName("traces")[0].getAttribute("count") ?? "0", userInfo.getElementsByTagName("traces")[0].getAttribute("count") ?? "0"
) )
data.img = undefined data.img = undefined
@ -320,7 +323,7 @@ export class OsmConnection {
action(this.userDetails.data) action(this.userDetails.data)
} }
this._onLoggedIn = [] this._onLoggedIn = []
}, }
) )
} }
@ -338,7 +341,7 @@ export class OsmConnection {
method: "GET" | "POST" | "PUT" | "DELETE", method: "GET" | "POST" | "PUT" | "DELETE",
header?: Record<string, string>, header?: Record<string, string>,
content?: string, content?: string,
allowAnonymous: boolean = false, allowAnonymous: boolean = false
): Promise<string> { ): Promise<string> {
const connection: osmAuth = this.auth const connection: osmAuth = this.auth
if (allowAnonymous && !this.auth.authenticated()) { if (allowAnonymous && !this.auth.authenticated()) {
@ -346,7 +349,7 @@ export class OsmConnection {
`${this.Backend()}/api/0.6/${path}`, `${this.Backend()}/api/0.6/${path}`,
header, header,
method, method,
content, content
) )
if (possibleResult["content"]) { if (possibleResult["content"]) {
return possibleResult["content"] return possibleResult["content"]
@ -361,7 +364,7 @@ export class OsmConnection {
method, method,
headers: header, headers: header,
content, content,
path: `/api/0.6/${path}` path: `/api/0.6/${path}`,
}, },
function (err, response) { function (err, response) {
if (err !== null) { if (err !== null) {
@ -369,7 +372,7 @@ export class OsmConnection {
} else { } else {
ok(response) ok(response)
} }
}, }
) )
}) })
} }
@ -378,7 +381,7 @@ export class OsmConnection {
path: string, path: string,
content?: string, content?: string,
header?: Record<string, string>, header?: Record<string, string>,
allowAnonymous: boolean = false, allowAnonymous: boolean = false
): Promise<T> { ): Promise<T> {
return <T>await this.interact(path, "POST", header, content, allowAnonymous) return <T>await this.interact(path, "POST", header, content, allowAnonymous)
} }
@ -386,7 +389,7 @@ export class OsmConnection {
public async put<T extends string>( public async put<T extends string>(
path: string, path: string,
content?: string, content?: string,
header?: Record<string, string>, header?: Record<string, string>
): Promise<T> { ): Promise<T> {
return <T>await this.interact(path, "PUT", header, content) return <T>await this.interact(path, "PUT", header, content)
} }
@ -394,7 +397,7 @@ export class OsmConnection {
public async get( public async get(
path: string, path: string,
header?: Record<string, string>, header?: Record<string, string>,
allowAnonymous: boolean = false, allowAnonymous: boolean = false
): Promise<string> { ): Promise<string> {
return await this.interact(path, "GET", header, undefined, allowAnonymous) return await this.interact(path, "GET", header, undefined, allowAnonymous)
} }
@ -433,7 +436,7 @@ export class OsmConnection {
return new Promise<{ id: number }>((ok) => { return new Promise<{ id: number }>((ok) => {
window.setTimeout( window.setTimeout(
() => ok({ id: Math.floor(Math.random() * 1000) }), () => ok({ id: Math.floor(Math.random() * 1000) }),
Math.random() * 5000, Math.random() * 5000
) )
}) })
} }
@ -443,9 +446,9 @@ export class OsmConnection {
"notes.json", "notes.json",
content, content,
{ {
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
}, },
true, true
) )
const parsed = JSON.parse(response) const parsed = JSON.parse(response)
console.log("Got result:", parsed) console.log("Got result:", parsed)
@ -455,9 +458,7 @@ export class OsmConnection {
} }
public async getNote(id: number): Promise<Feature<Point>> { public async getNote(id: number): Promise<Feature<Point>> {
return JSON.parse(await this.get( return JSON.parse(await this.get("notes/" + id + ".json"))
"notes/" + id + ".json"
))
} }
public static GpxTrackVisibility = ["private", "public", "trackable", "identifiable"] as const public static GpxTrackVisibility = ["private", "public", "trackable", "identifiable"] as const
@ -474,14 +475,14 @@ export class OsmConnection {
* Note: these are called 'tags' on the wiki, but I opted to name them 'labels' instead as they aren't "key=value" tags, but just words. * Note: these are called 'tags' on the wiki, but I opted to name them 'labels' instead as they aren't "key=value" tags, but just words.
*/ */
labels: string[] labels: string[]
}, }
): Promise<{ id: number }> { ): Promise<{ id: number }> {
if (this._dryRun.data) { if (this._dryRun.data) {
console.warn("Dryrun enabled - not actually uploading GPX ", gpx) console.warn("Dryrun enabled - not actually uploading GPX ", gpx)
return new Promise<{ id: number }>((ok) => { return new Promise<{ id: number }>((ok) => {
window.setTimeout( window.setTimeout(
() => ok({ id: Math.floor(Math.random() * 1000) }), () => ok({ id: Math.floor(Math.random() * 1000) }),
Math.random() * 5000, Math.random() * 5000
) )
}) })
} }
@ -490,7 +491,7 @@ export class OsmConnection {
file: gpx, file: gpx,
description: options.description, description: options.description,
tags: options.labels?.join(",") ?? "", tags: options.labels?.join(",") ?? "",
visibility: options.visibility visibility: options.visibility,
} }
if (!contents.description) { if (!contents.description) {
@ -498,7 +499,7 @@ export class OsmConnection {
} }
const extras = { const extras = {
file: file:
"; filename=\"" + '; filename="' +
(options.filename ?? "gpx_track_mapcomplete_" + new Date().toISOString()) + (options.filename ?? "gpx_track_mapcomplete_" + new Date().toISOString()) +
'"\r\nContent-Type: application/gpx+xml', '"\r\nContent-Type: application/gpx+xml',
} }
@ -508,7 +509,7 @@ user
let body = "" let body = ""
for (const key in contents) { for (const key in contents) {
body += "--" + boundary + "\r\n" body += "--" + boundary + "\r\n"
body += "Content-Disposition: form-data; name=\"" + key + "\"" body += 'Content-Disposition: form-data; name="' + key + '"'
if (extras[key] !== undefined) { if (extras[key] !== undefined) {
body += extras[key] body += extras[key]
} }
@ -519,7 +520,7 @@ user
const response = await this.post("gpx/create", body, { const response = await this.post("gpx/create", body, {
"Content-Type": "multipart/form-data; boundary=" + boundary, "Content-Type": "multipart/form-data; boundary=" + boundary,
"Content-Length": "" + body.length "Content-Length": "" + body.length,
}) })
const parsed = JSON.parse(response) const parsed = JSON.parse(response)
console.log("Uploaded GPX track", parsed) console.log("Uploaded GPX track", parsed)
@ -540,7 +541,7 @@ user
{ {
method: "POST", method: "POST",
path: `/api/0.6/notes/${id}/comment?text=${encodeURIComponent(text)}` path: `/api/0.6/notes/${id}/comment?text=${encodeURIComponent(text)}`,
}, },
function (err) { function (err) {
if (err !== null) { if (err !== null) {
@ -548,7 +549,7 @@ user
} else { } else {
ok() ok()
} }
}, }
) )
}) })
} }
@ -578,7 +579,7 @@ user
*/ */
singlepage: !this._iframeMode, singlepage: !this._iframeMode,
auto: true, auto: true,
apiUrl: this._oauth_config.api_url ?? this._oauth_config.url apiUrl: this._oauth_config.api_url ?? this._oauth_config.url,
}) })
} }
@ -637,13 +638,18 @@ user
return parsed return parsed
} }
private async FetchCapabilities(): Promise<{ api: OsmServiceState; gpx: OsmServiceState; database: OsmServiceState }> { private async FetchCapabilities(): Promise<{
api: OsmServiceState
gpx: OsmServiceState
database: OsmServiceState
}> {
if (Utils.runningFromConsole) { if (Utils.runningFromConsole) {
return { api: "online", gpx: "online", database: "online" } return { api: "online", gpx: "online", database: "online" }
} }
try { try {
const result = await Utils.downloadJson<CapabilityResult>(
const result = await Utils.downloadJson<CapabilityResult>(this.Backend() + "/api/0.6/capabilities.json") this.Backend() + "/api/0.6/capabilities.json"
)
if (result?.api?.status === undefined) { if (result?.api?.status === undefined) {
console.log("Something went wrong:", result) console.log("Something went wrong:", result)
return { api: "unreachable", gpx: "unreachable", database: "unreachable" } return { api: "unreachable", gpx: "unreachable", database: "unreachable" }
@ -652,7 +658,6 @@ user
} catch (e) { } catch (e) {
console.error("Could not fetch capabilities") console.error("Could not fetch capabilities")
return { api: "offline", gpx: "offline", database: "online" } return { api: "offline", gpx: "offline", database: "online" }
} }
} }
} }

View file

@ -4,11 +4,39 @@ export class ThemeMetaTagging {
public static readonly themeName = "usersettings" public static readonly themeName = "usersettings"
public metaTaggging_for_usersettings(feat: { properties: Record<string, string> }) { public metaTaggging_for_usersettings(feat: { properties: Record<string, string> }) {
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_md', () => feat.properties._description.match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/)?.at(1) ) Utils.AddLazyProperty(feat.properties, "_mastodon_candidate_md", () =>
Utils.AddLazyProperty(feat.properties, '_d', () => feat.properties._description?.replace(/&lt;/g,'<')?.replace(/&gt;/g,'>') ?? '' ) feat.properties._description
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_a', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.href.match(/mastodon|en.osm.town/) !== null)[0]?.href }) (feat) ) .match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/)
Utils.AddLazyProperty(feat.properties, '_mastodon_link', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.getAttribute("rel")?.indexOf('me') >= 0)[0]?.href})(feat) ) ?.at(1)
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate', () => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a ) )
feat.properties['__current_backgroun'] = 'initial_value' Utils.AddLazyProperty(
feat.properties,
"_d",
() => feat.properties._description?.replace(/&lt;/g, "<")?.replace(/&gt;/g, ">") ?? ""
)
Utils.AddLazyProperty(feat.properties, "_mastodon_candidate_a", () =>
((feat) => {
const e = document.createElement("div")
e.innerHTML = feat.properties._d
return Array.from(e.getElementsByTagName("a")).filter(
(a) => a.href.match(/mastodon|en.osm.town/) !== null
)[0]?.href
})(feat)
)
Utils.AddLazyProperty(feat.properties, "_mastodon_link", () =>
((feat) => {
const e = document.createElement("div")
e.innerHTML = feat.properties._d
return Array.from(e.getElementsByTagName("a")).filter(
(a) => a.getAttribute("rel")?.indexOf("me") >= 0
)[0]?.href
})(feat)
)
Utils.AddLazyProperty(
feat.properties,
"_mastodon_candidate",
() => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a
)
feat.properties["__current_backgroun"] = "initial_value"
} }
} }

View file

@ -68,7 +68,7 @@ export default class LinkedDataLoader {
coors coors
.trim() .trim()
.split(" ") .split(" ")
.map((n) => Number(n)), .map((n) => Number(n))
), ),
], ],
} }
@ -156,7 +156,7 @@ export default class LinkedDataLoader {
} }
const compacted = await jsonld.compact( const compacted = await jsonld.compact(
openingHoursSpecification, openingHoursSpecification,
<any>LinkedDataLoader.COMPACTING_CONTEXT_OH, <any>LinkedDataLoader.COMPACTING_CONTEXT_OH
) )
const spec: object = compacted["@graph"] const spec: object = compacted["@graph"]
if (!spec) { if (!spec) {
@ -190,12 +190,12 @@ export default class LinkedDataLoader {
const compacted = await jsonld.compact(data, <any>LinkedDataLoader.COMPACTING_CONTEXT) const compacted = await jsonld.compact(data, <any>LinkedDataLoader.COMPACTING_CONTEXT)
compacted["opening_hours"] = await LinkedDataLoader.ohToOsmFormat( compacted["opening_hours"] = await LinkedDataLoader.ohToOsmFormat(
compacted["opening_hours"], compacted["opening_hours"]
) )
if (compacted["openingHours"]) { if (compacted["openingHours"]) {
const ohspec: string[] = <any>compacted["openingHours"] const ohspec: string[] = <any>compacted["openingHours"]
compacted["opening_hours"] = OH.simplify( compacted["opening_hours"] = OH.simplify(
ohspec.map((r) => LinkedDataLoader.ohStringToOsmFormat(r)).join("; "), ohspec.map((r) => LinkedDataLoader.ohStringToOsmFormat(r)).join("; ")
) )
delete compacted["openingHours"] delete compacted["openingHours"]
} }
@ -236,7 +236,7 @@ export default class LinkedDataLoader {
static async fetchJsonLd( static async fetchJsonLd(
url: string, url: string,
options?: JsonLdLoaderOptions, options?: JsonLdLoaderOptions,
mode?: "fetch-lod" | "fetch-raw" | "proxy", mode?: "fetch-lod" | "fetch-raw" | "proxy"
): Promise<object> { ): Promise<object> {
mode ??= "fetch-lod" mode ??= "fetch-lod"
if (mode === "proxy") { if (mode === "proxy") {
@ -251,7 +251,7 @@ export default class LinkedDataLoader {
const div = document.createElement("div") const div = document.createElement("div")
div.innerHTML = htmlContent div.innerHTML = htmlContent
const script = Array.from(div.getElementsByTagName("script")).find( const script = Array.from(div.getElementsByTagName("script")).find(
(script) => script.type === "application/ld+json", (script) => script.type === "application/ld+json"
) )
const snippet = JSON.parse(script.textContent) const snippet = JSON.parse(script.textContent)
@ -266,7 +266,7 @@ export default class LinkedDataLoader {
*/ */
static removeDuplicateData( static removeDuplicateData(
externalData: Record<string, string>, externalData: Record<string, string>,
currentData: Record<string, string>, currentData: Record<string, string>
): Record<string, string> { ): Record<string, string> {
const d = { ...externalData } const d = { ...externalData }
delete d["@context"] delete d["@context"]
@ -332,7 +332,7 @@ export default class LinkedDataLoader {
} }
private static patchVeloparkProperties( private static patchVeloparkProperties(
input: Record<string, Set<string>>, input: Record<string, Set<string>>
): Record<string, string[]> { ): Record<string, string[]> {
const output: Record<string, string[]> = {} const output: Record<string, string[]> = {}
for (const k in input) { for (const k in input) {
@ -473,7 +473,7 @@ export default class LinkedDataLoader {
audience, audience,
"for", "for",
input["ref:velopark"], input["ref:velopark"],
" assuming yes", " assuming yes"
) )
return "yes" return "yes"
}) })
@ -517,7 +517,7 @@ export default class LinkedDataLoader {
private static async fetchVeloparkProperty<T extends string, G extends T>( private static async fetchVeloparkProperty<T extends string, G extends T>(
url: string, url: string,
property: string, property: string,
variable?: string, variable?: string
): Promise<SparqlResult<T, G>> { ): Promise<SparqlResult<T, G>> {
if (property === "schema:photos") { if (property === "schema:photos") {
console.log(">> Getting photos") console.log(">> Getting photos")
@ -533,7 +533,7 @@ export default class LinkedDataLoader {
[url], [url],
undefined, undefined,
" ?parking a <http://schema.mobivoc.org/BicycleParkingStation>", " ?parking a <http://schema.mobivoc.org/BicycleParkingStation>",
"?parking " + property + " " + (variable ?? ""), "?parking " + property + " " + (variable ?? "")
) )
return results return results
@ -549,7 +549,7 @@ export default class LinkedDataLoader {
private static async fetchVeloparkGraphProperty<T extends string>( private static async fetchVeloparkGraphProperty<T extends string>(
url: string, url: string,
property: string, property: string,
subExpr?: string, subExpr?: string
): Promise<SparqlResult<T, "g">> { ): Promise<SparqlResult<T, "g">> {
const result = await new TypedSparql().typedSparql<T, "g">( const result = await new TypedSparql().typedSparql<T, "g">(
{ {
@ -563,7 +563,12 @@ export default class LinkedDataLoader {
"g", "g",
" ?parking a <http://schema.mobivoc.org/BicycleParkingStation>", " ?parking a <http://schema.mobivoc.org/BicycleParkingStation>",
S.graph("g", "?section " + property + " " + (subExpr ?? ""), "?section a ?type", "BIND(STR(?section) AS ?id)"), S.graph(
"g",
"?section " + property + " " + (subExpr ?? ""),
"?section a ?type",
"BIND(STR(?section) AS ?id)"
)
) )
return result return result
@ -621,7 +626,6 @@ export default class LinkedDataLoader {
// The other 'sections' need to get those copied! Then, we delete the "default"-section // The other 'sections' need to get those copied! Then, we delete the "default"-section
if (r["default"] !== undefined && Object.keys(r).length > 1) { if (r["default"] !== undefined && Object.keys(r).length > 1) {
spreadSection("default") spreadSection("default")
} }
if (Object.keys(r).length > 1) { if (Object.keys(r).length > 1) {
// This result has multiple sections // This result has multiple sections
@ -630,10 +634,13 @@ export default class LinkedDataLoader {
if (Object.keys(r).length > 2) { if (Object.keys(r).length > 2) {
console.log("Multiple sections detected: ", JSON.stringify(keys)) console.log("Multiple sections detected: ", JSON.stringify(keys))
} }
const shortestKeyLength: number = Math.min(...keys.map(k => k.length)) const shortestKeyLength: number = Math.min(...keys.map((k) => k.length))
const key = keys.find(k => k.length === shortestKeyLength) const key = keys.find((k) => k.length === shortestKeyLength)
if (keys.some(k => !k.startsWith(key))) { if (keys.some((k) => !k.startsWith(key))) {
throw "Invalid multi-object: the shortest key is not the start of all the others: " + JSON.stringify(keys) throw (
"Invalid multi-object: the shortest key is not the start of all the others: " +
JSON.stringify(keys)
)
} }
spreadSection(key) spreadSection(key)
} }
@ -652,7 +659,7 @@ export default class LinkedDataLoader {
directUrl: string, directUrl: string,
propertiesWithoutGraph: PropertiesSpec<T>, propertiesWithoutGraph: PropertiesSpec<T>,
propertiesInGraph: PropertiesSpec<T>, propertiesInGraph: PropertiesSpec<T>,
extra?: string[], extra?: string[]
): Promise<SparqlResult<T, string>> { ): Promise<SparqlResult<T, string>> {
const allPartialResults: SparqlResult<T, string>[] = [] const allPartialResults: SparqlResult<T, string>[] = []
for (const propertyName in propertiesWithoutGraph) { for (const propertyName in propertiesWithoutGraph) {
@ -662,7 +669,7 @@ export default class LinkedDataLoader {
const result = await this.fetchVeloparkProperty( const result = await this.fetchVeloparkProperty(
directUrl, directUrl,
propertyName, propertyName,
"?" + variableName, "?" + variableName
) )
allPartialResults.push(result) allPartialResults.push(result)
} else { } else {
@ -671,7 +678,7 @@ export default class LinkedDataLoader {
const result = await this.fetchVeloparkProperty( const result = await this.fetchVeloparkProperty(
directUrl, directUrl,
propertyName, propertyName,
`[${subProperty} ?${variableName}] `, `[${subProperty} ?${variableName}] `
) )
allPartialResults.push(result) allPartialResults.push(result)
} }
@ -689,7 +696,7 @@ export default class LinkedDataLoader {
const result = await this.fetchVeloparkGraphProperty( const result = await this.fetchVeloparkGraphProperty(
directUrl, directUrl,
propertyName, propertyName,
variableName, variableName
) )
allPartialResults.push(result) allPartialResults.push(result)
} }
@ -701,7 +708,7 @@ export default class LinkedDataLoader {
const result = await this.fetchVeloparkGraphProperty( const result = await this.fetchVeloparkGraphProperty(
directUrl, directUrl,
propertyName, propertyName,
variableName, variableName
) )
allPartialResults.push(result) allPartialResults.push(result)
} else { } else {
@ -710,7 +717,7 @@ export default class LinkedDataLoader {
const result = await this.fetchVeloparkGraphProperty( const result = await this.fetchVeloparkGraphProperty(
directUrl, directUrl,
propertyName, propertyName,
`[${subProperty} ?${variableName}] `, `[${subProperty} ?${variableName}] `
) )
allPartialResults.push(result) allPartialResults.push(result)
} }
@ -735,7 +742,7 @@ export default class LinkedDataLoader {
*/ */
public static async fetchVeloparkEntry( public static async fetchVeloparkEntry(
url: string, url: string,
includeExtras: boolean = false, includeExtras: boolean = false
): Promise<Feature[]> { ): Promise<Feature[]> {
const cacheKey = includeExtras + url const cacheKey = includeExtras + url
if (this.veloparkCache[cacheKey]) { if (this.veloparkCache[cacheKey]) {
@ -796,12 +803,12 @@ export default class LinkedDataLoader {
withProxyUrl, withProxyUrl,
optionalPaths, optionalPaths,
graphOptionalPaths, graphOptionalPaths,
extra, extra
) )
for (const unpatchedKey in unpatched) { for (const unpatchedKey in unpatched) {
// Dirty hack // Dirty hack
const rawData = await Utils.downloadJsonCached<object>(url, 1000 * 60 * 60) const rawData = await Utils.downloadJsonCached<object>(url, 1000 * 60 * 60)
const images = rawData["photos"]?.map(ph => <string> ph.image) const images = rawData["photos"]?.map((ph) => <string>ph.image)
if (images) { if (images) {
unpatched[unpatchedKey].images = new Set<string>(images) unpatched[unpatchedKey].images = new Set<string>(images)
} }

View file

@ -71,7 +71,10 @@ export default class TypedSparql {
result[key.value].add(value.value) result[key.value].add(value.value)
}) })
if (graphVariable && result[graphVariable]?.size > 0) { if (graphVariable && result[graphVariable]?.size > 0) {
const id: string = (<string> Array.from(result["id"] ?? [])?.[0] ?? Array.from(result[graphVariable] ?? [])?.[0]) ?? "default" const id: string =
<string>Array.from(result["id"] ?? [])?.[0] ??
Array.from(result[graphVariable] ?? [])?.[0] ??
"default"
resultAllGraphs[id] = result resultAllGraphs[id] = result
} else { } else {
resultAllGraphs["default"] = result resultAllGraphs["default"] = result

View file

@ -2,7 +2,11 @@ import ThemeConfig from "./ThemeConfig/ThemeConfig"
import { SpecialVisualizationState } from "../UI/SpecialVisualization" import { SpecialVisualizationState } from "../UI/SpecialVisualization"
import { Changes } from "../Logic/Osm/Changes" import { Changes } from "../Logic/Osm/Changes"
import { Store, UIEventSource } from "../Logic/UIEventSource" import { Store, UIEventSource } from "../Logic/UIEventSource"
import { FeatureSource, IndexedFeatureSource, WritableFeatureSource } from "../Logic/FeatureSource/FeatureSource" import {
FeatureSource,
IndexedFeatureSource,
WritableFeatureSource,
} from "../Logic/FeatureSource/FeatureSource"
import { OsmConnection } from "../Logic/Osm/OsmConnection" import { OsmConnection } from "../Logic/Osm/OsmConnection"
import { ExportableMap, MapProperties } from "./MapProperties" import { ExportableMap, MapProperties } from "./MapProperties"
import LayerState from "../Logic/State/LayerState" import LayerState from "../Logic/State/LayerState"
@ -46,7 +50,9 @@ import BackgroundLayerResetter from "../Logic/Actors/BackgroundLayerResetter"
import SaveFeatureSourceToLocalStorage from "../Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage" import SaveFeatureSourceToLocalStorage from "../Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage"
import BBoxFeatureSource from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource" import BBoxFeatureSource from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource"
import ThemeViewStateHashActor from "../Logic/Web/ThemeViewStateHashActor" import ThemeViewStateHashActor from "../Logic/Web/ThemeViewStateHashActor"
import NoElementsInViewDetector, { FeatureViewState } from "../Logic/Actors/NoElementsInViewDetector" import NoElementsInViewDetector, {
FeatureViewState,
} from "../Logic/Actors/NoElementsInViewDetector"
import FilteredLayer from "./FilteredLayer" import FilteredLayer from "./FilteredLayer"
import { PreferredRasterLayerSelector } from "../Logic/Actors/PreferredRasterLayerSelector" import { PreferredRasterLayerSelector } from "../Logic/Actors/PreferredRasterLayerSelector"
import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager" import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager"
@ -57,7 +63,7 @@ import { GeolocationControlState } from "../UI/BigComponents/GeolocationControl"
import Zoomcontrol from "../UI/Zoomcontrol" import Zoomcontrol from "../UI/Zoomcontrol"
import { import {
SummaryTileSource, SummaryTileSource,
SummaryTileSourceRewriter SummaryTileSourceRewriter,
} from "../Logic/FeatureSource/TiledFeatureSource/SummaryTileSource" } from "../Logic/FeatureSource/TiledFeatureSource/SummaryTileSource"
import summaryLayer from "../assets/generated/layers/summary.json" import summaryLayer from "../assets/generated/layers/summary.json"
import last_click_layerconfig from "../assets/generated/layers/last_click.json" import last_click_layerconfig from "../assets/generated/layers/last_click.json"
@ -178,7 +184,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
"oauth_token", "oauth_token",
undefined, undefined,
"Used to complete the login" "Used to complete the login"
) ),
}) })
const initial = new InitialMapPositioning(layout, geolocationState, this.osmConnection) const initial = new InitialMapPositioning(layout, geolocationState, this.osmConnection)
this.mapProperties = new MapLibreAdaptor(this.map, initial, { correctClick: 20 }) this.mapProperties = new MapLibreAdaptor(this.map, initial, { correctClick: 20 })
@ -186,7 +192,6 @@ export default class ThemeViewState implements SpecialVisualizationState {
this.featureSwitchIsTesting = this.featureSwitches.featureSwitchIsTesting this.featureSwitchIsTesting = this.featureSwitches.featureSwitchIsTesting
this.featureSwitchUserbadge = this.featureSwitches.featureSwitchEnableLogin this.featureSwitchUserbadge = this.featureSwitches.featureSwitchEnableLogin
this.userRelatedState = new UserRelatedState( this.userRelatedState = new UserRelatedState(
this.osmConnection, this.osmConnection,
layout, layout,
@ -783,7 +788,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
const layers = this.theme.layers.filter( const layers = this.theme.layers.filter(
(l) => (l) =>
(<string[]><unknown>Constants.priviliged_layers).indexOf(l.id) < 0 && (<string[]>(<unknown>Constants.priviliged_layers)).indexOf(l.id) < 0 &&
l.source.geojsonSource === undefined && l.source.geojsonSource === undefined &&
l.doCount l.doCount
) )

View file

@ -128,8 +128,10 @@
{#if $unknownImages.length > 0} {#if $unknownImages.length > 0}
{#if readonly} {#if readonly}
<div class="flex w-full space-x-2 overflow-x-auto border border-gray-600 p-1" <div
style="scroll-snap-type: x proximity; border: 1px solid black"> class="flex w-full space-x-2 overflow-x-auto border border-gray-600 p-1"
style="scroll-snap-type: x proximity; border: 1px solid black"
>
{#each $unknownImages as image (image)} {#each $unknownImages as image (image)}
<div class="relative flex w-fit items-center bg-gray-200"> <div class="relative flex w-fit items-center bg-gray-200">
<AttributedImage <AttributedImage

View file

@ -56,10 +56,9 @@ export class PointImportButtonViz implements SpecialVisualization {
state: SpecialVisualizationState, state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>, tagSource: UIEventSource<Record<string, string>>,
argument: string[], argument: string[],
feature: Feature, feature: Feature
): BaseUIElement { ): BaseUIElement {
const to_point_index = this.args.findIndex((arg) => arg.name === "to_point")
const to_point_index = this.args.findIndex(arg => arg.name === "to_point")
const summarizePointArg = argument[to_point_index].toLowerCase() const summarizePointArg = argument[to_point_index].toLowerCase()
if (feature.geometry.type !== "Point") { if (feature.geometry.type !== "Point") {
if (summarizePointArg !== "no" && summarizePointArg !== "false") { if (summarizePointArg !== "no" && summarizePointArg !== "false") {
@ -75,7 +74,7 @@ export class PointImportButtonViz implements SpecialVisualization {
<Feature<Point>>feature, <Feature<Point>>feature,
baseArgs, baseArgs,
tagsToApply, tagsToApply,
tagSource, tagSource
) )
return new SvelteUIElement(PointImportFlow, { return new SvelteUIElement(PointImportFlow, {

View file

@ -88,7 +88,11 @@ export class PointImportFlowState extends ImportFlow<PointImportFlowArguments> {
) )
} else { } else {
console.log("Marking maproulette task as fixed") console.log("Marking maproulette task as fixed")
await Maproulette.singleton.closeTask(Number(maproulette_id), Maproulette.STATUS_FIXED, this.state) await Maproulette.singleton.closeTask(
Number(maproulette_id),
Maproulette.STATUS_FIXED,
this.state
)
originalFeatureTags.data["mr_taskStatus"] = "Fixed" originalFeatureTags.data["mr_taskStatus"] = "Fixed"
originalFeatureTags.ping() originalFeatureTags.ping()
} }

View file

@ -159,9 +159,14 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization
const maproulette_id = tags.data[maproulette_id_key] const maproulette_id = tags.data[maproulette_id_key]
const maproulette_feature = state.indexedFeatures.featuresById.data.get(maproulette_id) const maproulette_feature = state.indexedFeatures.featuresById.data.get(maproulette_id)
const maproulette_task_id = Number(maproulette_feature.properties.mr_taskId) const maproulette_task_id = Number(maproulette_feature.properties.mr_taskId)
await Maproulette.singleton.closeTask(maproulette_task_id, Maproulette.STATUS_FIXED, state, { await Maproulette.singleton.closeTask(
maproulette_task_id,
Maproulette.STATUS_FIXED,
state,
{
comment: "Tags are copied onto " + targetId + " with MapComplete", comment: "Tags are copied onto " + targetId + " with MapComplete",
}) }
)
maproulette_feature.properties["mr_taskStatus"] = "Fixed" maproulette_feature.properties["mr_taskStatus"] = "Fixed"
state.featureProperties.getStore(maproulette_id).ping() state.featureProperties.getStore(maproulette_id).ping()
} }

View file

@ -2,7 +2,11 @@ import Combine from "./Base/Combine"
import { FixedUiElement } from "./Base/FixedUiElement" import { FixedUiElement } from "./Base/FixedUiElement"
import BaseUIElement from "./BaseUIElement" import BaseUIElement from "./BaseUIElement"
import Title from "./Base/Title" import Title from "./Base/Title"
import { RenderingSpecification, SpecialVisualization, SpecialVisualizationState } from "./SpecialVisualization" import {
RenderingSpecification,
SpecialVisualization,
SpecialVisualizationState,
} from "./SpecialVisualization"
import { HistogramViz } from "./Popup/HistogramViz" import { HistogramViz } from "./Popup/HistogramViz"
import MinimapViz from "./Popup/MinimapViz.svelte" import MinimapViz from "./Popup/MinimapViz.svelte"
import { ShareLinkViz } from "./Popup/ShareLinkViz" import { ShareLinkViz } from "./Popup/ShareLinkViz"
@ -750,7 +754,7 @@ export default class SpecialVisualizations {
feature, feature,
labelText: args[1], labelText: args[1],
image: args[2], image: args[2],
noBlur: noBlur === "true" || noBlur === "yes" noBlur: noBlur === "true" || noBlur === "yes",
}) })
}, },
}, },
@ -1862,7 +1866,7 @@ export default class SpecialVisualizations {
}) })
const externalData: Store<{ success: GeoJsonProperties } | { error: any }> = const externalData: Store<{ success: GeoJsonProperties } | { error: any }> =
sourceUrl.bindD( sourceUrl.bindD(
url => { (url) => {
const country = countryStore.data const country = countryStore.data
if (url.startsWith("https://data.velopark.be/")) { if (url.startsWith("https://data.velopark.be/")) {
return Stores.FromPromiseWithErr( return Stores.FromPromiseWithErr(

View file

@ -206,13 +206,15 @@
{ {
const summaryTileServer = Constants.VectorTileServer const summaryTileServer = Constants.VectorTileServer
// "mvt_layer_server": "https://cache.mapcomplete.org/public.{type}_{layer}/{z}/{x}/{y}.pbf", // "mvt_layer_server": "https://cache.mapcomplete.org/public.{type}_{layer}/{z}/{x}/{y}.pbf",
const status = testDownload(Utils.SubstituteKeys(summaryTileServer, { const status = testDownload(
Utils.SubstituteKeys(summaryTileServer, {
type: "pois", type: "pois",
layer: "food", layer: "food",
z: 14, z: 14,
x: 8848, x: 8848,
y: 5828 y: 5828,
})) })
)
services.push({ services.push({
name: summaryTileServer, name: summaryTileServer,
status: status.mapD((s) => { status: status.mapD((s) => {
@ -221,11 +223,10 @@
} }
return "online" return "online"
}), }),
message: new ImmutableStore("See SettingUpPSQL.md to fix") message: new ImmutableStore("See SettingUpPSQL.md to fix"),
}) })
} }
{ {
const s = Constants.countryCoderEndpoint const s = Constants.countryCoderEndpoint
const status = testDownload(s + "/0.0.0.json") const status = testDownload(s + "/0.0.0.json")

View file

@ -105,11 +105,11 @@
let canZoomIn = mapproperties.maxzoom.map( let canZoomIn = mapproperties.maxzoom.map(
(mz) => mapproperties.zoom.data < mz, (mz) => mapproperties.zoom.data < mz,
[mapproperties.zoom], [mapproperties.zoom]
) )
let canZoomOut = mapproperties.minzoom.map( let canZoomOut = mapproperties.minzoom.map(
(mz) => mapproperties.zoom.data > mz, (mz) => mapproperties.zoom.data > mz,
[mapproperties.zoom], [mapproperties.zoom]
) )
let rasterLayerName = let rasterLayerName =
@ -118,7 +118,7 @@
onDestroy( onDestroy(
rasterLayer.addCallbackAndRunD((l) => { rasterLayer.addCallbackAndRunD((l) => {
rasterLayerName = l.properties.name rasterLayerName = l.properties.name
}), })
) )
debug.addCallbackAndRun((dbg) => { debug.addCallbackAndRun((dbg) => {

View file

@ -1,7 +1,7 @@
{ {
"contributors": [ "contributors": [
{ {
"commits": 8729, "commits": 8779,
"contributor": "Pieter Vander Vennet" "contributor": "Pieter Vander Vennet"
}, },
{ {

View file

@ -1,5 +1,6 @@
{ {
"ca": "català", "ca": "català",
"cs": "čeština",
"da": "dansk", "da": "dansk",
"de": "Deutsch", "de": "Deutsch",
"en": "English", "en": "English",
@ -23,7 +24,6 @@
"sl": "slovenščina", "sl": "slovenščina",
"sv": "svenska", "sv": "svenska",
"uk": "українська мова", "uk": "українська мова",
"zgh": "ⵜⴰⵎⴰⵣⵉⵖⵜ ⵜⴰⵏⴰⵡⴰⵢⵜ ⵜⴰⵎⵖⵔⵉⴱⵉⵜ",
"zh_Hans": "简体中文", "zh_Hans": "简体中文",
"zh_Hant": "繁體中文" "zh_Hant": "繁體中文"
} }

File diff suppressed because it is too large Load diff

View file

@ -13,7 +13,7 @@
"contributor": "paunofu" "contributor": "paunofu"
}, },
{ {
"commits": 150, "commits": 154,
"contributor": "Anonymous" "contributor": "Anonymous"
}, },
{ {
@ -49,17 +49,17 @@
"contributor": "gallegonovato" "contributor": "gallegonovato"
}, },
{ {
"commits": 45, "commits": 47,
"contributor": "Babos Gábor"
},
{
"commits": 44,
"contributor": "Supaplex" "contributor": "Supaplex"
}, },
{ {
"commits": 42, "commits": 46,
"contributor": "Midgard" "contributor": "Midgard"
}, },
{
"commits": 45,
"contributor": "Babos Gábor"
},
{ {
"commits": 38, "commits": 38,
"contributor": "Lucas" "contributor": "Lucas"
@ -364,6 +364,10 @@
"commits": 4, "commits": 4,
"contributor": "Jan Zabel" "contributor": "Jan Zabel"
}, },
{
"commits": 3,
"contributor": "Gábor"
},
{ {
"commits": 3, "commits": 3,
"contributor": "Michal Čermák" "contributor": "Michal Čermák"
@ -448,10 +452,6 @@
"commits": 3, "commits": 3,
"contributor": "SiegbjornSitumeang" "contributor": "SiegbjornSitumeang"
}, },
{
"commits": 2,
"contributor": "Gábor"
},
{ {
"commits": 2, "commits": 2,
"contributor": "Héctor Ochoa Ortiz" "contributor": "Héctor Ochoa Ortiz"