From 1979aadb4979ab915db0717a42edc5d829fc2ba4 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Fri, 29 Jan 2021 03:23:53 +0100 Subject: [PATCH] Add slick-carousel instead of homemade one --- Logic/Actors/ImageSearcher.ts | 10 ++-- UI/Image/Attribution.ts | 18 +++++++ UI/Image/DeleteImage.ts | 2 +- UI/Image/ImageCarousel.ts | 15 +++--- UI/Image/ImgurImage.ts | 24 ++++----- UI/Image/MapillaryImage.ts | 28 ++++++----- UI/Image/SlideShow.ts | 92 +++++++++++------------------------ UI/SpecialVisualizations.ts | 14 ++++-- css/ReviewElement.css | 1 + index.css | 61 +++++++---------------- index.html | 3 ++ package-lock.json | 27 ++++++++++ package.json | 5 +- test.html | 3 ++ test.ts | 47 +++++------------- 15 files changed, 164 insertions(+), 186 deletions(-) create mode 100644 UI/Image/Attribution.ts diff --git a/Logic/Actors/ImageSearcher.ts b/Logic/Actors/ImageSearcher.ts index fa650fb..6562b7e 100644 --- a/Logic/Actors/ImageSearcher.ts +++ b/Logic/Actors/ImageSearcher.ts @@ -17,17 +17,17 @@ import {UIEventSource} from "../UIEventSource"; * Note that this list is embedded into an UIEVentSource, ready to put it into a carousel. * */ -export class ImageSearcher { +export class ImageSearcher extends UIEventSource<{ key: string, url: string }[]>{ - public readonly images = new UIEventSource<{ key: string, url: string }[]>([]); private readonly _wdItem = new UIEventSource(""); private readonly _commons = new UIEventSource(""); constructor(tags: UIEventSource, imagePrefix = "image", loadSpecial = true) { + super([]) const self = this; function AddImages(images: { key: string, url: string }[]) { - const oldUrls = self.images.data.map(kurl => kurl.url); + const oldUrls = self.data.map(kurl => kurl.url); let somethingChanged = false; for (const image of images) { const url = image.url; @@ -41,11 +41,11 @@ export class ImageSearcher { continue; } - self.images.data.push(image); + self.data.push(image); somethingChanged = true; } if (somethingChanged) { - self.images.ping(); + self.ping(); } } diff --git a/UI/Image/Attribution.ts b/UI/Image/Attribution.ts new file mode 100644 index 0000000..3718059 --- /dev/null +++ b/UI/Image/Attribution.ts @@ -0,0 +1,18 @@ +import {UIElement} from "../UIElement"; +import Combine from "../Base/Combine"; +import Translations from "../i18n/Translations"; + +export default class Attribution extends Combine { + + constructor(author: UIElement | string, license: UIElement | string, icon: UIElement) { + 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 ?? "")) + ]).SetClass("flex flex-col") + ]); + this.SetClass("flex flex-row bg-black text-white text-sm absolute bottom-0 left-0 p-0.5 rounded"); + } + +} \ No newline at end of file diff --git a/UI/Image/DeleteImage.ts b/UI/Image/DeleteImage.ts index 7e5a2c1..09a5e25 100644 --- a/UI/Image/DeleteImage.ts +++ b/UI/Image/DeleteImage.ts @@ -42,7 +42,7 @@ export default class DeleteImage extends UIElement { } InnerRender(): string { - if(!State.state.featureSwitchUserbadge.data){ + if(! State.state?.featureSwitchUserbadge?.data){ return ""; } diff --git a/UI/Image/ImageCarousel.ts b/UI/Image/ImageCarousel.ts index 6388aa2..bca28e0 100644 --- a/UI/Image/ImageCarousel.ts +++ b/UI/Image/ImageCarousel.ts @@ -1,5 +1,4 @@ import {UIElement} from "../UIElement"; -import {ImageSearcher} from "../../Logic/Actors/ImageSearcher"; import {SlideShow} from "./SlideShow"; import {UIEventSource} from "../../Logic/UIEventSource"; import Combine from "../Base/Combine"; @@ -9,31 +8,31 @@ import {ImgurImage} from "./ImgurImage"; import {MapillaryImage} from "./MapillaryImage"; import {SimpleImageElement} from "./SimpleImageElement"; - export class ImageCarousel extends UIElement{ public readonly slideshow: UIElement; - constructor(tags: UIEventSource, imagePrefix: string = "image", loadSpecial: boolean =true) { - super(tags); - const searcher : UIEventSource<{url:string}[]> = new ImageSearcher(tags, imagePrefix, loadSpecial).images; - const uiElements = searcher.map((imageURLS: {key: string, url:string}[]) => { + constructor(images: UIEventSource<{key: string, url:string}[]>, tags: UIEventSource) { + super(images); + const uiElements = images.map((imageURLS: {key: string, url:string}[]) => { const uiElements: UIElement[] = []; for (const url of imageURLS) { - let image = ImageCarousel.CreateImageElement(url.url); + let image = ImageCarousel.CreateImageElement(url.url) if(url.key !== undefined){ image = new Combine([ image, new DeleteImage(url.key, tags) ]); } + image + .SetClass("w-full block") uiElements.push(image); } return uiElements; }); this.slideshow = new SlideShow(uiElements).HideOnEmpty(true); - + this.SetClass("block image-carousel-marker"); } /*** diff --git a/UI/Image/ImgurImage.ts b/UI/Image/ImgurImage.ts index 70cfb7f..e8639da 100644 --- a/UI/Image/ImgurImage.ts +++ b/UI/Image/ImgurImage.ts @@ -2,6 +2,9 @@ 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 {SimpleImageElement} from "./SimpleImageElement"; export class ImgurImage extends UIElement { @@ -33,21 +36,20 @@ export class ImgurImage extends UIElement { } InnerRender(): string { - const image = ``; + const image = new SimpleImageElement( new UIEventSource (this._imageLocation)); if(this._imageMeta.data === null){ - return image; + return image.Render(); } + + const meta = this._imageMeta.data; + return new Combine([ + image, + new Attribution(meta.artist, meta.license, undefined), + + ]).SetClass('block relative') + .Render(); - const attribution = - "" + (this._imageMeta.data.artist ?? "") + "" + " " + (this._imageMeta.data.licenseShortName ?? "") + ""; - - return "
" + - image + - "
" + - attribution + - "
" + - "
"; } diff --git a/UI/Image/MapillaryImage.ts b/UI/Image/MapillaryImage.ts index e3b9c5b..2c83f09 100644 --- a/UI/Image/MapillaryImage.ts +++ b/UI/Image/MapillaryImage.ts @@ -3,6 +3,9 @@ import {UIEventSource} from "../../Logic/UIEventSource"; import {LicenseInfo} from "../../Logic/Web/Wikimedia"; import {Mapillary} from "../../Logic/Web/Mapillary"; import Svg from "../../Svg"; +import {SimpleImageElement} from "./SimpleImageElement"; +import Combine from "../Base/Combine"; +import Attribution from "./Attribution"; export class MapillaryImage extends UIElement { @@ -39,22 +42,21 @@ export class MapillaryImage extends UIElement { InnerRender(): string { const url = `https://images.mapillary.com/${this._imageLocation}/thumb-640.jpg?client_id=TXhLaWthQ1d4RUg0czVxaTVoRjFJZzowNDczNjUzNmIyNTQyYzI2`; - const image = ``; + const image =new SimpleImageElement(new UIEventSource(url)) - if (this._imageMeta === undefined || this._imageMeta.data === null) { - return image; + if (!this._imageMeta?.data ) { + return image.Render(); } + + const meta = this._imageMeta.data; - const attribution = - "" + (this._imageMeta.data.artist ?? "") + "" + " " + (this._imageMeta.data.licenseShortName ?? "") + ""; - - return "
" + - image + - "
" + - Svg.mapillary_ui().Render() + - attribution + - "
" + - "
"; + + return new Combine([ + image, + new Attribution(meta.artist, meta.license, Svg.mapillary_svg()) + + ]).SetClass("relative block").Render(); + } diff --git a/UI/Image/SlideShow.ts b/UI/Image/SlideShow.ts index 3a36c36..3168fa6 100644 --- a/UI/Image/SlideShow.ts +++ b/UI/Image/SlideShow.ts @@ -1,86 +1,32 @@ import {UIEventSource} from "../../Logic/UIEventSource"; import {UIElement} from "../UIElement"; import Combine from "../Base/Combine"; -import Svg from "../../Svg"; +// @ts-ignore +import $ from "jquery" export class SlideShow extends UIElement { private readonly _embeddedElements: UIEventSource - public readonly _currentSlide: UIEventSource = new UIEventSource(0); - private _prev: UIElement; - private _next: UIElement; - constructor( embeddedElements: UIEventSource) { super(embeddedElements); this._embeddedElements = embeddedElements; - this.ListenTo(this._currentSlide); - this._embeddedElements - .stabilized(1000) - .addCallback(embedded => { - // Always move to the last image - but at most once per second - this._currentSlide.setData(this._embeddedElements.data.length - 1); - }); - - this.dumbMode = false; - const self = this; - this._prev = new Combine([ - "
", - Svg.arrow_left_smooth_img]) - .SetClass("prev-button") - .onClick(() => { - const current = self._currentSlide.data; - self.MoveTo(current - 1); - }); - this._next = new Combine([ - "
", - Svg.arrow_right_smooth_img]) - .SetClass("next-button") - .onClick(() => { - const current = self._currentSlide.data; - self.MoveTo(current + 1); - }); + this._embeddedElements.addCallbackAndRun(elements => { + for (const element of elements ?? []) { + element.SetClass("slick-carousel-content") + } + }) } InnerRender(): string { - if (this._embeddedElements.data.length == 0) { - return ""; - } - - if (this._embeddedElements.data.length == 1) { - return "
" + - this._embeddedElements.data[0].Render() + - "
"; - } - - - let slides = "" - for (let i = 0; i < this._embeddedElements.data.length; i++) { - let embeddedElement = this._embeddedElements.data[i]; - let state = "hidden" - if (this._currentSlide.data === i) { - state = "active-slide"; - } - slides += "
" + embeddedElement.Render() + "
\n"; - } - return new Combine([ - this._prev - , "
", slides, "
" - , this._next]) - .SetClass('image-slideshow') + return new Combine( + this._embeddedElements.data, + ).SetClass("block slick-carousel") .Render(); } - public MoveTo(index: number) { - if (index < 0) { - index = this._embeddedElements.data.length - 1; - } - index = index % this._embeddedElements.data.length; - this._currentSlide.setData(index); - } - Update() { super.Update(); for (const uiElement of this._embeddedElements.data) { @@ -88,4 +34,22 @@ export class SlideShow extends UIElement { } } + protected InnerUpdate(htmlElement: HTMLElement) { + super.InnerUpdate(htmlElement); + require("slick-carousel") + if(this._embeddedElements.data.length == 0){ + return; + } + $('.slick-carousel').not('.slick-initialized').slick({ + // autoplay: true, + arrows: true, + dots: true, + lazyLoad: 'progressive', + variableWidth: true, + centerMode: true, + centerPadding: "60px", + adaptive: true + }); + } + } \ No newline at end of file diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts index 41d3dd2..52dbc90 100644 --- a/UI/SpecialVisualizations.ts +++ b/UI/SpecialVisualizations.ts @@ -18,6 +18,7 @@ import ReviewForm from "./Reviews/ReviewForm"; import OpeningHoursVisualization from "./OpeningHours/OhVisualization"; import State from "../State"; +import {ImageSearcher} from "../Logic/Actors/ImageSearcher"; export class SubstitutedTranslation extends UIElement { private readonly tags: UIEventSource; @@ -40,11 +41,14 @@ export class SubstitutedTranslation extends UIElement { self.content = self.CreateContent(); self.Update(); }); - + this.SetClass("block w-full") } InnerRender(): string { - return new Combine(this.content).Render(); + if(this.content.length == 1){ + return this.content[0].Render(); + } + return new Combine(this.content).SetClass("block w-full").Render(); } private CreateContent(): UIElement[] { @@ -145,7 +149,11 @@ export default class SpecialVisualizations { doc: "Also include images given via 'Wikidata', 'wikimedia_commons' and 'mapillary" }], constr: (state: State,tags, args) => { - return new ImageCarousel(tags, args[0], args[1].toLowerCase() === "true"); + const imagePrefix = args[0]; + const loadSpecial = args[1].toLowerCase() === "true"; + const searcher : UIEventSource<{ key: string, url: string }[]> = new ImageSearcher(tags, imagePrefix, loadSpecial); + + return new ImageCarousel(searcher, tags); } }, diff --git a/css/ReviewElement.css b/css/ReviewElement.css index 4219c09..d480def 100644 --- a/css/ReviewElement.css +++ b/css/ReviewElement.css @@ -81,6 +81,7 @@ .review-attribution img { height: 3em; + width: auto; margin-left: 0.5em; } diff --git a/index.css b/index.css index 787fee0..8733825 100644 --- a/index.css +++ b/index.css @@ -71,12 +71,28 @@ --shadow-color: #00000066; --variable-title-height: 0px; /* Set by javascript */ --return-to-the-map-height: 2em; + + --image-carousel-height: 400px; } -.hide-when-fullscreen-is-shown { +.clutter { /*Clutter is actually a class indicating that the element should be hidden when a scrollableFullScreen is opened It doesn't actually define any rules*/ } +.slick-carousel-content { + width: 300px; + max-height: var(--image-carousel-height); + display: block; + margin-left: 10px; +} + +.slick-carousel-content img { + /** +Workaround to patch images within a slick carousel + */ + height: var(--image-carousel-height); + width: auto; +} html, body { height: 100%; @@ -416,49 +432,6 @@ a { } -.imgWithAttr { - max-height: 20em; - - position: relative; - overflow: hidden; - -} - -.attribution { - background-color: rgba(0, 0, 0, 0.5); - color: white; - font-weight: bold; - font-size: smaller; - - position: absolute; - bottom: 0; - left: 6em; /* Offset for the go left button*/ - padding: 0.25em; - margin-bottom: 0.25em; - border-radius: 0.5em; - -} - -.attribution img { - height: 1.2em !important; - padding-right: 0.5em; - padding-left: 0.2em; - -} - -.attribution-author { - display: inline-block; -} - -.license { - font-size: small; - font-weight: lighter; -} - -.attribution a { - color: white; -} - /***************** Info box (box containing features and questions ******************/ diff --git a/index.html b/index.html index ceb9d24..dc84ec5 100644 --- a/index.html +++ b/index.html @@ -14,6 +14,9 @@ + + + diff --git a/package-lock.json b/package-lock.json index f2eaa5b..4e97fe1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3745,6 +3745,14 @@ "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-1.0.6.tgz", "integrity": "sha512-Xqg/lIZMrUd0VRmSRbCAewtwGZiAk3mEUDvV4op1tGl+LvyPcb/MIOSxTl9z+9+J+R4/vpjiCAT4xeKzH9ji1w==" }, + "@types/jquery": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.5.tgz", + "integrity": "sha512-6RXU9Xzpc6vxNrS6FPPapN1SxSHgQ336WC6Jj/N8q30OiaBZ00l1GBgeP7usjVZPivSkGUfL1z/WW6TX989M+w==", + "requires": { + "@types/sizzle": "*" + } + }, "@types/leaflet": { "version": "1.5.17", "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.5.17.tgz", @@ -3788,6 +3796,20 @@ "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz", "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==" }, + "@types/sizzle": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.2.tgz", + "integrity": "sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg==" + }, + "@types/slick-carousel": { + "version": "1.6.34", + "resolved": "https://registry.npmjs.org/@types/slick-carousel/-/slick-carousel-1.6.34.tgz", + "integrity": "sha512-LdsK0BLNYqe3uBuWCBwzGQVy+8g8LDe3u1XGlaOelrDD07cjJGVVoKBSyKieK4dmfqilmP+DJp7lNRaTI7rRAg==", + "dev": true, + "requires": { + "@types/jquery": "*" + } + }, "@types/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", @@ -10562,6 +10584,11 @@ "resolved": "https://registry.npmjs.org/skmeans/-/skmeans-0.9.7.tgz", "integrity": "sha512-hNj1/oZ7ygsfmPZ7ZfN5MUBRoGg1gtpnImuJBgLO0ljQ67DtJuiQaiYdS4lUA6s0KCwnPhGivtC/WRwIZLkHyg==" }, + "slick-carousel": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/slick-carousel/-/slick-carousel-1.8.1.tgz", + "integrity": "sha512-XB9Ftrf2EEKfzoQXt3Nitrt/IPbT+f1fgqBdoxO3W/+JYvtEOW6EgxnWfr9GH6nmULv7Y2tPmEX3koxThVmebA==" + }, "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", diff --git a/package.json b/package.json index 3b43846..3cec0ba 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "license": "GPL", "dependencies": { "@tailwindcss/postcss7-compat": "^2.0.2", + "@types/jquery": "^3.5.5", "@types/leaflet-markercluster": "^1.0.3", "@types/leaflet-providers": "^1.2.0", "@types/leaflet.markercluster": "^1.4.3", @@ -52,13 +53,15 @@ "osmtogeojson": "^3.0.0-beta.4", "parcel": "^1.12.4", "postcss": "^7.0.35", - "tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.0.2", "sharp": "^0.27.0", + "slick-carousel": "^1.8.1", + "tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.0.2", "turf": "^3.0.14" }, "devDependencies": { "@babel/polyfill": "^7.10.4", "@types/node": "^7.0.5", + "@types/slick-carousel": "^1.6.34", "assert": "^2.0.0", "fs": "0.0.1-security", "marked": "^1.1.1", diff --git a/test.html b/test.html index cd57114..62745b9 100644 --- a/test.html +++ b/test.html @@ -9,6 +9,9 @@ + + + diff --git a/test.ts b/test.ts index d4ebb85..878126b 100644 --- a/test.ts +++ b/test.ts @@ -1,44 +1,19 @@ //* -import MangroveReviews from "./Logic/Web/MangroveReviews"; -import ReviewElement from "./UI/Reviews/ReviewElement"; + +import {ImageCarousel} from "./UI/Image/ImageCarousel"; import {UIEventSource} from "./Logic/UIEventSource"; -import ReviewForm from "./UI/Reviews/ReviewForm"; -import Combine from "./UI/Base/Combine"; -import {FixedUiElement} from "./UI/Base/FixedUiElement"; -/* -window.setTimeout( - () => { -mangroveReviews.AddReview({ - comment: "These are liars - not even an island here!", - author: "Lost Tourist", - date: new Date(), - affiliated: false, - rating: 10 -}, (() => {alert("Review added");return undefined;})); - - }, 1000 -) +const images = new UIEventSource<{ url: string, key: string }[]>( + [{url: "https://2.bp.blogspot.com/-fQiZkz9Zlzg/T_xe2X2Ia3I/AAAAAAAAA0Q/VPS8Mb8xtIQ/s1600/cat+15.jpg", key: "image:1"}, + { + url: "https://www.mapillary.com/map/im/VEOhKqPcJMuT4F2olz_wHQ", + key: "mapillary" + }, + {url: "https://i.imgur.com/mWlghx0.jpg", key: "image:1"}]) +new ImageCarousel(images, new UIEventSource({"image:1":"https://2.bp.blogspot.com/-fQiZkz9Zlzg/T_xe2X2Ia3I/AAAAAAAAA0Q/VPS8Mb8xtIQ/s1600/cat+15.jpg"})) + .AttachTo("maindiv") -window.setTimeout( - () => { - mangroveReviews.AddReview({ - comment: "Excellent conditions to measure weather!!", - author: "Weather-Boy", - date: new Date(), - affiliated: true, - rating: 90 - }, (() => { - alert("Review added"); - return undefined; - })); - - }, 1000 -) -*/ /*/ - - import {Utils} from "./Utils"; import {FixedUiElement} from "./UI/Base/FixedUiElement";