Support slideshow layout.

This commit is contained in:
John Preston 2024-02-13 14:55:27 +04:00
parent f9069144e5
commit fae10cfa6b
3 changed files with 180 additions and 129 deletions

View File

@ -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;

View File

@ -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) + '%';
}

View File

@ -69,6 +69,9 @@ private:
const QVector<MTPPageBlock> &list,
const std::vector<QSize> &dimensions,
int offset = 0);
[[nodiscard]] QByteArray slideshow(
const QVector<MTPPageBlock> &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<QSize> computeCollageDimensions(
const QVector<MTPPageBlock> &items);
[[nodiscard]] QSize computeSlideshowDimensions(
const QVector<MTPPageBlock> &items);
const Options _options;
@ -158,9 +165,6 @@ private:
Prepared _result;
bool _rtl = false;
bool _captionAsTitle = false;
bool _captionWrapped = false;
base::flat_map<uint64, Photo> _photosById;
base::flat_map<uint64, Document> _documentsById;
@ -186,6 +190,17 @@ private:
return voids.contains(name);
}
[[nodiscard]] QByteArray ArrowSvg(bool left) {
const auto rotate = QByteArray(left ? "180" : "0");
return R"(
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path
d="M14.9972363,18 L9.13865768,12.1414214 C9.06055283,12.0633165 9.06055283,11.9366835 9.13865768,11.8585786 L14.9972363,6 L14.9972363,6"
transform="translate(11.997236, 12) scale(-1, -1) rotate()" + rotate + ") translate(-11.997236, -12)" + R"(">
</path>
</svg>)";
}
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<MTPPageBlock> &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 + "</" + name + '>');
}
@ -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<QSize> Parser::computeCollageDimensions(
const QVector<MTPPageBlock> &items) {
auto result = std::vector<QSize>(items.size());
if (items.size() < 2) {
return {};
}
auto result = std::vector<QSize>(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<QSize> Parser::computeCollageDimensions(
return result;
}
QSize Parser::computeSlideshowDimensions(
const QVector<MTPPageBlock> &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) {