Add thumbnails to link tooltips if available (#1664)

This feature is off by default and can be enabled in the settings with the "Show link thumbnail" setting. This feature also requires the "Show link info when hovering" setting to be enabled.

thumbnails support is only there for direct image links, twitch clips, and youtube links. can be expanded in the future in the /api repo

Co-authored-by: Rasmus Karlsson <rasmus.karlsson@pajlada.com>
This commit is contained in:
Daniel Pasch 2020-05-10 12:11:10 +02:00 committed by GitHub
parent 1a4a468ab1
commit 8532c6d3bc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 140 additions and 27 deletions

View file

@ -1,6 +1,7 @@
# Changelog
## Unversioned
- Major: We now support image thumbnails coming from the link resolver. This feature is off by default and can be enabled in the settings with the "Show link thumbnail" setting. This feature also requires the "Show link info when hovering" setting to be enabled (#1664)
- Major: Added image upload functionality to i.nuuls.com. This works by dragging and dropping an image into a split, or pasting an image into the text edit field. (#1332)
- Minor: Emotes in the emote popup are now sorted in the same order as the tab completion (#1549)
- Minor: Removed "Online Logs" functionality as services are shut down (#1640)

View file

@ -419,7 +419,8 @@ void MessageBuilder::addLink(const QString &origLink,
LinkResolver::getLinkInfo(
matchedLink, nullptr,
[weakMessage = this->weakOf(), linkMELowercase, linkMEOriginal,
matchedLink](QString tooltipText, Link originalLink) {
matchedLink](QString tooltipText, Link originalLink,
ImagePtr thumbnail) {
auto shared = weakMessage.lock();
if (!shared)
{
@ -436,6 +437,12 @@ void MessageBuilder::addLink(const QString &origLink,
linkMELowercase->setLink(originalLink)->updateLink();
linkMEOriginal->setLink(originalLink)->updateLink();
}
linkMELowercase->setThumbnail(thumbnail);
linkMELowercase->setThumbnailType(
MessageElement::ThumbnailType::Link_Thumbnail);
linkMEOriginal->setThumbnail(thumbnail);
linkMEOriginal->setThumbnailType(
MessageElement::ThumbnailType::Link_Thumbnail);
});
}

View file

@ -40,6 +40,18 @@ MessageElement *MessageElement::setTooltip(const QString &tooltip)
return this;
}
MessageElement *MessageElement::setThumbnail(const ImagePtr &thumbnail)
{
this->thumbnail_ = thumbnail;
return this;
}
MessageElement *MessageElement::setThumbnailType(const ThumbnailType type)
{
this->thumbnailType_ = type;
return this;
}
MessageElement *MessageElement::setTrailingSpace(bool value)
{
this->trailingSpace = value;
@ -51,6 +63,16 @@ const QString &MessageElement::getTooltip() const
return this->tooltip_;
}
const ImagePtr &MessageElement::getThumbnail() const
{
return this->thumbnail_;
}
const MessageElement::ThumbnailType &MessageElement::getThumbnailType() const
{
return this->thumbnailType_;
}
const Link &MessageElement::getLink() const
{
return this->link_;

View file

@ -122,14 +122,23 @@ public:
Update_Images = 4,
Update_All = Update_Text | Update_Emotes | Update_Images
};
enum ThumbnailType : char {
Link_Thumbnail = 1,
};
virtual ~MessageElement();
MessageElement *setLink(const Link &link);
MessageElement *setText(const QString &text);
MessageElement *setTooltip(const QString &tooltip);
MessageElement *setThumbnailType(const ThumbnailType type);
MessageElement *setThumbnail(const ImagePtr &thumbnail);
MessageElement *setTrailingSpace(bool value);
const QString &getTooltip() const;
const ImagePtr &getThumbnail() const;
const ThumbnailType &getThumbnailType() const;
const Link &getLink() const;
bool hasTrailingSpace() const;
MessageElementFlags getFlags() const;
@ -148,6 +157,8 @@ private:
QString text_;
Link link_;
QString tooltip_;
ImagePtr thumbnail_;
ThumbnailType thumbnailType_;
MessageElementFlags flags_;
};

View file

@ -3,6 +3,7 @@
#include "common/Common.hpp"
#include "common/Env.hpp"
#include "common/NetworkRequest.hpp"
#include "messages/Image.hpp"
#include "messages/Link.hpp"
#include "singletons/Settings.hpp"
@ -12,11 +13,11 @@ namespace chatterino {
void LinkResolver::getLinkInfo(
const QString url, QObject *caller,
std::function<void(QString, Link)> successCallback)
std::function<void(QString, Link, ImagePtr)> successCallback)
{
if (!getSettings()->linkInfoTooltip)
{
successCallback("No link info loaded", Link(Link::Url, url));
successCallback("No link info loaded", Link(Link::Url, url), nullptr);
return;
}
// Uncomment to test crashes
@ -25,30 +26,35 @@ void LinkResolver::getLinkInfo(
QUrl::toPercentEncoding(url, "", "/:"))))
.caller(caller)
.timeout(30000)
.onSuccess([successCallback, url](auto result) mutable -> Outcome {
auto root = result.parseJson();
auto statusCode = root.value("status").toInt();
QString response = QString();
QString linkString = url;
if (statusCode == 200)
{
response = root.value("tooltip").toString();
if (getSettings()->unshortLinks)
.onSuccess(
[successCallback, url](NetworkResult result) mutable -> Outcome {
auto root = result.parseJson();
auto statusCode = root.value("status").toInt();
QString response = QString();
QString linkString = url;
ImagePtr thumbnail = nullptr;
if (statusCode == 200)
{
linkString = root.value("link").toString();
response = root.value("tooltip").toString();
thumbnail =
Image::fromUrl({root.value("thumbnail").toString()});
if (getSettings()->unshortLinks)
{
linkString = root.value("link").toString();
}
}
}
else
{
response = root.value("message").toString();
}
successCallback(QUrl::fromPercentEncoding(response.toUtf8()),
Link(Link::Url, linkString));
else
{
response = root.value("message").toString();
}
successCallback(QUrl::fromPercentEncoding(response.toUtf8()),
Link(Link::Url, linkString), thumbnail);
return Success;
})
return Success;
})
.onError([successCallback, url](auto /*result*/) {
successCallback("No link info found", Link(Link::Url, url));
successCallback("No link info found", Link(Link::Url, url),
nullptr);
})
.execute();
// });

View file

@ -3,6 +3,7 @@
#include <QString>
#include <functional>
#include "messages/Image.hpp"
#include "messages/Link.hpp"
namespace chatterino {
@ -10,8 +11,9 @@ namespace chatterino {
class LinkResolver
{
public:
static void getLinkInfo(const QString url, QObject *caller,
std::function<void(QString, Link)> callback);
static void getLinkInfo(
const QString url, QObject *caller,
std::function<void(QString, Link, ImagePtr)> callback);
};
} // namespace chatterino

View file

@ -161,6 +161,7 @@ public:
/// Links
BoolSetting linksDoubleClickOnly = {"/links/doubleClickToOpen", false};
BoolSetting linkInfoTooltip = {"/links/linkInfoTooltip", false};
IntSetting thumbnailSize = {"/appearance/thumbnailSize", 0};
BoolSetting unshortLinks = {"/links/unshortLinks", false};
BoolSetting lowercaseDomains = {"/links/linkLowercase", true};

View file

@ -38,6 +38,14 @@ void TooltipPreviewImage::setImage(ImagePtr image)
this->refreshTooltipWidgetPixmap();
}
void TooltipPreviewImage::setImageScale(int w, int h)
{
this->imageWidth_ = w;
this->imageHeight_ = h;
this->refreshTooltipWidgetPixmap();
}
void TooltipPreviewImage::refreshTooltipWidgetPixmap()
{
auto tooltipWidget = TooltipWidget::instance();
@ -46,7 +54,17 @@ void TooltipPreviewImage::refreshTooltipWidgetPixmap()
{
if (auto pixmap = this->image_->pixmapOrLoad())
{
tooltipWidget->setImage(*pixmap);
if (this->imageWidth_ != 0 && this->imageHeight_)
{
tooltipWidget->setImage(pixmap->scaled(this->imageWidth_,
this->imageHeight_,
Qt::KeepAspectRatio));
}
else
{
tooltipWidget->setImage(*pixmap);
}
this->attemptRefresh = false;
}
else

View file

@ -9,6 +9,7 @@ class TooltipPreviewImage
public:
static TooltipPreviewImage &instance();
void setImage(ImagePtr image);
void setImageScale(int w, int h);
TooltipPreviewImage(const TooltipPreviewImage &) = delete;
@ -17,6 +18,9 @@ private:
private:
ImagePtr image_ = nullptr;
int imageWidth_ = 0;
int imageHeight_ = 0;
std::vector<pajlada::Signals::ScopedConnection> connections_;
// attemptRefresh is set to true in case we want to preview an image that has not loaded yet (if pixmapOrLoad fails)

View file

@ -1259,6 +1259,7 @@ void ChannelView::mouseMoveEvent(QMouseEvent *event)
else
{
auto &tooltipPreviewImage = TooltipPreviewImage::instance();
tooltipPreviewImage.setImageScale(0, 0);
auto emoteElement = dynamic_cast<const EmoteElement *>(
&hoverLayoutElement->getCreator());
auto badgeElement = dynamic_cast<const BadgeElement *>(
@ -1288,7 +1289,22 @@ void ChannelView::mouseMoveEvent(QMouseEvent *event)
}
else
{
tooltipPreviewImage.setImage(nullptr);
auto element = &hoverLayoutElement->getCreator();
auto thumbnailSize = getSettings()->thumbnailSize;
if (thumbnailSize == 0)
{
tooltipPreviewImage.setImage(nullptr);
}
else
{
tooltipPreviewImage.setImage(element->getThumbnail());
if (element->getThumbnailType() ==
MessageElement::ThumbnailType::Link_Thumbnail)
{
tooltipPreviewImage.setImageScale(thumbnailSize,
thumbnailSize);
}
}
}
tooltipWidget->moveTo(this, event->globalPos());

View file

@ -521,6 +521,31 @@ void GeneralPage::initLayout(SettingsLayout &layout)
},
[](auto args) { return fuzzyToFloat(args.value, 63.f); });
layout.addCheckbox("Show link info when hovering", s.linkInfoTooltip);
layout.addDropdown<int>(
"Show link thumbnail", {"Off", "Small", "Medium", "Large"},
s.thumbnailSize,
[](auto val) {
if (val == 0)
return QString("Off");
else if (val == 100)
return QString("Small");
else if (val == 200)
return QString("Medium");
else if (val == 300)
return QString("Large");
else
return QString::number(val);
},
[](auto args) {
if (args.value == "Small")
return 100;
else if (args.value == "Medium")
return 200;
else if (args.value == "Large")
return 300;
return fuzzyToInt(args.value, 0);
});
layout.addCheckbox("Double click to open links and other elements in chat",
s.linksDoubleClickOnly);
layout.addCheckbox("Unshorten links", s.unshortLinks);