Add image support in notes

This commit is contained in:
pietervdvn 2022-01-08 14:08:04 +01:00
parent e8d1d5422e
commit b15eaff55e
8 changed files with 195 additions and 66 deletions

View file

@ -220,8 +220,8 @@ export class OsmConnection {
public closeNote(id: number | string, text?: string): Promise<any> {
let textSuffix = ""
if((text ?? "") !== "" ){
textSuffix = "?text="+encodeURIComponent(text)
if ((text ?? "") !== "") {
textSuffix = "?text=" + encodeURIComponent(text)
}
return new Promise((ok, error) => {
this.auth.xhr({
@ -241,8 +241,8 @@ export class OsmConnection {
public reopenNote(id: number | string, text?: string): Promise<any> {
let textSuffix = ""
if((text ?? "") !== "" ){
textSuffix = "?text="+encodeURIComponent(text)
if ((text ?? "") !== "") {
textSuffix = "?text=" + encodeURIComponent(text)
}
return new Promise((ok, error) => {
this.auth.xhr({

View file

@ -24,7 +24,7 @@ export default class Constants {
*/
public static readonly priviliged_layers: string[] = [...Constants.added_by_default, "type_node", "notes", ...Constants.no_include]
// The user journey states thresholds when a new feature gets unlocked
public static userJourney = {
moreScreenUnlock: 1,

26
UI/Popup/LoginButton.ts Normal file
View file

@ -0,0 +1,26 @@
import {SubtleButton} from "../Base/SubtleButton";
import BaseUIElement from "../BaseUIElement";
import Svg from "../../Svg";
import {OsmConnection} from "../../Logic/Osm/OsmConnection";
import Toggle from "../Input/Toggle";
export default class LoginButton extends SubtleButton {
constructor(text: BaseUIElement | string, state: {
osmConnection: OsmConnection
}) {
super(Svg.osm_logo_svg(), text);
this.onClick(() => {
state.osmConnection.AttemptLogin()
})
}
}
export class LoginToggle extends Toggle {
constructor(el, text: BaseUIElement | string, state: {
osmConnection: OsmConnection
}) {
super(el, new LoginButton(text, state), state.osmConnection.isLoggedIn)
}
}

View file

@ -0,0 +1,96 @@
import Combine from "../Base/Combine";
import BaseUIElement from "../BaseUIElement";
import Svg from "../../Svg";
import Link from "../Base/Link";
import {FixedUiElement} from "../Base/FixedUiElement";
import Translations from "../i18n/Translations";
import {Utils} from "../../Utils";
import Img from "../Base/Img";
import {ImageCarousel} from "../Image/ImageCarousel";
import {SlideShow} from "../Image/SlideShow";
import {UIEventSource} from "../../Logic/UIEventSource";
import {OsmConnection} from "../../Logic/Osm/OsmConnection";
export default class NoteCommentElement extends Combine {
constructor(comment: {
"date": string,
"uid": number,
"user": string,
"user_url": string,
"action": "closed" | "opened" | "reopened" | "commented",
"text": string, "html": string
}) {
const t = Translations.t.notes;
let actionIcon: BaseUIElement = undefined;
if (comment.action === "opened" || comment.action === "reopened") {
actionIcon = Svg.note_svg()
} else if (comment.action === "closed") {
actionIcon = Svg.resolved_svg()
} else {
actionIcon = Svg.addSmall_svg()
}
let user: BaseUIElement
if (comment.user === undefined) {
user = t.anonymous
} else {
user = new Link(comment.user, comment.user_url ?? "", true)
}
const htmlElement = document.createElement("div")
htmlElement.innerHTML = comment.html
const images = Array.from(htmlElement.getElementsByTagName("a"))
.map(link => link.href)
.filter(link => {
link = link.toLowerCase()
const lastDotIndex = link.lastIndexOf('.')
const extension = link.substring(lastDotIndex + 1, link.length)
return Utils.imageExtensions.has(extension)
})
let imagesEl: BaseUIElement = undefined;
if (images.length > 0) {
const imageEls = images.map(i => new Img(i)
.SetClass("w-full block")
.SetStyle("min-width: 50px; background: grey;"));
imagesEl = new SlideShow(new UIEventSource<BaseUIElement[]>(imageEls))
}
super([
new Combine([
actionIcon.SetClass("mr-4 w-6").SetStyle("flex-shrink: 0"),
new FixedUiElement(comment.html).SetClass("flex flex-col").SetStyle("margin: 0"),
]).SetClass("flex"),
imagesEl,
new Combine([user.SetClass("mr-2"), comment.date]).SetClass("flex justify-end subtle")
])
this.SetClass("flex flex-col")
}
public static addCommentTo(txt: string, tags: UIEventSource<any>, state: {osmConnection: OsmConnection}){
const comments: any[] = JSON.parse(tags.data["comments"])
const username = state.osmConnection.userDetails.data.name
var urlRegex = /(https?:\/\/[^\s]+)/g;
const html = txt.replace(urlRegex, function(url) {
return '<a href="' + url + '">' + url + '</a>';
})
comments.push({
"date": new Date().toISOString(),
"uid": state.osmConnection.userDetails.data.uid,
"user": username,
"user_url": "https://www.openstreetmap.org/user/" + username,
"action": "commented",
"text": txt,
"html": html
})
tags.data["comments"] = JSON.stringify(comments)
tags.ping()
}
}

View file

@ -41,7 +41,10 @@ import {OpenIdEditor} from "./BigComponents/CopyrightPanel";
import Toggle from "./Input/Toggle";
import Img from "./Base/Img";
import ValidatedTextField from "./Input/ValidatedTextField";
import Link from "./Base/Link";
import NoteCommentElement from "./Popup/NoteCommentElement";
import ImgurUploader from "../Logic/ImageProviders/ImgurUploader";
import FileSelectorButton from "./Input/FileSelectorButton";
import {LoginToggle} from "./Popup/LoginButton";
export interface SpecialVisualization {
funcName: string,
@ -693,11 +696,11 @@ export default class SpecialVisualizations {
tags.ping()
})
})
return new Toggle(
return new LoginToggle( new Toggle(
t.isClosed.SetClass("thanks"),
closeButton,
isClosed
)
), t.loginToClose, state)
}
},
{
@ -721,24 +724,17 @@ export default class SpecialVisualizations {
.onClick(async () => {
const id = tags.data[args[1] ?? "id"]
if ((txt.data ?? "") == "") {
return;
}
if (isClosed.data) {
await state.osmConnection.reopenNote(id, txt.data)
await state.osmConnection.closeNote(id)
} else {
await state.osmConnection.addCommentToNode(id, txt.data)
}
const comments: any[] = JSON.parse(tags.data["comments"])
const username = state.osmConnection.userDetails.data.name
comments.push({
"date": new Date().toISOString(),
"uid": state.osmConnection.userDetails.data.uid,
"user": username,
"user_url": "https://www.openstreetmap.org/user/" + username,
"action": "commented",
"text": txt.data
})
tags.data["comments"] = JSON.stringify(comments)
tags.ping()
NoteCommentElement.addCommentTo(txt.data, tags, state)
txt.setData("")
})
@ -779,13 +775,15 @@ export default class SpecialVisualizations {
})
const isClosed = tags.map(tags => (tags["closed_at"] ?? "") !== "");
const stateButtons = new Toggle(reopen, close, isClosed)
const stateButtons = new Toggle(new Toggle(reopen, close, isClosed), undefined, state.osmConnection.isLoggedIn)
return new Combine([
new Title("Add a comment"),
textField,
new Combine([addCommentButton.SetClass("mr-2"), stateButtons]).SetClass("flex justify-end")
]).SetClass("border-2 border-black rounded-xl p-4 block");
return new LoginToggle(
new Combine([
new Title("Add a comment"),
textField,
new Combine([addCommentButton.SetClass("mr-2"), stateButtons]).SetClass("flex justify-end")
]).SetClass("border-2 border-black rounded-xl p-4 block"),
t.loginToAddComment, state)
}
},
{
@ -798,52 +796,53 @@ export default class SpecialVisualizations {
defaultValue: "comments"
}
]
, constr: (state, tags, args) => {
const t = Translations.t.notes;
return new VariableUiElement(
, constr: (state, tags, args) =>
new VariableUiElement(
tags.map(tags => tags[args[0]])
.map(commentsStr => {
const comments:
{
"date": string,
"uid": number,
"user": string,
"user_url": string,
"action": "closed" | "opened" | "reopened" | "commented",
"text": string, "html": string
}[] = JSON.parse(commentsStr)
const comments: any[] = JSON.parse(commentsStr)
return new Combine(comments
.filter(c => c.text !== "")
.map(c => {
let actionIcon: BaseUIElement = undefined;
if (c.action === "opened" || c.action === "reopened") {
actionIcon = Svg.note_svg()
} else if (c.action === "closed") {
actionIcon = Svg.resolved_svg()
} else {
actionIcon = Svg.addSmall_svg()
}
let user: BaseUIElement
if (c.user === undefined) {
user = t.anonymous
} else {
user = new Link(c.user, c.user_url ?? "", true)
}
return new Combine([new Combine([
actionIcon.SetClass("mr-4 w-6").SetStyle("flex-shrink: 0"),
new FixedUiElement(c.html).SetClass("flex flex-col").SetStyle("margin: 0"),
]).SetClass("flex"),
new Combine([user.SetClass("mr-2"), c.date]).SetClass("flex justify-end subtle")
]).SetClass("flex flex-col")
})).SetClass("flex flex-col")
.map(c => new NoteCommentElement(c))).SetClass("flex flex-col")
})
)
},
{
funcName: "add_image_to_note",
docs: "Adds an image to a node",
args: [{
name: "Id-key",
doc: "The property name where the ID of the note to close can be found",
defaultValue: "id"
}],
constr: (state, tags, args) => {
const isUploading = new UIEventSource(false);
const t = Translations.t.notes;
const id = tags.data[args[0] ?? "id"]
const uploader = new ImgurUploader(url => {
isUploading.setData(false)
state.osmConnection.addCommentToNode(id, url)
NoteCommentElement.addCommentTo(url, tags, state)
})
const label = new Combine([
Svg.camera_plus_ui().SetClass("block w-12 h-12 p-1 text-4xl "),
"Add image to node. Your image will be published in the public domain."
]).SetClass("p-2 border-4 border-black rounded-full font-bold h-full align-middle w-full flex justify-center")
const fileSelector = new FileSelectorButton(label)
fileSelector.GetValue().addCallback(filelist => {
isUploading.setData(true)
uploader.uploadMany("Image for osm.org/note/" + id, "CC0", filelist)
})
return new LoginToggle( new Toggle(
Translations.t.image.uploadingPicture.SetClass("alert"),
fileSelector, isUploading), t.loginToAddPicture, state)
}
}
]

View file

@ -21,6 +21,7 @@ Remark that the syntax is slightly different then expected; it uses '$' to note
Note that these values can be prepare with javascript in the theme by using a [calculatedTag](calculatedTags.md#calculating-tags-with-javascript)
`
public static readonly imageExtensions = new Set(["jpg","png","svg","jpeg",".gif"])
public static readonly special_visualizations_importRequirementDocs = `#### Importing a dataset into OpenStreetMap: requirements

View file

@ -57,6 +57,10 @@
"id": "conversation",
"render": "{visualize_note_comments()}"
},
{
"id": "add_image",
"render": "{add_image_to_note()}"
},
{
"id": "comment",
"render": "{add_note_comment()}"

View file

@ -431,6 +431,9 @@
"closeNote": "Close note",
"reopenNote": "Reopen note",
"reopenNoteAndComment": "Reopen note and comment",
"anonymous": "Anonymous user"
"anonymous": "Anonymous user",
"loginToAddComment": "Login to add a comment",
"loginToAddPicture": "Login to add a picture",
"loginToClose": "Login to close this note"
}
}