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,23 +59,24 @@ 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 | ||||||
|           ) |       ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -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" }; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,62 +1,63 @@ | ||||||
| // @ts-ignore
 | // @ts-ignore
 | ||||||
| import { osmAuth } from "osm-auth" | import { osmAuth } from "osm-auth"; | ||||||
| import { Store, Stores, UIEventSource } from "../UIEventSource" | import { Store, Stores, UIEventSource } from "../UIEventSource"; | ||||||
| import { OsmPreferences } from "./OsmPreferences" | import { OsmPreferences } from "./OsmPreferences"; | ||||||
| import { Utils } from "../../Utils" | import { Utils } from "../../Utils"; | ||||||
| import { LocalStorageSource } from "../Web/LocalStorageSource" | import { LocalStorageSource } from "../Web/LocalStorageSource"; | ||||||
| import * as config from "../../../package.json" | import * as config from "../../../package.json"; | ||||||
|  | 
 | ||||||
| export default class UserDetails { | export default class UserDetails { | ||||||
|     public loggedIn = false |   public loggedIn = false; | ||||||
|     public name = "Not logged in" |   public name = "Not logged in"; | ||||||
|     public uid: number |   public uid: number; | ||||||
|     public csCount = 0 |   public csCount = 0; | ||||||
|     public img?: string |   public img?: string; | ||||||
|     public unreadMessages = 0 |   public unreadMessages = 0; | ||||||
|     public totalMessages: number = 0 |   public totalMessages: number = 0; | ||||||
|     public home: { lon: number; lat: number } |   public home: { lon: number; lat: number }; | ||||||
|     public backend: string |   public backend: string; | ||||||
|     public account_created: string |   public account_created: string; | ||||||
|     public tracesCount: number = 0 |   public tracesCount: number = 0; | ||||||
|     public description: string |   public description: string; | ||||||
| 
 | 
 | ||||||
|   constructor(backend: string) { |   constructor(backend: string) { | ||||||
|         this.backend = backend |     this.backend = backend; | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export interface AuthConfig { | export interface AuthConfig { | ||||||
|     "#"?: string // optional comment
 |   "#"?: string; // optional comment
 | ||||||
|     oauth_client_id: string |   oauth_client_id: string; | ||||||
|     oauth_secret: string |   oauth_secret: string; | ||||||
|     url: string |   url: string; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export type OsmServiceState = "online" | "readonly" | "offline" | "unknown" | "unreachable" | export type OsmServiceState = "online" | "readonly" | "offline" | "unknown" | "unreachable" | ||||||
| 
 | 
 | ||||||
| export class OsmConnection { | export class OsmConnection { | ||||||
|   public static readonly oauth_configs: Record<string, AuthConfig> = |   public static readonly oauth_configs: Record<string, AuthConfig> = | ||||||
|         config.config.oauth_credentials |     config.config.oauth_credentials; | ||||||
|     public auth |   public auth; | ||||||
|     public userDetails: UIEventSource<UserDetails> |   public userDetails: UIEventSource<UserDetails>; | ||||||
|     public isLoggedIn: Store<boolean> |   public isLoggedIn: Store<boolean>; | ||||||
|   public gpxServiceIsOnline: UIEventSource<OsmServiceState> = new UIEventSource<OsmServiceState>( |   public gpxServiceIsOnline: UIEventSource<OsmServiceState> = new UIEventSource<OsmServiceState>( | ||||||
|     "unknown" |     "unknown" | ||||||
|     ) |   ); | ||||||
|   public apiIsOnline: UIEventSource<OsmServiceState> = new UIEventSource<OsmServiceState>( |   public apiIsOnline: UIEventSource<OsmServiceState> = new UIEventSource<OsmServiceState>( | ||||||
|     "unknown" |     "unknown" | ||||||
|     ) |   ); | ||||||
| 
 | 
 | ||||||
|   public loadingStatus = new UIEventSource<"not-attempted" | "loading" | "error" | "logged-in">( |   public loadingStatus = new UIEventSource<"not-attempted" | "loading" | "error" | "logged-in">( | ||||||
|     "not-attempted" |     "not-attempted" | ||||||
|     ) |   ); | ||||||
|     public preferencesHandler: OsmPreferences |   public preferencesHandler: OsmPreferences; | ||||||
|     public readonly _oauth_config: AuthConfig |   public readonly _oauth_config: AuthConfig; | ||||||
|     private readonly _dryRun: Store<boolean> |   private readonly _dryRun: Store<boolean>; | ||||||
|     private fakeUser: boolean |   private fakeUser: boolean; | ||||||
|     private _onLoggedIn: ((userDetails: UserDetails) => void)[] = [] |   private _onLoggedIn: ((userDetails: UserDetails) => void)[] = []; | ||||||
|     private readonly _iframeMode: Boolean | boolean |   private readonly _iframeMode: Boolean | boolean; | ||||||
|     private readonly _singlePage: boolean |   private readonly _singlePage: boolean; | ||||||
|     private isChecking = false |   private isChecking = false; | ||||||
| 
 | 
 | ||||||
|   constructor(options?: { |   constructor(options?: { | ||||||
|     dryRun?: Store<boolean> |     dryRun?: Store<boolean> | ||||||
|  | @ -67,83 +68,83 @@ export class OsmConnection { | ||||||
|     osmConfiguration?: "osm" | "osm-test" |     osmConfiguration?: "osm" | "osm-test" | ||||||
|     attemptLogin?: true | boolean |     attemptLogin?: true | boolean | ||||||
|   }) { |   }) { | ||||||
|         options = options ?? {} |     options = options ?? {}; | ||||||
|         this.fakeUser = options.fakeUser ?? false |     this.fakeUser = options.fakeUser ?? false; | ||||||
|         this._singlePage = options.singlePage ?? true |     this._singlePage = options.singlePage ?? true; | ||||||
|     this._oauth_config = |     this._oauth_config = | ||||||
|       OsmConnection.oauth_configs[options.osmConfiguration ?? "osm"] ?? |       OsmConnection.oauth_configs[options.osmConfiguration ?? "osm"] ?? | ||||||
|             OsmConnection.oauth_configs.osm |       OsmConnection.oauth_configs.osm; | ||||||
|         console.debug("Using backend", this._oauth_config.url) |     console.debug("Using backend", this._oauth_config.url); | ||||||
|         this._iframeMode = Utils.runningFromConsole ? false : window !== window.top |     this._iframeMode = Utils.runningFromConsole ? false : window !== window.top; | ||||||
| 
 | 
 | ||||||
|     // Check if there are settings available in environment variables, and if so, use those
 |     // Check if there are settings available in environment variables, and if so, use those
 | ||||||
|     if ( |     if ( | ||||||
|       import.meta.env.VITE_OSM_OAUTH_CLIENT_ID !== undefined && |       import.meta.env.VITE_OSM_OAUTH_CLIENT_ID !== undefined && | ||||||
|       import.meta.env.VITE_OSM_OAUTH_SECRET !== undefined |       import.meta.env.VITE_OSM_OAUTH_SECRET !== undefined | ||||||
|     ) { |     ) { | ||||||
|             console.debug("Using environment variables for oauth config") |       console.debug("Using environment variables for oauth config"); | ||||||
|       this._oauth_config = { |       this._oauth_config = { | ||||||
|         oauth_client_id: import.meta.env.VITE_OSM_OAUTH_CLIENT_ID, |         oauth_client_id: import.meta.env.VITE_OSM_OAUTH_CLIENT_ID, | ||||||
|         oauth_secret: import.meta.env.VITE_OSM_OAUTH_SECRET, |         oauth_secret: import.meta.env.VITE_OSM_OAUTH_SECRET, | ||||||
|                 url: "https://api.openstreetmap.org", |         url: "https://api.openstreetmap.org" | ||||||
|             } |       }; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     this.userDetails = new UIEventSource<UserDetails>( |     this.userDetails = new UIEventSource<UserDetails>( | ||||||
|       new UserDetails(this._oauth_config.url), |       new UserDetails(this._oauth_config.url), | ||||||
|       "userDetails" |       "userDetails" | ||||||
|         ) |     ); | ||||||
|     if (options.fakeUser) { |     if (options.fakeUser) { | ||||||
|             const ud = this.userDetails.data |       const ud = this.userDetails.data; | ||||||
|             ud.csCount = 5678 |       ud.csCount = 5678; | ||||||
|             ud.loggedIn = true |       ud.loggedIn = true; | ||||||
|             ud.unreadMessages = 0 |       ud.unreadMessages = 0; | ||||||
|             ud.name = "Fake user" |       ud.name = "Fake user"; | ||||||
|             ud.totalMessages = 42 |       ud.totalMessages = 42; | ||||||
|     } |     } | ||||||
|         const self = this |     const self = this; | ||||||
|         this.UpdateCapabilities() |     this.UpdateCapabilities(); | ||||||
|     this.isLoggedIn = this.userDetails.map( |     this.isLoggedIn = this.userDetails.map( | ||||||
|       (user) => |       (user) => | ||||||
|         user.loggedIn && |         user.loggedIn && | ||||||
|         (self.apiIsOnline.data === "unknown" || self.apiIsOnline.data === "online"), |         (self.apiIsOnline.data === "unknown" || self.apiIsOnline.data === "online"), | ||||||
|       [this.apiIsOnline] |       [this.apiIsOnline] | ||||||
|         ) |     ); | ||||||
|     this.isLoggedIn.addCallback((isLoggedIn) => { |     this.isLoggedIn.addCallback((isLoggedIn) => { | ||||||
|       if (self.userDetails.data.loggedIn == false && isLoggedIn == true) { |       if (self.userDetails.data.loggedIn == false && isLoggedIn == true) { | ||||||
|         // We have an inconsistency: the userdetails say we _didn't_ log in, but this actor says we do
 |         // We have an inconsistency: the userdetails say we _didn't_ log in, but this actor says we do
 | ||||||
|         // This means someone attempted to toggle this; so we attempt to login!
 |         // This means someone attempted to toggle this; so we attempt to login!
 | ||||||
|                 self.AttemptLogin() |         self.AttemptLogin(); | ||||||
|       } |       } | ||||||
|         }) |     }); | ||||||
| 
 | 
 | ||||||
|         this._dryRun = options.dryRun ?? new UIEventSource<boolean>(false) |     this._dryRun = options.dryRun ?? new UIEventSource<boolean>(false); | ||||||
| 
 | 
 | ||||||
|         this.updateAuthObject() |     this.updateAuthObject(); | ||||||
| 
 | 
 | ||||||
|     this.preferencesHandler = new OsmPreferences( |     this.preferencesHandler = new OsmPreferences( | ||||||
|       this.auth, |       this.auth, | ||||||
|       <any /*This is needed to make the tests work*/>this |       <any /*This is needed to make the tests work*/>this | ||||||
|         ) |     ); | ||||||
| 
 | 
 | ||||||
|     if (options.oauth_token?.data !== undefined) { |     if (options.oauth_token?.data !== undefined) { | ||||||
|             console.log(options.oauth_token.data) |       console.log(options.oauth_token.data); | ||||||
|             const self = this |       const self = this; | ||||||
|       this.auth.bootstrapToken( |       this.auth.bootstrapToken( | ||||||
|         options.oauth_token.data, |         options.oauth_token.data, | ||||||
|         (x) => { |         (x) => { | ||||||
|                     console.log("Called back: ", x) |           console.log("Called back: ", x); | ||||||
|                     self.AttemptLogin() |           self.AttemptLogin(); | ||||||
|         }, |         }, | ||||||
|         this.auth |         this.auth | ||||||
|             ) |       ); | ||||||
| 
 | 
 | ||||||
|             options.oauth_token.setData(undefined) |       options.oauth_token.setData(undefined); | ||||||
|     } |     } | ||||||
|     if (this.auth.authenticated() && options.attemptLogin !== false) { |     if (this.auth.authenticated() && options.attemptLogin !== false) { | ||||||
|             this.AttemptLogin() // Also updates the user badge
 |       this.AttemptLogin(); // Also updates the user badge
 | ||||||
|     } else { |     } else { | ||||||
|             console.log("Not authenticated") |       console.log("Not authenticated"); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | @ -155,25 +156,25 @@ export class OsmConnection { | ||||||
|       prefix?: string |       prefix?: string | ||||||
|     } |     } | ||||||
|   ): UIEventSource<string> { |   ): UIEventSource<string> { | ||||||
|         return this.preferencesHandler.GetPreference(key, defaultValue, options) |     return this.preferencesHandler.GetPreference(key, defaultValue, options); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   public GetLongPreference(key: string, prefix: string = "mapcomplete-"): UIEventSource<string> { |   public GetLongPreference(key: string, prefix: string = "mapcomplete-"): UIEventSource<string> { | ||||||
|         return this.preferencesHandler.GetLongPreference(key, prefix) |     return this.preferencesHandler.GetLongPreference(key, prefix); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   public OnLoggedIn(action: (userDetails: UserDetails) => void) { |   public OnLoggedIn(action: (userDetails: UserDetails) => void) { | ||||||
|         this._onLoggedIn.push(action) |     this._onLoggedIn.push(action); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   public LogOut() { |   public LogOut() { | ||||||
|         this.auth.logout() |     this.auth.logout(); | ||||||
|         this.userDetails.data.loggedIn = false |     this.userDetails.data.loggedIn = false; | ||||||
|         this.userDetails.data.csCount = 0 |     this.userDetails.data.csCount = 0; | ||||||
|         this.userDetails.data.name = "" |     this.userDetails.data.name = ""; | ||||||
|         this.userDetails.ping() |     this.userDetails.ping(); | ||||||
|         console.log("Logged out") |     console.log("Logged out"); | ||||||
|         this.loadingStatus.setData("not-attempted") |     this.loadingStatus.setData("not-attempted"); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** |   /** | ||||||
|  | @ -182,95 +183,95 @@ export class OsmConnection { | ||||||
|    * new OsmConnection().Backend() // => "https://www.openstreetmap.org"
 |    * new OsmConnection().Backend() // => "https://www.openstreetmap.org"
 | ||||||
|    */ |    */ | ||||||
|   public Backend(): string { |   public Backend(): string { | ||||||
|         return this._oauth_config.url |     return this._oauth_config.url; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   public AttemptLogin() { |   public AttemptLogin() { | ||||||
|         this.UpdateCapabilities() |     this.UpdateCapabilities(); | ||||||
|         this.loadingStatus.setData("loading") |     this.loadingStatus.setData("loading"); | ||||||
|     if (this.fakeUser) { |     if (this.fakeUser) { | ||||||
|             this.loadingStatus.setData("logged-in") |       this.loadingStatus.setData("logged-in"); | ||||||
|             console.log("AttemptLogin called, but ignored as fakeUser is set") |       console.log("AttemptLogin called, but ignored as fakeUser is set"); | ||||||
|             return |       return; | ||||||
|     } |     } | ||||||
|         const self = this |     const self = this; | ||||||
|         console.log("Trying to log in...") |     console.log("Trying to log in..."); | ||||||
|         this.updateAuthObject() |     this.updateAuthObject(); | ||||||
|     LocalStorageSource.Get("location_before_login").setData( |     LocalStorageSource.Get("location_before_login").setData( | ||||||
|       Utils.runningFromConsole ? undefined : window.location.href |       Utils.runningFromConsole ? undefined : window.location.href | ||||||
|         ) |     ); | ||||||
|     this.auth.xhr( |     this.auth.xhr( | ||||||
|       { |       { | ||||||
|         method: "GET", |         method: "GET", | ||||||
|                 path: "/api/0.6/user/details", |         path: "/api/0.6/user/details" | ||||||
|       }, |       }, | ||||||
|       function(err, details) { |       function(err, details) { | ||||||
|         if (err != null) { |         if (err != null) { | ||||||
|                     console.log(err) |           console.log(err); | ||||||
|                     self.loadingStatus.setData("error") |           self.loadingStatus.setData("error"); | ||||||
|           if (err.status == 401) { |           if (err.status == 401) { | ||||||
|                         console.log("Clearing tokens...") |             console.log("Clearing tokens..."); | ||||||
|             // Not authorized - our token probably got revoked
 |             // Not authorized - our token probably got revoked
 | ||||||
|                         self.auth.logout() |             self.auth.logout(); | ||||||
|                         self.LogOut() |             self.LogOut(); | ||||||
|           } |           } | ||||||
|                     return |           return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (details == null) { |         if (details == null) { | ||||||
|                     self.loadingStatus.setData("error") |           self.loadingStatus.setData("error"); | ||||||
|                     return |           return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|                 self.CheckForMessagesContinuously() |         self.CheckForMessagesContinuously(); | ||||||
| 
 | 
 | ||||||
|         // details is an XML DOM of user details
 |         // details is an XML DOM of user details
 | ||||||
|                 let userInfo = details.getElementsByTagName("user")[0] |         let userInfo = details.getElementsByTagName("user")[0]; | ||||||
| 
 | 
 | ||||||
|                 let data = self.userDetails.data |         let data = self.userDetails.data; | ||||||
|                 data.loggedIn = true |         data.loggedIn = true; | ||||||
|                 console.log("Login completed, userinfo is ", userInfo) |         console.log("Login completed, userinfo is ", userInfo); | ||||||
|                 data.name = userInfo.getAttribute("display_name") |         data.name = userInfo.getAttribute("display_name"); | ||||||
|                 data.account_created = userInfo.getAttribute("account_created") |         data.account_created = userInfo.getAttribute("account_created"); | ||||||
|                 data.uid = Number(userInfo.getAttribute("id")) |         data.uid = Number(userInfo.getAttribute("id")); | ||||||
|         data.csCount = Number.parseInt( |         data.csCount = Number.parseInt( | ||||||
|           userInfo.getElementsByTagName("changesets")[0].getAttribute("count") ?? 0 |           userInfo.getElementsByTagName("changesets")[0].getAttribute("count") ?? 0 | ||||||
|                 ) |         ); | ||||||
|         data.tracesCount = Number.parseInt( |         data.tracesCount = Number.parseInt( | ||||||
|           userInfo.getElementsByTagName("traces")[0].getAttribute("count") ?? 0 |           userInfo.getElementsByTagName("traces")[0].getAttribute("count") ?? 0 | ||||||
|                 ) |         ); | ||||||
| 
 | 
 | ||||||
|                 data.img = undefined |         data.img = undefined; | ||||||
|                 const imgEl = userInfo.getElementsByTagName("img") |         const imgEl = userInfo.getElementsByTagName("img"); | ||||||
|         if (imgEl !== undefined && imgEl[0] !== undefined) { |         if (imgEl !== undefined && imgEl[0] !== undefined) { | ||||||
|                     data.img = imgEl[0].getAttribute("href") |           data.img = imgEl[0].getAttribute("href"); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|                 const description = userInfo.getElementsByTagName("description") |         const description = userInfo.getElementsByTagName("description"); | ||||||
|         if (description !== undefined && description[0] !== undefined) { |         if (description !== undefined && description[0] !== undefined) { | ||||||
|                     data.description = description[0]?.innerHTML |           data.description = description[0]?.innerHTML; | ||||||
|         } |         } | ||||||
|                 const homeEl = userInfo.getElementsByTagName("home") |         const homeEl = userInfo.getElementsByTagName("home"); | ||||||
|         if (homeEl !== undefined && homeEl[0] !== undefined) { |         if (homeEl !== undefined && homeEl[0] !== undefined) { | ||||||
|                     const lat = parseFloat(homeEl[0].getAttribute("lat")) |           const lat = parseFloat(homeEl[0].getAttribute("lat")); | ||||||
|                     const lon = parseFloat(homeEl[0].getAttribute("lon")) |           const lon = parseFloat(homeEl[0].getAttribute("lon")); | ||||||
|                     data.home = { lat: lat, lon: lon } |           data.home = { lat: lat, lon: lon }; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|                 self.loadingStatus.setData("logged-in") |         self.loadingStatus.setData("logged-in"); | ||||||
|         const messages = userInfo |         const messages = userInfo | ||||||
|           .getElementsByTagName("messages")[0] |           .getElementsByTagName("messages")[0] | ||||||
|                     .getElementsByTagName("received")[0] |           .getElementsByTagName("received")[0]; | ||||||
|                 data.unreadMessages = parseInt(messages.getAttribute("unread")) |         data.unreadMessages = parseInt(messages.getAttribute("unread")); | ||||||
|                 data.totalMessages = parseInt(messages.getAttribute("count")) |         data.totalMessages = parseInt(messages.getAttribute("count")); | ||||||
| 
 | 
 | ||||||
|                 self.userDetails.ping() |         self.userDetails.ping(); | ||||||
|         for (const action of self._onLoggedIn) { |         for (const action of self._onLoggedIn) { | ||||||
|                     action(self.userDetails.data) |           action(self.userDetails.data); | ||||||
|         } |         } | ||||||
|                 self._onLoggedIn = [] |         self._onLoggedIn = []; | ||||||
|       } |       } | ||||||
|         ) |     ); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** |   /** | ||||||
|  | @ -289,20 +290,20 @@ export class OsmConnection { | ||||||
|         { |         { | ||||||
|           method, |           method, | ||||||
|           options: { |           options: { | ||||||
|                         header, |             header | ||||||
|           }, |           }, | ||||||
|           content, |           content, | ||||||
|                     path: `/api/0.6/${path}`, |           path: `/api/0.6/${path}` | ||||||
|         }, |         }, | ||||||
|         function(err, response) { |         function(err, response) { | ||||||
|           if (err !== null) { |           if (err !== null) { | ||||||
|                         error(err) |             error(err); | ||||||
|           } else { |           } else { | ||||||
|                         ok(response) |             ok(response); | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|             ) |       ); | ||||||
|         }) |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   public async post( |   public async post( | ||||||
|  | @ -310,7 +311,7 @@ export class OsmConnection { | ||||||
|     content?: string, |     content?: string, | ||||||
|     header?: Record<string, string | number> |     header?: Record<string, string | number> | ||||||
|   ): Promise<any> { |   ): Promise<any> { | ||||||
|         return await this.interact(path, "POST", header, content) |     return await this.interact(path, "POST", header, content); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   public async put( |   public async put( | ||||||
|  | @ -318,59 +319,60 @@ export class OsmConnection { | ||||||
|     content?: string, |     content?: string, | ||||||
|     header?: Record<string, string | number> |     header?: Record<string, string | number> | ||||||
|   ): Promise<any> { |   ): Promise<any> { | ||||||
|         return await this.interact(path, "PUT", header, content) |     return await this.interact(path, "PUT", header, content); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   public async get(path: string, header?: Record<string, string | number>): Promise<any> { |   public async get(path: string, header?: Record<string, string | number>): Promise<any> { | ||||||
|         return await this.interact(path, "GET", header) |     return await this.interact(path, "GET", header); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   public closeNote(id: number | string, text?: string): Promise<void> { |   public closeNote(id: number | string, text?: string): Promise<void> { | ||||||
|         let textSuffix = "" |     let textSuffix = ""; | ||||||
|     if ((text ?? "") !== "") { |     if ((text ?? "") !== "") { | ||||||
|             textSuffix = "?text=" + encodeURIComponent(text) |       textSuffix = "?text=" + encodeURIComponent(text); | ||||||
|     } |     } | ||||||
|     if (this._dryRun.data) { |     if (this._dryRun.data) { | ||||||
|             console.warn("Dryrun enabled - not actually closing note ", id, " with text ", text) |       console.warn("Dryrun enabled - not actually closing note ", id, " with text ", text); | ||||||
|       return new Promise((ok) => { |       return new Promise((ok) => { | ||||||
|                 ok() |         ok(); | ||||||
|             }) |       }); | ||||||
|     } |     } | ||||||
|         return this.post(`notes/${id}/close${textSuffix}`) |     return this.post(`notes/${id}/close${textSuffix}`); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   public reopenNote(id: number | string, text?: string): Promise<void> { |   public reopenNote(id: number | string, text?: string): Promise<void> { | ||||||
|     if (this._dryRun.data) { |     if (this._dryRun.data) { | ||||||
|             console.warn("Dryrun enabled - not actually reopening note ", id, " with text ", text) |       console.warn("Dryrun enabled - not actually reopening note ", id, " with text ", text); | ||||||
|       return new Promise((ok) => { |       return new Promise((ok) => { | ||||||
|                 ok() |         ok(); | ||||||
|             }) |       }); | ||||||
|     } |     } | ||||||
|         let textSuffix = "" |     let textSuffix = ""; | ||||||
|     if ((text ?? "") !== "") { |     if ((text ?? "") !== "") { | ||||||
|             textSuffix = "?text=" + encodeURIComponent(text) |       textSuffix = "?text=" + encodeURIComponent(text); | ||||||
|     } |     } | ||||||
|         return this.post(`notes/${id}/reopen${textSuffix}`) |     return this.post(`notes/${id}/reopen${textSuffix}`); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   public async openNote(lat: number, lon: number, text: string): Promise<{ id: number }> { |   public async openNote(lat: number, lon: number, text: string): Promise<{ id: number }> { | ||||||
|     if (this._dryRun.data) { |     if (this._dryRun.data) { | ||||||
|             console.warn("Dryrun enabled - not actually opening note with text ", text) |       console.warn("Dryrun enabled - not actually opening note with text ", text); | ||||||
|       return new Promise<{ id: number }>((ok) => { |       return new Promise<{ id: number }>((ok) => { | ||||||
|         window.setTimeout( |         window.setTimeout( | ||||||
|           () => ok({ id: Math.floor(Math.random() * 1000) }), |           () => ok({ id: Math.floor(Math.random() * 1000) }), | ||||||
|           Math.random() * 5000 |           Math.random() * 5000 | ||||||
|                 ) |         ); | ||||||
|             }) |       }); | ||||||
|     } |     } | ||||||
|         const content = { lat, lon, text } |     // Lat and lon must be strings for the API to accept it
 | ||||||
|         const response = await this.post("notes.json", JSON.stringify(content), { |     const content = `lat=${lat}&lon=${lon}&text=${encodeURIComponent(text)}` | ||||||
|             "Content-Type": "application/json", |     const response = await this.post("notes.json", content, { | ||||||
|         }) |       "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" | ||||||
|         const parsed = JSON.parse(response) |     }); | ||||||
|         const id = parsed.properties |     const parsed = JSON.parse(response); | ||||||
|         console.log("OPENED NOTE", id) |     const id = parsed.properties; | ||||||
|         return id |     console.log("OPENED NOTE", id); | ||||||
|  |     return id; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   public async uploadGpxTrack( |   public async uploadGpxTrack( | ||||||
|  | @ -388,61 +390,61 @@ export class OsmConnection { | ||||||
|     } |     } | ||||||
|   ): Promise<{ id: number }> { |   ): Promise<{ id: number }> { | ||||||
|     if (this._dryRun.data) { |     if (this._dryRun.data) { | ||||||
|             console.warn("Dryrun enabled - not actually uploading GPX ", gpx) |       console.warn("Dryrun enabled - not actually uploading GPX ", gpx); | ||||||
|       return new Promise<{ id: number }>((ok, error) => { |       return new Promise<{ id: number }>((ok, error) => { | ||||||
|         window.setTimeout( |         window.setTimeout( | ||||||
|           () => ok({ id: Math.floor(Math.random() * 1000) }), |           () => ok({ id: Math.floor(Math.random() * 1000) }), | ||||||
|           Math.random() * 5000 |           Math.random() * 5000 | ||||||
|                 ) |         ); | ||||||
|             }) |       }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const contents = { |     const contents = { | ||||||
|       file: gpx, |       file: gpx, | ||||||
|       description: options.description ?? "", |       description: options.description ?? "", | ||||||
|       tags: options.labels?.join(",") ?? "", |       tags: options.labels?.join(",") ?? "", | ||||||
|             visibility: options.visibility, |       visibility: options.visibility | ||||||
|         } |     }; | ||||||
| 
 | 
 | ||||||
|     const extras = { |     const extras = { | ||||||
|       file: |       file: | ||||||
|                 '; filename="' + |         "; filename=\"" + | ||||||
|         (options.filename ?? "gpx_track_mapcomplete_" + new Date().toISOString()) + |         (options.filename ?? "gpx_track_mapcomplete_" + new Date().toISOString()) + | ||||||
|                 '"\r\nContent-Type: application/gpx+xml', |         "\"\r\nContent-Type: application/gpx+xml" | ||||||
|         } |     }; | ||||||
| 
 | 
 | ||||||
|         const boundary = "987654" |     const boundary = "987654"; | ||||||
| 
 | 
 | ||||||
|         let body = "" |     let body = ""; | ||||||
|     for (const key in contents) { |     for (const key in contents) { | ||||||
|             body += "--" + boundary + "\r\n" |       body += "--" + boundary + "\r\n"; | ||||||
|             body += 'Content-Disposition: form-data; name="' + key + '"' |       body += "Content-Disposition: form-data; name=\"" + key + "\""; | ||||||
|       if (extras[key] !== undefined) { |       if (extras[key] !== undefined) { | ||||||
|                 body += extras[key] |         body += extras[key]; | ||||||
|       } |       } | ||||||
|             body += "\r\n\r\n" |       body += "\r\n\r\n"; | ||||||
|             body += contents[key] + "\r\n" |       body += contents[key] + "\r\n"; | ||||||
|     } |     } | ||||||
|         body += "--" + boundary + "--\r\n" |     body += "--" + boundary + "--\r\n"; | ||||||
| 
 | 
 | ||||||
|     const response = await this.post("gpx/create", body, { |     const response = await this.post("gpx/create", body, { | ||||||
|       "Content-Type": "multipart/form-data; boundary=" + boundary, |       "Content-Type": "multipart/form-data; boundary=" + boundary, | ||||||
|             "Content-Length": body.length, |       "Content-Length": body.length | ||||||
|         }) |     }); | ||||||
|         const parsed = JSON.parse(response) |     const parsed = JSON.parse(response); | ||||||
|         console.log("Uploaded GPX track", parsed) |     console.log("Uploaded GPX track", parsed); | ||||||
|         return { id: parsed } |     return { id: parsed }; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   public addCommentToNote(id: number | string, text: string): Promise<void> { |   public addCommentToNote(id: number | string, text: string): Promise<void> { | ||||||
|     if (this._dryRun.data) { |     if (this._dryRun.data) { | ||||||
|             console.warn("Dryrun enabled - not actually adding comment ", text, "to  note ", id) |       console.warn("Dryrun enabled - not actually adding comment ", text, "to  note ", id); | ||||||
|       return new Promise((ok) => { |       return new Promise((ok) => { | ||||||
|                 ok() |         ok(); | ||||||
|             }) |       }); | ||||||
|     } |     } | ||||||
|     if ((text ?? "") === "") { |     if ((text ?? "") === "") { | ||||||
|             throw "Invalid text!" |       throw "Invalid text!"; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return new Promise((ok, error) => { |     return new Promise((ok, error) => { | ||||||
|  | @ -450,38 +452,50 @@ export class OsmConnection { | ||||||
|         { |         { | ||||||
|           method: "POST", |           method: "POST", | ||||||
| 
 | 
 | ||||||
|                     path: `/api/0.6/notes/${id}/comment?text=${encodeURIComponent(text)}`, |           path: `/api/0.6/notes/${id}/comment?text=${encodeURIComponent(text)}` | ||||||
|         }, |         }, | ||||||
|         function(err, _) { |         function(err, _) { | ||||||
|           if (err !== null) { |           if (err !== null) { | ||||||
|                         error(err) |             error(err); | ||||||
|           } else { |           } else { | ||||||
|                         ok() |             ok(); | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|             ) |       ); | ||||||
|         }) |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * To be called by land.html | ||||||
|  |    */ | ||||||
|  |   public finishLogin(callback: (previousURL: string) => void) { | ||||||
|  |     this.auth.authenticate(function() { | ||||||
|  |       // Fully authed at this point
 | ||||||
|  |       console.log("Authentication successful!"); | ||||||
|  |       const previousLocation = LocalStorageSource.Get("location_before_login"); | ||||||
|  |       callback(previousLocation.data); | ||||||
|  |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private updateAuthObject() { |   private updateAuthObject() { | ||||||
|         let pwaStandAloneMode = false |     let pwaStandAloneMode = false; | ||||||
|     try { |     try { | ||||||
|       if (Utils.runningFromConsole) { |       if (Utils.runningFromConsole) { | ||||||
|                 pwaStandAloneMode = true |         pwaStandAloneMode = true; | ||||||
|       } else if ( |       } else if ( | ||||||
|         window.matchMedia("(display-mode: standalone)").matches || |         window.matchMedia("(display-mode: standalone)").matches || | ||||||
|         window.matchMedia("(display-mode: fullscreen)").matches |         window.matchMedia("(display-mode: fullscreen)").matches | ||||||
|       ) { |       ) { | ||||||
|                 pwaStandAloneMode = true |         pwaStandAloneMode = true; | ||||||
|       } |       } | ||||||
|     } catch (e) { |     } catch (e) { | ||||||
|       console.warn( |       console.warn( | ||||||
|         "Detecting standalone mode failed", |         "Detecting standalone mode failed", | ||||||
|         e, |         e, | ||||||
|         ". Assuming in browser and not worrying furhter" |         ". Assuming in browser and not worrying furhter" | ||||||
|             ) |       ); | ||||||
|     } |     } | ||||||
|         const standalone = this._iframeMode || pwaStandAloneMode || !this._singlePage |     const standalone = this._iframeMode || pwaStandAloneMode || !this._singlePage; | ||||||
| 
 | 
 | ||||||
|     // In standalone mode, we DON'T use single page login, as 'redirecting' opens a new window anyway...
 |     // In standalone mode, we DON'T use single page login, as 'redirecting' opens a new window anyway...
 | ||||||
|     // Same for an iframe...
 |     // Same for an iframe...
 | ||||||
|  | @ -494,58 +508,46 @@ export class OsmConnection { | ||||||
|         ? "https://mapcomplete.org/land.html" |         ? "https://mapcomplete.org/land.html" | ||||||
|         : window.location.protocol + "//" + window.location.host + "/land.html", |         : window.location.protocol + "//" + window.location.host + "/land.html", | ||||||
|       singlepage: !standalone, |       singlepage: !standalone, | ||||||
|             auto: true, |       auto: true | ||||||
|         }) |     }); | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * To be called by land.html |  | ||||||
|      */ |  | ||||||
|     public finishLogin(callback: (previousURL: string) => void) { |  | ||||||
|         this.auth.authenticate(function () { |  | ||||||
|             // Fully authed at this point
 |  | ||||||
|             console.log("Authentication successful!") |  | ||||||
|             const previousLocation = LocalStorageSource.Get("location_before_login") |  | ||||||
|             callback(previousLocation.data) |  | ||||||
|         }) |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private CheckForMessagesContinuously() { |   private CheckForMessagesContinuously() { | ||||||
|         const self = this |     const self = this; | ||||||
|     if (this.isChecking) { |     if (this.isChecking) { | ||||||
|             return |       return; | ||||||
|     } |     } | ||||||
|         this.isChecking = true |     this.isChecking = true; | ||||||
|     Stores.Chronic(5 * 60 * 1000).addCallback((_) => { |     Stores.Chronic(5 * 60 * 1000).addCallback((_) => { | ||||||
|       if (self.isLoggedIn.data) { |       if (self.isLoggedIn.data) { | ||||||
|                 console.log("Checking for messages") |         console.log("Checking for messages"); | ||||||
|                 self.AttemptLogin() |         self.AttemptLogin(); | ||||||
|       } |       } | ||||||
|         }) |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private UpdateCapabilities(): void { |   private UpdateCapabilities(): void { | ||||||
|         const self = this |     const self = this; | ||||||
|     this.FetchCapabilities().then(({ api, gpx }) => { |     this.FetchCapabilities().then(({ api, gpx }) => { | ||||||
|             self.apiIsOnline.setData(api) |       self.apiIsOnline.setData(api); | ||||||
|             self.gpxServiceIsOnline.setData(gpx) |       self.gpxServiceIsOnline.setData(gpx); | ||||||
|         }) |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private async FetchCapabilities(): Promise<{ api: OsmServiceState; gpx: OsmServiceState }> { |   private async FetchCapabilities(): Promise<{ api: OsmServiceState; gpx: OsmServiceState }> { | ||||||
|     if (Utils.runningFromConsole) { |     if (Utils.runningFromConsole) { | ||||||
|             return { api: "online", gpx: "online" } |       return { api: "online", gpx: "online" }; | ||||||
|     } |     } | ||||||
|         const result = await Utils.downloadAdvanced(this.Backend() + "/api/0.6/capabilities") |     const result = await Utils.downloadAdvanced(this.Backend() + "/api/0.6/capabilities"); | ||||||
|     if (result["content"] === undefined) { |     if (result["content"] === undefined) { | ||||||
|             console.log("Something went wrong:", result) |       console.log("Something went wrong:", result); | ||||||
|             return { api: "unreachable", gpx: "unreachable" } |       return { api: "unreachable", gpx: "unreachable" }; | ||||||
|     } |     } | ||||||
|         const xmlRaw = result["content"] |     const xmlRaw = result["content"]; | ||||||
|         const parsed = new DOMParser().parseFromString(xmlRaw, "text/xml") |     const parsed = new DOMParser().parseFromString(xmlRaw, "text/xml"); | ||||||
|         const statusEl = parsed.getElementsByTagName("status")[0] |     const statusEl = parsed.getElementsByTagName("status")[0]; | ||||||
|         const api = <OsmServiceState>statusEl.getAttribute("api") |     const api = <OsmServiceState>statusEl.getAttribute("api"); | ||||||
|         const gpx = <OsmServiceState>statusEl.getAttribute("gpx") |     const gpx = <OsmServiceState>statusEl.getAttribute("gpx"); | ||||||
|         return { api, gpx } |     return { api, gpx }; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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