forked from MapComplete/MapComplete
Refactoring: add metatagging, add 'last edited by' element, add 'metacondition'
This commit is contained in:
parent
771783a31c
commit
105120060d
31 changed files with 217 additions and 142 deletions
|
@ -62,7 +62,11 @@ export default class DetermineLayout {
|
||||||
layoutId,
|
layoutId,
|
||||||
"The layout to load into MapComplete"
|
"The layout to load into MapComplete"
|
||||||
).data
|
).data
|
||||||
return AllKnownLayouts.allKnownLayouts.get(layoutId?.toLowerCase())
|
const layout = AllKnownLayouts.allKnownLayouts.get(layoutId?.toLowerCase())
|
||||||
|
if (layout === undefined) {
|
||||||
|
throw "No layout with name " + layoutId + " exists"
|
||||||
|
}
|
||||||
|
return layout
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LoadLayoutFromHash(userLayoutParam: UIEventSource<string>): LayoutConfig | null {
|
public static LoadLayoutFromHash(userLayoutParam: UIEventSource<string>): LayoutConfig | null {
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { UIEventSource } from "../../UIEventSource"
|
||||||
*/
|
*/
|
||||||
export default class FeaturePropertiesStore {
|
export default class FeaturePropertiesStore {
|
||||||
private readonly _source: FeatureSource & IndexedFeatureSource
|
private readonly _source: FeatureSource & IndexedFeatureSource
|
||||||
private readonly _elements = new Map<string, UIEventSource<any>>()
|
private readonly _elements = new Map<string, UIEventSource<Record<string, string>>>()
|
||||||
|
|
||||||
constructor(source: FeatureSource & IndexedFeatureSource) {
|
constructor(source: FeatureSource & IndexedFeatureSource) {
|
||||||
this._source = source
|
this._source = source
|
||||||
|
@ -83,7 +83,9 @@ export default class FeaturePropertiesStore {
|
||||||
return changeMade
|
return changeMade
|
||||||
}
|
}
|
||||||
|
|
||||||
addAlias(oldId: string, newId: string): void {
|
// noinspection JSUnusedGlobalSymbols
|
||||||
|
public addAlias(oldId: string, newId: string): void {
|
||||||
|
console.log("FeaturePropertiesStore: adding alias for", oldId, newId)
|
||||||
if (newId === undefined) {
|
if (newId === undefined) {
|
||||||
// We removed the node/way/relation with type 'type' and id 'oldId' on openstreetmap!
|
// We removed the node/way/relation with type 'type' and id 'oldId' on openstreetmap!
|
||||||
const element = this._elements.get(oldId)
|
const element = this._elements.get(oldId)
|
||||||
|
|
|
@ -94,8 +94,9 @@ export default class MetaTagging {
|
||||||
let definedTags = new Set(Object.getOwnPropertyNames(feature.properties))
|
let definedTags = new Set(Object.getOwnPropertyNames(feature.properties))
|
||||||
for (const metatag of metatagsToApply) {
|
for (const metatag of metatagsToApply) {
|
||||||
try {
|
try {
|
||||||
if (!metatag.keys.some((key) => feature.properties[key] === undefined)) {
|
if (!metatag.keys.some((key) => !(key in feature.properties))) {
|
||||||
// All keys are already defined, we probably already ran this one
|
// All keys are already defined, we probably already ran this one
|
||||||
|
// Note that we use 'key in properties', not 'properties[key] === undefined'. The latter will cause evaluation of lazy properties
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -96,16 +96,11 @@ export class ReferencingWaysMetaTagger extends SimpleMetaTagger {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
console.trace("Downloading referencing ways for", feature.properties.id)
|
Utils.AddLazyPropertyAsync(feature.properties, "_referencing_ways", async () => {
|
||||||
OsmObject.DownloadReferencingWays(id).then((referencingWays) => {
|
const referencingWays = await OsmObject.DownloadReferencingWays(id)
|
||||||
const currentTagsSource = state.allElements?.getEventSourceById(id) ?? []
|
|
||||||
const wayIds = referencingWays.map((w) => "way/" + w.id)
|
const wayIds = referencingWays.map((w) => "way/" + w.id)
|
||||||
wayIds.sort()
|
wayIds.sort()
|
||||||
const wayIdsStr = wayIds.join(";")
|
return wayIds.join(";")
|
||||||
if (wayIdsStr !== "" && currentTagsSource.data["_referencing_ways"] !== wayIdsStr) {
|
|
||||||
currentTagsSource.data["_referencing_ways"] = wayIdsStr
|
|
||||||
currentTagsSource.ping()
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
@ -221,6 +216,7 @@ class RewriteMetaInfoTags extends SimpleMetaTagger {
|
||||||
return movedSomething
|
return movedSomething
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class SimpleMetaTaggers {
|
export default class SimpleMetaTaggers {
|
||||||
/**
|
/**
|
||||||
* A simple metatagger which rewrites various metatags as needed
|
* A simple metatagger which rewrites various metatags as needed
|
||||||
|
|
|
@ -575,12 +575,14 @@ export class AddQuestionBox extends DesugaringStep<LayerConfigJson> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AddEditingElements extends DesugaringStep<LayerConfigJson> {
|
export class AddEditingElements extends DesugaringStep<LayerConfigJson> {
|
||||||
constructor() {
|
private readonly _desugaring: DesugaringContext
|
||||||
|
constructor(desugaring: DesugaringContext) {
|
||||||
super(
|
super(
|
||||||
"Add some editing elements, such as the delete button or the move button if they are configured. These used to be handled by the feature info box, but this has been replaced by special visualisation elements",
|
"Add some editing elements, such as the delete button or the move button if they are configured. These used to be handled by the feature info box, but this has been replaced by special visualisation elements",
|
||||||
[],
|
[],
|
||||||
"AddEditingElements"
|
"AddEditingElements"
|
||||||
)
|
)
|
||||||
|
this._desugaring = desugaring
|
||||||
}
|
}
|
||||||
|
|
||||||
convert(
|
convert(
|
||||||
|
@ -609,6 +611,30 @@ export class AddEditingElements extends DesugaringStep<LayerConfigJson> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (json.deletion && !ValidationUtils.hasSpecialVisualisation(json, "all_tags")) {
|
||||||
|
const trc: TagRenderingConfigJson = {
|
||||||
|
id: "all-tags",
|
||||||
|
render: { "*": "{all_tags()}" },
|
||||||
|
metacondition: {
|
||||||
|
or: [
|
||||||
|
"__featureSwitchIsTesting=true",
|
||||||
|
"__featureSwitchIsDebugging=true",
|
||||||
|
"mapcomplete-show_debug=yes",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
json.tagRenderings.push(trc)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
json.source !== "special" &&
|
||||||
|
json.source !== "special:library" &&
|
||||||
|
json.tagRenderings &&
|
||||||
|
!json.tagRenderings.some((tr) => tr["id"] === "last_edit")
|
||||||
|
) {
|
||||||
|
json.tagRenderings.push(this._desugaring.tagRenderings.get("last_edit"))
|
||||||
|
}
|
||||||
|
|
||||||
return { result: json }
|
return { result: json }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1145,7 +1171,7 @@ export class PrepareLayer extends Fuse<LayerConfigJson> {
|
||||||
new On("tagRenderings", new Each(new DetectInline())),
|
new On("tagRenderings", new Each(new DetectInline())),
|
||||||
new AddQuestionBox(),
|
new AddQuestionBox(),
|
||||||
new AddMiniMap(state),
|
new AddMiniMap(state),
|
||||||
new AddEditingElements(),
|
new AddEditingElements(state),
|
||||||
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",
|
||||||
|
|
|
@ -47,6 +47,10 @@ export default class TagRenderingConfig {
|
||||||
public readonly question?: TypedTranslation<object>
|
public readonly question?: TypedTranslation<object>
|
||||||
public readonly questionhint?: TypedTranslation<object>
|
public readonly questionhint?: TypedTranslation<object>
|
||||||
public readonly condition?: TagsFilter
|
public readonly condition?: TagsFilter
|
||||||
|
/**
|
||||||
|
* Evaluated against the current 'usersettings'-state
|
||||||
|
*/
|
||||||
|
public readonly metacondition?: TagsFilter
|
||||||
public readonly description?: Translation
|
public readonly description?: Translation
|
||||||
|
|
||||||
public readonly configuration_warnings: string[] = []
|
public readonly configuration_warnings: string[] = []
|
||||||
|
@ -70,14 +74,6 @@ export default class TagRenderingConfig {
|
||||||
if (json === undefined) {
|
if (json === undefined) {
|
||||||
throw "Initing a TagRenderingConfig with undefined in " + context
|
throw "Initing a TagRenderingConfig with undefined in " + context
|
||||||
}
|
}
|
||||||
if (json === "questions") {
|
|
||||||
// Very special value
|
|
||||||
this.render = null
|
|
||||||
this.question = null
|
|
||||||
this.condition = null
|
|
||||||
this.id = "questions"
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof json === "number") {
|
if (typeof json === "number") {
|
||||||
json = "" + json
|
json = "" + json
|
||||||
|
@ -114,11 +110,15 @@ export default class TagRenderingConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.labels = json.labels ?? []
|
this.labels = json.labels ?? []
|
||||||
this.render = Translations.T(json.render, translationKey + ".render")
|
this.render = Translations.T(<any>json.render, translationKey + ".render")
|
||||||
this.question = Translations.T(json.question, translationKey + ".question")
|
this.question = Translations.T(json.question, translationKey + ".question")
|
||||||
this.questionhint = Translations.T(json.questionHint, translationKey + ".questionHint")
|
this.questionhint = Translations.T(json.questionHint, translationKey + ".questionHint")
|
||||||
this.description = Translations.T(json.description, translationKey + ".description")
|
this.description = Translations.T(json.description, translationKey + ".description")
|
||||||
this.condition = TagUtils.Tag(json.condition ?? { and: [] }, `${context}.condition`)
|
this.condition = TagUtils.Tag(json.condition ?? { and: [] }, `${context}.condition`)
|
||||||
|
this.metacondition = TagUtils.Tag(
|
||||||
|
json.metacondition ?? { and: [] },
|
||||||
|
`${context}.metacondition`
|
||||||
|
)
|
||||||
if (json.freeform) {
|
if (json.freeform) {
|
||||||
if (
|
if (
|
||||||
json.freeform.addExtraTags !== undefined &&
|
json.freeform.addExtraTags !== undefined &&
|
||||||
|
|
|
@ -205,6 +205,12 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
||||||
*/
|
*/
|
||||||
private miscSetup() {
|
private miscSetup() {
|
||||||
this.userRelatedState.markLayoutAsVisited(this.layout)
|
this.userRelatedState.markLayoutAsVisited(this.layout)
|
||||||
|
|
||||||
|
this.selectedElement.addCallbackAndRunD(() => {
|
||||||
|
// As soon as we have a selected element, we clear it
|
||||||
|
// This is to work around maplibre, which'll _first_ register the click on the map and only _then_ on the feature
|
||||||
|
this.lastClickObject.features.setData([])
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private initHotkeys() {
|
private initHotkeys() {
|
||||||
|
|
|
@ -18,6 +18,11 @@
|
||||||
onDestroy(tags.addCallbackAndRun(tags => {
|
onDestroy(tags.addCallbackAndRun(tags => {
|
||||||
_tags = tags;
|
_tags = tags;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
let _metatags: Record<string, string>
|
||||||
|
onDestroy(state.userRelatedState.preferencesAsTags .addCallbackAndRun(tags => {
|
||||||
|
_metatags = tags;
|
||||||
|
}));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
@ -40,7 +45,7 @@
|
||||||
|
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
{#each layer.tagRenderings as config (config.id)}
|
{#each layer.tagRenderings as config (config.id)}
|
||||||
{#if config.condition === undefined || config.condition.matchesProperties(_tags)}
|
{#if (config.condition === undefined || config.condition.matchesProperties(_tags)) && (config.metacondition === undefined || config.metacondition.matchesProperties(_metatags))}
|
||||||
{#if config.IsKnown(_tags)}
|
{#if config.IsKnown(_tags)}
|
||||||
<TagRenderingEditable {tags} {config} {state} {selectedElement} {layer} {highlightedRendering}></TagRenderingEditable>
|
<TagRenderingEditable {tags} {config} {state} {selectedElement} {layer} {highlightedRendering}></TagRenderingEditable>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -91,6 +91,7 @@ export class MapLibreAdaptor implements MapProperties {
|
||||||
// Workaround, 'ShowPointLayer' sets this flag
|
// Workaround, 'ShowPointLayer' sets this flag
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
console.log(e)
|
||||||
const lon = e.lngLat.lng
|
const lon = e.lngLat.lng
|
||||||
const lat = e.lngLat.lat
|
const lat = e.lngLat.lat
|
||||||
lastClickLocation.setData({ lon, lat })
|
lastClickLocation.setData({ lon, lat })
|
||||||
|
|
|
@ -96,14 +96,15 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
state.newFeatures.features.ping();
|
state.newFeatures.features.ping();
|
||||||
|
const tagsStore = state.featureProperties.getStore(newId);
|
||||||
{
|
{
|
||||||
// Set some metainfo
|
// Set some metainfo
|
||||||
const tagsStore = state.featureProperties.getStore(newId);
|
|
||||||
const properties = tagsStore.data;
|
const properties = tagsStore.data;
|
||||||
if (snapTo) {
|
if (snapTo) {
|
||||||
// metatags (starting with underscore) are not uploaded, so we can safely mark this
|
// metatags (starting with underscore) are not uploaded, so we can safely mark this
|
||||||
properties["_referencing_ways"] = `["${snapTo}"]`;
|
properties["_referencing_ways"] = `["${snapTo}"]`;
|
||||||
}
|
}
|
||||||
|
properties["_backend"] = state.osmConnection.Backend()
|
||||||
properties["_last_edit:timestamp"] = new Date().toISOString();
|
properties["_last_edit:timestamp"] = new Date().toISOString();
|
||||||
const userdetails = state.osmConnection.userDetails.data;
|
const userdetails = state.osmConnection.userDetails.data;
|
||||||
properties["_last_edit:contributor"] = userdetails.name;
|
properties["_last_edit:contributor"] = userdetails.name;
|
||||||
|
@ -112,8 +113,9 @@
|
||||||
}
|
}
|
||||||
const feature = state.indexedFeatures.featuresById.data.get(newId);
|
const feature = state.indexedFeatures.featuresById.data.get(newId);
|
||||||
abort();
|
abort();
|
||||||
state.selectedElement.setData(feature);
|
|
||||||
state.selectedLayer.setData(selectedPreset.layer);
|
state.selectedLayer.setData(selectedPreset.layer);
|
||||||
|
state.selectedElement.setData(feature);
|
||||||
|
tagsStore.ping()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,46 +1,63 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import ToSvelte from "../Base/ToSvelte.svelte"
|
import ToSvelte from "../Base/ToSvelte.svelte";
|
||||||
import Table from "../Base/Table"
|
import Table from "../Base/Table";
|
||||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
import { UIEventSource } from "../../Logic/UIEventSource";
|
||||||
|
import SimpleMetaTaggers from "../../Logic/SimpleMetaTagger";
|
||||||
|
import { FixedUiElement } from "../Base/FixedUiElement";
|
||||||
|
import { onDestroy } from "svelte";
|
||||||
|
import Toggle, { ClickableToggle } from "../Input/Toggle";
|
||||||
|
import Lazy from "../Base/Lazy";
|
||||||
|
import BaseUIElement from "../BaseUIElement";
|
||||||
|
|
||||||
//Svelte props
|
//Svelte props
|
||||||
export let tags: UIEventSource<any>
|
export let tags: UIEventSource<any>;
|
||||||
export let state: any
|
export let state: any;
|
||||||
|
|
||||||
const calculatedTags = [].concat(
|
const calculatedTags = [].concat(
|
||||||
// SimpleMetaTagger.lazyTags,
|
|
||||||
...(state?.layoutToUse?.layers?.map((l) => l.calculatedTags?.map((c) => c[0]) ?? []) ?? [])
|
...(state?.layoutToUse?.layers?.map((l) => l.calculatedTags?.map((c) => c[0]) ?? []) ?? [])
|
||||||
)
|
);
|
||||||
|
|
||||||
const allTags = tags.map((tags) => {
|
const allTags = tags.map((tags) => {
|
||||||
const parts = []
|
const parts: (string | BaseUIElement)[][] = [];
|
||||||
for (const key in tags) {
|
for (const key in tags) {
|
||||||
if (!tags.hasOwnProperty(key)) {
|
let v = tags[key];
|
||||||
continue
|
|
||||||
}
|
|
||||||
let v = tags[key]
|
|
||||||
if (v === "") {
|
if (v === "") {
|
||||||
v = "<b>empty string</b>"
|
v = "<b>empty string</b>";
|
||||||
}
|
}
|
||||||
parts.push([key, v ?? "<b>undefined</b>"])
|
parts.push([key, v ?? "<b>undefined</b>"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const key of calculatedTags) {
|
for (const key of calculatedTags) {
|
||||||
const value = tags[key]
|
const value = tags[key];
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
continue
|
continue;
|
||||||
}
|
}
|
||||||
let type = ""
|
let type = "";
|
||||||
if (typeof value !== "string") {
|
if (typeof value !== "string") {
|
||||||
type = " <i>" + typeof value + "</i>"
|
type = " <i>" + typeof value + "</i>";
|
||||||
}
|
}
|
||||||
parts.push(["<i>" + key + "</i>", value])
|
parts.push(["<i>" + key + "</i>", value]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return parts
|
for (const metatag of SimpleMetaTaggers.metatags.filter(mt => mt.isLazy)) {
|
||||||
})
|
const title = "<i>" + metatag.keys.join(";") + "</i> (lazy)";
|
||||||
|
const toggleState = new UIEventSource(false)
|
||||||
|
const toggle: BaseUIElement = new Toggle(
|
||||||
|
new Lazy(() => new FixedUiElement(metatag.keys.map(key => tags[key]).join(";"))),
|
||||||
|
new FixedUiElement("Evaluate").onClick(() => toggleState.setData(true)) ,
|
||||||
|
toggleState
|
||||||
|
);
|
||||||
|
parts.push([title, toggle]);
|
||||||
|
}
|
||||||
|
|
||||||
const tagsTable = new Table(["Key", "Value"], $allTags).SetClass("zebra-table")
|
return parts;
|
||||||
|
});
|
||||||
|
|
||||||
|
let _allTags = [];
|
||||||
|
onDestroy(allTags.addCallbackAndRunD(allTags => {
|
||||||
|
_allTags = allTags;
|
||||||
|
}));
|
||||||
|
const tagsTable = new Table(["Key", "Value"], _allTags).SetClass("zebra-table");
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
|
|
|
@ -99,63 +99,7 @@ export default class FeatureInfoBox extends ScrollableFullScreen {
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
allRenderings.push(
|
|
||||||
new Toggle(
|
|
||||||
new Lazy(() =>
|
|
||||||
FeatureInfoBox.createEditElements(questionBoxes, layerConfig, tags, state)
|
|
||||||
),
|
|
||||||
undefined,
|
|
||||||
state.featureSwitchUserbadge
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return new Combine(allRenderings).SetClass("block")
|
return new Combine(allRenderings).SetClass("block")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* All the edit elements, together (note that the question boxes are passed though)
|
|
||||||
* @param questionBoxes
|
|
||||||
* @param layerConfig
|
|
||||||
* @param tags
|
|
||||||
* @param state
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private static createEditElements(
|
|
||||||
questionBoxes: Map<string, QuestionBox>,
|
|
||||||
layerConfig: LayerConfig,
|
|
||||||
tags: UIEventSource<any>,
|
|
||||||
state: FeaturePipelineState
|
|
||||||
): BaseUIElement {
|
|
||||||
let editElements: BaseUIElement[] = []
|
|
||||||
questionBoxes.forEach((questionBox) => {
|
|
||||||
editElements.push(questionBox)
|
|
||||||
})
|
|
||||||
|
|
||||||
editElements.push(
|
|
||||||
new VariableUiElement(
|
|
||||||
state.osmConnection.userDetails
|
|
||||||
.map((ud) => ud.csCount)
|
|
||||||
.map(
|
|
||||||
(csCount) => {
|
|
||||||
if (
|
|
||||||
csCount <= Constants.userJourney.historyLinkVisible &&
|
|
||||||
state.featureSwitchIsDebugging.data == false &&
|
|
||||||
state.featureSwitchIsTesting.data === false
|
|
||||||
) {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
return new TagRenderingAnswer(
|
|
||||||
tags,
|
|
||||||
SharedTagRenderings.SharedTagRendering.get("last_edit"),
|
|
||||||
state
|
|
||||||
)
|
|
||||||
},
|
|
||||||
[state.featureSwitchIsDebugging, state.featureSwitchIsTesting]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return new Combine(editElements).SetClass("flex flex-col")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,10 @@
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseQuestions = (layer.tagRenderings ?? [])?.filter(tr => allowed(tr.labels) && tr.question !== undefined);
|
let baseQuestions = []
|
||||||
|
$: {
|
||||||
|
baseQuestions = (layer.tagRenderings ?? [])?.filter(tr => allowed(tr.labels) && tr.question !== undefined);
|
||||||
|
}
|
||||||
let skippedQuestions = new UIEventSource<Set<string>>(new Set<string>());
|
let skippedQuestions = new UIEventSource<Set<string>>(new Set<string>());
|
||||||
|
|
||||||
let questionsToAsk = tags.map(tags => {
|
let questionsToAsk = tags.map(tags => {
|
||||||
|
@ -80,6 +83,7 @@
|
||||||
skipped++;
|
skipped++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$: console.log("Current questionbox state:", {answered, skipped, questionsToAsk, layer, selectedElement, tags})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if _questionsToAsk.length === 0}
|
{#if _questionsToAsk.length === 0}
|
||||||
|
|
|
@ -32,7 +32,6 @@
|
||||||
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;
|
||||||
|
|
||||||
function mappingIsHidden(mapping: Mapping): boolean {
|
function mappingIsHidden(mapping: Mapping): boolean {
|
||||||
|
|
|
@ -63,6 +63,7 @@ export interface SpecialVisualizationState {
|
||||||
readonly userRelatedState: {
|
readonly userRelatedState: {
|
||||||
readonly mangroveIdentity: MangroveIdentity
|
readonly mangroveIdentity: MangroveIdentity
|
||||||
readonly showAllQuestionsAtOnce: UIEventSource<boolean>
|
readonly showAllQuestionsAtOnce: UIEventSource<boolean>
|
||||||
|
readonly preferencesAsTags: Store<Record<string, string>>
|
||||||
}
|
}
|
||||||
readonly lastClickObject: WritableFeatureSource
|
readonly lastClickObject: WritableFeatureSource
|
||||||
}
|
}
|
||||||
|
|
|
@ -1265,21 +1265,24 @@ export default class SpecialVisualizations {
|
||||||
doc: "The URL to link to",
|
doc: "The URL to link to",
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "class",
|
||||||
|
doc: "CSS-classes to add to the element",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
constr(
|
constr(
|
||||||
state: SpecialVisualizationState,
|
state: SpecialVisualizationState,
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
args: string[]
|
args: string[]
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
const [text, href] = args
|
const [text, href, classnames] = args
|
||||||
return new VariableUiElement(
|
return new VariableUiElement(
|
||||||
tagSource.map(
|
tagSource.map((tags) =>
|
||||||
(tags) =>
|
new Link(
|
||||||
new Link(
|
Utils.SubstituteKeys(text, tags),
|
||||||
Utils.SubstituteKeys(text, tags),
|
Utils.SubstituteKeys(href, tags),
|
||||||
Utils.SubstituteKeys(href, tags),
|
true
|
||||||
true
|
).SetClass(classnames)
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
|
@ -29,7 +29,14 @@ export class Translation extends BaseUIElement {
|
||||||
}
|
}
|
||||||
count++
|
count++
|
||||||
if (typeof translations[translationsKey] != "string") {
|
if (typeof translations[translationsKey] != "string") {
|
||||||
console.error("Non-string object in translation: ", translations[translationsKey])
|
console.error(
|
||||||
|
"Non-string object at",
|
||||||
|
context,
|
||||||
|
"in translation: ",
|
||||||
|
translations[translationsKey],
|
||||||
|
"\n current translations are: ",
|
||||||
|
translations
|
||||||
|
)
|
||||||
throw (
|
throw (
|
||||||
"Error in an object depicting a translation: a non-string object was found. (" +
|
"Error in an object depicting a translation: a non-string object was found. (" +
|
||||||
context +
|
context +
|
||||||
|
|
11
Utils.ts
11
Utils.ts
|
@ -307,13 +307,21 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
* @param init
|
* @param init
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
public static AddLazyProperty(object: any, name: string, init: () => any) {
|
public static AddLazyProperty(
|
||||||
|
object: any,
|
||||||
|
name: string,
|
||||||
|
init: () => any,
|
||||||
|
whenDone?: () => void
|
||||||
|
) {
|
||||||
Object.defineProperty(object, name, {
|
Object.defineProperty(object, name, {
|
||||||
enumerable: false,
|
enumerable: false,
|
||||||
configurable: true,
|
configurable: true,
|
||||||
get: () => {
|
get: () => {
|
||||||
delete object[name]
|
delete object[name]
|
||||||
object[name] = init()
|
object[name] = init()
|
||||||
|
if (whenDone) {
|
||||||
|
whenDone()
|
||||||
|
}
|
||||||
return object[name]
|
return object[name]
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -332,6 +340,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
enumerable: false,
|
enumerable: false,
|
||||||
configurable: true,
|
configurable: true,
|
||||||
get: () => {
|
get: () => {
|
||||||
|
console.trace("Property", name, "got requested")
|
||||||
init().then((r) => {
|
init().then((r) => {
|
||||||
delete object[name]
|
delete object[name]
|
||||||
object[name] = r
|
object[name] = r
|
||||||
|
|
|
@ -19,4 +19,4 @@
|
||||||
"https://commons.wikimedia.org/wiki/File:ISO_7010_P018.svg"
|
"https://commons.wikimedia.org/wiki/File:ISO_7010_P018.svg"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
|
@ -55,7 +55,16 @@
|
||||||
"*": "{open_note()}"
|
"*": "{open_note()}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"all_tags"
|
{
|
||||||
|
"metacondition": {
|
||||||
|
"or": [
|
||||||
|
"__featureSwitchDebugging=true"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"render": {
|
||||||
|
"*": "{all_tags()}"
|
||||||
|
}
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"mapRendering": [
|
"mapRendering": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -1365,10 +1365,26 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"last_edit": {
|
"last_edit": {
|
||||||
"#": "Gives some metainfo about the last edit and who did edit it - rendering only",
|
"description": "Gives some metainfo about the last edit and who did edit it - rendering only",
|
||||||
"condition": "_last_edit:contributor~*",
|
"condition": "_last_edit:contributor~*",
|
||||||
|
"metacondition": {
|
||||||
|
"or": [
|
||||||
|
"__featureSwitchIsTesting=true",
|
||||||
|
"__featureSwitchIsDebugging=true",
|
||||||
|
"mapcomplete-show_debug=yes",
|
||||||
|
"_csCount>=10"
|
||||||
|
]
|
||||||
|
},
|
||||||
"render": {
|
"render": {
|
||||||
"*": "<div class='subtle' style='font-size: small; margin-top: 2em; margin-bottom: 0.5em;'><a href='https://www.openStreetMap.org/changeset/{_last_edit:changeset}' target='_blank'>Last edited on {_last_edit:timestamp}</a> by <a href='https://www.openStreetMap.org/user/{_last_edit:contributor}' target='_blank'>{_last_edit:contributor}</a></div>"
|
"special": {
|
||||||
|
"type": "link",
|
||||||
|
"href": "{_backend}/changeset/{_last_edit:changeset}",
|
||||||
|
"text": {
|
||||||
|
"en": "Last edited on {_last_edit:timestamp} by {_last_edit:contributor}",
|
||||||
|
"nl": "Laatst gewijzigd op {_last_edit:timestamp} door {_last_edit:contributor}"
|
||||||
|
},
|
||||||
|
"class": "subtle font-small"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"all_tags": {
|
"all_tags": {
|
||||||
|
|
|
@ -3560,6 +3560,9 @@
|
||||||
"19": {
|
"19": {
|
||||||
"then": "Aquí es poden reciclar sabates"
|
"then": "Aquí es poden reciclar sabates"
|
||||||
},
|
},
|
||||||
|
"20": {
|
||||||
|
"then": "Aquí es poden reciclar petits aparells elèctrics"
|
||||||
|
},
|
||||||
"21": {
|
"21": {
|
||||||
"then": "Aquí es poden reciclar petits aparells elèctrics"
|
"then": "Aquí es poden reciclar petits aparells elèctrics"
|
||||||
},
|
},
|
||||||
|
@ -4554,4 +4557,4 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -6927,13 +6927,13 @@
|
||||||
"16": {
|
"16": {
|
||||||
"question": "Recycling von Kunststoffen"
|
"question": "Recycling von Kunststoffen"
|
||||||
},
|
},
|
||||||
"18": {
|
"17": {
|
||||||
"question": "Recycling von Metallschrott"
|
"question": "Recycling von Metallschrott"
|
||||||
},
|
},
|
||||||
"19": {
|
"18": {
|
||||||
"question": "Recycling von Elektrokleingeräten"
|
"question": "Recycling von Elektrokleingeräten"
|
||||||
},
|
},
|
||||||
"20": {
|
"19": {
|
||||||
"question": "Recycling von Restabfällen"
|
"question": "Recycling von Restabfällen"
|
||||||
},
|
},
|
||||||
"20": {
|
"20": {
|
||||||
|
|
|
@ -6946,15 +6946,12 @@
|
||||||
"question": "Recycling of plastic"
|
"question": "Recycling of plastic"
|
||||||
},
|
},
|
||||||
"17": {
|
"17": {
|
||||||
"question": "Recycling of printer cartridges"
|
|
||||||
},
|
|
||||||
"18": {
|
|
||||||
"question": "Recycling of scrap metal"
|
"question": "Recycling of scrap metal"
|
||||||
},
|
},
|
||||||
"19": {
|
"18": {
|
||||||
"question": "Recycling of small electrical appliances"
|
"question": "Recycling of small electrical appliances"
|
||||||
},
|
},
|
||||||
"20": {
|
"19": {
|
||||||
"question": "Recycling of residual waste"
|
"question": "Recycling of residual waste"
|
||||||
},
|
},
|
||||||
"20": {
|
"20": {
|
||||||
|
@ -9182,4 +9179,4 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -3448,7 +3448,7 @@
|
||||||
"16": {
|
"16": {
|
||||||
"question": "Reciclaje de plástico"
|
"question": "Reciclaje de plástico"
|
||||||
},
|
},
|
||||||
"18": {
|
"17": {
|
||||||
"question": "Reciclaje de chatarra"
|
"question": "Reciclaje de chatarra"
|
||||||
},
|
},
|
||||||
"18": {
|
"18": {
|
||||||
|
|
|
@ -1799,13 +1799,13 @@
|
||||||
"16": {
|
"16": {
|
||||||
"question": "Riciclo di plastica"
|
"question": "Riciclo di plastica"
|
||||||
},
|
},
|
||||||
"18": {
|
"17": {
|
||||||
"question": "Riciclo di rottami metallici"
|
"question": "Riciclo di rottami metallici"
|
||||||
},
|
},
|
||||||
"19": {
|
"18": {
|
||||||
"question": "Riciclo di piccoli elettrodomestici"
|
"question": "Riciclo di piccoli elettrodomestici"
|
||||||
},
|
},
|
||||||
"20": {
|
"19": {
|
||||||
"question": "Riciclo di secco"
|
"question": "Riciclo di secco"
|
||||||
},
|
},
|
||||||
"20": {
|
"20": {
|
||||||
|
|
|
@ -6512,14 +6512,14 @@
|
||||||
"question": "Recycling van plastic"
|
"question": "Recycling van plastic"
|
||||||
},
|
},
|
||||||
"17": {
|
"17": {
|
||||||
"question": "Recycling van printer cartridges"
|
|
||||||
},
|
|
||||||
"18": {
|
|
||||||
"question": "Recycling van oud metaal"
|
"question": "Recycling van oud metaal"
|
||||||
},
|
},
|
||||||
"19": {
|
"18": {
|
||||||
"question": "Recycling van kleine elektrische apparaten"
|
"question": "Recycling van kleine elektrische apparaten"
|
||||||
},
|
},
|
||||||
|
"19": {
|
||||||
|
"question": "Recycling van restafval"
|
||||||
|
},
|
||||||
"20": {
|
"20": {
|
||||||
"question": "Recycling van restafval"
|
"question": "Recycling van restafval"
|
||||||
}
|
}
|
||||||
|
@ -8652,4 +8652,4 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -131,6 +131,13 @@
|
||||||
"question": "What is the network name for the wireless internet access?",
|
"question": "What is the network name for the wireless internet access?",
|
||||||
"render": "The network name is <b>{internet_access:ssid}</b>"
|
"render": "The network name is <b>{internet_access:ssid}</b>"
|
||||||
},
|
},
|
||||||
|
"last_edit": {
|
||||||
|
"render": {
|
||||||
|
"special": {
|
||||||
|
"text": "Last edited on {_last_edit:timestamp} by {_last_edit:contributor}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"level": {
|
"level": {
|
||||||
"mappings": {
|
"mappings": {
|
||||||
"0": {
|
"0": {
|
||||||
|
|
|
@ -131,6 +131,13 @@
|
||||||
"question": "Wat is de netwerknaam voor de draadloze internettoegang?",
|
"question": "Wat is de netwerknaam voor de draadloze internettoegang?",
|
||||||
"render": "De netwerknaam is <b>{internet_access:ssid}</b>"
|
"render": "De netwerknaam is <b>{internet_access:ssid}</b>"
|
||||||
},
|
},
|
||||||
|
"last_edit": {
|
||||||
|
"render": {
|
||||||
|
"special": {
|
||||||
|
"text": "Laatst gewijzigd op {_last_edit:timestamp} door {_last_edit:contributor} "
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"level": {
|
"level": {
|
||||||
"mappings": {
|
"mappings": {
|
||||||
"0": {
|
"0": {
|
||||||
|
|
|
@ -16,7 +16,7 @@ import { Translation } from "../UI/i18n/Translation"
|
||||||
import { TagRenderingConfigJson } from "../Models/ThemeConfig/Json/TagRenderingConfigJson"
|
import { TagRenderingConfigJson } from "../Models/ThemeConfig/Json/TagRenderingConfigJson"
|
||||||
import questions from "../assets/tagRenderings/questions.json"
|
import questions from "../assets/tagRenderings/questions.json"
|
||||||
import PointRenderingConfigJson from "../Models/ThemeConfig/Json/PointRenderingConfigJson"
|
import PointRenderingConfigJson from "../Models/ThemeConfig/Json/PointRenderingConfigJson"
|
||||||
import { PrepareLayer } from "../Models/ThemeConfig/Conversion/PrepareLayer"
|
import { PrepareLayer, RewriteSpecial } from "../Models/ThemeConfig/Conversion/PrepareLayer"
|
||||||
import { PrepareTheme } from "../Models/ThemeConfig/Conversion/PrepareTheme"
|
import { PrepareTheme } from "../Models/ThemeConfig/Conversion/PrepareTheme"
|
||||||
import { DesugaringContext } from "../Models/ThemeConfig/Conversion/Conversion"
|
import { DesugaringContext } from "../Models/ThemeConfig/Conversion/Conversion"
|
||||||
import { Utils } from "../Utils"
|
import { Utils } from "../Utils"
|
||||||
|
@ -156,6 +156,7 @@ class LayerOverviewUtils extends Script {
|
||||||
getSharedTagRenderings(doesImageExist: DoesImageExist): Map<string, TagRenderingConfigJson> {
|
getSharedTagRenderings(doesImageExist: DoesImageExist): Map<string, TagRenderingConfigJson> {
|
||||||
const dict = new Map<string, TagRenderingConfigJson>()
|
const dict = new Map<string, TagRenderingConfigJson>()
|
||||||
|
|
||||||
|
const prep = new RewriteSpecial()
|
||||||
const validator = new ValidateTagRenderings(undefined, doesImageExist)
|
const validator = new ValidateTagRenderings(undefined, doesImageExist)
|
||||||
for (const key in questions) {
|
for (const key in questions) {
|
||||||
if (key === "id") {
|
if (key === "id") {
|
||||||
|
@ -163,7 +164,12 @@ class LayerOverviewUtils extends Script {
|
||||||
}
|
}
|
||||||
questions[key].id = key
|
questions[key].id = key
|
||||||
questions[key]["source"] = "shared-questions"
|
questions[key]["source"] = "shared-questions"
|
||||||
const config = <TagRenderingConfigJson>questions[key]
|
const config = prep.convertStrict(
|
||||||
|
<TagRenderingConfigJson>questions[key],
|
||||||
|
"questions.json:" + key
|
||||||
|
)
|
||||||
|
delete config.description
|
||||||
|
delete config["#"]
|
||||||
validator.convertStrict(
|
validator.convertStrict(
|
||||||
config,
|
config,
|
||||||
"generate-layer-overview:tagRenderings/questions.json:" + key
|
"generate-layer-overview:tagRenderings/questions.json:" + key
|
||||||
|
|
|
@ -883,7 +883,10 @@ describe("ReplaceGeometryAction", () => {
|
||||||
const data = await Utils.downloadJson(url)
|
const data = await Utils.downloadJson(url)
|
||||||
const fullNodeDatabase = undefined // TODO new FullNodeDatabaseSource(undefined)
|
const fullNodeDatabase = undefined // TODO new FullNodeDatabaseSource(undefined)
|
||||||
// TODO fullNodeDatabase.handleOsmJson(data, 0)
|
// TODO fullNodeDatabase.handleOsmJson(data, 0)
|
||||||
const changes = new Changes()
|
const changes = new Changes({
|
||||||
|
dryRun: new ImmutableStore(true),
|
||||||
|
osmConnection: new OsmConnection()
|
||||||
|
})
|
||||||
const osmConnection = new OsmConnection({
|
const osmConnection = new OsmConnection({
|
||||||
dryRun: new ImmutableStore(true),
|
dryRun: new ImmutableStore(true),
|
||||||
})
|
})
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue