mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-16 06:07:06 +02:00
Support slideshow layout.
This commit is contained in:
parent
f9069144e5
commit
fae10cfa6b
3 changed files with 180 additions and 129 deletions
|
@ -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;
|
||||
|
|
|
@ -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) + '%';
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Add table
Reference in a new issue