forked from MapComplete/MapComplete
		
	Refactoring of Attribute Images, fix more or less decent slideshow. Turns out a few lines of css can get us there!
This commit is contained in:
		
							parent
							
								
									6ba4cb18c6
								
							
						
					
					
						commit
						1609c63f3b
					
				
					 20 changed files with 363 additions and 361 deletions
				
			
		
							
								
								
									
										29
									
								
								Logic/Web/ImageAttributionSource.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								Logic/Web/ImageAttributionSource.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,29 @@ | |||
| import {UIEventSource} from "../UIEventSource"; | ||||
| import {LicenseInfo} from "./Wikimedia"; | ||||
| import BaseUIElement from "../../UI/BaseUIElement"; | ||||
| 
 | ||||
| 
 | ||||
| export default abstract class ImageAttributionSource { | ||||
| 
 | ||||
| 
 | ||||
|     private _cache = new Map<string, UIEventSource<LicenseInfo>>() | ||||
| 
 | ||||
|     GetAttributionFor(url: string): UIEventSource<LicenseInfo> { | ||||
|         const cached = this._cache.get(url); | ||||
|         if (cached !== undefined) { | ||||
|             return cached; | ||||
|         } | ||||
|         const src = this.DownloadAttribution(url) | ||||
|         this._cache.set(url, src) | ||||
|         return src; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|      | ||||
|     public abstract SourceIcon(backlinkSource?: string) : BaseUIElement; | ||||
|     protected abstract DownloadAttribution(url: string): UIEventSource<LicenseInfo>; | ||||
|     public PrepareUrl(value: string): string{ | ||||
|         return value; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -1,16 +1,24 @@ | |||
| // @ts-ignore
 | ||||
| import $ from "jquery" | ||||
| import {LicenseInfo} from "./Wikimedia"; | ||||
| import ImageAttributionSource from "./ImageAttributionSource"; | ||||
| import {UIEventSource} from "../UIEventSource"; | ||||
| import BaseUIElement from "../../UI/BaseUIElement"; | ||||
| 
 | ||||
| export class Imgur { | ||||
| export class Imgur extends ImageAttributionSource { | ||||
|      | ||||
|     public static readonly singleton = new Imgur();  | ||||
| 
 | ||||
|     private constructor() { | ||||
|         super(); | ||||
|     } | ||||
| 
 | ||||
|     static uploadMultiple( | ||||
|         title: string, description: string, blobs: FileList, | ||||
|         handleSuccessfullUpload: ((imageURL: string) => void), | ||||
|         allDone: (() => void), | ||||
|         onFail: ((reason: string) => void), | ||||
|         offset:number = 0) { | ||||
|         offset: number = 0) { | ||||
| 
 | ||||
|         if (blobs.length == offset) { | ||||
|             allDone(); | ||||
|  | @ -34,54 +42,9 @@ export class Imgur { | |||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     static getDescriptionOfImage(url: string, | ||||
|                        handleDescription: ((license: LicenseInfo) => void)) { | ||||
| 
 | ||||
|         const hash = url.substr("https://i.imgur.com/".length).split(".jpg")[0]; | ||||
|          | ||||
|         const apiUrl = 'https://api.imgur.com/3/image/'+hash; | ||||
|         const apiKey = '7070e7167f0a25a'; | ||||
| 
 | ||||
|         const settings = { | ||||
|             async: true, | ||||
|             crossDomain: true, | ||||
|             processData: false, | ||||
|             contentType: false, | ||||
|             type: 'GET', | ||||
|             url: apiUrl, | ||||
|             headers: { | ||||
|                 Authorization: 'Client-ID ' + apiKey, | ||||
|                 Accept: 'application/json', | ||||
|             }, | ||||
|         }; | ||||
|         // @ts-ignore
 | ||||
|         $.ajax(settings).done(function (response) { | ||||
|             const descr: string = response.data.description ?? ""; | ||||
|             const data: any = {}; | ||||
|             for (const tag of descr.split("\n")) { | ||||
|                 const kv = tag.split(":"); | ||||
|                 const k = kv[0]; | ||||
|                 const v = kv[1].replace("\r", ""); | ||||
|                 data[k] = v; | ||||
|             } | ||||
| 
 | ||||
|              | ||||
|             const licenseInfo = new LicenseInfo(); | ||||
|              | ||||
|             licenseInfo.licenseShortName = data.license; | ||||
|             licenseInfo.artist = data.author; | ||||
|              | ||||
|             handleDescription(licenseInfo); | ||||
|              | ||||
|         }).fail((reason) => { | ||||
|             console.log("Getting metadata from to IMGUR failed", reason) | ||||
|         }); | ||||
|      | ||||
|     } | ||||
| 
 | ||||
|     static uploadImage(title: string, description: string, blob, | ||||
|                        handleSuccessfullUpload: ((imageURL: string) => void), | ||||
|                        onFail: (reason:string) => void) { | ||||
|                        onFail: (reason: string) => void) { | ||||
| 
 | ||||
|         const apiUrl = 'https://api.imgur.com/3/image'; | ||||
|         const apiKey = '7070e7167f0a25a'; | ||||
|  | @ -119,4 +82,55 @@ export class Imgur { | |||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     SourceIcon(): BaseUIElement { | ||||
|         return undefined; | ||||
|     } | ||||
| 
 | ||||
|     protected DownloadAttribution(url: string): UIEventSource<LicenseInfo> { | ||||
|         const src = new UIEventSource<LicenseInfo>(undefined) | ||||
| 
 | ||||
| 
 | ||||
|         const hash = url.substr("https://i.imgur.com/".length).split(".jpg")[0]; | ||||
| 
 | ||||
|         const apiUrl = 'https://api.imgur.com/3/image/' + hash; | ||||
|         const apiKey = '7070e7167f0a25a'; | ||||
| 
 | ||||
|         const settings = { | ||||
|             async: true, | ||||
|             crossDomain: true, | ||||
|             processData: false, | ||||
|             contentType: false, | ||||
|             type: 'GET', | ||||
|             url: apiUrl, | ||||
|             headers: { | ||||
|                 Authorization: 'Client-ID ' + apiKey, | ||||
|                 Accept: 'application/json', | ||||
|             }, | ||||
|         }; | ||||
|         // @ts-ignore
 | ||||
|         $.ajax(settings).done(function (response) { | ||||
|             const descr: string = response.data.description ?? ""; | ||||
|             const data: any = {}; | ||||
|             for (const tag of descr.split("\n")) { | ||||
|                 const kv = tag.split(":"); | ||||
|                 const k = kv[0]; | ||||
|                 data[k] = kv[1].replace("\r", ""); | ||||
|             } | ||||
| 
 | ||||
| 
 | ||||
|             const licenseInfo = new LicenseInfo(); | ||||
| 
 | ||||
|             licenseInfo.licenseShortName = data.license; | ||||
|             licenseInfo.artist = data.author; | ||||
| 
 | ||||
|             src.setData(licenseInfo) | ||||
| 
 | ||||
|         }).fail((reason) => { | ||||
|             console.log("Getting metadata from to IMGUR failed", reason) | ||||
|         }); | ||||
| 
 | ||||
|         return src; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  | @ -1,26 +1,57 @@ | |||
| import $ from "jquery" | ||||
| import {LicenseInfo} from "./Wikimedia"; | ||||
| import ImageAttributionSource from "./ImageAttributionSource"; | ||||
| import BaseUIElement from "../../UI/BaseUIElement"; | ||||
| import {UIEventSource} from "../UIEventSource"; | ||||
| import Svg from "../../Svg"; | ||||
| 
 | ||||
| export class Mapillary { | ||||
| export class Mapillary extends ImageAttributionSource { | ||||
| 
 | ||||
|     public static readonly singleton = new Mapillary(); | ||||
| 
 | ||||
|     static getDescriptionOfImage(key: string, | ||||
|                                  handleDescription: ((license: LicenseInfo) => void)) { | ||||
|         const url = `https://a.mapillary.com/v3/images/${key}?client_id=TXhLaWthQ1d4RUg0czVxaTVoRjFJZzowNDczNjUzNmIyNTQyYzI2` | ||||
|     private constructor() { | ||||
|         super(); | ||||
|     } | ||||
| 
 | ||||
|         const settings = { | ||||
|             async: true, | ||||
|             type: 'GET', | ||||
|             url: url | ||||
|         }; | ||||
|         $.getJSON(url, function(data) { | ||||
|     private static ExtractKeyFromURL(value: string) { | ||||
|         if (value.startsWith("https://a.mapillary.com")) { | ||||
|             return value.substring(0, value.lastIndexOf("?")).substring(value.lastIndexOf("/") + 1); | ||||
|         } | ||||
|         const matchApi = value.match(/https?:\/\/images.mapillary.com\/([^/]*)/) | ||||
|         if (matchApi !== null) { | ||||
|             return matchApi[1]; | ||||
|         } | ||||
| 
 | ||||
|         if (value.toLowerCase().startsWith("https://www.mapillary.com/map/im/")) { | ||||
|             // Extract the key of the image
 | ||||
|             value = value.substring("https://www.mapillary.com/map/im/".length); | ||||
|         } | ||||
|         return value; | ||||
|     } | ||||
| 
 | ||||
|     SourceIcon(backlinkSource?: string): BaseUIElement { | ||||
|         return Svg.mapillary_svg(); | ||||
|     } | ||||
| 
 | ||||
|     PrepareUrl(value: string): string { | ||||
|         const key = Mapillary.ExtractKeyFromURL(value) | ||||
|         return `https://images.mapillary.com/${key}/thumb-640.jpg?client_id=TXhLaWthQ1d4RUg0czVxaTVoRjFJZzowNDczNjUzNmIyNTQyYzI2` | ||||
|     } | ||||
| 
 | ||||
|     protected DownloadAttribution(url: string): UIEventSource<LicenseInfo> { | ||||
| 
 | ||||
|         const key = Mapillary.ExtractKeyFromURL(url) | ||||
|         const metadataURL = `https://a.mapillary.com/v3/images/${key}?client_id=TXhLaWthQ1d4RUg0czVxaTVoRjFJZzowNDczNjUzNmIyNTQyYzI2` | ||||
|         const source = new UIEventSource<LicenseInfo>(undefined) | ||||
|         $.getJSON(metadataURL, function (data) { | ||||
|             const license = new LicenseInfo(); | ||||
|             license.artist = data.properties?.username; | ||||
|             license.licenseShortName = "CC BY-SA 4.0"; | ||||
|             license.license = "Creative Commons Attribution-ShareAlike 4.0 International License"; | ||||
|             license.attributionRequired = true; | ||||
|             handleDescription(license); | ||||
|             source.setData(license); | ||||
|         }) | ||||
| 
 | ||||
|         return source | ||||
|     } | ||||
| } | ||||
|  | @ -1,47 +1,28 @@ | |||
| import * as $ from "jquery" | ||||
| import ImageAttributionSource from "./ImageAttributionSource"; | ||||
| import BaseUIElement from "../../UI/BaseUIElement"; | ||||
| import Svg from "../../Svg"; | ||||
| import {UIEventSource} from "../UIEventSource"; | ||||
| import Link from "../../UI/Base/Link"; | ||||
| 
 | ||||
| /** | ||||
|  * This module provides endpoints for wikipedia/wikimedia and others | ||||
|  */ | ||||
| export class Wikimedia { | ||||
| export class Wikimedia extends ImageAttributionSource { | ||||
| 
 | ||||
| 
 | ||||
|     public static readonly singleton = new Wikimedia(); | ||||
| 
 | ||||
|     private constructor() { | ||||
|         super(); | ||||
|     } | ||||
| 
 | ||||
|     private static knownLicenses = {}; | ||||
| 
 | ||||
|     static ImageNameToUrl(filename: string, width: number = 500, height: number = 200): string { | ||||
|         filename = encodeURIComponent(filename); | ||||
|         return "https://commons.wikimedia.org/wiki/Special:FilePath/" + filename + "?width=" + width + "&height=" + height; | ||||
|     } | ||||
| 
 | ||||
|     static LicenseData(filename: string, handle: ((LicenseInfo) => void)): void { | ||||
|         if (filename in this.knownLicenses) { | ||||
|             return this.knownLicenses[filename]; | ||||
|         } | ||||
|         if (filename === "") { | ||||
|             return; | ||||
|         } | ||||
|         const url = "https://en.wikipedia.org/w/" + | ||||
|             "api.php?action=query&prop=imageinfo&iiprop=extmetadata&" + | ||||
|             "titles=" + filename + | ||||
|             "&format=json&origin=*"; | ||||
|         $.getJSON(url, function (data) { | ||||
|             const licenseInfo = new LicenseInfo(); | ||||
|             const license = data.query.pages[-1].imageinfo[0].extmetadata; | ||||
| 
 | ||||
|             licenseInfo.artist = license.Artist?.value; | ||||
|             licenseInfo.license = license.License?.value; | ||||
|             licenseInfo.copyrighted = license.Copyrighted?.value; | ||||
|             licenseInfo.attributionRequired = license.AttributionRequired?.value; | ||||
|             licenseInfo.usageTerms = license.UsageTerms?.value; | ||||
|             licenseInfo.licenseShortName = license.LicenseShortName?.value; | ||||
|             licenseInfo.credit = license.Credit?.value; | ||||
|             licenseInfo.description = license.ImageDescription?.value; | ||||
| 
 | ||||
|             Wikimedia.knownLicenses[filename] = licenseInfo; | ||||
|             handle(licenseInfo); | ||||
|         }); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     static GetCategoryFiles(categoryName: string, handleCategory: ((ImagesInCategory: ImagesInCategory) => void), | ||||
|                             alreadyLoaded = 0, | ||||
|                             continueParameter: { k: string, param: string } = undefined) { | ||||
|  | @ -111,6 +92,71 @@ export class Wikimedia { | |||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     private static ExtractFileName(url: string) { | ||||
|         if (!url.startsWith("http")) { | ||||
|             return url; | ||||
|         } | ||||
|         const path = new URL(url).pathname | ||||
|         return path.substring(path.lastIndexOf("/") + 1); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     SourceIcon(backlink: string): BaseUIElement { | ||||
|         const img = Svg.wikimedia_commons_white_svg() | ||||
|             .SetStyle("width:2em;height: 2em"); | ||||
|         if (backlink === undefined) { | ||||
|             return img | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         return new Link(Svg.wikimedia_commons_white_img, | ||||
|             `https://commons.wikimedia.org/wiki/${backlink}`, true) | ||||
| 
 | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     PrepareUrl(value: string): string { | ||||
| 
 | ||||
|         if (value.toLowerCase().startsWith("https://commons.wikimedia.org/wiki/")) { | ||||
|             return value; | ||||
|         } | ||||
|         return Wikimedia.ImageNameToUrl(value, 500, 400) | ||||
|             .replace(/'/g, '%27'); | ||||
|     } | ||||
| 
 | ||||
|     protected DownloadAttribution(filename: string): UIEventSource<LicenseInfo> { | ||||
| 
 | ||||
|         const source = new UIEventSource<LicenseInfo>(undefined); | ||||
| 
 | ||||
|         filename = Wikimedia.ExtractFileName(filename) | ||||
| 
 | ||||
|         if (filename === "") { | ||||
|             return source; | ||||
|         } | ||||
| 
 | ||||
|         const url = "https://en.wikipedia.org/w/" + | ||||
|             "api.php?action=query&prop=imageinfo&iiprop=extmetadata&" + | ||||
|             "titles=" + filename + | ||||
|             "&format=json&origin=*"; | ||||
|         console.log("Getting attribution at ", url) | ||||
|         $.getJSON(url, function (data) { | ||||
|             const licenseInfo = new LicenseInfo(); | ||||
|             const license = data.query.pages[-1].imageinfo[0].extmetadata; | ||||
| 
 | ||||
|             licenseInfo.artist = license.Artist?.value; | ||||
|             licenseInfo.license = license.License?.value; | ||||
|             licenseInfo.copyrighted = license.Copyrighted?.value; | ||||
|             licenseInfo.attributionRequired = license.AttributionRequired?.value; | ||||
|             licenseInfo.usageTerms = license.UsageTerms?.value; | ||||
|             licenseInfo.licenseShortName = license.LicenseShortName?.value; | ||||
|             licenseInfo.credit = license.Credit?.value; | ||||
|             licenseInfo.description = license.ImageDescription?.value; | ||||
|             source.setData(licenseInfo); | ||||
|         }); | ||||
|         return source; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										126
									
								
								Svg.ts
									
										
									
									
									
								
							
							
						
						
									
										126
									
								
								Svg.ts
									
										
									
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							|  | @ -3,10 +3,12 @@ import BaseUIElement from "../BaseUIElement"; | |||
| 
 | ||||
| export default class Img extends BaseUIElement { | ||||
|     private _src: string; | ||||
|     private readonly _rawSvg: boolean; | ||||
| 
 | ||||
|     constructor(src: string) { | ||||
|     constructor(src: string, rawSvg = false) { | ||||
|         super(); | ||||
|         this._src = src; | ||||
|         this._rawSvg = rawSvg; | ||||
|     } | ||||
| 
 | ||||
|     static AsData(source: string) { | ||||
|  | @ -21,6 +23,13 @@ export default class Img extends BaseUIElement { | |||
|     } | ||||
| 
 | ||||
|     protected InnerConstructElement(): HTMLElement { | ||||
| 
 | ||||
|         if (this._rawSvg) { | ||||
|             const e = document.createElement("div") | ||||
|             e.innerHTML = this._src | ||||
|             return e; | ||||
|         } | ||||
| 
 | ||||
|         const el = document.createElement("img") | ||||
|         el.src = this._src; | ||||
|         el.onload = () => { | ||||
|  |  | |||
|  | @ -29,14 +29,14 @@ export class SubtleButton extends UIElement { | |||
|         } else { | ||||
|             img = imageUrl; | ||||
|         } | ||||
|         img?.SetClass("block flex items-center justify-center h-11 w-11 flex-shrink0") | ||||
|         img?.SetClass("block flex items-center justify-center h-11 w-11 flex-shrink0 mr-4") | ||||
|         const image = new Combine([img]) | ||||
|             .SetClass("flex-shrink-0"); | ||||
| 
 | ||||
|         if (linkTo == undefined) { | ||||
|             return new Combine([ | ||||
|                 image, | ||||
|                 message?.SetClass("blcok ml-4 overflow-ellipsis"), | ||||
|                 message?.SetClass("block overflow-ellipsis"), | ||||
|             ]).SetClass("flex group w-full"); | ||||
|         } | ||||
| 
 | ||||
|  | @ -44,7 +44,7 @@ export class SubtleButton extends UIElement { | |||
|         return new Link( | ||||
|             new Combine([ | ||||
|                 image, | ||||
|                 message?.SetClass("block ml-4 overflow-ellipsis") | ||||
|                 message?.SetClass("block overflow-ellipsis") | ||||
|             ]).SetClass("flex group w-full"), | ||||
|             linkTo.url, | ||||
|             linkTo.newTab ?? false | ||||
|  |  | |||
|  | @ -34,11 +34,11 @@ export class Basemap { | |||
|         this.map.setMaxBounds( | ||||
|             [[-100, -200], [100, 200]] | ||||
|         ); | ||||
| 
 | ||||
|         this.map.attributionControl.setPrefix( | ||||
|             "<span id='leaflet-attribution'></span> | <a href='https://osm.org'>OpenStreetMap</a>"); | ||||
| 
 | ||||
|         extraAttribution.AttachTo('leaflet-attribution') | ||||
|          | ||||
|         const self = this; | ||||
| 
 | ||||
|         let previousLayer = currentLayer.data; | ||||
|  |  | |||
|  | @ -20,7 +20,7 @@ export default class ThemeIntroductionPanel extends VariableUiElement { | |||
|         ; | ||||
|          | ||||
|         const toTheMap = new SubtleButton( | ||||
|             new FixedUiElement(""), | ||||
|             undefined, | ||||
|             Translations.t.general.openTheMap.Clone().SetClass("text-xl font-bold w-full text-center") | ||||
|         ).onClick(() =>{ | ||||
|             isShown.setData(false) | ||||
|  |  | |||
							
								
								
									
										19
									
								
								UI/Image/AttributedImage.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								UI/Image/AttributedImage.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | |||
| import Combine from "../Base/Combine"; | ||||
| import Attribution from "./Attribution"; | ||||
| import Img from "../Base/Img"; | ||||
| import ImageAttributionSource from "../../Logic/Web/ImageAttributionSource"; | ||||
| 
 | ||||
| 
 | ||||
| export class AttributedImage extends Combine { | ||||
| 
 | ||||
|     constructor(urlSource: string, imgSource: ImageAttributionSource) { | ||||
|         urlSource = imgSource.PrepareUrl(urlSource) | ||||
|         super([ | ||||
|             new Img( urlSource), | ||||
|             new Attribution(imgSource.GetAttributionFor(urlSource), imgSource.SourceIcon()) | ||||
|         ]); | ||||
|         this.SetClass('block relative h-full'); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  | @ -1,19 +1,33 @@ | |||
| import {UIElement} from "../UIElement"; | ||||
| import Combine from "../Base/Combine"; | ||||
| import Translations from "../i18n/Translations"; | ||||
| import BaseUIElement from "../BaseUIElement"; | ||||
| import {VariableUiElement} from "../Base/VariableUIElement"; | ||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | ||||
| import {LicenseInfo} from "../../Logic/Web/Wikimedia"; | ||||
| 
 | ||||
| export default class Attribution extends Combine { | ||||
| export default class Attribution extends VariableUiElement { | ||||
| 
 | ||||
|     constructor(license: UIEventSource<LicenseInfo>, icon: BaseUIElement) { | ||||
|         if (license === undefined) { | ||||
|             throw "No license source given in the attribution element" | ||||
|         } | ||||
|         super( | ||||
|             license.map((license : LicenseInfo) => { | ||||
| 
 | ||||
|                 if (license?.artist === undefined) { | ||||
|                     return undefined; | ||||
|                 } | ||||
|                  | ||||
|                 return new Combine([ | ||||
|                     icon?.SetClass("block left").SetStyle("height: 2em; width: 2em; padding-right: 0.5em;"), | ||||
| 
 | ||||
|     constructor(author: BaseUIElement | string, license: BaseUIElement | string, icon: BaseUIElement) { | ||||
|         super([ | ||||
|             icon?.SetClass("block left").SetStyle("height: 2em; width: 2em; padding-right: 0.5em"), | ||||
|                     new Combine([ | ||||
|                 Translations.W(author).SetClass("block font-bold"), | ||||
|                 Translations.W((license ?? "") === "undefined" ? "CC0" : (license ?? "")) | ||||
|                         Translations.W(license.artist).SetClass("block font-bold"), | ||||
|                         Translations.W((license.license ?? "") === "" ? "CC0" : (license.license ?? "")) | ||||
|                     ]).SetClass("flex flex-col") | ||||
|         ]); | ||||
|         this.SetClass("flex flex-row bg-black text-white text-sm absolute bottom-0 left-0 p-0.5 pl-5 pr-3 rounded-lg"); | ||||
|                 ]).SetClass("flex flex-row bg-black text-white text-sm absolute bottom-0 left-0 p-0.5 pl-5 pr-3 rounded-lg") | ||||
| 
 | ||||
|             })); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -2,12 +2,14 @@ import {SlideShow} from "./SlideShow"; | |||
| import {UIEventSource} from "../../Logic/UIEventSource"; | ||||
| import Combine from "../Base/Combine"; | ||||
| import DeleteImage from "./DeleteImage"; | ||||
| import {WikimediaImage} from "./WikimediaImage"; | ||||
| import {ImgurImage} from "./ImgurImage"; | ||||
| import {MapillaryImage} from "./MapillaryImage"; | ||||
| import {AttributedImage} from "./AttributedImage"; | ||||
| import BaseUIElement from "../BaseUIElement"; | ||||
| import Img from "../Base/Img"; | ||||
| import Toggle from "../Input/Toggle"; | ||||
| import ImageAttributionSource from "../../Logic/Web/ImageAttributionSource"; | ||||
| import {Wikimedia} from "../../Logic/Web/Wikimedia"; | ||||
| import {Mapillary} from "../../Logic/Web/Mapillary"; | ||||
| import {Imgur} from "../../Logic/Web/Imgur"; | ||||
| 
 | ||||
| export class ImageCarousel extends Toggle { | ||||
| 
 | ||||
|  | @ -45,17 +47,20 @@ export class ImageCarousel extends Toggle { | |||
|      */ | ||||
|     private static CreateImageElement(url: string): BaseUIElement { | ||||
|         // @ts-ignore
 | ||||
|         let attrSource : ImageAttributionSource = undefined; | ||||
|         if (url.startsWith("File:")) { | ||||
|             return new WikimediaImage(url); | ||||
|             attrSource = Wikimedia.singleton | ||||
|         } else if (url.toLowerCase().startsWith("https://commons.wikimedia.org/wiki/")) { | ||||
|             const commons = url.substr("https://commons.wikimedia.org/wiki/".length); | ||||
|             return new WikimediaImage(commons); | ||||
|             attrSource = Wikimedia.singleton; | ||||
|         } else if (url.toLowerCase().startsWith("https://i.imgur.com/")) { | ||||
|             return new ImgurImage(url); | ||||
|             attrSource = Imgur.singleton | ||||
|         } else if (url.toLowerCase().startsWith("https://www.mapillary.com/map/im/")) { | ||||
|             return new MapillaryImage(url); | ||||
|             attrSource = Mapillary.singleton | ||||
|         } else { | ||||
|             return new Img(url); | ||||
|         } | ||||
|          | ||||
|         return new AttributedImage(url, attrSource) | ||||
|          | ||||
|     } | ||||
| } | ||||
|  | @ -1,48 +0,0 @@ | |||
| import {UIElement} from "../UIElement"; | ||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | ||||
| import {LicenseInfo} from "../../Logic/Web/Wikimedia"; | ||||
| import {Imgur} from "../../Logic/Web/Imgur"; | ||||
| import Combine from "../Base/Combine"; | ||||
| import Attribution from "./Attribution"; | ||||
| import BaseUIElement from "../BaseUIElement"; | ||||
| import Img from "../Base/Img"; | ||||
| import {VariableUiElement} from "../Base/VariableUIElement"; | ||||
| 
 | ||||
| 
 | ||||
| export class ImgurImage extends UIElement { | ||||
| 
 | ||||
| 
 | ||||
|     /*** | ||||
|      * Dictionary from url to alreayd known license info | ||||
|      */ | ||||
|     private static allLicenseInfos: any = {}; | ||||
|     private readonly _imageMeta: UIEventSource<LicenseInfo>; | ||||
|     private readonly _imageLocation: string; | ||||
| 
 | ||||
|     constructor(source: string) { | ||||
|         super() | ||||
|         this._imageLocation = source; | ||||
|         if (ImgurImage.allLicenseInfos[source] !== undefined) { | ||||
|             this._imageMeta = ImgurImage.allLicenseInfos[source]; | ||||
|         } else { | ||||
|             this._imageMeta = new UIEventSource<LicenseInfo>(null); | ||||
|             ImgurImage.allLicenseInfos[source] = this._imageMeta; | ||||
|             const self = this; | ||||
|             Imgur.getDescriptionOfImage(source, (license) => { | ||||
|                 self._imageMeta.setData(license) | ||||
|             }) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     InnerRender(): BaseUIElement { | ||||
|         const image = new Img( this._imageLocation); | ||||
|          | ||||
|         return new Combine([ | ||||
|             image, | ||||
|            new VariableUiElement(this._imageMeta.map(meta => (meta === undefined || meta === null) ? undefined : new Attribution(meta.artist, meta.license, undefined))) | ||||
|         ]).SetClass('block relative h-full'); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  | @ -1,61 +0,0 @@ | |||
| import {UIElement} from "../UIElement"; | ||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | ||||
| import {LicenseInfo} from "../../Logic/Web/Wikimedia"; | ||||
| import {Mapillary} from "../../Logic/Web/Mapillary"; | ||||
| import Svg from "../../Svg"; | ||||
| import Combine from "../Base/Combine"; | ||||
| import Attribution from "./Attribution"; | ||||
| import Img from "../Base/Img"; | ||||
| import BaseUIElement from "../BaseUIElement"; | ||||
| 
 | ||||
| 
 | ||||
| export class MapillaryImage extends UIElement { | ||||
| 
 | ||||
|     /*** | ||||
|      * Dictionary from url to already known license info | ||||
|      */ | ||||
|     private static allLicenseInfos: any = {}; | ||||
|     private readonly _imageMeta: UIEventSource<LicenseInfo>; | ||||
|     private readonly _imageLocation: string; | ||||
| 
 | ||||
|     constructor(source: string) { | ||||
|         super() | ||||
| 
 | ||||
|         if (source.toLowerCase().startsWith("https://www.mapillary.com/map/im/")) { | ||||
|             source = source.substring("https://www.mapillary.com/map/im/".length); | ||||
|         } | ||||
| 
 | ||||
|         this._imageLocation = source; | ||||
|         if (MapillaryImage.allLicenseInfos[source] !== undefined) { | ||||
|             this._imageMeta = MapillaryImage.allLicenseInfos[source]; | ||||
|         } else { | ||||
|             this._imageMeta = new UIEventSource<LicenseInfo>(null); | ||||
|             MapillaryImage.allLicenseInfos[source] = this._imageMeta; | ||||
|             const self = this; | ||||
|             Mapillary.getDescriptionOfImage(source, (license) => { | ||||
|                 self._imageMeta.setData(license) | ||||
|             }) | ||||
|         } | ||||
| 
 | ||||
|         this.ListenTo(this._imageMeta); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     InnerRender(): BaseUIElement { | ||||
|         const url = `https://images.mapillary.com/${this._imageLocation}/thumb-640.jpg?client_id=TXhLaWthQ1d4RUg0czVxaTVoRjFJZzowNDczNjUzNmIyNTQyYzI2`; | ||||
|         const image = new Img(url) | ||||
|          | ||||
|         const meta = this._imageMeta?.data; | ||||
|         if (!meta) { | ||||
|             return image; | ||||
|         } | ||||
| 
 | ||||
|         return new Combine([ | ||||
|             image, | ||||
|             new Attribution(meta.artist, meta.license, Svg.mapillary_svg()) | ||||
|         ]).SetClass("relative block h-full"); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  | @ -11,23 +11,27 @@ export class SlideShow extends BaseUIElement { | |||
|     constructor(embeddedElements: UIEventSource<BaseUIElement[]>) { | ||||
|         super() | ||||
|         this.embeddedElements =embeddedElements; | ||||
|         this.SetStyle("scroll-snap-type: x mandatory; overflow-x: scroll") | ||||
|     }    | ||||
| 
 | ||||
|     protected InnerConstructElement(): HTMLElement { | ||||
|         const el = document.createElement("div") | ||||
|         el.style.overflowX = "auto" | ||||
|         el.style.width = "min-content" | ||||
|         el.style.minWidth = "min-content" | ||||
|         el.style.display = "flex" | ||||
| 
 | ||||
|         el.style.justifyContent = "center" | ||||
|         this.embeddedElements.addCallbackAndRun(elements => { | ||||
|              | ||||
|             if(elements.length > 1){ | ||||
|                 el.style.justifyContent = "unset" | ||||
|             } | ||||
|              | ||||
|             while (el.firstChild) { | ||||
|                 el.removeChild(el.lastChild) | ||||
|             } | ||||
| 
 | ||||
|             elements = Utils.NoNull(elements).map(el => new Combine([el])  | ||||
|                 .SetClass("block relative ml-1 bg-gray-200 m-1 rounded slideshow-item") | ||||
|                 .SetStyle("min-width: 150px; width: max-content; height: var(--image-carousel-height);max-height: var(--image-carousel-height);") | ||||
|                 .SetStyle("min-width: 150px; width: max-content; height: var(--image-carousel-height);max-height: var(--image-carousel-height);scroll-snap-align: start;") | ||||
|             ) | ||||
|              | ||||
|             for (const element of elements ?? []) { | ||||
|  |  | |||
|  | @ -1,59 +0,0 @@ | |||
| import {UIElement} from "../UIElement"; | ||||
| import {LicenseInfo, Wikimedia} from "../../Logic/Web/Wikimedia"; | ||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | ||||
| import Svg from "../../Svg"; | ||||
| import Link from "../Base/Link"; | ||||
| import Combine from "../Base/Combine"; | ||||
| import Attribution from "./Attribution"; | ||||
| import BaseUIElement from "../BaseUIElement"; | ||||
| import Img from "../Base/Img"; | ||||
| 
 | ||||
| 
 | ||||
| export class WikimediaImage extends UIElement { | ||||
| 
 | ||||
| 
 | ||||
|     static allLicenseInfos: any = {}; | ||||
|     private readonly _imageMeta: UIEventSource<LicenseInfo>; | ||||
|     private readonly _imageLocation: string; | ||||
| 
 | ||||
|     constructor(source: string) { | ||||
|         super(undefined) | ||||
|         this._imageLocation = source; | ||||
|         if (WikimediaImage.allLicenseInfos[source] !== undefined) { | ||||
|             this._imageMeta = WikimediaImage.allLicenseInfos[source]; | ||||
|         } else { | ||||
|             this._imageMeta = new UIEventSource<LicenseInfo>(new LicenseInfo()); | ||||
|             WikimediaImage.allLicenseInfos[source] = this._imageMeta; | ||||
|             const self = this; | ||||
|             Wikimedia.LicenseData(source, (info) => { | ||||
|                 self._imageMeta.setData(info); | ||||
|             }) | ||||
|         } | ||||
| 
 | ||||
|         this.ListenTo(this._imageMeta); | ||||
| 
 | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     InnerRender(): BaseUIElement { | ||||
|         const url = Wikimedia.ImageNameToUrl(this._imageLocation, 500, 400) | ||||
|             .replace(/'/g, '%27'); | ||||
|         const image = new Img(url) | ||||
|         const meta = this._imageMeta?.data; | ||||
| 
 | ||||
|         if (!meta) { | ||||
|             return image; | ||||
|         } | ||||
|         new Link(Svg.wikimedia_commons_white_img, | ||||
|             `https://commons.wikimedia.org/wiki/${this._imageLocation}`, true) | ||||
|             .SetStyle("width:2em;height: 2em"); | ||||
|          | ||||
|         return new Combine([ | ||||
|             image, | ||||
|             new Attribution(meta.artist, meta.license, Svg.wikimedia_commons_white_svg()) | ||||
|         ]).SetClass("relative block h-full") | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  | @ -62,7 +62,7 @@ | |||
|     --variable-title-height: 0px; /* Set by javascript */ | ||||
|     --return-to-the-map-height: 2em; | ||||
|      | ||||
|     --image-carousel-height: 400px; | ||||
|     --image-carousel-height: 350px; | ||||
| } | ||||
| 
 | ||||
| html, body { | ||||
|  | @ -148,10 +148,6 @@ li::marker { | |||
| 
 | ||||
| .border-attention-catch{ border: 5px solid var(--catch-detail-color);} | ||||
| 
 | ||||
| .slick-prev:before, .slick-next:before { | ||||
|     /*Slideshow workaround*/ | ||||
|     color:black !important; | ||||
| } | ||||
| 
 | ||||
| #topleft-tools svg { | ||||
|     fill: var(--foreground-color) !important; | ||||
|  | @ -360,6 +356,6 @@ li::marker { | |||
| 
 | ||||
| 
 | ||||
| .slideshow-item img{ | ||||
|     height: 100%; | ||||
|     height: var(--image-carousel-height); | ||||
|     width: unset; | ||||
| } | ||||
|  | @ -1,8 +1,10 @@ | |||
| { | ||||
|   "name": "index", | ||||
|   "short_name": "MapComplete", | ||||
|   "start_url": "index.html", | ||||
|   "display": "standalone", | ||||
|   "background_color": "#fff", | ||||
|   "description": "A thematic map viewer and editor based on OpenStreetMap", | ||||
|   "orientation": "portrait-primary, landscape-primary", | ||||
|   "icons": [ | ||||
|     { | ||||
|  |  | |||
|  | @ -28,7 +28,7 @@ function genImages() { | |||
|             .replace(/[ -]/g, "_"); | ||||
|         module += `    public static ${name} = "${svg}"\n` | ||||
|         module += `    public static ${name}_img = Img.AsImageElement(Svg.${name})\n` | ||||
|         module += `    public static ${name}_svg() { return new FixedUiElement(Svg.${name});}\n` | ||||
|         module += `    public static ${name}_svg() { return new Img(Svg.${name}, true);}\n` | ||||
|         module += `    public static ${name}_ui() { return new FixedUiElement(Svg.${name}_img);}\n\n` | ||||
|         allNames.push(`"${path}": Svg.${name}`) | ||||
|     } | ||||
|  |  | |||
							
								
								
									
										5
									
								
								test.ts
									
										
									
									
									
								
							
							
						
						
									
										5
									
								
								test.ts
									
										
									
									
									
								
							|  | @ -8,7 +8,8 @@ import TagRenderingQuestion from "./UI/Popup/TagRenderingQuestion"; | |||
| import {SlideShow} from "./UI/Image/SlideShow"; | ||||
| import {FixedUiElement} from "./UI/Base/FixedUiElement"; | ||||
| import Img from "./UI/Base/Img"; | ||||
| import {ImgurImage} from "./UI/Image/ImgurImage"; | ||||
| import {AttributedImage} from "./UI/Image/AttributedImage"; | ||||
| import {Imgur} from "./Logic/Web/Imgur"; | ||||
| 
 | ||||
| 
 | ||||
| function TestSlideshow(){ | ||||
|  | @ -16,7 +17,7 @@ function TestSlideshow(){ | |||
|         new FixedUiElement("A"), | ||||
|         new FixedUiElement("qmsldkfjqmlsdkjfmqlskdjfmqlksdf").SetClass("text-xl"), | ||||
|         new Img("https://i.imgur.com/8lIQ5Hv.jpg"), | ||||
|         new ImgurImage("https://i.imgur.com/y5XudzW.jpg"), | ||||
|         new AttributedImage("https://i.imgur.com/y5XudzW.jpg", new Imgur()), | ||||
|         new Img("https://www.grunge.com/img/gallery/the-real-reason-your-cat-sleeps-so-much/intro-1601496900.webp") | ||||
|     ]) | ||||
|     new SlideShow(elems).AttachTo("maindiv") | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue