forked from MapComplete/MapComplete
Add import from notes functionality
This commit is contained in:
parent
2697feebe0
commit
6999a73d44
41 changed files with 545 additions and 1043 deletions
|
@ -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
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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>{
|
||||
|
|
|
@ -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")
|
||||
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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'
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue