forked from MapComplete/MapComplete
		
	Refactoring: port add-image-to-note to new element as well, remove obsolete classes, fix note creation
This commit is contained in:
		
							parent
							
								
									94ba18785d
								
							
						
					
					
						commit
						9a5a2e9924
					
				
					 10 changed files with 617 additions and 1001 deletions
				
			
		|  | @ -7,6 +7,7 @@ import { Store, UIEventSource } from "../UIEventSource"; | ||||||
| import { OsmConnection } from "../Osm/OsmConnection"; | import { OsmConnection } from "../Osm/OsmConnection"; | ||||||
| import { Changes } from "../Osm/Changes"; | import { Changes } from "../Osm/Changes"; | ||||||
| import Translations from "../../UI/i18n/Translations"; | import Translations from "../../UI/i18n/Translations"; | ||||||
|  | import NoteCommentElement from "../../UI/Popup/NoteCommentElement"; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  | @ -58,24 +59,25 @@ export class ImageUploadManager { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** |   /** | ||||||
|    * Uploads the given image, applies the correct title and license for the known user |    * Uploads the given image, applies the correct title and license for the known user. | ||||||
|  |    * Will then add this image to the OSM-feature or the OSM-note | ||||||
|    */ |    */ | ||||||
|   public async uploadImageAndApply(file: File, tags: OsmTags) { |   public async uploadImageAndApply(file: File, tagsStore: UIEventSource<OsmTags>) : Promise<void>{ | ||||||
| 
 | 
 | ||||||
|       const sizeInBytes = file.size |     const sizeInBytes = file.size; | ||||||
|     const featureId = <OsmId> tags.id |     const tags= tagsStore.data | ||||||
|       console.log(file.name + " has a size of " + sizeInBytes + " Bytes, attaching to", tags.id) |     const featureId = <OsmId>tags.id; | ||||||
|       const self = this |     const self = this; | ||||||
|       if (sizeInBytes > this._uploader.maxFileSizeInMegabytes * 1000000) { |     if (sizeInBytes > this._uploader.maxFileSizeInMegabytes * 1000000) { | ||||||
|           this.increaseCountFor(this._uploadStarted, featureId) |       this.increaseCountFor(this._uploadStarted, featureId); | ||||||
|           this.increaseCountFor(this._uploadFailed, featureId) |       this.increaseCountFor(this._uploadFailed, featureId); | ||||||
|           throw( |       throw ( | ||||||
|             Translations.t.image.toBig.Subs({ |         Translations.t.image.toBig.Subs({ | ||||||
|                 actual_size: Math.floor(sizeInBytes / 1000000) + "MB", |           actual_size: Math.floor(sizeInBytes / 1000000) + "MB", | ||||||
|                 max_size: self._uploader.maxFileSizeInMegabytes + "MB", |           max_size: self._uploader.maxFileSizeInMegabytes + "MB" | ||||||
|             }).txt |         }).txt | ||||||
|           ) |       ); | ||||||
|       } |     } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     const licenseStore = this._osmConnection?.GetPreference("pictures-license", "CC0"); |     const licenseStore = this._osmConnection?.GetPreference("pictures-license", "CC0"); | ||||||
|  | @ -93,8 +95,15 @@ export class ImageUploadManager { | ||||||
|       "osmid:" + tags.id |       "osmid:" + tags.id | ||||||
|     ].join("\n"); |     ].join("\n"); | ||||||
| 
 | 
 | ||||||
|     console.log("Upload done, creating ") |     console.log("Upload done, creating "); | ||||||
|     const action = await this.uploadImageWithLicense(featureId, title, description, file); |     const action = await this.uploadImageWithLicense(featureId, title, description, file); | ||||||
|  |     if(!isNaN(Number( featureId))){ | ||||||
|  |       // THis is a map note
 | ||||||
|  |       const url = action._url | ||||||
|  |       await this._osmConnection.addCommentToNote(featureId, url) | ||||||
|  |       NoteCommentElement.addCommentTo(url, <UIEventSource<any>> tagsStore, {osmConnection: this._osmConnection}) | ||||||
|  |       return | ||||||
|  |     } | ||||||
|     await this._changes.applyAction(action); |     await this._changes.applyAction(action); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | @ -121,7 +130,7 @@ export class ImageUploadManager { | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|     } |     } | ||||||
|     console.log("Uploading done, creating action for", featureId) |     console.log("Uploading done, creating action for", featureId); | ||||||
|     const action = new LinkImageAction(featureId, key, value, properties, { |     const action = new LinkImageAction(featureId, key, value, properties, { | ||||||
|       theme: this._layout.id, |       theme: this._layout.id, | ||||||
|       changeType: "add-image" |       changeType: "add-image" | ||||||
|  |  | ||||||
|  | @ -7,7 +7,7 @@ import { Store } from "../../UIEventSource"; | ||||||
| 
 | 
 | ||||||
| export default class LinkImageAction extends OsmChangeAction { | export default class LinkImageAction extends OsmChangeAction { | ||||||
|     private readonly _proposedKey: "image" | "mapillary" | "wiki_commons" | string; |     private readonly _proposedKey: "image" | "mapillary" | "wiki_commons" | string; | ||||||
|     private readonly _url: string; |     public readonly _url: string; | ||||||
|     private readonly _currentTags: Store<Record<string, string>>; |     private readonly _currentTags: Store<Record<string, string>>; | ||||||
|     private readonly _meta: { theme: string; changeType: "add-image" | "link-image" }; |     private readonly _meta: { theme: string; changeType: "add-image" | "link-image" }; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -1,192 +0,0 @@ | ||||||
| import { Store, UIEventSource } from "../../Logic/UIEventSource" |  | ||||||
| import Combine from "../Base/Combine" |  | ||||||
| import Translations from "../i18n/Translations" |  | ||||||
| import Svg from "../../Svg" |  | ||||||
| import { Tag } from "../../Logic/Tags/Tag" |  | ||||||
| import BaseUIElement from "../BaseUIElement" |  | ||||||
| import Toggle from "../Input/Toggle" |  | ||||||
| import FileSelectorButton from "../Input/FileSelectorButton" |  | ||||||
| import ImgurUploader from "../../Logic/ImageProviders/ImgurUploader" |  | ||||||
| import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction" |  | ||||||
| import LayerConfig from "../../Models/ThemeConfig/LayerConfig" |  | ||||||
| import { FixedUiElement } from "../Base/FixedUiElement" |  | ||||||
| import { VariableUiElement } from "../Base/VariableUIElement" |  | ||||||
| import Loading from "../Base/Loading" |  | ||||||
| import { LoginToggle } from "../Popup/LoginButton" |  | ||||||
| import Constants from "../../Models/Constants" |  | ||||||
| import { SpecialVisualizationState } from "../SpecialVisualization" |  | ||||||
| import exp from "constants"; |  | ||||||
| 
 |  | ||||||
| export class ImageUploadFlow extends Combine { |  | ||||||
|     private static readonly uploadCountsPerId = new Map<string, UIEventSource<number>>() |  | ||||||
| 
 |  | ||||||
|     constructor( |  | ||||||
|         tagsSource: Store<any>, |  | ||||||
|         state: SpecialVisualizationState, |  | ||||||
|         imagePrefix: string = "image", |  | ||||||
|         text: string = undefined |  | ||||||
|     ) { |  | ||||||
|         const perId = ImageUploadFlow.uploadCountsPerId |  | ||||||
|         const id = tagsSource.data.id |  | ||||||
|         if (!perId.has(id)) { |  | ||||||
|             perId.set(id, new UIEventSource<number>(0)) |  | ||||||
|         } |  | ||||||
|         const uploadedCount = perId.get(id) |  | ||||||
|         const uploader = new ImgurUploader(async (url) => { |  | ||||||
|             // A file was uploaded - we add it to the tags of the object
 |  | ||||||
| 
 |  | ||||||
|             const tags = tagsSource.data |  | ||||||
|             let key = imagePrefix |  | ||||||
|             if (tags[imagePrefix] !== undefined) { |  | ||||||
|                 let freeIndex = 0 |  | ||||||
|                 while (tags[imagePrefix + ":" + freeIndex] !== undefined) { |  | ||||||
|                     freeIndex++ |  | ||||||
|                 } |  | ||||||
|                 key = imagePrefix + ":" + freeIndex |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             await state.changes.applyAction( |  | ||||||
|                 new ChangeTagAction(tags.id, new Tag(key, url), tagsSource.data, { |  | ||||||
|                     changeType: "add-image", |  | ||||||
|                     theme: state.layout.id, |  | ||||||
|                 }) |  | ||||||
|             ) |  | ||||||
|             console.log("Adding image:" + key, url) |  | ||||||
|             uploadedCount.data++ |  | ||||||
|             uploadedCount.ping() |  | ||||||
|         }) |  | ||||||
| 
 |  | ||||||
|         const t = Translations.t.image |  | ||||||
| 
 |  | ||||||
|         let labelContent: BaseUIElement |  | ||||||
|         if (text === undefined) { |  | ||||||
|             labelContent = Translations.t.image.addPicture |  | ||||||
|                 .Clone() |  | ||||||
|                 .SetClass("block align-middle mt-1 ml-3 text-4xl ") |  | ||||||
|         } else { |  | ||||||
|             labelContent = new FixedUiElement(text).SetClass( |  | ||||||
|                 "block align-middle mt-1 ml-3 text-2xl " |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
|         const label = new Combine([ |  | ||||||
|             Svg.camera_plus_svg().SetClass("block w-12 h-12 p-1 text-4xl "), |  | ||||||
|             labelContent, |  | ||||||
|         ]).SetClass("w-full flex justify-center items-center") |  | ||||||
| 
 |  | ||||||
|         const licenseStore = state?.osmConnection?.GetPreference("pictures-license", "CC0") |  | ||||||
| 
 |  | ||||||
|         const fileSelector = new FileSelectorButton(label, { |  | ||||||
|             acceptType: "image/*", |  | ||||||
|             allowMultiple: true, |  | ||||||
|             labelClasses: "rounded-full border-2 border-black font-bold", |  | ||||||
|         }) |  | ||||||
|         /*    fileSelector.SetClass( |  | ||||||
|             "p-2 border-4 border-detail rounded-full font-bold h-full align-middle w-full flex justify-center" |  | ||||||
|         ) |  | ||||||
|             .SetStyle(" border-color: var(--foreground-color);")*/ |  | ||||||
|         fileSelector.GetValue().addCallback((filelist) => { |  | ||||||
|             if (filelist === undefined || filelist.length === 0) { |  | ||||||
|                 return |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             for (var i = 0; i < filelist.length; i++) { |  | ||||||
|                 const sizeInBytes = filelist[i].size |  | ||||||
|                 console.log(filelist[i].name + " has a size of " + sizeInBytes + " Bytes") |  | ||||||
|                 if (sizeInBytes > uploader.maxFileSizeInMegabytes * 1000000) { |  | ||||||
|                     alert( |  | ||||||
|                         Translations.t.image.toBig.Subs({ |  | ||||||
|                             actual_size: Math.floor(sizeInBytes / 1000000) + "MB", |  | ||||||
|                             max_size: uploader.maxFileSizeInMegabytes + "MB", |  | ||||||
|                         }).txt |  | ||||||
|                     ) |  | ||||||
|                     return |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             const license = licenseStore?.data ?? "CC0" |  | ||||||
| 
 |  | ||||||
|             const tags = tagsSource.data |  | ||||||
| 
 |  | ||||||
|             const layout = state?.layout |  | ||||||
|             let matchingLayer: LayerConfig = undefined |  | ||||||
|             for (const layer of layout?.layers ?? []) { |  | ||||||
|                 if (layer.source.osmTags.matchesProperties(tags)) { |  | ||||||
|                     matchingLayer = layer |  | ||||||
|                     break |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             const title = |  | ||||||
|                 matchingLayer?.title?.GetRenderValue(tags)?.Subs(tags)?.ConstructElement() |  | ||||||
|                     ?.textContent ?? |  | ||||||
|                 tags.name ?? |  | ||||||
|                 "https//osm.org/" + tags.id |  | ||||||
|             const description = [ |  | ||||||
|                 "author:" + state.osmConnection.userDetails.data.name, |  | ||||||
|                 "license:" + license, |  | ||||||
|                 "osmid:" + tags.id, |  | ||||||
|             ].join("\n") |  | ||||||
| 
 |  | ||||||
|             uploader.uploadMany(title, description, filelist) |  | ||||||
|         }) |  | ||||||
| 
 |  | ||||||
|         super([ |  | ||||||
|             new VariableUiElement( |  | ||||||
|                 uploader.queue |  | ||||||
|                     .map((q) => q.length) |  | ||||||
|                     .map((l) => { |  | ||||||
|                         if (l == 0) { |  | ||||||
|                             return undefined |  | ||||||
|                         } |  | ||||||
|                         if (l == 1) { |  | ||||||
|                             return new Loading(t.uploadingPicture).SetClass("alert") |  | ||||||
|                         } else { |  | ||||||
|                             return new Loading( |  | ||||||
|                                 t.uploadingMultiple.Subs({ count: "" + l }) |  | ||||||
|                             ).SetClass("alert") |  | ||||||
|                         } |  | ||||||
|                     }) |  | ||||||
|             ), |  | ||||||
|             new VariableUiElement( |  | ||||||
|                 uploader.failed |  | ||||||
|                     .map((q) => q.length) |  | ||||||
|                     .map((l) => { |  | ||||||
|                         if (l == 0) { |  | ||||||
|                             return undefined |  | ||||||
|                         } |  | ||||||
|                         console.log(l) |  | ||||||
|                         return t.uploadFailed.SetClass("block alert") |  | ||||||
|                     }) |  | ||||||
|             ), |  | ||||||
|             new VariableUiElement( |  | ||||||
|                 uploadedCount.map((l) => { |  | ||||||
|                     if (l == 0) { |  | ||||||
|                         return undefined |  | ||||||
|                     } |  | ||||||
|                     if (l == 1) { |  | ||||||
|                         return t.uploadDone.Clone().SetClass("thanks block") |  | ||||||
|                     } |  | ||||||
|                     return t.uploadMultipleDone.Subs({ count: l }).SetClass("thanks block") |  | ||||||
|                 }) |  | ||||||
|             ), |  | ||||||
| 
 |  | ||||||
|             fileSelector, |  | ||||||
|             new Combine([ |  | ||||||
|                 Translations.t.image.respectPrivacy, |  | ||||||
|                 new VariableUiElement( |  | ||||||
|                     licenseStore.map((license) => |  | ||||||
|                         Translations.t.image.currentLicense.Subs({ license }) |  | ||||||
|                     ) |  | ||||||
|                 ) |  | ||||||
|                     .onClick(() => { |  | ||||||
|                         console.log("Opening the license settings... ") |  | ||||||
|                         state.guistate.openUsersettings("picture-license") |  | ||||||
|                     }) |  | ||||||
|                     .SetClass("underline"), |  | ||||||
|             ]).SetStyle("font-size:small;"), |  | ||||||
|         ]) |  | ||||||
|         this.SetClass("flex flex-col image-upload-flow mt-4 mb-8 text-center leading-none") |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -16,6 +16,10 @@ import Svg from "../../Svg"; | ||||||
| export let state: SpecialVisualizationState; | export let state: SpecialVisualizationState; | ||||||
| 
 | 
 | ||||||
| export let tags: Store<OsmTags>; | export let tags: Store<OsmTags>; | ||||||
|  | /** | ||||||
|  |  * Image to show in the button | ||||||
|  |  * NOT the image to upload! | ||||||
|  |  */ | ||||||
| export let image: string = undefined; | export let image: string = undefined; | ||||||
| if (image === "") { | if (image === "") { | ||||||
|   image = undefined; |   image = undefined; | ||||||
|  | @ -30,7 +34,7 @@ function handleFiles(files: FileList) { | ||||||
|     const file = files.item(i); |     const file = files.item(i); | ||||||
|     console.log("Got file", file.name) |     console.log("Got file", file.name) | ||||||
|     try { |     try { | ||||||
|       state.imageUploadManager.uploadImageAndApply(file, tags.data); |       state.imageUploadManager.uploadImageAndApply(file, tags); | ||||||
|     } catch (e) { |     } catch (e) { | ||||||
|       alert(e); |       alert(e); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -1,111 +0,0 @@ | ||||||
| import BaseUIElement from "../BaseUIElement" |  | ||||||
| import { InputElement } from "./InputElement" |  | ||||||
| import { UIEventSource } from "../../Logic/UIEventSource" |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * @deprecated |  | ||||||
|  */ |  | ||||||
| export default class FileSelectorButton extends InputElement<FileList> { |  | ||||||
|     private static _nextid = 0 |  | ||||||
|     private readonly _value = new UIEventSource<FileList>(undefined) |  | ||||||
|     private readonly _label: BaseUIElement |  | ||||||
|     private readonly _acceptType: string |  | ||||||
|     private readonly allowMultiple: boolean |  | ||||||
|     private readonly _labelClasses: string |  | ||||||
| 
 |  | ||||||
|     constructor( |  | ||||||
|         label: BaseUIElement, |  | ||||||
|         options?: { |  | ||||||
|             acceptType: "image/*" | string |  | ||||||
|             allowMultiple: true | boolean |  | ||||||
|             labelClasses?: string |  | ||||||
|         } |  | ||||||
|     ) { |  | ||||||
|         super() |  | ||||||
|         this._label = label |  | ||||||
|         this._acceptType = options?.acceptType ?? "image/*" |  | ||||||
|         this._labelClasses = options?.labelClasses ?? "" |  | ||||||
|         this.SetClass("block cursor-pointer") |  | ||||||
|         label.SetClass("cursor-pointer") |  | ||||||
|         this.allowMultiple = options?.allowMultiple ?? true |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     GetValue(): UIEventSource<FileList> { |  | ||||||
|         return this._value |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     IsValid(t: FileList): boolean { |  | ||||||
|         return true |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     protected InnerConstructElement(): HTMLElement { |  | ||||||
|         const self = this |  | ||||||
|         const el = document.createElement("form") |  | ||||||
|         const label = document.createElement("label") |  | ||||||
|         label.appendChild(this._label.ConstructElement()) |  | ||||||
|         label.classList.add(...this._labelClasses.split(" ").filter((t) => t !== "")) |  | ||||||
|         el.appendChild(label) |  | ||||||
| 
 |  | ||||||
|         const actualInputElement = document.createElement("input") |  | ||||||
|         actualInputElement.style.cssText = "display:none" |  | ||||||
|         actualInputElement.type = "file" |  | ||||||
|         actualInputElement.accept = this._acceptType |  | ||||||
|         actualInputElement.name = "picField" |  | ||||||
|         actualInputElement.multiple = this.allowMultiple |  | ||||||
|         actualInputElement.id = "fileselector" + FileSelectorButton._nextid |  | ||||||
|         FileSelectorButton._nextid++ |  | ||||||
| 
 |  | ||||||
|         label.htmlFor = actualInputElement.id |  | ||||||
| 
 |  | ||||||
|         actualInputElement.onchange = () => { |  | ||||||
|             if (actualInputElement.files !== null) { |  | ||||||
|                 self._value.setData(actualInputElement.files) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         el.addEventListener("submit", (e) => { |  | ||||||
|             if (actualInputElement.files !== null) { |  | ||||||
|                 self._value.setData(actualInputElement.files) |  | ||||||
|             } |  | ||||||
|             actualInputElement.classList.remove("glowing-shadow") |  | ||||||
| 
 |  | ||||||
|             e.preventDefault() |  | ||||||
|         }) |  | ||||||
| 
 |  | ||||||
|         el.appendChild(actualInputElement) |  | ||||||
| 
 |  | ||||||
|         function setDrawAttention(isOn: boolean) { |  | ||||||
|             if (isOn) { |  | ||||||
|                 label.classList.add("glowing-shadow") |  | ||||||
|             } else { |  | ||||||
|                 label.classList.remove("glowing-shadow") |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         el.addEventListener("dragover", (event) => { |  | ||||||
|             event.stopPropagation() |  | ||||||
|             event.preventDefault() |  | ||||||
|             setDrawAttention(true) |  | ||||||
|             // Style the drag-and-drop as a "copy file" operation.
 |  | ||||||
|             event.dataTransfer.dropEffect = "copy" |  | ||||||
|         }) |  | ||||||
| 
 |  | ||||||
|         window.document.addEventListener("dragenter", () => { |  | ||||||
|             setDrawAttention(true) |  | ||||||
|         }) |  | ||||||
| 
 |  | ||||||
|         window.document.addEventListener("dragend", () => { |  | ||||||
|             setDrawAttention(false) |  | ||||||
|         }) |  | ||||||
| 
 |  | ||||||
|         el.addEventListener("drop", (event) => { |  | ||||||
|             event.stopPropagation() |  | ||||||
|             event.preventDefault() |  | ||||||
|             label.classList.remove("glowing-shadow") |  | ||||||
|             const fileList = event.dataTransfer.files |  | ||||||
|             this._value.setData(fileList) |  | ||||||
|         }) |  | ||||||
| 
 |  | ||||||
|         return el |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,62 +0,0 @@ | ||||||
| import { InputElement } from "./InputElement" |  | ||||||
| import { UIEventSource } from "../../Logic/UIEventSource" |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * @deprecated |  | ||||||
|  */ |  | ||||||
| export default class Slider extends InputElement<number> { |  | ||||||
|     private readonly _value: UIEventSource<number> |  | ||||||
|     private readonly min: number |  | ||||||
|     private readonly max: number |  | ||||||
|     private readonly step: number |  | ||||||
|     private readonly vertical: boolean |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Constructs a slider input element for natural numbers |  | ||||||
|      * @param min: the minimum value that is allowed, inclusive |  | ||||||
|      * @param max: the max value that is allowed, inclusive |  | ||||||
|      * @param options: value: injectable value; step: the step size of the slider |  | ||||||
|      */ |  | ||||||
|     constructor( |  | ||||||
|         min: number, |  | ||||||
|         max: number, |  | ||||||
|         options?: { |  | ||||||
|             value?: UIEventSource<number> |  | ||||||
|             step?: 1 | number |  | ||||||
|             vertical?: false | boolean |  | ||||||
|         } |  | ||||||
|     ) { |  | ||||||
|         super() |  | ||||||
|         this.max = max |  | ||||||
|         this.min = min |  | ||||||
|         this._value = options?.value ?? new UIEventSource<number>(min) |  | ||||||
|         this.step = options?.step ?? 1 |  | ||||||
|         this.vertical = options?.vertical ?? false |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     GetValue(): UIEventSource<number> { |  | ||||||
|         return this._value |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     protected InnerConstructElement(): HTMLElement { |  | ||||||
|         const el = document.createElement("input") |  | ||||||
|         el.type = "range" |  | ||||||
|         el.min = "" + this.min |  | ||||||
|         el.max = "" + this.max |  | ||||||
|         el.step = "" + this.step |  | ||||||
|         const valuestore = this._value |  | ||||||
|         el.oninput = () => { |  | ||||||
|             valuestore.setData(Number(el.value)) |  | ||||||
|         } |  | ||||||
|         if (this.vertical) { |  | ||||||
|             el.classList.add("vertical") |  | ||||||
|             el.setAttribute("orient", "vertical") // firefox only workaround...
 |  | ||||||
|         } |  | ||||||
|         valuestore.addCallbackAndRunD((v) => (el.value = "" + valuestore.data)) |  | ||||||
|         return el |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     IsValid(t: number): boolean { |  | ||||||
|         return Math.round(t) == t && t >= this.min && t <= this.max |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -62,8 +62,13 @@ | ||||||
|     state.newFeatures.features.data.push(feature) |     state.newFeatures.features.data.push(feature) | ||||||
|     state.newFeatures.features.ping() |     state.newFeatures.features.ping() | ||||||
|     state.selectedElement?.setData(feature) |     state.selectedElement?.setData(feature) | ||||||
|  |     if(state.featureProperties.trackFeature){ | ||||||
|  |       state.featureProperties.trackFeature(feature) | ||||||
|  |     } | ||||||
|     comment.setData("") |     comment.setData("") | ||||||
|     created = true |     created = true | ||||||
|  |     state.selectedElement.setData(feature) | ||||||
|  |     state.selectedLayer.setData(state.layerState.filteredLayers.get("note")) | ||||||
|   } |   } | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -16,6 +16,7 @@ import { MenuState } from "../Models/MenuState"; | ||||||
| import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader"; | import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader"; | ||||||
| import { RasterLayerPolygon } from "../Models/RasterLayers"; | import { RasterLayerPolygon } from "../Models/RasterLayers"; | ||||||
| import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager"; | import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager"; | ||||||
|  | import { OsmTags } from "../Models/OsmFeature"; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * The state needed to render a special Visualisation. |  * The state needed to render a special Visualisation. | ||||||
|  | @ -26,7 +27,7 @@ export interface SpecialVisualizationState { | ||||||
|   readonly featureSwitches: FeatureSwitchState; |   readonly featureSwitches: FeatureSwitchState; | ||||||
| 
 | 
 | ||||||
|   readonly layerState: LayerState; |   readonly layerState: LayerState; | ||||||
|   readonly featureProperties: { getStore(id: string): UIEventSource<Record<string, string>> }; |   readonly featureProperties: { getStore(id: string): UIEventSource<Record<string, string>>, trackFeature?(feature: { properties: OsmTags }) }; | ||||||
| 
 | 
 | ||||||
|   readonly indexedFeatures: IndexedFeatureSource; |   readonly indexedFeatures: IndexedFeatureSource; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,78 +1,70 @@ | ||||||
| import Combine from "./Base/Combine" | import Combine from "./Base/Combine"; | ||||||
| import { FixedUiElement } from "./Base/FixedUiElement" | import { FixedUiElement } from "./Base/FixedUiElement"; | ||||||
| import BaseUIElement from "./BaseUIElement" | import BaseUIElement from "./BaseUIElement"; | ||||||
| import Title from "./Base/Title" | import Title from "./Base/Title"; | ||||||
| import Table from "./Base/Table" | import Table from "./Base/Table"; | ||||||
| import { | import { RenderingSpecification, SpecialVisualization, SpecialVisualizationState } from "./SpecialVisualization"; | ||||||
|     RenderingSpecification, | import { HistogramViz } from "./Popup/HistogramViz"; | ||||||
|     SpecialVisualization, | import { MinimapViz } from "./Popup/MinimapViz"; | ||||||
|     SpecialVisualizationState, | import { ShareLinkViz } from "./Popup/ShareLinkViz"; | ||||||
| } from "./SpecialVisualization" | import { UploadToOsmViz } from "./Popup/UploadToOsmViz"; | ||||||
| import { HistogramViz } from "./Popup/HistogramViz" | import { MultiApplyViz } from "./Popup/MultiApplyViz"; | ||||||
| import { MinimapViz } from "./Popup/MinimapViz" | import { AddNoteCommentViz } from "./Popup/AddNoteCommentViz"; | ||||||
| import { ShareLinkViz } from "./Popup/ShareLinkViz" | import { PlantNetDetectionViz } from "./Popup/PlantNetDetectionViz"; | ||||||
| import { UploadToOsmViz } from "./Popup/UploadToOsmViz" | import TagApplyButton from "./Popup/TagApplyButton"; | ||||||
| import { MultiApplyViz } from "./Popup/MultiApplyViz" | import { CloseNoteButton } from "./Popup/CloseNoteButton"; | ||||||
| import { AddNoteCommentViz } from "./Popup/AddNoteCommentViz" | import { MapillaryLinkVis } from "./Popup/MapillaryLinkVis"; | ||||||
| import { PlantNetDetectionViz } from "./Popup/PlantNetDetectionViz" | import { Store, Stores, UIEventSource } from "../Logic/UIEventSource"; | ||||||
| import TagApplyButton from "./Popup/TagApplyButton" | import AllTagsPanel from "./Popup/AllTagsPanel.svelte"; | ||||||
| import { CloseNoteButton } from "./Popup/CloseNoteButton" | import AllImageProviders from "../Logic/ImageProviders/AllImageProviders"; | ||||||
| import { MapillaryLinkVis } from "./Popup/MapillaryLinkVis" | import { ImageCarousel } from "./Image/ImageCarousel"; | ||||||
| import { Store, Stores, UIEventSource } from "../Logic/UIEventSource" | import { VariableUiElement } from "./Base/VariableUIElement"; | ||||||
| import AllTagsPanel from "./Popup/AllTagsPanel.svelte" | import { Utils } from "../Utils"; | ||||||
| import AllImageProviders from "../Logic/ImageProviders/AllImageProviders" | import Wikidata, { WikidataResponse } from "../Logic/Web/Wikidata"; | ||||||
| import { ImageCarousel } from "./Image/ImageCarousel" | import { Translation } from "./i18n/Translation"; | ||||||
| import { ImageUploadFlow } from "./Image/ImageUploadFlow" | import Translations from "./i18n/Translations"; | ||||||
| import { VariableUiElement } from "./Base/VariableUIElement" | import ReviewForm from "./Reviews/ReviewForm"; | ||||||
| import { Utils } from "../Utils" | import ReviewElement from "./Reviews/ReviewElement"; | ||||||
| import Wikidata, { WikidataResponse } from "../Logic/Web/Wikidata" | import OpeningHoursVisualization from "./OpeningHours/OpeningHoursVisualization"; | ||||||
| import { Translation } from "./i18n/Translation" | import LiveQueryHandler from "../Logic/Web/LiveQueryHandler"; | ||||||
| import Translations from "./i18n/Translations" | import { SubtleButton } from "./Base/SubtleButton"; | ||||||
| import ReviewForm from "./Reviews/ReviewForm" | import Svg from "../Svg"; | ||||||
| import ReviewElement from "./Reviews/ReviewElement" | import NoteCommentElement from "./Popup/NoteCommentElement"; | ||||||
| import OpeningHoursVisualization from "./OpeningHours/OpeningHoursVisualization" | import { SubstitutedTranslation } from "./SubstitutedTranslation"; | ||||||
| import LiveQueryHandler from "../Logic/Web/LiveQueryHandler" | import List from "./Base/List"; | ||||||
| import { SubtleButton } from "./Base/SubtleButton" | import StatisticsPanel from "./BigComponents/StatisticsPanel"; | ||||||
| import Svg from "../Svg" | import AutoApplyButton from "./Popup/AutoApplyButton"; | ||||||
| import NoteCommentElement from "./Popup/NoteCommentElement" | import { LanguageElement } from "./Popup/LanguageElement"; | ||||||
| import FileSelectorButton from "./Input/FileSelectorButton" | import FeatureReviews from "../Logic/Web/MangroveReviews"; | ||||||
| import { LoginToggle } from "./Popup/LoginButton" | import Maproulette from "../Logic/Maproulette"; | ||||||
| import Toggle from "./Input/Toggle" | import SvelteUIElement from "./Base/SvelteUIElement"; | ||||||
| import { SubstitutedTranslation } from "./SubstitutedTranslation" | import { BBoxFeatureSourceForLayer } from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource"; | ||||||
| import List from "./Base/List" | import QuestionViz from "./Popup/QuestionViz"; | ||||||
| import StatisticsPanel from "./BigComponents/StatisticsPanel" | import { Feature, Point } from "geojson"; | ||||||
| import AutoApplyButton from "./Popup/AutoApplyButton" | import { GeoOperations } from "../Logic/GeoOperations"; | ||||||
| import { LanguageElement } from "./Popup/LanguageElement" | import CreateNewNote from "./Popup/CreateNewNote.svelte"; | ||||||
| import FeatureReviews from "../Logic/Web/MangroveReviews" | import AddNewPoint from "./Popup/AddNewPoint/AddNewPoint.svelte"; | ||||||
| import Maproulette from "../Logic/Maproulette" | import UserProfile from "./BigComponents/UserProfile.svelte"; | ||||||
| import SvelteUIElement from "./Base/SvelteUIElement" | import LanguagePicker from "./LanguagePicker"; | ||||||
| import { BBoxFeatureSourceForLayer } from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource" | import Link from "./Base/Link"; | ||||||
| import QuestionViz from "./Popup/QuestionViz" | import LayerConfig from "../Models/ThemeConfig/LayerConfig"; | ||||||
| import { Feature, Point } from "geojson" | import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig"; | ||||||
| import { GeoOperations } from "../Logic/GeoOperations" | import { OsmTags, WayId } from "../Models/OsmFeature"; | ||||||
| import CreateNewNote from "./Popup/CreateNewNote.svelte" | import MoveWizard from "./Popup/MoveWizard"; | ||||||
| import AddNewPoint from "./Popup/AddNewPoint/AddNewPoint.svelte" | import SplitRoadWizard from "./Popup/SplitRoadWizard"; | ||||||
| import UserProfile from "./BigComponents/UserProfile.svelte" | import { ExportAsGpxViz } from "./Popup/ExportAsGpxViz"; | ||||||
| import LanguagePicker from "./LanguagePicker" | import WikipediaPanel from "./Wikipedia/WikipediaPanel.svelte"; | ||||||
| import Link from "./Base/Link" | import TagRenderingEditable from "./Popup/TagRendering/TagRenderingEditable.svelte"; | ||||||
| import LayerConfig from "../Models/ThemeConfig/LayerConfig" | import { PointImportButtonViz } from "./Popup/ImportButtons/PointImportButtonViz"; | ||||||
| import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig" | import WayImportButtonViz from "./Popup/ImportButtons/WayImportButtonViz"; | ||||||
| import { OsmTags, WayId } from "../Models/OsmFeature" | import ConflateImportButtonViz from "./Popup/ImportButtons/ConflateImportButtonViz"; | ||||||
| import MoveWizard from "./Popup/MoveWizard" | import DeleteWizard from "./Popup/DeleteFlow/DeleteWizard.svelte"; | ||||||
| import SplitRoadWizard from "./Popup/SplitRoadWizard" | import { OpenJosm } from "./BigComponents/OpenJosm"; | ||||||
| import { ExportAsGpxViz } from "./Popup/ExportAsGpxViz" | import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte"; | ||||||
| import WikipediaPanel from "./Wikipedia/WikipediaPanel.svelte" | import FediverseValidator from "./InputElement/Validators/FediverseValidator"; | ||||||
| import TagRenderingEditable from "./Popup/TagRendering/TagRenderingEditable.svelte" | import SendEmail from "./Popup/SendEmail.svelte"; | ||||||
| import { PointImportButtonViz } from "./Popup/ImportButtons/PointImportButtonViz" | import NearbyImages from "./Popup/NearbyImages.svelte"; | ||||||
| import WayImportButtonViz from "./Popup/ImportButtons/WayImportButtonViz" | import NearbyImagesCollapsed from "./Popup/NearbyImagesCollapsed.svelte"; | ||||||
| import ConflateImportButtonViz from "./Popup/ImportButtons/ConflateImportButtonViz" |  | ||||||
| import DeleteWizard from "./Popup/DeleteFlow/DeleteWizard.svelte" |  | ||||||
| import { OpenJosm } from "./BigComponents/OpenJosm" |  | ||||||
| import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte" |  | ||||||
| import FediverseValidator from "./InputElement/Validators/FediverseValidator" |  | ||||||
| import SendEmail from "./Popup/SendEmail.svelte" |  | ||||||
| import NearbyImages from "./Popup/NearbyImages.svelte" |  | ||||||
| import NearbyImagesCollapsed from "./Popup/NearbyImagesCollapsed.svelte" |  | ||||||
| import UploadImage from "./Image/UploadImage.svelte"; | import UploadImage from "./Image/UploadImage.svelte"; | ||||||
| 
 | 
 | ||||||
| class NearbyImageVis implements SpecialVisualization { | class NearbyImageVis implements SpecialVisualization { | ||||||
|  | @ -272,6 +264,7 @@ export default class SpecialVisualizations { | ||||||
|                     SpecialVisualizations.specialVisualizations |                     SpecialVisualizations.specialVisualizations | ||||||
|                         .map((sp) => sp.funcName + "()") |                         .map((sp) => sp.funcName + "()") | ||||||
|                         .join(", ") |                         .join(", ") | ||||||
|  | 
 | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -628,7 +621,6 @@ export default class SpecialVisualizations { | ||||||
|                     return new SvelteUIElement(UploadImage, { |                     return new SvelteUIElement(UploadImage, { | ||||||
|                         state,tags, labelText: args[1], image: args[0] |                         state,tags, labelText: args[1], image: args[0] | ||||||
|                     }) |                     }) | ||||||
|                    // return new ImageUploadFlow(tags, state, args[0], args[1])
 |  | ||||||
|                 }, |                 }, | ||||||
|             }, |             }, | ||||||
|             { |             { | ||||||
|  | @ -867,43 +859,11 @@ export default class SpecialVisualizations { | ||||||
|                     }, |                     }, | ||||||
|                 ], |                 ], | ||||||
|                 constr: (state, tags, args) => { |                 constr: (state, tags, args) => { | ||||||
|                     const isUploading = new UIEventSource(false) |  | ||||||
|                     const t = Translations.t.notes |  | ||||||
|                     const id = tags.data[args[0] ?? "id"] |                     const id = tags.data[args[0] ?? "id"] | ||||||
| 
 |                     tags = state.featureProperties.getStore(id) | ||||||
|                     const uploader = new ImgurUploader(async (url) => { |                     console.log("Id is", id) | ||||||
|                         isUploading.setData(false) |                     return new SvelteUIElement(UploadImage, {state, tags}) | ||||||
|                         await state.osmConnection.addCommentToNote(id, url) |                     } | ||||||
|                         NoteCommentElement.addCommentTo(url, tags, state) |  | ||||||
|                     }) |  | ||||||
| 
 |  | ||||||
|                     const label = new Combine([ |  | ||||||
|                         Svg.camera_plus_svg().SetClass("block w-12 h-12 p-1 text-4xl "), |  | ||||||
|                         Translations.t.image.addPicture, |  | ||||||
|                     ]).SetClass( |  | ||||||
|                         "p-2 border-4 border-black rounded-full font-bold h-full align-center 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) |  | ||||||
|                     }) |  | ||||||
|                     const ti = Translations.t.image |  | ||||||
|                     const uploadPanel = new Combine([ |  | ||||||
|                         fileSelector, |  | ||||||
|                         ti.respectPrivacy.SetClass("text-sm"), |  | ||||||
|                     ]).SetClass("flex flex-col") |  | ||||||
|                     return new LoginToggle( |  | ||||||
|                         new Toggle( |  | ||||||
|                             Translations.t.image.uploadingPicture.SetClass("alert"), |  | ||||||
|                             uploadPanel, |  | ||||||
|                             isUploading |  | ||||||
|                         ), |  | ||||||
|                         t.loginToAddPicture, |  | ||||||
|                         state |  | ||||||
|                     ) |  | ||||||
|                 }, |  | ||||||
|             }, |             }, | ||||||
|             { |             { | ||||||
|                 funcName: "title", |                 funcName: "title", | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue