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
 | // @ts-ignore
 | ||||||
| import $ from "jquery" | import $ from "jquery" | ||||||
| import {LicenseInfo} from "./Wikimedia"; | 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( |     static uploadMultiple( | ||||||
|         title: string, description: string, blobs: FileList, |         title: string, description: string, blobs: FileList, | ||||||
|         handleSuccessfullUpload: ((imageURL: string) => void), |         handleSuccessfullUpload: ((imageURL: string) => void), | ||||||
|         allDone: (() => void), |         allDone: (() => void), | ||||||
|         onFail: ((reason: string) => void), |         onFail: ((reason: string) => void), | ||||||
|         offset:number = 0) { |         offset: number = 0) { | ||||||
| 
 | 
 | ||||||
|         if (blobs.length == offset) { |         if (blobs.length == offset) { | ||||||
|             allDone(); |             allDone(); | ||||||
|  | @ -32,56 +40,11 @@ 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, |     static uploadImage(title: string, description: string, blob, | ||||||
|                        handleSuccessfullUpload: ((imageURL: string) => void), |                        handleSuccessfullUpload: ((imageURL: string) => void), | ||||||
|                        onFail: (reason:string) => void) { |                        onFail: (reason: string) => void) { | ||||||
| 
 | 
 | ||||||
|         const apiUrl = 'https://api.imgur.com/3/image'; |         const apiUrl = 'https://api.imgur.com/3/image'; | ||||||
|         const apiKey = '7070e7167f0a25a'; |         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 $ from "jquery" | ||||||
| import {LicenseInfo} from "./Wikimedia"; | 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, |     private constructor() { | ||||||
|                                  handleDescription: ((license: LicenseInfo) => void)) { |         super(); | ||||||
|         const url = `https://a.mapillary.com/v3/images/${key}?client_id=TXhLaWthQ1d4RUg0czVxaTVoRjFJZzowNDczNjUzNmIyNTQyYzI2` |     } | ||||||
| 
 | 
 | ||||||
|         const settings = { |     private static ExtractKeyFromURL(value: string) { | ||||||
|             async: true, |         if (value.startsWith("https://a.mapillary.com")) { | ||||||
|             type: 'GET', |             return value.substring(0, value.lastIndexOf("?")).substring(value.lastIndexOf("/") + 1); | ||||||
|             url: url |         } | ||||||
|         }; |         const matchApi = value.match(/https?:\/\/images.mapillary.com\/([^/]*)/) | ||||||
|         $.getJSON(url, function(data) { |         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(); |             const license = new LicenseInfo(); | ||||||
|             license.artist = data.properties?.username; |             license.artist = data.properties?.username; | ||||||
|             license.licenseShortName = "CC BY-SA 4.0"; |             license.licenseShortName = "CC BY-SA 4.0"; | ||||||
|             license.license = "Creative Commons Attribution-ShareAlike 4.0 International License"; |             license.license = "Creative Commons Attribution-ShareAlike 4.0 International License"; | ||||||
|             license.attributionRequired = true; |             license.attributionRequired = true; | ||||||
|             handleDescription(license); |             source.setData(license); | ||||||
|         }) |         }) | ||||||
| 
 | 
 | ||||||
|  |         return source | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -1,47 +1,28 @@ | ||||||
| import * as $ from "jquery" | 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 |  * 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 { |     static ImageNameToUrl(filename: string, width: number = 500, height: number = 200): string { | ||||||
|         filename = encodeURIComponent(filename); |         filename = encodeURIComponent(filename); | ||||||
|         return "https://commons.wikimedia.org/wiki/Special:FilePath/" + filename + "?width=" + width + "&height=" + height; |         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), |     static GetCategoryFiles(categoryName: string, handleCategory: ((ImagesInCategory: ImagesInCategory) => void), | ||||||
|                             alreadyLoaded = 0, |                             alreadyLoaded = 0, | ||||||
|                             continueParameter: { k: string, param: string } = undefined) { |                             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 { | export default class Img extends BaseUIElement { | ||||||
|     private _src: string; |     private _src: string; | ||||||
|  |     private readonly _rawSvg: boolean; | ||||||
| 
 | 
 | ||||||
|     constructor(src: string) { |     constructor(src: string, rawSvg = false) { | ||||||
|         super(); |         super(); | ||||||
|         this._src = src; |         this._src = src; | ||||||
|  |         this._rawSvg = rawSvg; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     static AsData(source: string) { |     static AsData(source: string) { | ||||||
|  | @ -21,6 +23,13 @@ export default class Img extends BaseUIElement { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     protected InnerConstructElement(): HTMLElement { |     protected InnerConstructElement(): HTMLElement { | ||||||
|  | 
 | ||||||
|  |         if (this._rawSvg) { | ||||||
|  |             const e = document.createElement("div") | ||||||
|  |             e.innerHTML = this._src | ||||||
|  |             return e; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         const el = document.createElement("img") |         const el = document.createElement("img") | ||||||
|         el.src = this._src; |         el.src = this._src; | ||||||
|         el.onload = () => { |         el.onload = () => { | ||||||
|  |  | ||||||
|  | @ -29,14 +29,14 @@ export class SubtleButton extends UIElement { | ||||||
|         } else { |         } else { | ||||||
|             img = imageUrl; |             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]) |         const image = new Combine([img]) | ||||||
|             .SetClass("flex-shrink-0"); |             .SetClass("flex-shrink-0"); | ||||||
| 
 | 
 | ||||||
|         if (linkTo == undefined) { |         if (linkTo == undefined) { | ||||||
|             return new Combine([ |             return new Combine([ | ||||||
|                 image, |                 image, | ||||||
|                 message?.SetClass("blcok ml-4 overflow-ellipsis"), |                 message?.SetClass("block overflow-ellipsis"), | ||||||
|             ]).SetClass("flex group w-full"); |             ]).SetClass("flex group w-full"); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -44,7 +44,7 @@ export class SubtleButton extends UIElement { | ||||||
|         return new Link( |         return new Link( | ||||||
|             new Combine([ |             new Combine([ | ||||||
|                 image, |                 image, | ||||||
|                 message?.SetClass("block ml-4 overflow-ellipsis") |                 message?.SetClass("block overflow-ellipsis") | ||||||
|             ]).SetClass("flex group w-full"), |             ]).SetClass("flex group w-full"), | ||||||
|             linkTo.url, |             linkTo.url, | ||||||
|             linkTo.newTab ?? false |             linkTo.newTab ?? false | ||||||
|  |  | ||||||
|  | @ -34,11 +34,11 @@ export class Basemap { | ||||||
|         this.map.setMaxBounds( |         this.map.setMaxBounds( | ||||||
|             [[-100, -200], [100, 200]] |             [[-100, -200], [100, 200]] | ||||||
|         ); |         ); | ||||||
|  | 
 | ||||||
|         this.map.attributionControl.setPrefix( |         this.map.attributionControl.setPrefix( | ||||||
|             "<span id='leaflet-attribution'></span> | <a href='https://osm.org'>OpenStreetMap</a>"); |             "<span id='leaflet-attribution'></span> | <a href='https://osm.org'>OpenStreetMap</a>"); | ||||||
| 
 | 
 | ||||||
|         extraAttribution.AttachTo('leaflet-attribution') |         extraAttribution.AttachTo('leaflet-attribution') | ||||||
|          |  | ||||||
|         const self = this; |         const self = this; | ||||||
| 
 | 
 | ||||||
|         let previousLayer = currentLayer.data; |         let previousLayer = currentLayer.data; | ||||||
|  |  | ||||||
|  | @ -20,7 +20,7 @@ export default class ThemeIntroductionPanel extends VariableUiElement { | ||||||
|         ; |         ; | ||||||
|          |          | ||||||
|         const toTheMap = new SubtleButton( |         const toTheMap = new SubtleButton( | ||||||
|             new FixedUiElement(""), |             undefined, | ||||||
|             Translations.t.general.openTheMap.Clone().SetClass("text-xl font-bold w-full text-center") |             Translations.t.general.openTheMap.Clone().SetClass("text-xl font-bold w-full text-center") | ||||||
|         ).onClick(() =>{ |         ).onClick(() =>{ | ||||||
|             isShown.setData(false) |             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 Combine from "../Base/Combine"; | ||||||
| import Translations from "../i18n/Translations"; | import Translations from "../i18n/Translations"; | ||||||
| import BaseUIElement from "../BaseUIElement"; | 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(author: BaseUIElement | string, license: BaseUIElement | string, icon: BaseUIElement) { |     constructor(license: UIEventSource<LicenseInfo>, icon: BaseUIElement) { | ||||||
|         super([ |         if (license === undefined) { | ||||||
|             icon?.SetClass("block left").SetStyle("height: 2em; width: 2em; padding-right: 0.5em"), |             throw "No license source given in the attribution element" | ||||||
|             new Combine([ |         } | ||||||
|                 Translations.W(author).SetClass("block font-bold"), |         super( | ||||||
|                 Translations.W((license ?? "") === "undefined" ? "CC0" : (license ?? "")) |             license.map((license : LicenseInfo) => { | ||||||
|             ]).SetClass("flex flex-col") | 
 | ||||||
|         ]); |                 if (license?.artist === undefined) { | ||||||
|         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"); |                     return undefined; | ||||||
|  |                 } | ||||||
|  |                  | ||||||
|  |                 return new Combine([ | ||||||
|  |                     icon?.SetClass("block left").SetStyle("height: 2em; width: 2em; padding-right: 0.5em;"), | ||||||
|  | 
 | ||||||
|  |                     new Combine([ | ||||||
|  |                         Translations.W(license.artist).SetClass("block font-bold"), | ||||||
|  |                         Translations.W((license.license ?? "") === "" ? "CC0" : (license.license ?? "")) | ||||||
|  |                     ]).SetClass("flex flex-col") | ||||||
|  |                 ]).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 {UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import Combine from "../Base/Combine"; | import Combine from "../Base/Combine"; | ||||||
| import DeleteImage from "./DeleteImage"; | import DeleteImage from "./DeleteImage"; | ||||||
| import {WikimediaImage} from "./WikimediaImage"; | import {AttributedImage} from "./AttributedImage"; | ||||||
| import {ImgurImage} from "./ImgurImage"; |  | ||||||
| import {MapillaryImage} from "./MapillaryImage"; |  | ||||||
| import BaseUIElement from "../BaseUIElement"; | import BaseUIElement from "../BaseUIElement"; | ||||||
| import Img from "../Base/Img"; | import Img from "../Base/Img"; | ||||||
| import Toggle from "../Input/Toggle"; | 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 { | export class ImageCarousel extends Toggle { | ||||||
| 
 | 
 | ||||||
|  | @ -45,17 +47,20 @@ export class ImageCarousel extends Toggle { | ||||||
|      */ |      */ | ||||||
|     private static CreateImageElement(url: string): BaseUIElement { |     private static CreateImageElement(url: string): BaseUIElement { | ||||||
|         // @ts-ignore
 |         // @ts-ignore
 | ||||||
|  |         let attrSource : ImageAttributionSource = undefined; | ||||||
|         if (url.startsWith("File:")) { |         if (url.startsWith("File:")) { | ||||||
|             return new WikimediaImage(url); |             attrSource = Wikimedia.singleton | ||||||
|         } else if (url.toLowerCase().startsWith("https://commons.wikimedia.org/wiki/")) { |         } else if (url.toLowerCase().startsWith("https://commons.wikimedia.org/wiki/")) { | ||||||
|             const commons = url.substr("https://commons.wikimedia.org/wiki/".length); |             attrSource = Wikimedia.singleton; | ||||||
|             return new WikimediaImage(commons); |  | ||||||
|         } else if (url.toLowerCase().startsWith("https://i.imgur.com/")) { |         } 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/")) { |         } else if (url.toLowerCase().startsWith("https://www.mapillary.com/map/im/")) { | ||||||
|             return new MapillaryImage(url); |             attrSource = Mapillary.singleton | ||||||
|         } else { |         } else { | ||||||
|             return new Img(url); |             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[]>) { |     constructor(embeddedElements: UIEventSource<BaseUIElement[]>) { | ||||||
|         super() |         super() | ||||||
|         this.embeddedElements =embeddedElements; |         this.embeddedElements =embeddedElements; | ||||||
|     } |         this.SetStyle("scroll-snap-type: x mandatory; overflow-x: scroll") | ||||||
|  |     }    | ||||||
| 
 | 
 | ||||||
|     protected InnerConstructElement(): HTMLElement { |     protected InnerConstructElement(): HTMLElement { | ||||||
|         const el = document.createElement("div") |         const el = document.createElement("div") | ||||||
|         el.style.overflowX = "auto" |  | ||||||
|         el.style.width = "min-content" |  | ||||||
|         el.style.minWidth = "min-content" |         el.style.minWidth = "min-content" | ||||||
|         el.style.display = "flex" |         el.style.display = "flex" | ||||||
| 
 |         el.style.justifyContent = "center" | ||||||
|         this.embeddedElements.addCallbackAndRun(elements => { |         this.embeddedElements.addCallbackAndRun(elements => { | ||||||
|  |              | ||||||
|  |             if(elements.length > 1){ | ||||||
|  |                 el.style.justifyContent = "unset" | ||||||
|  |             } | ||||||
|  |              | ||||||
|             while (el.firstChild) { |             while (el.firstChild) { | ||||||
|                 el.removeChild(el.lastChild) |                 el.removeChild(el.lastChild) | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             elements = Utils.NoNull(elements).map(el => new Combine([el])  |             elements = Utils.NoNull(elements).map(el => new Combine([el])  | ||||||
|                 .SetClass("block relative ml-1 bg-gray-200 m-1 rounded slideshow-item") |                 .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 ?? []) { |             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 */ |     --variable-title-height: 0px; /* Set by javascript */ | ||||||
|     --return-to-the-map-height: 2em; |     --return-to-the-map-height: 2em; | ||||||
|      |      | ||||||
|     --image-carousel-height: 400px; |     --image-carousel-height: 350px; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| html, body { | html, body { | ||||||
|  | @ -148,10 +148,6 @@ li::marker { | ||||||
| 
 | 
 | ||||||
| .border-attention-catch{ border: 5px solid var(--catch-detail-color);} | .border-attention-catch{ border: 5px solid var(--catch-detail-color);} | ||||||
| 
 | 
 | ||||||
| .slick-prev:before, .slick-next:before { |  | ||||||
|     /*Slideshow workaround*/ |  | ||||||
|     color:black !important; |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| #topleft-tools svg { | #topleft-tools svg { | ||||||
|     fill: var(--foreground-color) !important; |     fill: var(--foreground-color) !important; | ||||||
|  | @ -360,6 +356,6 @@ li::marker { | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| .slideshow-item img{ | .slideshow-item img{ | ||||||
|     height: 100%; |     height: var(--image-carousel-height); | ||||||
|     width: unset; |     width: unset; | ||||||
| } | } | ||||||
|  | @ -1,8 +1,10 @@ | ||||||
| { | { | ||||||
|   "name": "index", |   "name": "index", | ||||||
|  |   "short_name": "MapComplete", | ||||||
|   "start_url": "index.html", |   "start_url": "index.html", | ||||||
|   "display": "standalone", |   "display": "standalone", | ||||||
|   "background_color": "#fff", |   "background_color": "#fff", | ||||||
|  |   "description": "A thematic map viewer and editor based on OpenStreetMap", | ||||||
|   "orientation": "portrait-primary, landscape-primary", |   "orientation": "portrait-primary, landscape-primary", | ||||||
|   "icons": [ |   "icons": [ | ||||||
|     { |     { | ||||||
|  |  | ||||||
|  | @ -28,7 +28,7 @@ function genImages() { | ||||||
|             .replace(/[ -]/g, "_"); |             .replace(/[ -]/g, "_"); | ||||||
|         module += `    public static ${name} = "${svg}"\n` |         module += `    public static ${name} = "${svg}"\n` | ||||||
|         module += `    public static ${name}_img = Img.AsImageElement(Svg.${name})\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` |         module += `    public static ${name}_ui() { return new FixedUiElement(Svg.${name}_img);}\n\n` | ||||||
|         allNames.push(`"${path}": Svg.${name}`) |         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 {SlideShow} from "./UI/Image/SlideShow"; | ||||||
| import {FixedUiElement} from "./UI/Base/FixedUiElement"; | import {FixedUiElement} from "./UI/Base/FixedUiElement"; | ||||||
| import Img from "./UI/Base/Img"; | 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(){ | function TestSlideshow(){ | ||||||
|  | @ -16,7 +17,7 @@ function TestSlideshow(){ | ||||||
|         new FixedUiElement("A"), |         new FixedUiElement("A"), | ||||||
|         new FixedUiElement("qmsldkfjqmlsdkjfmqlskdjfmqlksdf").SetClass("text-xl"), |         new FixedUiElement("qmsldkfjqmlsdkjfmqlskdjfmqlksdf").SetClass("text-xl"), | ||||||
|         new Img("https://i.imgur.com/8lIQ5Hv.jpg"), |         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 Img("https://www.grunge.com/img/gallery/the-real-reason-your-cat-sleeps-so-much/intro-1601496900.webp") | ||||||
|     ]) |     ]) | ||||||
|     new SlideShow(elems).AttachTo("maindiv") |     new SlideShow(elems).AttachTo("maindiv") | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue