Merge branch 'master' into develop

This commit is contained in:
Pieter Vander Vennet 2025-10-13 10:21:32 +02:00
commit 969a878bb1
10 changed files with 64 additions and 30 deletions

View file

@ -164,7 +164,7 @@
"special": { "special": {
"classes": "p-2 m-1 my-4 border-2 border-dashed border-black", "classes": "p-2 m-1 my-4 border-2 border-dashed border-black",
"key": "_nearby_osm_poi:props", "key": "_nearby_osm_poi:props",
"tagrendering": "<b><a href='#{id}'>{id}</a></b> ({_distance}m, {openbenches:id}) {minimap(17,id;_original:id)} {tag_apply($_original:tags,Link this object.,link,id,_original:id)}", "tagrendering": "<b><a href='#{id}'>{id}</a></b> ({_distance}m, {openbenches:id}) {minimap(17,id;_original:id)} {tag_apply($_original:tags,Link this object.,link,id,_original:id,import:attribute)}",
"type": "multi" "type": "multi"
} }
} }

View file

@ -608,6 +608,7 @@
"doDelete": "Remove image", "doDelete": "Remove image",
"isDeleted": "Deleted", "isDeleted": "Deleted",
"loadingFailed": "Loading this image failed", "loadingFailed": "Loading this image failed",
"maintenance": "Due to maintenance, uploading images is currently not possible. Sorry about this!",
"mapillaryTrackingProtection": "Strict tracking protection blocks loading images from Mapillary, as Mapillary is owned by Facebook/Meta. Disable strict tracking protection if you want to see this image.", "mapillaryTrackingProtection": "Strict tracking protection blocks loading images from Mapillary, as Mapillary is owned by Facebook/Meta. Disable strict tracking protection if you want to see this image.",
"nearby": { "nearby": {
"close": "Collapse panel with nearby images", "close": "Collapse panel with nearby images",
@ -617,6 +618,7 @@
"seeNearby": "Browse nearby pictures", "seeNearby": "Browse nearby pictures",
"title": "Nearby streetview imagery" "title": "Nearby streetview imagery"
}, },
"noteReopen": "Adding a picture will reopen the note",
"openOnWebsite": "Open this image on {name}", "openOnWebsite": "Open this image on {name}",
"panoramax": { "panoramax": {
"deletionRequested": "The report has been sent. A moderator will look to it shortly", "deletionRequested": "The report has been sent. A moderator will look to it shortly",

View file

@ -221,6 +221,7 @@ export class GenerateDocs extends Script {
this.generateSidebar("nl") this.generateSidebar("nl")
this.generatedPaths.push(".gitignore") this.generatedPaths.push(".gitignore")
this.generatedPaths.push("nl/index.html")
writeFileSync( writeFileSync(
"./Docs/.gitignore", "./Docs/.gitignore",
this.generatedPaths.map((p) => p.replace("./Docs/", "")).join("\n"), this.generatedPaths.map((p) => p.replace("./Docs/", "")).join("\n"),

View file

@ -16,6 +16,8 @@
import type { Feature } from "geojson" import type { Feature } from "geojson"
import Camera from "@babeard/svelte-heroicons/mini/Camera" import Camera from "@babeard/svelte-heroicons/mini/Camera"
import ArrowUpTray from "@babeard/svelte-heroicons/solid/ArrowUpTray" import ArrowUpTray from "@babeard/svelte-heroicons/solid/ArrowUpTray"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import NoteCommentElement from "../Popup/Notes/NoteCommentElement"
export let state: SpecialVisualizationState export let state: SpecialVisualizationState
@ -23,6 +25,7 @@
export let targetKey: string = undefined export let targetKey: string = undefined
export let noBlur: boolean = false export let noBlur: boolean = false
export let feature: Feature export let feature: Feature
export let layer: LayerConfig
/** /**
* Image to show in the button * Image to show in the button
* NOT the image to upload! * NOT the image to upload!
@ -33,6 +36,8 @@
} }
export let labelText: string = undefined export let labelText: string = undefined
const t = Translations.t.image const t = Translations.t.image
const isNote = layer.id === "note"
const noteIsOpened = tags.mapD(tags => tags.closed_at === undefined)
let errors = new UIEventSource<Translation[]>([]) let errors = new UIEventSource<Translation[]>([])
@ -47,7 +52,15 @@
errs.push(canBeUploaded.error) errs.push(canBeUploaded.error)
continue continue
} }
await state?.imageUploadManager?.uploadImageAndApply(
if (isNote) {
if (!noteIsOpened.data) {
await state.osmConnection.reopenNote(tags.data.id)
NoteCommentElement.mimickStatusChange(tags, true)
}
}
state?.imageUploadManager?.uploadImageAndApply(
file, file,
tags, tags,
targetKey, targetKey,
@ -72,7 +85,7 @@
</LoginButton> </LoginButton>
{#if maintenanceBusy} {#if maintenanceBusy}
<div class="alert"> <div class="alert">
Due to maintenance, uploading images is currently not possible. Sorry about this! <Tr t={Translations.t.image.maintenance}></Tr>
</div> </div>
{:else} {:else}
<div class="my-4 flex flex-col"> <div class="my-4 flex flex-col">
@ -109,6 +122,9 @@
<Tr t={t.upload.noBlur} /> <Tr t={t.upload.noBlur} />
</span> </span>
{/if} {/if}
{#if isNote && !$noteIsOpened}
<Tr t={t.noteReopen} />
{/if}
</div> </div>
</div> </div>
</FileSelector> </FileSelector>
@ -150,14 +166,20 @@
<Tr t={t.upload.noBlur} /> <Tr t={t.upload.noBlur} />
</span> </span>
{/if} {/if}
{#if isNote && !$noteIsOpened}
<Tr t={t.noteReopen} />
{/if}
</div> </div>
</FileSelector> </FileSelector>
<div class="subtle text-xs italic"> <div class="subtle text-xs italic">
<Tr t={Translations.t.general.attribution.panoramaxLicenseCCBYSA} /> <Tr t={Translations.t.general.attribution.panoramaxLicenseCCBYSA} />
<span class="mx-1"></span> <span class="mx-1"></span>
<Tr t={t.respectPrivacy} /> <Tr t={t.respectPrivacy} />
</div> </div>
</div> </div>
{/if} {/if}
</LoginToggle> </LoginToggle>

View file

@ -7,6 +7,7 @@ import Maproulette from "../../../Logic/Maproulette"
import { GeoOperations } from "../../../Logic/GeoOperations" import { GeoOperations } from "../../../Logic/GeoOperations"
import { Tag } from "../../../Logic/Tags/Tag" import { Tag } from "../../../Logic/Tags/Tag"
import { SpecialVisualizationState } from "../../SpecialVisualization" import { SpecialVisualizationState } from "../../SpecialVisualization"
import NoteCommentElement from "../Notes/NoteCommentElement"
export interface PointImportFlowArguments extends ImportFlowArguments { export interface PointImportFlowArguments extends ImportFlowArguments {
max_snap_distance?: string max_snap_distance?: string
@ -74,8 +75,7 @@ export class PointImportFlowState extends ImportFlow<PointImportFlowArguments> {
if (note_id !== undefined) { if (note_id !== undefined) {
await this.state.osmConnection.closeNote(note_id, "imported") await this.state.osmConnection.closeNote(note_id, "imported")
originalFeatureTags.data["closed_at"] = new Date().toISOString() NoteCommentElement.mimickStatusChange(originalFeatureTags, false)
originalFeatureTags.ping()
} }
const maproulette_id = originalFeatureTags.data[this.args.maproulette_id] const maproulette_id = originalFeatureTags.data[this.args.maproulette_id]

View file

@ -43,6 +43,7 @@
if (isClosed.data) { if (isClosed.data) {
await state.osmConnection.reopenNote(id, txt.data) await state.osmConnection.reopenNote(id, txt.data)
await state.osmConnection.closeNote(id) await state.osmConnection.closeNote(id)
NoteCommentElement.mimickStatusChange(tags, false)
} else { } else {
await state.osmConnection.addCommentToNote(id, txt.data) await state.osmConnection.addCommentToNote(id, txt.data)
} }
@ -54,16 +55,15 @@
async function closeNote() { async function closeNote() {
isProcessing.set(true) isProcessing.set(true)
await state.osmConnection.closeNote(id, txt.data) await state.osmConnection.closeNote(id, txt.data)
isProcessing.set(false)
tags.data["closed_at"] = new Date().toISOString()
NoteCommentElement.addCommentTo(txt.data, tags, state) NoteCommentElement.addCommentTo(txt.data, tags, state)
tags.ping() NoteCommentElement.mimickStatusChange(tags, false) // Will ping
isProcessing.set(false)
} }
async function reopenNote() { async function reopenNote() {
isProcessing.set(true) isProcessing.set(true)
await state.osmConnection.reopenNote(id, txt.data) await state.osmConnection.reopenNote(id, txt.data)
tags.data["closed_at"] = undefined NoteCommentElement.mimickStatusChange(tags, true)
NoteCommentElement.addCommentTo(txt.data, tags, state) NoteCommentElement.addCommentTo(txt.data, tags, state)
tags.ping() tags.ping()
txt.set(undefined) txt.set(undefined)

View file

@ -26,8 +26,7 @@
const id = tags.data[idkey] const id = tags.data[idkey]
await state.osmConnection.closeNote(id, message) await state.osmConnection.closeNote(id, message)
NoteCommentElement.addCommentTo(message, tags, state) NoteCommentElement.addCommentTo(message, tags, state)
tags.data["closed_at"] = new Date().toISOString() NoteCommentElement.mimickStatusChange(tags, false)
tags.ping()
} }
</script> </script>

View file

@ -1,6 +1,17 @@
import { Store, UIEventSource } from "../../../Logic/UIEventSource" import { Store, UIEventSource } from "../../../Logic/UIEventSource"
export default class NoteCommentElement { export default class NoteCommentElement {
public static mimickStatusChange(tags: UIEventSource<Record<string, string>>, opened: boolean) {
if (opened) {
tags.data.status = "open"
tags.data.closed_at = undefined
} else {
tags.data.status = "closed"
tags.data.closed_at = new Date().toISOString()
}
tags.ping()
}
/** /**
* Adds the comment to the _visualisation_ of the given note; doesn't _actually_ upload * Adds the comment to the _visualisation_ of the given note; doesn't _actually_ upload
* @param txt * @param txt
@ -16,9 +27,9 @@ export default class NoteCommentElement {
const username = state.osmConnection.userDetails.data.name const username = state.osmConnection.userDetails.data.name
const urlRegex = /(https?:\/\/[^\s]+)/g const urlRegex = /(https?:\/\/[^\s]+)/g
const html = txt.replace(urlRegex, function (url) { const html = txt?.replace(urlRegex, function(url) {
return '<a href="' + url + '">' + url + "</a>" return '<a href="' + url + '">' + url + "</a>"
}) }) ?? ""
comments.push({ comments.push({
date: new Date().toISOString(), date: new Date().toISOString(),

View file

@ -1,8 +1,4 @@
import { import { SpecialVisualisationParams, SpecialVisualization, SpecialVisualizationSvelte } from "../SpecialVisualization"
SpecialVisualisationParams,
SpecialVisualization,
SpecialVisualizationSvelte,
} from "../SpecialVisualization"
import Constants from "../../Models/Constants" import Constants from "../../Models/Constants"
import { UIEventSource } from "../../Logic/UIEventSource" import { UIEventSource } from "../../Logic/UIEventSource"
import { Feature } from "geojson" import { Feature } from "geojson"
@ -126,10 +122,10 @@ class AddImageToNote extends SpecialVisualizationSvelte {
group = "notes" group = "notes"
needsUrls = [] needsUrls = []
constr({ state, tags, args, feature }: SpecialVisualisationParams) { constr(params: SpecialVisualisationParams) {
const id = tags.data[args[0] ?? "id"] const id = params.tags.data[params.args[0] ?? "id"]
tags = state.featureProperties.getStore(id) const tags = params.state.featureProperties.getStore(id)
return new SvelteUIElement(UploadImage, { state, tags, feature }) return new SvelteUIElement(UploadImage, { ...params, tags })
} }
} }

View file

@ -1,9 +1,5 @@
import { AutoAction } from "../Popup/AutoApplyButtonVis" import { AutoAction } from "../Popup/AutoApplyButtonVis"
import { import { SpecialVisualisationParams, SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization"
SpecialVisualisationParams,
SpecialVisualization,
SpecialVisualizationState,
} from "../SpecialVisualization"
import { Utils } from "../../Utils" import { Utils } from "../../Utils"
import { Store, UIEventSource } from "../../Logic/UIEventSource" import { Store, UIEventSource } from "../../Logic/UIEventSource"
import { Tag } from "../../Logic/Tags/Tag" import { Tag } from "../../Logic/Tags/Tag"
@ -47,6 +43,11 @@ export default class TagApplyViz extends SpecialVisualization implements AutoAct
defaultValue: undefined, defaultValue: undefined,
doc: "If specified, this maproulette-challenge will be closed when the tags are applied. This should be the `id` of the individual task, _not_ the task_id (which corresponds with the challenge).", doc: "If specified, this maproulette-challenge will be closed when the tags are applied. This should be the `id` of the individual task, _not_ the task_id (which corresponds with the challenge).",
}, },
{
name: "changeset_type",
defaultValue: "answer",
doc: "If set, this attribute will be set on the changeset. A typical example is `answer` if it answers a question or `import:attribute` if attributes are copied"
}
] ]
public readonly example = public readonly example =
"`{tag_apply(survey_date=$_now:date, Surveyed today!)}`, `{tag_apply(addr:street=$addr:street, Apply the address, apply_icon.svg, _closest_osm_id)" "`{tag_apply(survey_date=$_now:date, Surveyed today!)}`, `{tag_apply(addr:street=$addr:street, Apply the address, apply_icon.svg, _closest_osm_id)"
@ -133,7 +134,8 @@ export default class TagApplyViz extends SpecialVisualization implements AutoAct
_: Feature, _: Feature,
state: SpecialVisualizationState, state: SpecialVisualizationState,
tags: UIEventSource<any>, tags: UIEventSource<any>,
args: string[] args: string[],
changesetMetaattribute: string = "change"
): Promise<void> { ): Promise<void> {
const tagsToApply = TagApplyViz.generateTagsToApply(args[0], tags) const tagsToApply = TagApplyViz.generateTagsToApply(args[0], tags)
const targetIdKey = args[3] const targetIdKey = args[3]
@ -145,7 +147,7 @@ export default class TagApplyViz extends SpecialVisualization implements AutoAct
tags.data, // We pass in the tags of the selected element, not the tags of the target element! tags.data, // We pass in the tags of the selected element, not the tags of the target element!
{ {
theme: state.theme.id, theme: state.theme.id,
changeType: "answer", changeType: changesetMetaattribute
} }
) )
await state.changes.applyAction(changeAction) await state.changes.applyAction(changeAction)
@ -184,12 +186,13 @@ export default class TagApplyViz extends SpecialVisualization implements AutoAct
let image = args[2]?.trim() let image = args[2]?.trim()
const targetIdKey = args[3] const targetIdKey = args[3]
const maprouletteId = args[4] const maprouletteId = args[4]
const changeType = args[5]
if (image === "" || image === "undefined") { if (image === "" || image === "undefined") {
image = undefined image = undefined
} }
const onApply = async () => { const onApply = async () => {
await this.applyActionOn(feature, state, tags, args) await this.applyActionOn(feature, state, tags, args, changeType)
} }
return new SvelteUIElement(TagApplyButton, { return new SvelteUIElement(TagApplyButton, {