diff --git a/Telegram/Resources/iv_html/page.css b/Telegram/Resources/iv_html/page.css index 58ac7a6d16..8569223336 100644 --- a/Telegram/Resources/iv_html/page.css +++ b/Telegram/Resources/iv_html/page.css @@ -598,70 +598,22 @@ figure.slideshow-wrap { position: relative; } figure.slideshow { - position: relative; + position: absolute; + top: 0px; white-space: nowrap; width: 100%; background: #000; overflow: hidden; } -figure.slideshow > figure { +figure.slideshow a { + transition: margin 200ms ease-in-out; +} +figure.slideshow .photo-wrap, +figure.slideshow .video-wrap { position: static !important; display: inline-block; - width: 100%; + margin: 0; vertical-align: middle; - transition: margin .3s; -} -figure.slideshow > figure figcaption { - box-sizing: border-box; - position: absolute; - bottom: 0; - width: 100%; - padding-bottom: 36px; -} -figure.slideshow > figure figcaption:after { - content: ''; - display: block; - position: absolute; - left: 0; - right: 0; - bottom: 0; - top: -75px; - background: -moz-linear-gradient(top,rgba(64,64,64,0),rgba(64,64,64,.55)); - background: -webkit-gradient(linear,0 0,0 100%,from(rgba(64,64,64,0)),to(rgba(64,64,64,.55))); - background: -o-linear-gradient(rgba(64,64,64,0),rgba(64,64,64,.55)); - pointer-events: none; -} -figure.slideshow > figure figcaption > span, -figure.slideshow > figure figcaption > cite { - position: relative; - color: #fff; - text-shadow: 0 1px rgba(0, 0, 0, .4); - z-index: 1; - display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -figure.slideshow > figure figcaption > span { - display: -webkit-box; - max-height: 3.8em; - -webkit-line-clamp: 3; - -webkit-box-orient: vertical; - white-space: pre-wrap; -} -figure.slideshow > figure figcaption code { - text-shadow: none; - background: rgba(204, 204, 204, .7); - color: #fff; -} -figure.slideshow > figure figcaption mark { - text-shadow: none; - background: rgba(33, 123, 134, .7); - color: #fff; -} -figure.slideshow > figure figcaption a, -figure.slideshow > figure figcaption a:hover { - color: #66baff; } .slideshow-buttons { position: absolute; @@ -672,8 +624,8 @@ figure.slideshow > figure figcaption a:hover { z-index: 3; } .slideshow-buttons > fieldset { - padding: 0 10px 20px; - margin: 0 0 -20px; + padding: 0; + margin: 0; border: none; line-height: 0; overflow: hidden; @@ -702,6 +654,54 @@ figure.slideshow > figure figcaption a:hover { .slideshow-buttons input:checked ~ i { opacity: 1; } +.slideshow-next, +.slideshow-prev { + position: absolute; + z-index: 4; + top: 0; + width: 25%; + max-width: 128px; + height: 100%; + cursor: pointer; + transition: opacity 200ms ease-in-out; + user-select: none; + opacity: 0.6; +} +.slideshow-next { + right: 0; + background: linear-gradient(to right, rgba(0,0,0,0) 0%, rgba(0,0,0,0.6) 100%); +} +.slideshow-prev { + left: 0; + background: linear-gradient(to left, rgba(0,0,0,0) 0%, rgba(0,0,0,0.6) 100%); +} +.slideshow-next:hover { + opacity: 1; +} +.slideshow-prev:hover { + opacity: 1; +} +.slideshow-prev svg, +.slideshow-next svg { + fill: none; + top: calc(50% - 12px); + position: absolute; + z-index: 5; + width: 24px; + height: 24px; + pointer-events: none; +} +.slideshow-prev svg { + left: calc(min(50% - 12px, 20px)); +} +.slideshow-next svg { + right: calc(min(50% - 12px, 20px)); +} +.slideshow-prev path, +.slideshow-next path { + stroke-width: 1.4; + stroke: #fff; +} figure.collage-wrap { margin: 0px 12px; diff --git a/Telegram/Resources/iv_html/page.js b/Telegram/Resources/iv_html/page.js index 181a114987..dca833feed 100644 --- a/Telegram/Resources/iv_html/page.js +++ b/Telegram/Resources/iv_html/page.js @@ -106,14 +106,16 @@ var IV = { } } }, - slideshowSlide: function(el, next) { + slideshowSlide: function(el, delta) { var dir = window.getComputedStyle(el, null).direction || 'ltr'; var marginProp = dir == 'rtl' ? 'marginRight' : 'marginLeft'; - if (next) { - var s = el.previousSibling.s; - s.value = (+s.value + 1 == s.length) ? 0 : +s.value + 1; + if (delta) { + var form = el.parentNode.firstChild; + var s = form.s; + const next = +s.value + delta; + s.value = (next == s.length) ? 0 : (next == -1) ? (s.length - 1) : next; s.forEach(function(el){ el.checked && el.parentNode.scrollIntoView && el.parentNode.scrollIntoView({behavior: 'smooth', block: 'center', inline: 'center'}); }); - el.firstChild.style[marginProp] = (-100 * s.value) + '%'; + form.nextSibling.firstChild.style[marginProp] = (-100 * s.value) + '%'; } else { el.form.nextSibling.firstChild.style[marginProp] = (-100 * el.value) + '%'; } diff --git a/Telegram/SourceFiles/iv/iv_prepare.cpp b/Telegram/SourceFiles/iv/iv_prepare.cpp index ebfbadd230..a9186997f5 100644 --- a/Telegram/SourceFiles/iv/iv_prepare.cpp +++ b/Telegram/SourceFiles/iv/iv_prepare.cpp @@ -69,6 +69,9 @@ private: const QVector &list, const std::vector &dimensions, int offset = 0); + [[nodiscard]] QByteArray slideshow( + const QVector &list, + QSize dimensions); [[nodiscard]] QByteArray block(const MTPDpageBlockUnsupported &data); [[nodiscard]] QByteArray block(const MTPDpageBlockTitle &data); @@ -87,11 +90,13 @@ private: [[nodiscard]] QByteArray block( const MTPDpageBlockPhoto &data, const Ui::GroupMediaLayout &layout = {}, - QSize outer = {}); + QSize outer = {}, + int slideshowIndex = -1); [[nodiscard]] QByteArray block( const MTPDpageBlockVideo &data, const Ui::GroupMediaLayout &layout = {}, - QSize outer = {}); + QSize outer = {}, + int slideshowIndex = -1); [[nodiscard]] QByteArray block(const MTPDpageBlockCover &data); [[nodiscard]] QByteArray block(const MTPDpageBlockEmbed &data); [[nodiscard]] QByteArray block(const MTPDpageBlockEmbedPost &data); @@ -151,6 +156,8 @@ private: [[nodiscard]] std::vector computeCollageDimensions( const QVector &items); + [[nodiscard]] QSize computeSlideshowDimensions( + const QVector &items); const Options _options; @@ -158,9 +165,6 @@ private: Prepared _result; - bool _rtl = false; - bool _captionAsTitle = false; - bool _captionWrapped = false; base::flat_map _photosById; base::flat_map _documentsById; @@ -186,6 +190,17 @@ private: return voids.contains(name); } +[[nodiscard]] QByteArray ArrowSvg(bool left) { + const auto rotate = QByteArray(left ? "180" : "0"); + return R"( + + + +)"; +} + Parser::Parser(const Source &source, const Options &options) : _options(options) { process(source); @@ -292,6 +307,56 @@ QByteArray Parser::collage( return wrapped; } +QByteArray Parser::slideshow( + const QVector &list, + QSize dimensions) { + auto result = QByteArray(); + for (auto i = 0, count = int(list.size()); i != count; ++i) { + list[i].match([&](const MTPDpageBlockPhoto &data) { + result += block(data, {}, dimensions); + }, [&](const MTPDpageBlockVideo &data) { + result += block(data, {}, dimensions); + }, [](const auto &) { + Unexpected("Block type in collage layout."); + }); + } + + auto inputs = QByteArrayList(); + for (auto i = 0; i != int(list.size()); ++i) { + auto attributes = Attributes{ + { "type", "radio" }, + { "name", "s" }, + { "value", Number(i) }, + { "onchange", "return IV.slideshowSlide(this);" }, + }; + if (!i) { + attributes.push_back({ "checked", std::nullopt }); + } + inputs.append(tag("label", tag("input", attributes, tag("i")))); + } + const auto form = tag( + "form", + { { "class", "slideshow-buttons" } }, + tag("fieldset", inputs.join(QByteArray()))); + const auto navigation = tag("a", { + { "class", "slideshow-prev" }, + { "onclick", "IV.slideshowSlide(this, -1);" }, + }, ArrowSvg(true)) + tag("a", { + { "class", "slideshow-next" }, + { "onclick", "IV.slideshowSlide(this, 1);" }, + }, ArrowSvg(false)); + auto wrapStyle = "padding-top: calc(min(" + + Percent(dimensions.height() / float64(dimensions.width())) + + "%, 480px));"; + result = form + tag("figure", { + { "class", "slideshow" }, + }, result) + navigation; + return tag("figure", { + { "class", "slideshow-wrap" }, + { "style", wrapStyle }, + }, result); +} + QByteArray Parser::block(const MTPDpageBlockUnsupported &data) { return "Unsupported."_q; } @@ -375,8 +440,10 @@ QByteArray Parser::block(const MTPDpageBlockPullquote &data) { QByteArray Parser::block( const MTPDpageBlockPhoto &data, const Ui::GroupMediaLayout &layout, - QSize outer) { + QSize outer, + int slideshowIndex) { const auto collage = !layout.geometry.isEmpty(); + const auto slideshow = !collage && !outer.isEmpty(); const auto photo = photoById(data.vphoto_id().v); if (!photo.id) { return "Photo not found."; @@ -390,7 +457,7 @@ QByteArray Parser::block( + "top: " + Percent(layout.geometry.y() * hcoef) + "%; " + "width: " + Percent(layout.geometry.width() * wcoef) + "%; " + "height: " + Percent(layout.geometry.height() * hcoef) + "%"; - } else if (photo.width) { + } else if (!slideshow && photo.width) { wrapStyle += "max-width:" + Number(photo.width) + "px"; } const auto dimension = collage @@ -420,16 +487,6 @@ QByteArray Parser::block( { "class", "photo-wrap" }, { "style", wrapStyle } }; - if (_captionAsTitle) { - const auto caption = plain(data.vcaption().data().vtext()); - const auto credit = plain(data.vcaption().data().vtext()); - if (!caption.isEmpty() || !credit.isEmpty()) { - const auto title = (!caption.isEmpty() && !credit.isEmpty()) - ? (caption + " / " + credit) - : (caption + credit); - attributes.push_back({ "title", title }); - } - } auto result = tag("div", attributes, inner); const auto href = data.vurl() @@ -439,20 +496,19 @@ QByteArray Parser::block( result = tag("a", { { "href", href }, { "data-context", data.vurl() ? QByteArray() : "viewer-photo" + id }, - }, result); - if (!_captionAsTitle) { - result += caption(data.vcaption()); - } + }, result) + caption(data.vcaption()); return result; } QByteArray Parser::block( const MTPDpageBlockVideo &data, const Ui::GroupMediaLayout &layout, - QSize outer) { + QSize outer, + int slideshowIndex) { const auto collage = !layout.geometry.isEmpty(); const auto collageSmall = collage && (layout.geometry.width() < outer.width()); + const auto slideshow = !collage && !outer.isEmpty(); const auto video = documentById(data.vvideo_id().v); if (!video.id) { return "Video not found."; @@ -512,16 +568,6 @@ QByteArray Parser::block( { "class", "video-wrap" }, { "style", wrapStyle }, }; - if (_captionAsTitle) { - const auto caption = plain(data.vcaption().data().vtext()); - const auto credit = plain(data.vcaption().data().vtext()); - if (!caption.isEmpty() || !credit.isEmpty()) { - const auto title = (!caption.isEmpty() && !credit.isEmpty()) - ? (caption + " / " + credit) - : (caption + credit); - attributes.push_back({ "title", title }); - } - } auto result = tag("div", attributes, inner); if (data.is_autoplay() || collageSmall) { const auto id = Number(video.id); @@ -531,9 +577,7 @@ QByteArray Parser::block( { "data-context", "viewer-video" + id }, }, result); } - if (!_captionAsTitle) { - result += caption(data.vcaption()); - } + result += caption(data.vcaption()); return result; } @@ -644,32 +688,12 @@ QByteArray Parser::block(const MTPDpageBlockCollage &data) { } QByteArray Parser::block(const MTPDpageBlockSlideshow &data) { - auto inputs = QByteArrayList(); - auto i = 0; - for (auto i = 0; i != int(data.vitems().v.size()); ++i) { - auto attributes = Attributes{ - { "type", "radio" }, - { "name", "s" }, - { "value", Number(i) }, - { "onchange", "return IV.slideshowSlide(this);" }, - }; - if (!i) { - attributes.push_back({ "checked", std::nullopt }); - } - inputs.append(tag("label", tag("input", attributes, tag("i")))); + const auto &items = data.vitems().v; + const auto dimensions = computeSlideshowDimensions(items); + if (dimensions.isEmpty()) { + return list(data.vitems()); } - const auto form = tag( - "form", - { { "class", "slideshow-buttons" } }, - tag("fieldset", inputs.join(QByteArray()))); - auto inner = form + tag("figure", { - { "class", "slideshow" }, - { "onclick", "return IV.slideshowSlide(this, 1);" }, - }, list(data.vitems())); - auto result = tag( - "figure", - { { "class", "slideshow-wrap" } }, - inner); + const auto result = slideshow(items, dimensions); return tag("figure", result + caption(data.vcaption())); } @@ -909,7 +933,7 @@ QByteArray Parser::tag( list.push_back(' ' + name + (value ? "=\"" + *value + "\"" : "")); } const auto serialized = list.join(QByteArray()); - return IsVoidElement(name) + return (IsVoidElement(name) && body.isEmpty()) ? ('<' + name + serialized + " />") : ('<' + name + serialized + '>' + body + "'); } @@ -1024,9 +1048,6 @@ QByteArray Parser::plain(const MTPRichText &text) { QByteArray Parser::caption(const MTPPageCaption &caption) { auto text = rich(caption.data().vtext()); const auto credit = rich(caption.data().vcredit()); - if (_captionWrapped && !text.isEmpty()) { - text = tag("span", text); - } if (!credit.isEmpty()) { text += tag("cite", credit); } else if (text.isEmpty()) { @@ -1170,12 +1191,11 @@ QByteArray Parser::resource(QByteArray id) { std::vector Parser::computeCollageDimensions( const QVector &items) { - auto result = std::vector(items.size()); if (items.size() < 2) { return {}; } + auto result = std::vector(items.size()); for (auto i = 0, count = int(items.size()); i != count; ++i) { - auto size = QSize(); items[i].match([&](const MTPDpageBlockPhoto &data) { const auto photo = photoById(data.vphoto_id().v); if (photo.id && photo.width > 0 && photo.height > 0) { @@ -1194,6 +1214,35 @@ std::vector Parser::computeCollageDimensions( return result; } +QSize Parser::computeSlideshowDimensions( + const QVector &items) { + if (items.size() < 2) { + return {}; + } + auto result = QSize(); + for (const auto &item : items) { + auto size = QSize(); + item.match([&](const MTPDpageBlockPhoto &data) { + const auto photo = photoById(data.vphoto_id().v); + if (photo.id && photo.width > 0 && photo.height > 0) { + size = QSize(photo.width, photo.height); + } + }, [&](const MTPDpageBlockVideo &data) { + const auto document = documentById(data.vvideo_id().v); + if (document.id && document.width > 0 && document.height > 0) { + size = QSize(document.width, document.height); + } + }, [](const auto &) {}); + if (size.isEmpty()) { + return {}; + } else if (result.height() * size.width() + < result.width() * size.height()) { + result = size; + } + } + return result; +} + } // namespace Prepared Prepare(const Source &source, const Options &options) {