Add import from notes functionality

This commit is contained in:
Pieter Vander Vennet 2022-01-12 02:31:51 +01:00
parent 2697feebe0
commit 6999a73d44
41 changed files with 545 additions and 1043 deletions

View file

@ -4,46 +4,33 @@ import LanguagePicker from "../LanguagePicker";
import Translations from "../i18n/Translations";
import Toggle from "../Input/Toggle";
import {SubtleButton} from "../Base/SubtleButton";
import Svg from "../../Svg";
import {UIEventSource} from "../../Logic/UIEventSource";
import {LoginToggle} from "../Popup/LoginButton";
export default class ThemeIntroductionPanel extends Combine {
constructor(isShown: UIEventSource<boolean>) {
const t = Translations.t.general
const layout = State.state.layoutToUse
const languagePicker = LanguagePicker.CreateLanguagePicker(layout.language, Translations.t.general.pickLanguage.Clone())
const languagePicker = LanguagePicker.CreateLanguagePicker(layout.language, t.pickLanguage.Clone())
const toTheMap = new SubtleButton(
undefined,
Translations.t.general.openTheMap.Clone().SetClass("text-xl font-bold w-full text-center")
t.openTheMap.Clone().SetClass("text-xl font-bold w-full text-center")
).onClick(() => {
isShown.setData(false)
}).SetClass("only-on-mobile")
const plzLogIn =
new SubtleButton(
Svg.osm_logo_ui(),
new Combine([Translations.t.general.loginWithOpenStreetMap
.Clone().SetClass("text-xl font-bold"),
Translations.t.general.loginOnlyNeededToEdit.Clone().SetClass("font-bold")]
).SetClass("flex flex-col text-center w-full")
)
.onClick(() => {
State.state.osmConnection.AttemptLogin()
});
const welcomeBack = Translations.t.general.welcomeBack.Clone();
const loginStatus =
new Toggle(
new Toggle(
welcomeBack,
plzLogIn,
State.state.osmConnection.isLoggedIn
new LoginToggle(
t.welcomeBack,
new Combine([Translations.t.general.loginWithOpenStreetMap.SetClass("text-xl font-bold"),
Translations.t.general.loginOnlyNeededToEdit.Clone().SetClass("font-bold")]
).SetClass("flex flex-col"),
State.state
),
undefined,
State.state.featureSwitchUserbadge

View file

@ -3,13 +3,14 @@ import {UIEventSource} from "../../Logic/UIEventSource";
import BaseUIElement from "../BaseUIElement";
import {Translation} from "../i18n/Translation";
import {SubstitutedTranslation} from "../SubstitutedTranslation";
import FeaturePipelineState from "../../Logic/State/FeaturePipelineState";
export default class InputElementWrapper<T> extends InputElement<T> {
public readonly IsSelected: UIEventSource<boolean>;
private readonly _inputElement: InputElement<T>;
private readonly _renderElement: BaseUIElement
constructor(inputElement: InputElement<T>, translation: Translation, key: string, tags: UIEventSource<any>) {
constructor(inputElement: InputElement<T>, translation: Translation, key: string, tags: UIEventSource<any>, state: FeaturePipelineState) {
super()
this._inputElement = inputElement;
this.IsSelected = inputElement.IsSelected
@ -17,7 +18,12 @@ export default class InputElementWrapper<T> extends InputElement<T> {
mapping.set(key, inputElement)
this._renderElement = new SubstitutedTranslation(translation, tags, mapping)
// Bit of a hack: the SubstitutedTranslation expects a special rendering, but those are formatted '{key()}' instead of '{key}', so we substitute it first
const newTranslations ={}
for (const lang in translation.translations) {
newTranslations[lang] = translation.translations[lang].replace("{"+key+"}", "{"+key+"()}")
}
this._renderElement = new SubstitutedTranslation(new Translation(newTranslations), tags, state, mapping)
}
GetValue(): UIEventSource<T> {

View file

@ -37,7 +37,6 @@ import TagApplyButton from "./TagApplyButton";
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
import * as conflation_json from "../../assets/layers/conflation/conflation.json";
import {GeoOperations} from "../../Logic/GeoOperations";
import {Or} from "../../Logic/Tags/Or";
abstract class AbstractImportButton implements SpecialVisualizations {
@ -93,7 +92,7 @@ ${Utils.special_visualizations_importRequirementDocs}
onCancelClicked: () => void): BaseUIElement;
constr(state, tagSource, argsRaw, guiState) {
constr(state, tagSource: UIEventSource<any>, argsRaw, guiState) {
const self = this;
/**
* Some generic import button pre-validation is implemented here:
@ -135,7 +134,14 @@ ${Utils.special_visualizations_importRequirementDocs}
// Explanation of the tags that will be applied onto the imported/conflated object
const newTags = TagApplyButton.generateTagsToApply(args.tags, tagSource)
let tagSpec = args.tags;
if(tagSpec.indexOf(" ")< 0 && tagSpec.indexOf(";") < 0){
// This is probably a key
tagSpec = tagSource.data[args.tags]
}
const newTags = TagApplyButton.generateTagsToApply(tagSpec, tagSource)
const appliedTags = new Toggle(
new VariableUiElement(
newTags.map(tgs => {
@ -503,6 +509,9 @@ export class ImportPointButton extends AbstractImportButton {
name: "max_snap_distance",
doc: "The maximum distance that the imported point will be moved to snap onto a way in an already existing layer (in meters). This is previewed to the contributor, similar to the 'add new point'-action of MapComplete",
defaultValue: "5"
},{
name:"note_id",
doc:"If given, this key will be read. The corresponding note on OSM will be closed, stating 'imported'"
}],
false
)
@ -524,7 +533,7 @@ export class ImportPointButton extends AbstractImportButton {
}
private static createConfirmPanelForPoint(
args: { max_snap_distance: string, snap_onto_layers: string, icon: string, text: string, newTags: UIEventSource<any>, targetLayer: string },
args: { max_snap_distance: string, snap_onto_layers: string, icon: string, text: string, newTags: UIEventSource<any>, targetLayer: string, note_id: string },
state: FeaturePipelineState,
guiState: DefaultGuiState,
originalFeatureTags: UIEventSource<any>,
@ -550,6 +559,11 @@ export class ImportPointButton extends AbstractImportButton {
state.selectedElement.setData(state.allElements.ContainingFeatures.get(
newElementAction.newElementId
))
if(args.note_id !== undefined){
state.osmConnection.closeNote(args.note_id, "imported")
originalFeatureTags.data["closed_at"] = new Date().toISOString()
originalFeatureTags.ping()
}
}
const presetInfo = <PresetInfo>{

View file

@ -418,7 +418,7 @@ export default class TagRenderingQuestion extends Combine {
if (freeform.inline) {
inputTagsFilter.SetClass("w-16-imp")
inputTagsFilter = new InputElementWrapper(inputTagsFilter, configuration.render, freeform.key, tags)
inputTagsFilter = new InputElementWrapper(inputTagsFilter, configuration.render, freeform.key, tags, State.state)
inputTagsFilter.SetClass("block")
}

View file

@ -45,6 +45,7 @@ import NoteCommentElement from "./Popup/NoteCommentElement";
import ImgurUploader from "../Logic/ImageProviders/ImgurUploader";
import FileSelectorButton from "./Input/FileSelectorButton";
import {LoginToggle} from "./Popup/LoginButton";
import {start} from "repl";
export interface SpecialVisualization {
funcName: string,
@ -650,7 +651,7 @@ export default class SpecialVisualizations {
},
{
funcName: "close_note",
docs: "Button to close a note - eventually with a prefixed text",
docs: "Button to close a note. A predifined text can be defined to close the note with. If the note is already closed, will show a small text.",
args: [
{
name: "text",
@ -675,8 +676,8 @@ export default class SpecialVisualizations {
const t = Translations.t.notes;
let icon = Svg.checkmark_svg()
if (args[2] !== "checkmark.svg" && (args[2] ?? "") !== "") {
icon = new Img(args[2])
if (args[1] !== "checkmark.svg" && (args[2] ?? "") !== "") {
icon = new Img(args[1])
}
let textToShow = t.closeNote;
if ((args[0] ?? "") !== "") {
@ -684,14 +685,11 @@ export default class SpecialVisualizations {
}
const closeButton = new SubtleButton(icon, textToShow)
const isClosed = tags.map(tags => (tags["closed_at"] ?? "") === "");
const isClosed = tags.map(tags => (tags["closed_at"] ?? "") !== "");
closeButton.onClick(() => {
const id = tags.data[args[1] ?? "id"]
if (state.featureSwitchIsTesting.data) {
console.log("Not actually closing note...")
return;
}
state.osmConnection.closeNote(id, args[3]).then(_ => {
const id = tags.data[args[2] ?? "id"]
state.osmConnection.closeNote(id, args[3])
?.then(_ => {
tags.data["closed_at"] = new Date().toISOString();
tags.ping()
})
@ -720,7 +718,7 @@ export default class SpecialVisualizations {
textField.SetClass("rounded-l border border-grey")
const txt = textField.GetValue()
const addCommentButton = new SubtleButton(undefined, t.addCommentPlaceholder)
const addCommentButton = new SubtleButton(Svg.addSmall_svg().SetClass("max-h-7"), t.addCommentPlaceholder)
.onClick(async () => {
const id = tags.data[args[1] ?? "id"]
@ -740,7 +738,7 @@ export default class SpecialVisualizations {
})
const close = new SubtleButton(undefined, new VariableUiElement(txt.map(txt => {
const close = new SubtleButton(Svg.resolved_svg().SetClass("max-h-7"), new VariableUiElement(txt.map(txt => {
if (txt === undefined || txt === "") {
return t.closeNote
}
@ -757,7 +755,7 @@ export default class SpecialVisualizations {
})
})
const reopen = new SubtleButton(undefined, new VariableUiElement(txt.map(txt => {
const reopen = new SubtleButton(Svg.note_svg().SetClass("max-h-7"), new VariableUiElement(txt.map(txt => {
if (txt === undefined || txt === "") {
return t.reopenNote
}
@ -788,12 +786,17 @@ export default class SpecialVisualizations {
},
{
funcName: "visualize_note_comments",
docs: "Visualises the comments for nodes",
docs: "Visualises the comments for notes",
args: [
{
name: "commentsKey",
doc: "The property name of the comments, which should be stringified json",
defaultValue: "comments"
},
{
name: "start",
doc:"Drop the first 'start' comments",
defaultValue: "0"
}
]
, constr: (state, tags, args) =>
@ -801,6 +804,10 @@ export default class SpecialVisualizations {
tags.map(tags => tags[args[0]])
.map(commentsStr => {
const comments: any[] = JSON.parse(commentsStr)
const startLoc = Number(args[1] ?? 0)
if(!isNaN(startLoc) && startLoc > 0){
comments.splice(0, startLoc)
}
return new Combine(comments
.filter(c => c.text !== "")
.map(c => new NoteCommentElement(c))).SetClass("flex flex-col")

View file

@ -8,13 +8,14 @@ import {VariableUiElement} from "./Base/VariableUIElement";
import Combine from "./Base/Combine";
import BaseUIElement from "./BaseUIElement";
import {DefaultGuiState} from "./DefaultGuiState";
import FeaturePipelineState from "../Logic/State/FeaturePipelineState";
export class SubstitutedTranslation extends VariableUiElement {
public constructor(
translation: Translation,
tagsSource: UIEventSource<any>,
state,
state: FeaturePipelineState,
mapping: Map<string, BaseUIElement> = undefined) {
const extraMappings: SpecialVisualization[] = [];
@ -71,7 +72,7 @@ export class SubstitutedTranslation extends VariableUiElement {
style: string
}
}[] {
for (const knownSpecial of SpecialVisualizations.specialVisualizations.concat(extraMappings)) {
// Note: the '.*?' in the regex reads as 'any character, but in a non-greedy way'