mapcomplete/UI/Popup/ImportButtons/ImportFlow.ts

206 lines
7.8 KiB
TypeScript

import {SpecialVisualizationState} from "../../SpecialVisualization";
import {Utils} from "../../../Utils";
import {Store, UIEventSource} from "../../../Logic/UIEventSource";
import {Tag} from "../../../Logic/Tags/Tag";
import TagApplyButton from "../TagApplyButton";
import {PointImportFlowArguments} from "./PointImportFlowState";
import {Translation} from "../../i18n/Translation";
import Translations from "../../i18n/Translations";
import {OsmConnection} from "../../../Logic/Osm/OsmConnection";
import FilteredLayer from "../../../Models/FilteredLayer";
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
import {LayerConfigJson} from "../../../Models/ThemeConfig/Json/LayerConfigJson";
import conflation_json from "../../../assets/layers/conflation/conflation.json";
export interface ImportFlowArguments {
readonly text: string
readonly tags: string
readonly targetLayer: string
readonly icon?: string
}
export class ImportFlowUtils {
public static importedIds = new Set<string>()
public static readonly conflationLayer = new LayerConfig(
<LayerConfigJson>conflation_json,
"all_known_layers",
true
)
public static readonly documentationGeneral = `\n\n\nNote that the contributor must zoom to at least zoomlevel 18 to be able to use this functionality.
It is only functional in official themes, but can be tested in unoffical themes.
#### Specifying which tags to copy or add
The argument \`tags\` of the import button takes a \`;\`-seperated list of tags to add (or the name of a property which contains a JSON-list of properties).
${Utils.Special_visualizations_tagsToApplyHelpText}
${Utils.special_visualizations_importRequirementDocs}
`
public static generalArguments = [{
name: "targetLayer",
doc: "The id of the layer where this point should end up. This is not very strict, it will simply result in checking that this layer is shown preventing possible duplicate elements",
required: true,
},
{
name: "tags",
doc: "The tags to add onto the new object - see specification above. If this is a key (a single word occuring in the properties of the object), the corresponding value is taken and expanded instead",
required: true,
},
{
name: "text",
doc: "The text to show on the button",
defaultValue: "Import this data into OpenStreetMap",
},
{
name: "icon",
doc: "A nice icon to show in the button",
defaultValue: "./assets/svg/addSmall.svg",
},]
/**
* Given the tagsstore of the point which represents the challenge, creates a new store with tags that should be applied onto the newly created point,
*/
public static getTagsToApply(
originalFeatureTags: UIEventSource<any>,
args: { tags: string }
): Store<Tag[]> {
if (originalFeatureTags === undefined) {
return undefined
}
let newTags: Store<Tag[]>
const tags = args.tags
if (
tags.indexOf(" ") < 0 &&
tags.indexOf(";") < 0 &&
originalFeatureTags.data[tags] !== undefined
) {
// This is a property to expand...
const items: string = originalFeatureTags.data[tags]
console.debug(
"The import button is using tags from properties[" +
tags +
"] of this object, namely ",
items
)
newTags = TagApplyButton.generateTagsToApply(items, originalFeatureTags)
} else {
newTags = TagApplyButton.generateTagsToApply(tags, originalFeatureTags)
}
return newTags
}
/**
* Lists:
* - targetLayer
*
* Others (e.g.: snapOnto-layers) are not to be handled here
* @param argsRaw
*/
public static getLayerDependencies(argsRaw: string[], argSpec?) {
const args: ImportFlowArguments = <any>Utils.ParseVisArgs(argSpec ?? ImportFlowUtils.generalArguments, argsRaw)
return [args.targetLayer]
}
public static getLayerDependenciesWithSnapOnto(argSpec: {
name: string,
defaultValue?: string
}[], argsRaw: string[]): string[] {
const deps = ImportFlowUtils.getLayerDependencies(argsRaw, argSpec)
const argsParsed: PointImportFlowArguments = <any>Utils.ParseVisArgs(argSpec, argsRaw)
const snapOntoLayers = argsParsed.snap_onto_layers?.split(";")?.map(l => l.trim()) ?? []
deps.push(...snapOntoLayers)
return deps
}
public static buildTagSpec(args: ImportFlowArguments, tagSource: Store<Record<string, string>>): Store<string> {
let tagSpec = args.tags
return tagSource.mapD(tags => {
if (
tagSpec.indexOf(" ") < 0 &&
tagSpec.indexOf(";") < 0 &&
tags[args.tags] !== undefined
) {
// This is probably a key
tagSpec = tags[args.tags]
console.debug(
"The import button is using tags from properties[" +
args.tags +
"] of this object, namely ",
tagSpec
)
}
return tagSpec
})
}
}
/**
* The ImportFlow dictates some aspects of the import flow, e.g. what type of map should be shown and, in the case of a preview map, what layers that should be added.
*
* This class works together closely with ImportFlow.svelte
*/
export default abstract class ImportFlow<ArgT extends ImportFlowArguments> {
public readonly state: SpecialVisualizationState;
public readonly args: ArgT;
public readonly targetLayer: FilteredLayer;
public readonly tagsToApply: Store<Tag[]>
protected readonly _originalFeatureTags: UIEventSource<Record<string, string>>;
constructor(state: SpecialVisualizationState, args: ArgT, tagsToApply: Store<Tag[]>, originalTags: UIEventSource<Record<string, string>>) {
this.state = state;
this.args = args;
this.tagsToApply = tagsToApply;
this._originalFeatureTags = originalTags;
this.targetLayer = state.layerState.filteredLayers.get(args.targetLayer)
}
/**
* Constructs a store that contains either 'true' or gives a translation with the reason why it cannot be imported
*/
public canBeImported(): Store<true | { error: Translation, extraHelp?: Translation }> {
const state = this.state
return state.featureSwitchIsTesting.map(isTesting => {
const t = Translations.t.general.add.import
if(this._originalFeatureTags.data["_imported"] === "yes"){
return {error: t.hasBeenImported}
}
const usesTestUrl = this.state.osmConnection._oauth_config.url === OsmConnection.oauth_configs["osm-test"].url
if (!state.layout.official && !(isTesting || usesTestUrl)) {
// Unofficial theme - imports not allowed
return {
error: t.officialThemesOnly,
extraHelp: t.howToTest
}
}
if (this.targetLayer === undefined) {
const e = `Target layer not defined: error in import button for theme: ${this.state.layout.id}: layer ${this.args.targetLayer} not found`
console.error(e)
return {error: new Translation({"*": e})}
}
if (state.mapProperties.zoom.data < 18) {
return {error: t.zoomInMore}
}
if(state.dataIsLoading.data){
return {error: Translations.t.general.add.stillLoading}
}
return undefined
}, [state.mapProperties.zoom, state.dataIsLoading, this._originalFeatureTags])
}
}