mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-13 19:49:51 +01:00
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:
parent
1a4a468ab1
commit
8532c6d3bc
|
@ -1,6 +1,7 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## Unversioned
|
## 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)
|
- 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: 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)
|
- Minor: Removed "Online Logs" functionality as services are shut down (#1640)
|
||||||
|
|
|
@ -419,7 +419,8 @@ void MessageBuilder::addLink(const QString &origLink,
|
||||||
LinkResolver::getLinkInfo(
|
LinkResolver::getLinkInfo(
|
||||||
matchedLink, nullptr,
|
matchedLink, nullptr,
|
||||||
[weakMessage = this->weakOf(), linkMELowercase, linkMEOriginal,
|
[weakMessage = this->weakOf(), linkMELowercase, linkMEOriginal,
|
||||||
matchedLink](QString tooltipText, Link originalLink) {
|
matchedLink](QString tooltipText, Link originalLink,
|
||||||
|
ImagePtr thumbnail) {
|
||||||
auto shared = weakMessage.lock();
|
auto shared = weakMessage.lock();
|
||||||
if (!shared)
|
if (!shared)
|
||||||
{
|
{
|
||||||
|
@ -436,6 +437,12 @@ void MessageBuilder::addLink(const QString &origLink,
|
||||||
linkMELowercase->setLink(originalLink)->updateLink();
|
linkMELowercase->setLink(originalLink)->updateLink();
|
||||||
linkMEOriginal->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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,18 @@ MessageElement *MessageElement::setTooltip(const QString &tooltip)
|
||||||
return this;
|
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)
|
MessageElement *MessageElement::setTrailingSpace(bool value)
|
||||||
{
|
{
|
||||||
this->trailingSpace = value;
|
this->trailingSpace = value;
|
||||||
|
@ -51,6 +63,16 @@ const QString &MessageElement::getTooltip() const
|
||||||
return this->tooltip_;
|
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
|
const Link &MessageElement::getLink() const
|
||||||
{
|
{
|
||||||
return this->link_;
|
return this->link_;
|
||||||
|
|
|
@ -122,14 +122,23 @@ public:
|
||||||
Update_Images = 4,
|
Update_Images = 4,
|
||||||
Update_All = Update_Text | Update_Emotes | Update_Images
|
Update_All = Update_Text | Update_Emotes | Update_Images
|
||||||
};
|
};
|
||||||
|
enum ThumbnailType : char {
|
||||||
|
Link_Thumbnail = 1,
|
||||||
|
};
|
||||||
|
|
||||||
virtual ~MessageElement();
|
virtual ~MessageElement();
|
||||||
|
|
||||||
MessageElement *setLink(const Link &link);
|
MessageElement *setLink(const Link &link);
|
||||||
MessageElement *setText(const QString &text);
|
MessageElement *setText(const QString &text);
|
||||||
MessageElement *setTooltip(const QString &tooltip);
|
MessageElement *setTooltip(const QString &tooltip);
|
||||||
|
MessageElement *setThumbnailType(const ThumbnailType type);
|
||||||
|
MessageElement *setThumbnail(const ImagePtr &thumbnail);
|
||||||
|
|
||||||
MessageElement *setTrailingSpace(bool value);
|
MessageElement *setTrailingSpace(bool value);
|
||||||
const QString &getTooltip() const;
|
const QString &getTooltip() const;
|
||||||
|
const ImagePtr &getThumbnail() const;
|
||||||
|
const ThumbnailType &getThumbnailType() const;
|
||||||
|
|
||||||
const Link &getLink() const;
|
const Link &getLink() const;
|
||||||
bool hasTrailingSpace() const;
|
bool hasTrailingSpace() const;
|
||||||
MessageElementFlags getFlags() const;
|
MessageElementFlags getFlags() const;
|
||||||
|
@ -148,6 +157,8 @@ private:
|
||||||
QString text_;
|
QString text_;
|
||||||
Link link_;
|
Link link_;
|
||||||
QString tooltip_;
|
QString tooltip_;
|
||||||
|
ImagePtr thumbnail_;
|
||||||
|
ThumbnailType thumbnailType_;
|
||||||
MessageElementFlags flags_;
|
MessageElementFlags flags_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include "common/Common.hpp"
|
#include "common/Common.hpp"
|
||||||
#include "common/Env.hpp"
|
#include "common/Env.hpp"
|
||||||
#include "common/NetworkRequest.hpp"
|
#include "common/NetworkRequest.hpp"
|
||||||
|
#include "messages/Image.hpp"
|
||||||
#include "messages/Link.hpp"
|
#include "messages/Link.hpp"
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
|
|
||||||
|
@ -12,11 +13,11 @@ namespace chatterino {
|
||||||
|
|
||||||
void LinkResolver::getLinkInfo(
|
void LinkResolver::getLinkInfo(
|
||||||
const QString url, QObject *caller,
|
const QString url, QObject *caller,
|
||||||
std::function<void(QString, Link)> successCallback)
|
std::function<void(QString, Link, ImagePtr)> successCallback)
|
||||||
{
|
{
|
||||||
if (!getSettings()->linkInfoTooltip)
|
if (!getSettings()->linkInfoTooltip)
|
||||||
{
|
{
|
||||||
successCallback("No link info loaded", Link(Link::Url, url));
|
successCallback("No link info loaded", Link(Link::Url, url), nullptr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Uncomment to test crashes
|
// Uncomment to test crashes
|
||||||
|
@ -25,14 +26,18 @@ void LinkResolver::getLinkInfo(
|
||||||
QUrl::toPercentEncoding(url, "", "/:"))))
|
QUrl::toPercentEncoding(url, "", "/:"))))
|
||||||
.caller(caller)
|
.caller(caller)
|
||||||
.timeout(30000)
|
.timeout(30000)
|
||||||
.onSuccess([successCallback, url](auto result) mutable -> Outcome {
|
.onSuccess(
|
||||||
|
[successCallback, url](NetworkResult result) mutable -> Outcome {
|
||||||
auto root = result.parseJson();
|
auto root = result.parseJson();
|
||||||
auto statusCode = root.value("status").toInt();
|
auto statusCode = root.value("status").toInt();
|
||||||
QString response = QString();
|
QString response = QString();
|
||||||
QString linkString = url;
|
QString linkString = url;
|
||||||
|
ImagePtr thumbnail = nullptr;
|
||||||
if (statusCode == 200)
|
if (statusCode == 200)
|
||||||
{
|
{
|
||||||
response = root.value("tooltip").toString();
|
response = root.value("tooltip").toString();
|
||||||
|
thumbnail =
|
||||||
|
Image::fromUrl({root.value("thumbnail").toString()});
|
||||||
if (getSettings()->unshortLinks)
|
if (getSettings()->unshortLinks)
|
||||||
{
|
{
|
||||||
linkString = root.value("link").toString();
|
linkString = root.value("link").toString();
|
||||||
|
@ -43,12 +48,13 @@ void LinkResolver::getLinkInfo(
|
||||||
response = root.value("message").toString();
|
response = root.value("message").toString();
|
||||||
}
|
}
|
||||||
successCallback(QUrl::fromPercentEncoding(response.toUtf8()),
|
successCallback(QUrl::fromPercentEncoding(response.toUtf8()),
|
||||||
Link(Link::Url, linkString));
|
Link(Link::Url, linkString), thumbnail);
|
||||||
|
|
||||||
return Success;
|
return Success;
|
||||||
})
|
})
|
||||||
.onError([successCallback, url](auto /*result*/) {
|
.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();
|
.execute();
|
||||||
// });
|
// });
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
|
#include "messages/Image.hpp"
|
||||||
#include "messages/Link.hpp"
|
#include "messages/Link.hpp"
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
@ -10,8 +11,9 @@ namespace chatterino {
|
||||||
class LinkResolver
|
class LinkResolver
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static void getLinkInfo(const QString url, QObject *caller,
|
static void getLinkInfo(
|
||||||
std::function<void(QString, Link)> callback);
|
const QString url, QObject *caller,
|
||||||
|
std::function<void(QString, Link, ImagePtr)> callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -161,6 +161,7 @@ public:
|
||||||
/// Links
|
/// Links
|
||||||
BoolSetting linksDoubleClickOnly = {"/links/doubleClickToOpen", false};
|
BoolSetting linksDoubleClickOnly = {"/links/doubleClickToOpen", false};
|
||||||
BoolSetting linkInfoTooltip = {"/links/linkInfoTooltip", false};
|
BoolSetting linkInfoTooltip = {"/links/linkInfoTooltip", false};
|
||||||
|
IntSetting thumbnailSize = {"/appearance/thumbnailSize", 0};
|
||||||
BoolSetting unshortLinks = {"/links/unshortLinks", false};
|
BoolSetting unshortLinks = {"/links/unshortLinks", false};
|
||||||
BoolSetting lowercaseDomains = {"/links/linkLowercase", true};
|
BoolSetting lowercaseDomains = {"/links/linkLowercase", true};
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,14 @@ void TooltipPreviewImage::setImage(ImagePtr image)
|
||||||
this->refreshTooltipWidgetPixmap();
|
this->refreshTooltipWidgetPixmap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TooltipPreviewImage::setImageScale(int w, int h)
|
||||||
|
{
|
||||||
|
this->imageWidth_ = w;
|
||||||
|
this->imageHeight_ = h;
|
||||||
|
|
||||||
|
this->refreshTooltipWidgetPixmap();
|
||||||
|
}
|
||||||
|
|
||||||
void TooltipPreviewImage::refreshTooltipWidgetPixmap()
|
void TooltipPreviewImage::refreshTooltipWidgetPixmap()
|
||||||
{
|
{
|
||||||
auto tooltipWidget = TooltipWidget::instance();
|
auto tooltipWidget = TooltipWidget::instance();
|
||||||
|
@ -45,8 +53,18 @@ void TooltipPreviewImage::refreshTooltipWidgetPixmap()
|
||||||
if (this->image_ && !tooltipWidget->isHidden())
|
if (this->image_ && !tooltipWidget->isHidden())
|
||||||
{
|
{
|
||||||
if (auto pixmap = this->image_->pixmapOrLoad())
|
if (auto pixmap = this->image_->pixmapOrLoad())
|
||||||
|
{
|
||||||
|
if (this->imageWidth_ != 0 && this->imageHeight_)
|
||||||
|
{
|
||||||
|
tooltipWidget->setImage(pixmap->scaled(this->imageWidth_,
|
||||||
|
this->imageHeight_,
|
||||||
|
Qt::KeepAspectRatio));
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
tooltipWidget->setImage(*pixmap);
|
tooltipWidget->setImage(*pixmap);
|
||||||
|
}
|
||||||
|
|
||||||
this->attemptRefresh = false;
|
this->attemptRefresh = false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
@ -9,6 +9,7 @@ class TooltipPreviewImage
|
||||||
public:
|
public:
|
||||||
static TooltipPreviewImage &instance();
|
static TooltipPreviewImage &instance();
|
||||||
void setImage(ImagePtr image);
|
void setImage(ImagePtr image);
|
||||||
|
void setImageScale(int w, int h);
|
||||||
|
|
||||||
TooltipPreviewImage(const TooltipPreviewImage &) = delete;
|
TooltipPreviewImage(const TooltipPreviewImage &) = delete;
|
||||||
|
|
||||||
|
@ -17,6 +18,9 @@ private:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ImagePtr image_ = nullptr;
|
ImagePtr image_ = nullptr;
|
||||||
|
int imageWidth_ = 0;
|
||||||
|
int imageHeight_ = 0;
|
||||||
|
|
||||||
std::vector<pajlada::Signals::ScopedConnection> connections_;
|
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)
|
// attemptRefresh is set to true in case we want to preview an image that has not loaded yet (if pixmapOrLoad fails)
|
||||||
|
|
|
@ -1259,6 +1259,7 @@ void ChannelView::mouseMoveEvent(QMouseEvent *event)
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
auto &tooltipPreviewImage = TooltipPreviewImage::instance();
|
auto &tooltipPreviewImage = TooltipPreviewImage::instance();
|
||||||
|
tooltipPreviewImage.setImageScale(0, 0);
|
||||||
auto emoteElement = dynamic_cast<const EmoteElement *>(
|
auto emoteElement = dynamic_cast<const EmoteElement *>(
|
||||||
&hoverLayoutElement->getCreator());
|
&hoverLayoutElement->getCreator());
|
||||||
auto badgeElement = dynamic_cast<const BadgeElement *>(
|
auto badgeElement = dynamic_cast<const BadgeElement *>(
|
||||||
|
@ -1287,9 +1288,24 @@ void ChannelView::mouseMoveEvent(QMouseEvent *event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
auto element = &hoverLayoutElement->getCreator();
|
||||||
|
auto thumbnailSize = getSettings()->thumbnailSize;
|
||||||
|
if (thumbnailSize == 0)
|
||||||
{
|
{
|
||||||
tooltipPreviewImage.setImage(nullptr);
|
tooltipPreviewImage.setImage(nullptr);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tooltipPreviewImage.setImage(element->getThumbnail());
|
||||||
|
if (element->getThumbnailType() ==
|
||||||
|
MessageElement::ThumbnailType::Link_Thumbnail)
|
||||||
|
{
|
||||||
|
tooltipPreviewImage.setImageScale(thumbnailSize,
|
||||||
|
thumbnailSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tooltipWidget->moveTo(this, event->globalPos());
|
tooltipWidget->moveTo(this, event->globalPos());
|
||||||
tooltipWidget->setWordWrap(isLinkValid);
|
tooltipWidget->setWordWrap(isLinkValid);
|
||||||
|
|
|
@ -521,6 +521,31 @@ void GeneralPage::initLayout(SettingsLayout &layout)
|
||||||
},
|
},
|
||||||
[](auto args) { return fuzzyToFloat(args.value, 63.f); });
|
[](auto args) { return fuzzyToFloat(args.value, 63.f); });
|
||||||
layout.addCheckbox("Show link info when hovering", s.linkInfoTooltip);
|
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",
|
layout.addCheckbox("Double click to open links and other elements in chat",
|
||||||
s.linksDoubleClickOnly);
|
s.linksDoubleClickOnly);
|
||||||
layout.addCheckbox("Unshorten links", s.unshortLinks);
|
layout.addCheckbox("Unshorten links", s.unshortLinks);
|
||||||
|
|
Loading…
Reference in a new issue