forked from MapComplete/MapComplete
refactoring: slight cleanup of tests
This commit is contained in:
parent
2e9b1016de
commit
f8d34648a0
28 changed files with 252 additions and 353 deletions
|
@ -23,18 +23,18 @@ export default class SelectedElementTagsUpdater {
|
||||||
|
|
||||||
private readonly state: {
|
private readonly state: {
|
||||||
selectedElement: UIEventSource<Feature>
|
selectedElement: UIEventSource<Feature>
|
||||||
allElements: FeaturePropertiesStore
|
featureProperties: FeaturePropertiesStore
|
||||||
changes: Changes
|
changes: Changes
|
||||||
osmConnection: OsmConnection
|
osmConnection: OsmConnection
|
||||||
layoutToUse: LayoutConfig
|
layout: LayoutConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(state: {
|
constructor(state: {
|
||||||
selectedElement: UIEventSource<Feature>
|
selectedElement: UIEventSource<Feature>
|
||||||
allElements: FeaturePropertiesStore
|
featureProperties: FeaturePropertiesStore
|
||||||
changes: Changes
|
changes: Changes
|
||||||
osmConnection: OsmConnection
|
osmConnection: OsmConnection
|
||||||
layoutToUse: LayoutConfig
|
layout: LayoutConfig
|
||||||
}) {
|
}) {
|
||||||
this.state = state
|
this.state = state
|
||||||
state.osmConnection.isLoggedIn.addCallbackAndRun((isLoggedIn) => {
|
state.osmConnection.isLoggedIn.addCallbackAndRun((isLoggedIn) => {
|
||||||
|
@ -73,7 +73,7 @@ export default class SelectedElementTagsUpdater {
|
||||||
const latestTags = await OsmObject.DownloadPropertiesOf(id)
|
const latestTags = await OsmObject.DownloadPropertiesOf(id)
|
||||||
if (latestTags === "deleted") {
|
if (latestTags === "deleted") {
|
||||||
console.warn("The current selected element has been deleted upstream!")
|
console.warn("The current selected element has been deleted upstream!")
|
||||||
const currentTagsSource = state.allElements.getStore(id)
|
const currentTagsSource = state.featureProperties.getStore(id)
|
||||||
if (currentTagsSource.data["_deleted"] === "yes") {
|
if (currentTagsSource.data["_deleted"] === "yes") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -91,7 +91,7 @@ export default class SelectedElementTagsUpdater {
|
||||||
private applyUpdate(latestTags: OsmTags, id: string) {
|
private applyUpdate(latestTags: OsmTags, id: string) {
|
||||||
const state = this.state
|
const state = this.state
|
||||||
try {
|
try {
|
||||||
const leftRightSensitive = state.layoutToUse.isLeftRightSensitive()
|
const leftRightSensitive = state.layout.isLeftRightSensitive()
|
||||||
|
|
||||||
if (leftRightSensitive) {
|
if (leftRightSensitive) {
|
||||||
SimpleMetaTagger.removeBothTagging(latestTags)
|
SimpleMetaTagger.removeBothTagging(latestTags)
|
||||||
|
@ -116,7 +116,7 @@ export default class SelectedElementTagsUpdater {
|
||||||
|
|
||||||
// With the changes applied, we merge them onto the upstream object
|
// With the changes applied, we merge them onto the upstream object
|
||||||
let somethingChanged = false
|
let somethingChanged = false
|
||||||
const currentTagsSource = state.allElements.getStore(id)
|
const currentTagsSource = state.featureProperties.getStore(id)
|
||||||
const currentTags = currentTagsSource.data
|
const currentTags = currentTagsSource.data
|
||||||
for (const key in latestTags) {
|
for (const key in latestTags) {
|
||||||
let osmValue = latestTags[key]
|
let osmValue = latestTags[key]
|
||||||
|
|
|
@ -82,7 +82,7 @@ export default class DeleteAction extends OsmChangeAction {
|
||||||
return await new ChangeTagAction(this._id, this._softDeletionTags, osmObject.tags, {
|
return await new ChangeTagAction(this._id, this._softDeletionTags, osmObject.tags, {
|
||||||
...this.meta,
|
...this.meta,
|
||||||
changeType: "soft-delete",
|
changeType: "soft-delete",
|
||||||
}).CreateChangeDescriptions(changes)
|
}).CreateChangeDescriptions()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ export class ChangesetHandler {
|
||||||
constructor(
|
constructor(
|
||||||
dryRun: UIEventSource<boolean>,
|
dryRun: UIEventSource<boolean>,
|
||||||
osmConnection: OsmConnection,
|
osmConnection: OsmConnection,
|
||||||
allElements: { addAlias: (id0: String, id1: string) => void },
|
allElements: { addAlias: (id0: string, id1: string) => void },
|
||||||
changes: Changes
|
changes: Changes
|
||||||
) {
|
) {
|
||||||
this.osmConnection = osmConnection
|
this.osmConnection = osmConnection
|
||||||
|
@ -68,9 +68,9 @@ export class ChangesetHandler {
|
||||||
* The key is changed _in place_; true will be returned if a change has been applied
|
* The key is changed _in place_; true will be returned if a change has been applied
|
||||||
* @param extraMetaTags
|
* @param extraMetaTags
|
||||||
* @param rewriteIds
|
* @param rewriteIds
|
||||||
* @private
|
* @public for testing purposes
|
||||||
*/
|
*/
|
||||||
private static rewriteMetaTags(extraMetaTags: ChangesetTag[], rewriteIds: Map<string, string>) {
|
public static rewriteMetaTags(extraMetaTags: ChangesetTag[], rewriteIds: Map<string, string>) {
|
||||||
let hasChange = false
|
let hasChange = false
|
||||||
for (const tag of extraMetaTags) {
|
for (const tag of extraMetaTags) {
|
||||||
const match = tag.key.match(/^([a-zA-Z0-9_]+):(node\/-[0-9])$/)
|
const match = tag.key.match(/^([a-zA-Z0-9_]+):(node\/-[0-9])$/)
|
||||||
|
@ -185,8 +185,10 @@ export class ChangesetHandler {
|
||||||
* @param extraMetaTags: new changeset tags to add/fuse with this changeset
|
* @param extraMetaTags: new changeset tags to add/fuse with this changeset
|
||||||
* @param rewriteIds: the mapping of ids
|
* @param rewriteIds: the mapping of ids
|
||||||
* @param oldChangesetMeta: the metadata-object of the already existing changeset
|
* @param oldChangesetMeta: the metadata-object of the already existing changeset
|
||||||
|
*
|
||||||
|
* @public for testing purposes
|
||||||
*/
|
*/
|
||||||
private RewriteTagsOf(
|
public RewriteTagsOf(
|
||||||
extraMetaTags: ChangesetTag[],
|
extraMetaTags: ChangesetTag[],
|
||||||
rewriteIds: Map<string, string>,
|
rewriteIds: Map<string, string>,
|
||||||
oldChangesetMeta: {
|
oldChangesetMeta: {
|
||||||
|
@ -305,6 +307,7 @@ export class ChangesetHandler {
|
||||||
return new Map<string, string>(mappings)
|
return new Map<string, string>(mappings)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// noinspection JSUnusedLocalSymbols
|
||||||
private async CloseChangeset(changesetId: number = undefined): Promise<void> {
|
private async CloseChangeset(changesetId: number = undefined): Promise<void> {
|
||||||
if (changesetId === undefined) {
|
if (changesetId === undefined) {
|
||||||
return
|
return
|
||||||
|
|
|
@ -59,7 +59,7 @@ export class OsmConnection {
|
||||||
oauth_secret: string
|
oauth_secret: string
|
||||||
url: string
|
url: string
|
||||||
}
|
}
|
||||||
private readonly _dryRun: UIEventSource<boolean>
|
private readonly _dryRun: Store<boolean>
|
||||||
private fakeUser: boolean
|
private fakeUser: boolean
|
||||||
private _onLoggedIn: ((userDetails: UserDetails) => void)[] = []
|
private _onLoggedIn: ((userDetails: UserDetails) => void)[] = []
|
||||||
private readonly _iframeMode: Boolean | boolean
|
private readonly _iframeMode: Boolean | boolean
|
||||||
|
@ -67,7 +67,7 @@ export class OsmConnection {
|
||||||
private isChecking = false
|
private isChecking = false
|
||||||
|
|
||||||
constructor(options?: {
|
constructor(options?: {
|
||||||
dryRun?: UIEventSource<boolean>
|
dryRun?: Store<boolean>
|
||||||
fakeUser?: false | boolean
|
fakeUser?: false | boolean
|
||||||
oauth_token?: UIEventSource<string>
|
oauth_token?: UIEventSource<string>
|
||||||
// Used to keep multiple changesets open and to write to the correct changeset
|
// Used to keep multiple changesets open and to write to the correct changeset
|
||||||
|
|
|
@ -51,6 +51,8 @@ export default class UserRelatedState {
|
||||||
<LayerConfigJson>usersettings,
|
<LayerConfigJson>usersettings,
|
||||||
"userinformationpanel"
|
"userinformationpanel"
|
||||||
)
|
)
|
||||||
|
public static readonly availableUserSettingsIds: string[] =
|
||||||
|
UserRelatedState.usersettingsConfig.tagRenderings.map((tr) => tr.id)
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
osmConnection: OsmConnection,
|
osmConnection: OsmConnection,
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import LayerConfig from "./ThemeConfig/LayerConfig"
|
import LayerConfig from "./ThemeConfig/LayerConfig"
|
||||||
import { UIEventSource } from "../Logic/UIEventSource"
|
import { UIEventSource } from "../Logic/UIEventSource"
|
||||||
|
import UserRelatedState from "../Logic/State/UserRelatedState"
|
||||||
|
import { Utils } from "../Utils"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates if a menu is open, and if so, which tab is selected;
|
* Indicates if a menu is open, and if so, which tab is selected;
|
||||||
|
@ -61,6 +63,19 @@ export class MenuState {
|
||||||
public openUsersettings(highlightTagRendering?: string) {
|
public openUsersettings(highlightTagRendering?: string) {
|
||||||
this.menuIsOpened.setData(true)
|
this.menuIsOpened.setData(true)
|
||||||
this.menuViewTab.setData("settings")
|
this.menuViewTab.setData("settings")
|
||||||
|
if (
|
||||||
|
highlightTagRendering !== undefined &&
|
||||||
|
!UserRelatedState.availableUserSettingsIds.some((tr) => tr === highlightTagRendering)
|
||||||
|
) {
|
||||||
|
console.error(
|
||||||
|
"No tagRendering with id '" + highlightTagRendering + "'; maybe you meant:",
|
||||||
|
Utils.sortedByLevenshteinDistance(
|
||||||
|
highlightTagRendering,
|
||||||
|
UserRelatedState.availableUserSettingsIds,
|
||||||
|
(x) => x
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
this.highlightedUserSetting.setData(highlightTagRendering)
|
this.highlightedUserSetting.setData(highlightTagRendering)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ import PointRenderingConfigJson from "../Json/PointRenderingConfigJson"
|
||||||
import LineRenderingConfigJson from "../Json/LineRenderingConfigJson"
|
import LineRenderingConfigJson from "../Json/LineRenderingConfigJson"
|
||||||
import ValidationUtils from "./ValidationUtils"
|
import ValidationUtils from "./ValidationUtils"
|
||||||
import { RenderingSpecification } from "../../../UI/SpecialVisualization"
|
import { RenderingSpecification } from "../../../UI/SpecialVisualization"
|
||||||
|
import { QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson"
|
||||||
|
|
||||||
class ExpandFilter extends DesugaringStep<LayerConfigJson> {
|
class ExpandFilter extends DesugaringStep<LayerConfigJson> {
|
||||||
private static readonly predefinedFilters = ExpandFilter.load_filters()
|
private static readonly predefinedFilters = ExpandFilter.load_filters()
|
||||||
|
@ -410,6 +411,62 @@ class ExpandTagRendering extends Conversion<
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class DetectInline extends DesugaringStep<QuestionableTagRenderingConfigJson> {
|
||||||
|
constructor() {
|
||||||
|
super(
|
||||||
|
"If no 'inline' is set on the freeform key, it will be automatically added. If no special renderings are used, it'll be set to true",
|
||||||
|
["freeform.inline"],
|
||||||
|
"DetectInline"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
convert(
|
||||||
|
json: QuestionableTagRenderingConfigJson,
|
||||||
|
context: string
|
||||||
|
): {
|
||||||
|
result: QuestionableTagRenderingConfigJson
|
||||||
|
errors?: string[]
|
||||||
|
warnings?: string[]
|
||||||
|
information?: string[]
|
||||||
|
} {
|
||||||
|
if (json.freeform === undefined) {
|
||||||
|
return { result: json }
|
||||||
|
}
|
||||||
|
let spec: Record<string, string>
|
||||||
|
if (typeof json.render === "string") {
|
||||||
|
spec = { "*": json.render }
|
||||||
|
} else {
|
||||||
|
spec = json.render
|
||||||
|
}
|
||||||
|
const errors: string[] = []
|
||||||
|
for (const key in spec) {
|
||||||
|
if (spec[key].indexOf("<a ") >= 0) {
|
||||||
|
// We have a link element, it probably contains something that needs to be substituted...
|
||||||
|
// Let's play this safe and not inline it
|
||||||
|
return { result: json }
|
||||||
|
}
|
||||||
|
const fullSpecification = SpecialVisualizations.constructSpecification(spec[key])
|
||||||
|
if (fullSpecification.length > 1) {
|
||||||
|
// We found a special rendering!
|
||||||
|
if (json.freeform.inline === true) {
|
||||||
|
errors.push(
|
||||||
|
"At " +
|
||||||
|
context +
|
||||||
|
": 'inline' is set, but the rendering contains a special visualisation...\n " +
|
||||||
|
spec[key]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
json = JSON.parse(JSON.stringify(json))
|
||||||
|
json.freeform.inline = false
|
||||||
|
return { result: json, errors }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
json = JSON.parse(JSON.stringify(json))
|
||||||
|
json.freeform.inline ??= true
|
||||||
|
return { result: json, errors }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class AddQuestionBox extends DesugaringStep<LayerConfigJson> {
|
export class AddQuestionBox extends DesugaringStep<LayerConfigJson> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(
|
super(
|
||||||
|
@ -1014,6 +1071,7 @@ export class PrepareLayer extends Fuse<LayerConfigJson> {
|
||||||
new On("tagRenderings", new Each(new RewriteSpecial())),
|
new On("tagRenderings", new Each(new RewriteSpecial())),
|
||||||
new On("tagRenderings", new Concat(new ExpandRewrite()).andThenF(Utils.Flatten)),
|
new On("tagRenderings", new Concat(new ExpandRewrite()).andThenF(Utils.Flatten)),
|
||||||
new On("tagRenderings", (layer) => new Concat(new ExpandTagRendering(state, layer))),
|
new On("tagRenderings", (layer) => new Concat(new ExpandTagRendering(state, layer))),
|
||||||
|
new On("tagRenderings", new Each(new DetectInline())),
|
||||||
new On("mapRendering", new Concat(new ExpandRewrite()).andThenF(Utils.Flatten)),
|
new On("mapRendering", new Concat(new ExpandRewrite()).andThenF(Utils.Flatten)),
|
||||||
new On<(PointRenderingConfigJson | LineRenderingConfigJson)[], LayerConfigJson>(
|
new On<(PointRenderingConfigJson | LineRenderingConfigJson)[], LayerConfigJson>(
|
||||||
"mapRendering",
|
"mapRendering",
|
||||||
|
|
|
@ -25,13 +25,13 @@ export interface LayerConfigJson {
|
||||||
*
|
*
|
||||||
* If not given, will be hidden (and thus not toggable) in the layer control
|
* If not given, will be hidden (and thus not toggable) in the layer control
|
||||||
*/
|
*/
|
||||||
name?: string | any
|
name?: string | Record<string, string>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A description for this layer.
|
* A description for this layer.
|
||||||
* Shown in the layer selections and in the personel theme
|
* Shown in the layer selections and in the personel theme
|
||||||
*/
|
*/
|
||||||
description?: string | any
|
description?: string | Record<string, string>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This determines where the data for the layer is fetched: from OSM or from an external geojson dataset.
|
* This determines where the data for the layer is fetched: from OSM or from an external geojson dataset.
|
||||||
|
@ -45,49 +45,52 @@ export interface LayerConfigJson {
|
||||||
source:
|
source:
|
||||||
| "special"
|
| "special"
|
||||||
| "special:library"
|
| "special:library"
|
||||||
| ({
|
| (
|
||||||
/**
|
| {
|
||||||
* Every source must set which tags have to be present in order to load the given layer.
|
/**
|
||||||
*/
|
* Every source must set which tags have to be present in order to load the given layer.
|
||||||
osmTags: TagConfigJson
|
*/
|
||||||
/**
|
osmTags: TagConfigJson
|
||||||
* The maximum amount of seconds that a tile is allowed to linger in the cache
|
/**
|
||||||
*/
|
* The maximum amount of seconds that a tile is allowed to linger in the cache
|
||||||
maxCacheAge?: number
|
*/
|
||||||
} & {
|
maxCacheAge?: number
|
||||||
/**
|
}
|
||||||
* The actual source of the data to load, if loaded via geojson.
|
| {
|
||||||
*
|
/**
|
||||||
* # A single geojson-file
|
* The actual source of the data to load, if loaded via geojson.
|
||||||
* source: {geoJson: "https://my.source.net/some-geo-data.geojson"}
|
*
|
||||||
* fetches a geojson from a third party source
|
* # A single geojson-file
|
||||||
*
|
* source: {geoJson: "https://my.source.net/some-geo-data.geojson"}
|
||||||
* # A tiled geojson source
|
* fetches a geojson from a third party source
|
||||||
* source: {geoJson: "https://my.source.net/some-tile-geojson-{layer}-{z}-{x}-{y}.geojson", geoJsonZoomLevel: 14}
|
*
|
||||||
* to use a tiled geojson source. The web server must offer multiple geojsons. {z}, {x} and {y} are substituted by the location; {layer} is substituted with the id of the loaded layer
|
* # A tiled geojson source
|
||||||
*
|
* source: {geoJson: "https://my.source.net/some-tile-geojson-{layer}-{z}-{x}-{y}.geojson", geoJsonZoomLevel: 14}
|
||||||
* Some API's use a BBOX instead of a tile, this can be used by specifying {y_min}, {y_max}, {x_min} and {x_max}
|
* to use a tiled geojson source. The web server must offer multiple geojsons. {z}, {x} and {y} are substituted by the location; {layer} is substituted with the id of the loaded layer
|
||||||
*/
|
*
|
||||||
geoJson: string
|
* Some API's use a BBOX instead of a tile, this can be used by specifying {y_min}, {y_max}, {x_min} and {x_max}
|
||||||
/**
|
*/
|
||||||
* To load a tiled geojson layer, set the zoomlevel of the tiles
|
geoJson: string
|
||||||
*/
|
/**
|
||||||
geoJsonZoomLevel?: number
|
* To load a tiled geojson layer, set the zoomlevel of the tiles
|
||||||
/**
|
*/
|
||||||
* Indicates that the upstream geojson data is OSM-derived.
|
geoJsonZoomLevel?: number
|
||||||
* Useful for e.g. merging or for scripts generating this cache
|
/**
|
||||||
*/
|
* Indicates that the upstream geojson data is OSM-derived.
|
||||||
isOsmCache?: boolean
|
* Useful for e.g. merging or for scripts generating this cache
|
||||||
/**
|
*/
|
||||||
* Some API's use a mercator-projection (EPSG:900913) instead of WGS84. Set the flag `mercatorCrs: true` in the source for this
|
isOsmCache?: boolean
|
||||||
*/
|
/**
|
||||||
mercatorCrs?: boolean
|
* Some API's use a mercator-projection (EPSG:900913) instead of WGS84. Set the flag `mercatorCrs: true` in the source for this
|
||||||
/**
|
*/
|
||||||
* Some API's have an id-field, but give it a different name.
|
mercatorCrs?: boolean
|
||||||
* Setting this key will rename this field into 'id'
|
/**
|
||||||
*/
|
* Some API's have an id-field, but give it a different name.
|
||||||
idKey?: string
|
* Setting this key will rename this field into 'id'
|
||||||
})
|
*/
|
||||||
|
idKey?: string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -212,7 +215,7 @@ export interface LayerConfigJson {
|
||||||
*
|
*
|
||||||
* Do _not_ indicate 'new': 'add a new shop here' is incorrect, as the shop might have existed forever, it could just be unmapped!
|
* Do _not_ indicate 'new': 'add a new shop here' is incorrect, as the shop might have existed forever, it could just be unmapped!
|
||||||
*/
|
*/
|
||||||
title: string | any
|
title: string | Record<string, string>
|
||||||
/**
|
/**
|
||||||
* The tags to add. It determines the icon too
|
* The tags to add. It determines the icon too
|
||||||
*/
|
*/
|
||||||
|
@ -223,7 +226,7 @@ export interface LayerConfigJson {
|
||||||
*
|
*
|
||||||
* (The first sentence is until the first '.'-character in the description)
|
* (The first sentence is until the first '.'-character in the description)
|
||||||
*/
|
*/
|
||||||
description?: string | any
|
description?: string | Record<string, string>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Example images, which show real-life pictures of what such a feature might look like
|
* Example images, which show real-life pictures of what such a feature might look like
|
||||||
|
|
|
@ -41,23 +41,23 @@ export interface LayoutConfigJson {
|
||||||
/**
|
/**
|
||||||
* The title, as shown in the welcome message and the more-screen.
|
* The title, as shown in the welcome message and the more-screen.
|
||||||
*/
|
*/
|
||||||
title: string | any
|
title: string | Record<string, string>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A short description, showed as social description and in the 'more theme'-buttons.
|
* A short description, showed as social description and in the 'more theme'-buttons.
|
||||||
* Note that if this one is not defined, the first sentence of 'description' is used
|
* Note that if this one is not defined, the first sentence of 'description' is used
|
||||||
*/
|
*/
|
||||||
shortDescription?: string | any
|
shortDescription?: string | Record<string, string>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The description, as shown in the welcome message and the more-screen
|
* The description, as shown in the welcome message and the more-screen
|
||||||
*/
|
*/
|
||||||
description: string | any
|
description: string | Record<string, string>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A part of the description, shown under the login-button.
|
* A part of the description, shown under the login-button.
|
||||||
*/
|
*/
|
||||||
descriptionTail?: string | any
|
descriptionTail?: string | Record<string, string>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The icon representing this theme.
|
* The icon representing this theme.
|
||||||
|
@ -196,7 +196,7 @@ export interface LayoutConfigJson {
|
||||||
| string
|
| string
|
||||||
| {
|
| {
|
||||||
builtin: string | string[]
|
builtin: string | string[]
|
||||||
override: any
|
override: Partial<LayerConfigJson>
|
||||||
/**
|
/**
|
||||||
* TagRenderings with any of these labels will be removed from the layer.
|
* TagRenderings with any of these labels will be removed from the layer.
|
||||||
* Note that the 'id' and 'group' are considered labels too
|
* Note that the 'id' and 'group' are considered labels too
|
||||||
|
|
|
@ -186,9 +186,10 @@ export interface QuestionableTagRenderingConfigJson extends TagRenderingConfigJs
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When set, influences the way a question is asked.
|
* When set, influences the way a question is asked.
|
||||||
* Instead of showing a full-widht text field, the text field will be shown within the rendering of the question.
|
* Instead of showing a full-width text field, the text field will be shown within the rendering of the question.
|
||||||
*
|
*
|
||||||
* This combines badly with special input elements, as it'll distort the layout.
|
* This combines badly with special input elements, as it'll distort the layout.
|
||||||
|
* Note that this will be set automatically if no special elements are present.
|
||||||
*/
|
*/
|
||||||
inline?: boolean
|
inline?: boolean
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ export interface TagRenderingConfigJson {
|
||||||
/**
|
/**
|
||||||
* A human-readable text explaining what this tagRendering does
|
* A human-readable text explaining what this tagRendering does
|
||||||
*/
|
*/
|
||||||
description?: string | any
|
description?: string | Record<string, string>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders this value. Note that "{key}"-parts are substituted by the corresponding values of the element.
|
* Renders this value. Note that "{key}"-parts are substituted by the corresponding values of the element.
|
||||||
|
@ -30,7 +30,10 @@ export interface TagRenderingConfigJson {
|
||||||
* Note that this is a HTML-interpreted value, so you can add links as e.g. '<a href='{website}'>{website}</a>' or include images such as `This is of type A <br><img src='typeA-icon.svg' />`
|
* Note that this is a HTML-interpreted value, so you can add links as e.g. '<a href='{website}'>{website}</a>' or include images such as `This is of type A <br><img src='typeA-icon.svg' />`
|
||||||
* type: rendered
|
* type: rendered
|
||||||
*/
|
*/
|
||||||
render?: string | any
|
render?:
|
||||||
|
| string
|
||||||
|
| Record<string, string>
|
||||||
|
| { special: Record<string, string | Record<string, string>> & { type: string } }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.
|
* Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.
|
||||||
|
@ -102,7 +105,7 @@ export interface TagRenderingConfigJson {
|
||||||
* If not known yet, the user will be presented with `then` as an option
|
* If not known yet, the user will be presented with `then` as an option
|
||||||
* Type: rendered
|
* Type: rendered
|
||||||
*/
|
*/
|
||||||
then: string | any
|
then: string | Record<string, string>
|
||||||
/**
|
/**
|
||||||
* An icon supporting this mapping; typically shown pretty small
|
* An icon supporting this mapping; typically shown pretty small
|
||||||
* Type: icon
|
* Type: icon
|
||||||
|
|
|
@ -322,12 +322,6 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
||||||
new TitleHandler(this.selectedElement, this.selectedLayer, this.featureProperties, this)
|
new TitleHandler(this.selectedElement, this.selectedLayer, this.featureProperties, this)
|
||||||
new ChangeToElementsActor(this.changes, this.featureProperties)
|
new ChangeToElementsActor(this.changes, this.featureProperties)
|
||||||
new PendingChangesUploader(this.changes, this.selectedElement)
|
new PendingChangesUploader(this.changes, this.selectedElement)
|
||||||
new SelectedElementTagsUpdater({
|
new SelectedElementTagsUpdater(this)
|
||||||
allElements: this.featureProperties,
|
|
||||||
changes: this.changes,
|
|
||||||
selectedElement: this.selectedElement,
|
|
||||||
layoutToUse: this.layout,
|
|
||||||
osmConnection: this.osmConnection,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,10 +15,19 @@
|
||||||
|
|
||||||
let dispatch = createEventDispatcher<{ "selected" }>();
|
let dispatch = createEventDispatcher<{ "selected" }>();
|
||||||
</script>
|
</script>
|
||||||
<Inline key={config.freeform.key} {tags} template={config.render}>
|
|
||||||
<ValidatedInput {feedback} type={config.freeform.type}
|
{#if config.freeform.inline}
|
||||||
{value} on:selected={() => dispatch("selected")}></ValidatedInput>
|
<Inline key={config.freeform.key} {tags} template={config.render}>
|
||||||
</Inline>
|
<ValidatedInput {feedback} on:selected={() => dispatch("selected")}
|
||||||
|
type={config.freeform.type} {value}></ValidatedInput>
|
||||||
|
</Inline>
|
||||||
|
{:else}
|
||||||
|
<ValidatedInput {feedback} on:selected={() => dispatch("selected")}
|
||||||
|
type={config.freeform.type} {value}></ValidatedInput>
|
||||||
|
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
|
||||||
{#if $feedback !== undefined}
|
{#if $feedback !== undefined}
|
||||||
<div class="alert">
|
<div class="alert">
|
||||||
<Tr t={$feedback} />
|
<Tr t={$feedback} />
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
|
|
||||||
let htmlElem: HTMLElement;
|
let htmlElem: HTMLElement;
|
||||||
if (highlightedRendering) {
|
if (highlightedRendering) {
|
||||||
onDestroy(highlightedRendering.addCallbackAndRun(highlighted => {
|
$: onDestroy(highlightedRendering.addCallbackAndRun(highlighted => {
|
||||||
console.log("Highlighted rendering is", highlighted)
|
console.log("Highlighted rendering is", highlighted)
|
||||||
if(htmlElem === undefined){
|
if(htmlElem === undefined){
|
||||||
return
|
return
|
||||||
|
|
|
@ -28,21 +28,12 @@
|
||||||
let selectedMapping: number = undefined;
|
let selectedMapping: number = undefined;
|
||||||
let checkedMappings: boolean[];
|
let checkedMappings: boolean[];
|
||||||
$: {
|
$: {
|
||||||
|
if (config.mappings?.length > 0 && (checkedMappings === undefined || checkedMappings?.length < config.mappings.length)) {
|
||||||
if (config.mappings?.length > 0) {
|
|
||||||
checkedMappings = [...config.mappings.map(_ => false), false /*One element extra in case a freeform value is added*/];
|
checkedMappings = [...config.mappings.map(_ => false), false /*One element extra in case a freeform value is added*/];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$: console.log("Checked mappings:", checkedMappings)
|
||||||
let selectedTags: TagsFilter = undefined;
|
let selectedTags: TagsFilter = undefined;
|
||||||
$: {
|
|
||||||
try {
|
|
||||||
|
|
||||||
selectedTags = config?.constructChangeSpecification($freeformInput, selectedMapping, checkedMappings);
|
|
||||||
} catch (e) {
|
|
||||||
console.debug("Could not calculate changeSpecification:", e);
|
|
||||||
selectedTags = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function mappingIsHidden(mapping: Mapping): boolean {
|
function mappingIsHidden(mapping: Mapping): boolean {
|
||||||
if (mapping.hideInAnswer === undefined || mapping.hideInAnswer === false) {
|
if (mapping.hideInAnswer === undefined || mapping.hideInAnswer === false) {
|
||||||
|
@ -54,6 +45,18 @@
|
||||||
return (<TagsFilter>mapping.hideInAnswer).matchesProperties(tags.data);
|
return (<TagsFilter>mapping.hideInAnswer).matchesProperties(tags.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mappings: Mapping[];
|
||||||
|
$: {
|
||||||
|
mappings = config.mappings?.filter(m => !mappingIsHidden(m));
|
||||||
|
try {
|
||||||
|
selectedTags = config?.constructChangeSpecification($freeformInput, selectedMapping, checkedMappings);
|
||||||
|
} catch (e) {
|
||||||
|
console.debug("Could not calculate changeSpecification:", e);
|
||||||
|
selectedTags = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
let dispatch = createEventDispatcher<{
|
let dispatch = createEventDispatcher<{
|
||||||
"saved": {
|
"saved": {
|
||||||
config: TagRenderingConfig,
|
config: TagRenderingConfig,
|
||||||
|
@ -122,15 +125,14 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if config.freeform?.key && !(config.mappings?.length > 0)}
|
{#if config.freeform?.key && !(mappings?.length > 0)}
|
||||||
<!-- There are no options to choose from, simply show the input element: fill out the text field -->
|
<!-- There are no options to choose from, simply show the input element: fill out the text field -->
|
||||||
<FreeformInput {config} {tags} value={freeformInput} />
|
<FreeformInput {config} {tags} value={freeformInput} />
|
||||||
{/if}
|
{:else if mappings !== undefined && !config.multiAnswer}
|
||||||
|
|
||||||
{#if config.mappings !== undefined && !config.multiAnswer}
|
|
||||||
<!-- Simple radiobuttons as mapping -->
|
<!-- Simple radiobuttons as mapping -->
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
{#each config.mappings as mapping, i (mapping.then)}
|
{#each config.mappings as mapping, i (mapping.then)}
|
||||||
|
<!-- Even though we have a list of 'mappings' already, we still iterate over the list as to keep the original indices-->
|
||||||
{#if !mappingIsHidden(mapping) }
|
{#if !mappingIsHidden(mapping) }
|
||||||
<label>
|
<label>
|
||||||
<input type="radio" bind:group={selectedMapping} name={"mappings-radio-"+config.id} value={i}>
|
<input type="radio" bind:group={selectedMapping} name={"mappings-radio-"+config.id} value={i}>
|
||||||
|
@ -147,19 +149,15 @@
|
||||||
</label>
|
</label>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{:else if mappings !== undefined && config.multiAnswer}
|
||||||
|
|
||||||
|
|
||||||
{#if config.mappings !== undefined && config.multiAnswer}
|
|
||||||
<!-- Multiple answers can be chosen: checkboxes -->
|
<!-- Multiple answers can be chosen: checkboxes -->
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
{#each config.mappings as mapping, i (mapping.then)}
|
{#each config.mappings as mapping, i (mapping.then)}
|
||||||
{#if !mappingIsHidden(mapping) }
|
{#if !mappingIsHidden(mapping)}
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" name={"mappings-checkbox-"+config.id+"-"+i} bind:checked={checkedMappings[i]}>
|
<input type="checkbox" name={"mappings-checkbox-"+config.id+"-"+i} bind:checked={checkedMappings[i]}>
|
||||||
<TagRenderingMapping {mapping} {tags} {state} {selectedElement}></TagRenderingMapping>
|
<TagRenderingMapping {mapping} {tags} {state} {selectedElement}></TagRenderingMapping>
|
||||||
</label>
|
</label>{/if}
|
||||||
{/if}
|
|
||||||
{/each}
|
{/each}
|
||||||
{#if config.freeform?.key}
|
{#if config.freeform?.key}
|
||||||
<label>
|
<label>
|
||||||
|
|
|
@ -83,10 +83,10 @@ export default class SpecialVisualizations {
|
||||||
* Note that _normal_ substitutions are ignored.
|
* Note that _normal_ substitutions are ignored.
|
||||||
*
|
*
|
||||||
* // Return empty list on empty input
|
* // Return empty list on empty input
|
||||||
* SubstitutedTranslation.ExtractSpecialComponents("") // => []
|
* SubstitutedTranslation.constructSpecification("") // => []
|
||||||
*
|
*
|
||||||
* // Advanced cases with commas, braces and newlines should be handled without problem
|
* // Advanced cases with commas, braces and newlines should be handled without problem
|
||||||
* const templates = SubstitutedTranslation.ExtractSpecialComponents("{send_email(&LBRACEemail&RBRACE,Broken bicycle pump,Hello&COMMA\n\nWith this email&COMMA I'd like to inform you that the bicycle pump located at https://mapcomplete.osm.be/cyclofix?lat=&LBRACE_lat&RBRACE&lon=&LBRACE_lon&RBRACE&z=18#&LBRACEid&RBRACE is broken.\n\n Kind regards,Report this bicycle pump as broken)}")
|
* const templates = SubstitutedTranslation.constructSpecification("{send_email(&LBRACEemail&RBRACE,Broken bicycle pump,Hello&COMMA\n\nWith this email&COMMA I'd like to inform you that the bicycle pump located at https://mapcomplete.osm.be/cyclofix?lat=&LBRACE_lat&RBRACE&lon=&LBRACE_lon&RBRACE&z=18#&LBRACEid&RBRACE is broken.\n\n Kind regards,Report this bicycle pump as broken)}")
|
||||||
* const templ = templates[0]
|
* const templ = templates[0]
|
||||||
* templ.special.func.funcName // => "send_email"
|
* templ.special.func.funcName // => "send_email"
|
||||||
* templ.special.args[0] = "{email}"
|
* templ.special.args[0] = "{email}"
|
||||||
|
|
|
@ -129,6 +129,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"phone": {
|
"phone": {
|
||||||
|
"label": "contact-info",
|
||||||
"question": {
|
"question": {
|
||||||
"en": "What is the phone number of {title()}?",
|
"en": "What is the phone number of {title()}?",
|
||||||
"nl": "Wat is het telefoonnummer van {title()}?",
|
"nl": "Wat is het telefoonnummer van {title()}?",
|
||||||
|
@ -239,6 +240,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"email": {
|
"email": {
|
||||||
|
"label": "contact-info",
|
||||||
"render": {
|
"render": {
|
||||||
"*": "<a href='mailto:{email}' target='_blank'>{email}</a>"
|
"*": "<a href='mailto:{email}' target='_blank'>{email}</a>"
|
||||||
},
|
},
|
||||||
|
@ -283,6 +285,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"website": {
|
"website": {
|
||||||
|
"label": "contact-info",
|
||||||
"question": {
|
"question": {
|
||||||
"en": "What is the website of {title()}?",
|
"en": "What is the website of {title()}?",
|
||||||
"nl": "Wat is de website van {title()}?",
|
"nl": "Wat is de website van {title()}?",
|
||||||
|
@ -2016,4 +2019,4 @@
|
||||||
"pl": "Nazwa sieci to <b>{internet_access:ssid}</b>"
|
"pl": "Nazwa sieci to <b>{internet_access:ssid}</b>"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8575,6 +8575,13 @@
|
||||||
},
|
},
|
||||||
"question": "Under what license do you want to publish your pictures?"
|
"question": "Under what license do you want to publish your pictures?"
|
||||||
},
|
},
|
||||||
|
"settings-link": {
|
||||||
|
"render": {
|
||||||
|
"special": {
|
||||||
|
"text": "Open your settings on OpenStreetMap.org"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"show_debug": {
|
"show_debug": {
|
||||||
"mappings": {
|
"mappings": {
|
||||||
"0": {
|
"0": {
|
||||||
|
|
|
@ -7,10 +7,10 @@ import * as bookcaseJson from "../../../assets/generated/themes/bookcases.json"
|
||||||
import { UIEventSource } from "../../../Logic/UIEventSource"
|
import { UIEventSource } from "../../../Logic/UIEventSource"
|
||||||
import Loc from "../../../Models/Loc"
|
import Loc from "../../../Models/Loc"
|
||||||
import SelectedFeatureHandler from "../../../Logic/Actors/SelectedFeatureHandler"
|
import SelectedFeatureHandler from "../../../Logic/Actors/SelectedFeatureHandler"
|
||||||
import { ElementStorage } from "../../../Logic/ElementStorage"
|
|
||||||
import { OsmTags } from "../../../Models/OsmFeature"
|
import { OsmTags } from "../../../Models/OsmFeature"
|
||||||
import { Feature, Geometry } from "geojson"
|
import { Feature, Geometry } from "geojson"
|
||||||
import { expect, it } from "vitest"
|
import { expect, it } from "vitest"
|
||||||
|
import ThemeViewState from "../../../Models/ThemeViewState";
|
||||||
|
|
||||||
const latestTags = {
|
const latestTags = {
|
||||||
amenity: "public_bookcase",
|
amenity: "public_bookcase",
|
||||||
|
@ -48,7 +48,7 @@ Utils.injectJsonDownloadForTests("https://www.openstreetmap.org/api/0.6/node/556
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should download the latest version", () => {
|
it("should download the latest version", () => {
|
||||||
const state = new UserRelatedState(new LayoutConfig(<any>bookcaseJson, true))
|
const state = new ThemeViewState(new LayoutConfig(<any>bookcaseJson, true))
|
||||||
const feature: Feature<Geometry, OsmTags> = {
|
const feature: Feature<Geometry, OsmTags> = {
|
||||||
type: "Feature",
|
type: "Feature",
|
||||||
id: "node/5568693115",
|
id: "node/5568693115",
|
||||||
|
@ -73,15 +73,14 @@ it("should download the latest version", () => {
|
||||||
coordinates: [3.2154662, 51.2179199],
|
coordinates: [3.2154662, 51.2179199],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
state.allElements.addOrGetElement(feature)
|
state.newFeatures.features.data.push(feature)
|
||||||
SelectedElementTagsUpdater.installCallback(state)
|
state.newFeatures.features.ping()
|
||||||
|
new SelectedElementTagsUpdater(state)
|
||||||
|
|
||||||
// THis should trigger a download of the latest feaures and update the tags
|
// THis should trigger a download of the latest feaures and update the tags
|
||||||
// However, this doesn't work with ts-node for some reason
|
// However, this doesn't work with ts-node for some reason
|
||||||
state.selectedElement.setData(feature)
|
state.selectedElement.setData(feature)
|
||||||
|
|
||||||
SelectedElementTagsUpdater.applyUpdate(state, latestTags, feature.properties.id)
|
|
||||||
|
|
||||||
// The name should be updated
|
// The name should be updated
|
||||||
expect(feature.properties.name).toEqual("Stubbekwartier-buurtbibliotheek")
|
expect(feature.properties.name).toEqual("Stubbekwartier-buurtbibliotheek")
|
||||||
// The fixme should be removed
|
// The fixme should be removed
|
||||||
|
@ -100,11 +99,12 @@ it("Hash without selected element should download geojson from OSM-API", async (
|
||||||
expect(selected.data.properties.id).toEqual("node/5568693115")
|
expect(selected.data.properties.id).toEqual("node/5568693115")
|
||||||
expect(loc.data.zoom).toEqual(14)
|
expect(loc.data.zoom).toEqual(14)
|
||||||
expect(loc.data.lat).toEqual(51.2179199)
|
expect(loc.data.lat).toEqual(51.2179199)
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
new SelectedFeatureHandler(hash, {
|
new SelectedFeatureHandler(hash, {
|
||||||
selectedElement: selected,
|
selectedElement: selected,
|
||||||
allElements: new ElementStorage(),
|
allElements: new(),
|
||||||
featurePipeline: undefined,
|
featurePipeline: undefined,
|
||||||
locationControl: loc,
|
locationControl: loc,
|
||||||
layoutToUse: undefined,
|
layoutToUse: undefined,
|
||||||
|
|
|
@ -113,7 +113,7 @@ describe("OverlapFunc", () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const params: ExtraFuncParams = {
|
const params: ExtraFuncParams = {
|
||||||
getFeatureById: (id) => undefined,
|
getFeatureById: () => undefined,
|
||||||
getFeaturesWithin: () => [[door]],
|
getFeaturesWithin: () => [[door]],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,164 +0,0 @@
|
||||||
import OsmFeatureSource from "../../../Logic/FeatureSource/TiledFeatureSource/OsmFeatureSource"
|
|
||||||
import { UIEventSource } from "../../../Logic/UIEventSource"
|
|
||||||
import ScriptUtils from "../../../scripts/ScriptUtils"
|
|
||||||
import FilteredLayer, { FilterState } from "../../../Models/FilteredLayer"
|
|
||||||
import { Tiles } from "../../../Models/TileRange"
|
|
||||||
import { readFileSync } from "fs"
|
|
||||||
import { Utils } from "../../../Utils"
|
|
||||||
import { Tag } from "../../../Logic/Tags/Tag"
|
|
||||||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
|
|
||||||
import { describe, expect, it } from "vitest"
|
|
||||||
|
|
||||||
const expected = {
|
|
||||||
type: "Feature",
|
|
||||||
id: "relation/5759328",
|
|
||||||
properties: {
|
|
||||||
timestamp: "2022-06-10T00:46:55Z",
|
|
||||||
version: 6,
|
|
||||||
changeset: 122187206,
|
|
||||||
user: "Pieter Vander Vennet",
|
|
||||||
uid: 3818858,
|
|
||||||
amenity: "school",
|
|
||||||
"isced:2011:level": "vocational_lower_secondary;vocational_upper_secondary",
|
|
||||||
name: "Koninklijk Technisch Atheneum Pro Technica",
|
|
||||||
"school:gender": "mixed",
|
|
||||||
type: "multipolygon",
|
|
||||||
website: "http://ktahalle.be/",
|
|
||||||
id: "relation/5759328",
|
|
||||||
_backend: "https://osm.org",
|
|
||||||
},
|
|
||||||
geometry: {
|
|
||||||
type: "MultiPolygon",
|
|
||||||
coordinates: [
|
|
||||||
[
|
|
||||||
[
|
|
||||||
[4.2461832, 50.7335751],
|
|
||||||
[4.2463167, 50.7336785],
|
|
||||||
[4.2463473, 50.7337021],
|
|
||||||
[4.2464497, 50.7337814],
|
|
||||||
[4.2471698, 50.7343389],
|
|
||||||
[4.2469541, 50.7344768],
|
|
||||||
[4.2467571, 50.7346116],
|
|
||||||
[4.2467727, 50.7346199],
|
|
||||||
[4.2465714, 50.7347511],
|
|
||||||
[4.2462398, 50.7349687],
|
|
||||||
[4.2453546, 50.734601],
|
|
||||||
[4.2451895, 50.7345103],
|
|
||||||
[4.2448867, 50.7342629],
|
|
||||||
[4.244899, 50.7342069],
|
|
||||||
[4.2461832, 50.7335751],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
[
|
|
||||||
[4.2444209, 50.7353737],
|
|
||||||
[4.2439986, 50.7352034],
|
|
||||||
[4.2440303, 50.7351755],
|
|
||||||
[4.2440602, 50.7351058],
|
|
||||||
[4.2439776, 50.7350326],
|
|
||||||
[4.2439558, 50.7350132],
|
|
||||||
[4.2438246, 50.7348961],
|
|
||||||
[4.2437848, 50.73486],
|
|
||||||
[4.2436555, 50.7347455],
|
|
||||||
[4.2435905, 50.734689],
|
|
||||||
[4.2435494, 50.7346601],
|
|
||||||
[4.2435038, 50.7346256],
|
|
||||||
[4.2434769, 50.7346026],
|
|
||||||
[4.2430948, 50.734275],
|
|
||||||
[4.2427978, 50.7340052],
|
|
||||||
[4.2430556, 50.7338391],
|
|
||||||
[4.2438957, 50.7334942],
|
|
||||||
[4.2440204, 50.7336368],
|
|
||||||
[4.2442806, 50.7338922],
|
|
||||||
[4.2444173, 50.7340119],
|
|
||||||
[4.2447379, 50.7342925],
|
|
||||||
[4.2450107, 50.7345294],
|
|
||||||
[4.2450236, 50.7346021],
|
|
||||||
[4.2449643, 50.7347019],
|
|
||||||
[4.244711, 50.7350821],
|
|
||||||
[4.2444209, 50.7353737],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
function test(done: () => void) {
|
|
||||||
let fetchedTile = undefined
|
|
||||||
const neededTiles = new UIEventSource<number[]>([Tiles.tile_index(17, 67081, 44033)])
|
|
||||||
new OsmFeatureSource({
|
|
||||||
allowedFeatures: new Tag("amenity", "school"),
|
|
||||||
handleTile: (tile) => {
|
|
||||||
fetchedTile = tile
|
|
||||||
const data = tile.features.data[0].feature
|
|
||||||
expect(data.properties).toEqual({
|
|
||||||
id: "relation/5759328",
|
|
||||||
timestamp: "2022-06-10T00:46:55Z",
|
|
||||||
version: 6,
|
|
||||||
changeset: 122187206,
|
|
||||||
user: "Pieter Vander Vennet",
|
|
||||||
uid: 3818858,
|
|
||||||
amenity: "school",
|
|
||||||
"isced:2011:level": "vocational_lower_secondary;vocational_upper_secondary",
|
|
||||||
name: "Koninklijk Technisch Atheneum Pro Technica",
|
|
||||||
"school:gender": "mixed",
|
|
||||||
type: "multipolygon",
|
|
||||||
website: "http://ktahalle.be/",
|
|
||||||
_backend: "https://osm.org",
|
|
||||||
})
|
|
||||||
expect(data.geometry.type).toBe("MultiPolygon")
|
|
||||||
expect(data).toEqual(expected)
|
|
||||||
done()
|
|
||||||
},
|
|
||||||
isActive: new UIEventSource<boolean>(true),
|
|
||||||
neededTiles,
|
|
||||||
state: {
|
|
||||||
osmConnection: {
|
|
||||||
Backend(): string {
|
|
||||||
return "https://osm.org"
|
|
||||||
},
|
|
||||||
},
|
|
||||||
filteredLayers: new UIEventSource<FilteredLayer[]>([
|
|
||||||
{
|
|
||||||
appliedFilters: new UIEventSource<Map<string, FilterState>>(undefined),
|
|
||||||
layerDef: new LayerConfig({
|
|
||||||
id: "school",
|
|
||||||
source: {
|
|
||||||
osmTags: "amenity=school",
|
|
||||||
},
|
|
||||||
mapRendering: null,
|
|
||||||
}),
|
|
||||||
isDisplayed: new UIEventSource<boolean>(true),
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("OsmFeatureSource", () => {
|
|
||||||
it("downloading the full school should give a multipolygon", (done) => {
|
|
||||||
ScriptUtils.fixUtils()
|
|
||||||
let data = JSON.parse(readFileSync("./test/Logic/FeatureSource/osmdata.json", "utf8"))
|
|
||||||
Utils.injectJsonDownloadForTests(
|
|
||||||
"https://osm.org/api/0.6/map?bbox=4.24346923828125,50.732978448277514,4.2462158203125,50.73471682490244",
|
|
||||||
data
|
|
||||||
)
|
|
||||||
test(done)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("downloading the partial school polygon should give a multipolygon", (done) => {
|
|
||||||
ScriptUtils.fixUtils()
|
|
||||||
Utils.injectJsonDownloadForTests(
|
|
||||||
"https://www.openstreetmap.org/api/0.6/relation/5759328/full",
|
|
||||||
JSON.parse(readFileSync("./test/data/relation_5759328.json", { encoding: "utf-8" }))
|
|
||||||
)
|
|
||||||
let data = JSON.parse(
|
|
||||||
readFileSync("./test/Logic/FeatureSource/small_box.json", { encoding: "utf-8" })
|
|
||||||
)
|
|
||||||
Utils.injectJsonDownloadForTests(
|
|
||||||
"https://osm.org/api/0.6/map?bbox=4.24346923828125,50.732978448277514,4.2462158203125,50.73471682490244",
|
|
||||||
data
|
|
||||||
)
|
|
||||||
test(done)
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,22 +0,0 @@
|
||||||
import TileFreshnessCalculator from "../../../Logic/FeatureSource/TileFreshnessCalculator"
|
|
||||||
import { Tiles } from "../../../Models/TileRange"
|
|
||||||
import { describe, expect, it } from "vitest"
|
|
||||||
|
|
||||||
describe("TileFreshnessCalculator", () => {
|
|
||||||
it("should get the freshness for loaded tiles", () => {
|
|
||||||
const calc = new TileFreshnessCalculator()
|
|
||||||
// 19/266407/175535
|
|
||||||
const date = new Date()
|
|
||||||
date.setTime(42)
|
|
||||||
calc.addTileLoad(Tiles.tile_index(19, 266406, 175534), date)
|
|
||||||
|
|
||||||
expect(calc.freshnessFor(19, 266406, 175534).getTime()).toBe(42)
|
|
||||||
expect(calc.freshnessFor(20, 266406 * 2, 175534 * 2 + 1).getTime()).toBe(42)
|
|
||||||
expect(calc.freshnessFor(19, 266406, 175535)).toBeUndefined()
|
|
||||||
expect(calc.freshnessFor(18, 266406 / 2, 175534 / 2)).toBeUndefined()
|
|
||||||
calc.addTileLoad(Tiles.tile_index(19, 266406, 175534 + 1), date)
|
|
||||||
calc.addTileLoad(Tiles.tile_index(19, 266406 + 1, 175534), date)
|
|
||||||
calc.addTileLoad(Tiles.tile_index(19, 266406 + 1, 175534 + 1), date)
|
|
||||||
expect(calc.freshnessFor(18, 266406 / 2, 175534 / 2).getTime()).toBe(42)
|
|
||||||
})
|
|
||||||
})
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,11 +1,11 @@
|
||||||
import Minimap from "../../../../UI/Base/Minimap"
|
|
||||||
import { Utils } from "../../../../Utils"
|
import { Utils } from "../../../../Utils"
|
||||||
import LayoutConfig from "../../../../Models/ThemeConfig/LayoutConfig"
|
import LayoutConfig from "../../../../Models/ThemeConfig/LayoutConfig"
|
||||||
import State from "../../../../State"
|
|
||||||
import { BBox } from "../../../../Logic/BBox"
|
import { BBox } from "../../../../Logic/BBox"
|
||||||
import ReplaceGeometryAction from "../../../../Logic/Osm/Actions/ReplaceGeometryAction"
|
import ReplaceGeometryAction from "../../../../Logic/Osm/Actions/ReplaceGeometryAction"
|
||||||
import ShowDataLayer from "../../../../UI/ShowDataLayer/ShowDataLayer"
|
|
||||||
import { describe, expect, it } from "vitest"
|
import { describe, expect, it } from "vitest"
|
||||||
|
import { OsmConnection } from "../../../../Logic/Osm/OsmConnection"
|
||||||
|
import { ImmutableStore } from "../../../../Logic/UIEventSource"
|
||||||
|
import { Changes } from "../../../../Logic/Osm/Changes"
|
||||||
|
|
||||||
describe("ReplaceGeometryAction", () => {
|
describe("ReplaceGeometryAction", () => {
|
||||||
const grbStripped = {
|
const grbStripped = {
|
||||||
|
@ -300,8 +300,6 @@ describe("ReplaceGeometryAction", () => {
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
Minimap.createMiniMap = () => undefined
|
|
||||||
|
|
||||||
const coordinates = <[number, number][]>[
|
const coordinates = <[number, number][]>[
|
||||||
[3.216690793633461, 51.21474084112525],
|
[3.216690793633461, 51.21474084112525],
|
||||||
[3.2167256623506546, 51.214696737309964],
|
[3.2167256623506546, 51.214696737309964],
|
||||||
|
@ -876,22 +874,27 @@ describe("ReplaceGeometryAction", () => {
|
||||||
|
|
||||||
it("should move nodes accordingly", async () => {
|
it("should move nodes accordingly", async () => {
|
||||||
const layout = new LayoutConfig(<any>grbStripped)
|
const layout = new LayoutConfig(<any>grbStripped)
|
||||||
ShowDataLayer.actualContstructor = (_) => undefined
|
|
||||||
|
|
||||||
const state = new State(layout)
|
|
||||||
State.state = state
|
|
||||||
const bbox = new BBox([
|
const bbox = new BBox([
|
||||||
[3.2166673243045807, 51.21467321525788],
|
[3.2166673243045807, 51.21467321525788],
|
||||||
[3.217007964849472, 51.21482442824023],
|
[3.217007964849472, 51.21482442824023],
|
||||||
])
|
])
|
||||||
const url = `https://www.openstreetmap.org/api/0.6/map.json?bbox=${bbox.minLon},${bbox.minLat},${bbox.maxLon},${bbox.maxLat}`
|
const url = `https://www.openstreetmap.org/api/0.6/map.json?bbox=${bbox.minLon},${bbox.minLat},${bbox.maxLon},${bbox.maxLat}`
|
||||||
const data = await Utils.downloadJson(url)
|
const data = await Utils.downloadJson(url)
|
||||||
|
const fullNodeDatabase = undefined // TODO new FullNodeDatabaseSource(undefined)
|
||||||
state.featurePipeline.fullNodeDatabase.handleOsmJson(data, 0)
|
// TODO fullNodeDatabase.handleOsmJson(data, 0)
|
||||||
|
const changes = new Changes()
|
||||||
const action = new ReplaceGeometryAction(state, targetFeature, wayId, {
|
const osmConnection = new OsmConnection({
|
||||||
theme: "test",
|
dryRun: new ImmutableStore(true),
|
||||||
})
|
})
|
||||||
|
const action = new ReplaceGeometryAction(
|
||||||
|
{ osmConnection, fullNodeDatabase },
|
||||||
|
targetFeature,
|
||||||
|
wayId,
|
||||||
|
{
|
||||||
|
theme: "test",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const closestIds = await action.GetClosestIds()
|
const closestIds = await action.GetClosestIds()
|
||||||
expect(closestIds.closestIds).toEqual([
|
expect(closestIds.closestIds).toEqual([
|
||||||
|
@ -914,8 +917,8 @@ describe("ReplaceGeometryAction", () => {
|
||||||
expect(reproj.newLon).toEqual(3.2168880864669203)
|
expect(reproj.newLon).toEqual(3.2168880864669203)
|
||||||
expect(reproj.newLat).toEqual(51.214739524104694)
|
expect(reproj.newLat).toEqual(51.214739524104694)
|
||||||
expect(closestIds.detachedNodes.size).toEqual(0)
|
expect(closestIds.detachedNodes.size).toEqual(0)
|
||||||
const changes = await action.Perform(state.changes)
|
const changed = await action.Perform(changes)
|
||||||
expect(changes[11].changes["coordinates"]).toEqual([
|
expect(changed[11].changes["coordinates"]).toEqual([
|
||||||
[3.216690793633461, 51.21474084112525],
|
[3.216690793633461, 51.21474084112525],
|
||||||
[3.2167256623506546, 51.214696737309964],
|
[3.2167256623506546, 51.214696737309964],
|
||||||
[3.2168880864669203, 51.214739524104694],
|
[3.2168880864669203, 51.214739524104694],
|
||||||
|
|
|
@ -2,19 +2,21 @@ import { Utils } from "../../../Utils"
|
||||||
import { ChangesetHandler, ChangesetTag } from "../../../Logic/Osm/ChangesetHandler"
|
import { ChangesetHandler, ChangesetTag } from "../../../Logic/Osm/ChangesetHandler"
|
||||||
import { UIEventSource } from "../../../Logic/UIEventSource"
|
import { UIEventSource } from "../../../Logic/UIEventSource"
|
||||||
import { OsmConnection } from "../../../Logic/Osm/OsmConnection"
|
import { OsmConnection } from "../../../Logic/Osm/OsmConnection"
|
||||||
import { ElementStorage } from "../../../Logic/ElementStorage"
|
|
||||||
import { Changes } from "../../../Logic/Osm/Changes"
|
import { Changes } from "../../../Logic/Osm/Changes"
|
||||||
import { describe, expect, it } from "vitest"
|
import { describe, expect, it } from "vitest"
|
||||||
|
|
||||||
|
function elstorage() {
|
||||||
|
return { addAlias: (a, b) => {} }
|
||||||
|
}
|
||||||
|
|
||||||
describe("ChangesetHanlder", () => {
|
describe("ChangesetHanlder", () => {
|
||||||
describe("RewriteTagsOf", () => {
|
describe("RewriteTagsOf", () => {
|
||||||
it("should insert new tags", () => {
|
it("should insert new tags", () => {
|
||||||
const changesetHandler = new ChangesetHandler(
|
const changesetHandler = new ChangesetHandler(
|
||||||
new UIEventSource<boolean>(true),
|
new UIEventSource<boolean>(true),
|
||||||
new OsmConnection({}),
|
new OsmConnection({}),
|
||||||
new ElementStorage(),
|
elstorage(),
|
||||||
new Changes(),
|
new Changes()
|
||||||
new UIEventSource(undefined)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const oldChangesetMeta = {
|
const oldChangesetMeta = {
|
||||||
|
@ -57,7 +59,9 @@ describe("ChangesetHanlder", () => {
|
||||||
const d = Utils.asDict(rewritten)
|
const d = Utils.asDict(rewritten)
|
||||||
expect(d.size).toEqual(10)
|
expect(d.size).toEqual(10)
|
||||||
expect(d.get("answer")).toEqual("5")
|
expect(d.get("answer")).toEqual("5")
|
||||||
expect(d.get("comment")).toEqual("Adding data with #MapComplete for theme #toerisme_vlaanderen")
|
expect(d.get("comment")).toEqual(
|
||||||
|
"Adding data with #MapComplete for theme #toerisme_vlaanderen"
|
||||||
|
)
|
||||||
expect(d.get("created_by")).toEqual("MapComplete 0.16.6")
|
expect(d.get("created_by")).toEqual("MapComplete 0.16.6")
|
||||||
expect(d.get("host")).toEqual("https://mapcomplete.osm.be/toerisme_vlaanderen.html")
|
expect(d.get("host")).toEqual("https://mapcomplete.osm.be/toerisme_vlaanderen.html")
|
||||||
expect(d.get("imagery")).toEqual("osm")
|
expect(d.get("imagery")).toEqual("osm")
|
||||||
|
@ -70,9 +74,8 @@ describe("ChangesetHanlder", () => {
|
||||||
const changesetHandler = new ChangesetHandler(
|
const changesetHandler = new ChangesetHandler(
|
||||||
new UIEventSource<boolean>(true),
|
new UIEventSource<boolean>(true),
|
||||||
new OsmConnection({}),
|
new OsmConnection({}),
|
||||||
new ElementStorage(),
|
elstorage(),
|
||||||
new Changes(),
|
new Changes()
|
||||||
new UIEventSource(undefined)
|
|
||||||
)
|
)
|
||||||
const oldChangesetMeta = {
|
const oldChangesetMeta = {
|
||||||
type: "changeset",
|
type: "changeset",
|
||||||
|
@ -115,7 +118,9 @@ describe("ChangesetHanlder", () => {
|
||||||
|
|
||||||
expect(d.size).toEqual(9)
|
expect(d.size).toEqual(9)
|
||||||
expect(d.get("answer")).toEqual("42")
|
expect(d.get("answer")).toEqual("42")
|
||||||
expect(d.get("comment")).toEqual("Adding data with #MapComplete for theme #toerisme_vlaanderen")
|
expect(d.get("comment")).toEqual(
|
||||||
|
"Adding data with #MapComplete for theme #toerisme_vlaanderen"
|
||||||
|
)
|
||||||
expect(d.get("created_by")).toEqual("MapComplete 0.16.6")
|
expect(d.get("created_by")).toEqual("MapComplete 0.16.6")
|
||||||
expect(d.get("host")).toEqual("https://mapcomplete.osm.be/toerisme_vlaanderen.html")
|
expect(d.get("host")).toEqual("https://mapcomplete.osm.be/toerisme_vlaanderen.html")
|
||||||
expect(d.get("imagery")).toEqual("osm")
|
expect(d.get("imagery")).toEqual("osm")
|
||||||
|
@ -127,9 +132,8 @@ describe("ChangesetHanlder", () => {
|
||||||
const changesetHandler = new ChangesetHandler(
|
const changesetHandler = new ChangesetHandler(
|
||||||
new UIEventSource<boolean>(true),
|
new UIEventSource<boolean>(true),
|
||||||
new OsmConnection({}),
|
new OsmConnection({}),
|
||||||
new ElementStorage(),
|
elstorage(),
|
||||||
new Changes(),
|
new Changes()
|
||||||
new UIEventSource(undefined)
|
|
||||||
)
|
)
|
||||||
const oldChangesetMeta = {
|
const oldChangesetMeta = {
|
||||||
type: "changeset",
|
type: "changeset",
|
||||||
|
@ -166,7 +170,9 @@ describe("ChangesetHanlder", () => {
|
||||||
|
|
||||||
expect(d.size).toEqual(9)
|
expect(d.size).toEqual(9)
|
||||||
expect(d.get("answer")).toEqual("5")
|
expect(d.get("answer")).toEqual("5")
|
||||||
expect(d.get("comment")).toEqual("Adding data with #MapComplete for theme #toerisme_vlaanderen")
|
expect(d.get("comment")).toEqual(
|
||||||
|
"Adding data with #MapComplete for theme #toerisme_vlaanderen"
|
||||||
|
)
|
||||||
expect(d.get("created_by")).toEqual("MapComplete 0.16.6")
|
expect(d.get("created_by")).toEqual("MapComplete 0.16.6")
|
||||||
expect(d.get("host")).toEqual("https://mapcomplete.osm.be/toerisme_vlaanderen.html")
|
expect(d.get("host")).toEqual("https://mapcomplete.osm.be/toerisme_vlaanderen.html")
|
||||||
expect(d.get("imagery")).toEqual("osm")
|
expect(d.get("imagery")).toEqual("osm")
|
||||||
|
|
|
@ -23,7 +23,9 @@ describe("CreateNoteImportLayer", () => {
|
||||||
layer,
|
layer,
|
||||||
"ImportLayerGeneratorTest: convert"
|
"ImportLayerGeneratorTest: convert"
|
||||||
)
|
)
|
||||||
expect(generatedLayer.isShown["and"][1].or[0].and[0]).toEqual("_tags~(^|.*;)amenity=public_bookcase($|;.*)")
|
expect(generatedLayer.isShown["and"][1].or[0].and[0]).toEqual(
|
||||||
|
"_tags~(^|.*;)amenity=public_bookcase($|;.*)"
|
||||||
|
)
|
||||||
// "Zoomlevel is to high"
|
// "Zoomlevel is to high"
|
||||||
expect(generatedLayer.minzoom <= layer.minzoom).toBe(true)
|
expect(generatedLayer.minzoom <= layer.minzoom).toBe(true)
|
||||||
let renderings = Utils.NoNull(
|
let renderings = Utils.NoNull(
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
import { describe } from "mocha"
|
|
||||||
import ValidatedTextField from "../../UI/Input/ValidatedTextField"
|
|
||||||
import { fail } from "assert"
|
|
||||||
import Translations from "../../UI/i18n/Translations"
|
|
||||||
|
|
||||||
describe("ValidatedTextFields", () => {
|
|
||||||
it("should all have description in the translations", () => {
|
|
||||||
const ts = Translations.t.validation
|
|
||||||
const missingTranslations = Array.from(ValidatedTextField.allTypes.keys())
|
|
||||||
.filter((key) => ts[key] === undefined || ts[key].description === undefined)
|
|
||||||
.filter((key) => key !== "distance")
|
|
||||||
if (missingTranslations.length > 0) {
|
|
||||||
fail(
|
|
||||||
"The validated text fields don't have a description defined in en.json for " +
|
|
||||||
missingTranslations.join(", ") +
|
|
||||||
". (Did you just add one? Run `npm run generate:translations`)"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
Loading…
Reference in a new issue