mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-29 14:18:49 +03:00
Fix reaction display
This commit is contained in:
parent
d467568a65
commit
6f2bc908ba
12 changed files with 151 additions and 152 deletions
|
@ -251,7 +251,7 @@ set(SRC_FILES
|
||||||
|
|
||||||
# Timeline
|
# Timeline
|
||||||
src/timeline/EventStore.cpp
|
src/timeline/EventStore.cpp
|
||||||
src/timeline/ReactionsModel.cpp
|
src/timeline/Reaction.cpp
|
||||||
src/timeline/TimelineViewManager.cpp
|
src/timeline/TimelineViewManager.cpp
|
||||||
src/timeline/TimelineModel.cpp
|
src/timeline/TimelineModel.cpp
|
||||||
src/timeline/DelegateChooser.cpp
|
src/timeline/DelegateChooser.cpp
|
||||||
|
@ -455,7 +455,7 @@ qt5_wrap_cpp(MOC_HEADERS
|
||||||
|
|
||||||
# Timeline
|
# Timeline
|
||||||
src/timeline/EventStore.h
|
src/timeline/EventStore.h
|
||||||
src/timeline/ReactionsModel.h
|
src/timeline/Reaction.h
|
||||||
src/timeline/TimelineViewManager.h
|
src/timeline/TimelineViewManager.h
|
||||||
src/timeline/TimelineModel.h
|
src/timeline/TimelineModel.h
|
||||||
src/timeline/DelegateChooser.h
|
src/timeline/DelegateChooser.h
|
||||||
|
|
|
@ -30,11 +30,11 @@ Flow {
|
||||||
implicitHeight: contentItem.childrenRect.height
|
implicitHeight: contentItem.childrenRect.height
|
||||||
|
|
||||||
ToolTip.visible: hovered
|
ToolTip.visible: hovered
|
||||||
ToolTip.text: model.users
|
ToolTip.text: modelData.users
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
console.debug("Picked " + model.key + "in response to " + reactionFlow.eventId + " in room " + reactionFlow.roomId + ". selfReactedEvent: " + model.selfReactedEvent)
|
console.debug("Picked " + modelData.key + "in response to " + reactionFlow.eventId + " in room " + reactionFlow.roomId + ". selfReactedEvent: " + modelData.selfReactedEvent)
|
||||||
timelineManager.reactToMessage(reactionFlow.roomId, reactionFlow.eventId, model.key, model.selfReactedEvent)
|
timelineManager.reactToMessage(reactionFlow.roomId, reactionFlow.eventId, modelData.key, modelData.selfReactedEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -49,13 +49,13 @@ Flow {
|
||||||
font.family: settings.emojiFont
|
font.family: settings.emojiFont
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
elideWidth: 150
|
elideWidth: 150
|
||||||
text: model.key
|
text: modelData.key
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
anchors.baseline: reactionCounter.baseline
|
anchors.baseline: reactionCounter.baseline
|
||||||
id: reactionText
|
id: reactionText
|
||||||
text: textMetrics.elidedText + (textMetrics.elidedText == model.key ? "" : "…")
|
text: textMetrics.elidedText + (textMetrics.elidedText == modelData.key ? "" : "…")
|
||||||
font.family: settings.emojiFont
|
font.family: settings.emojiFont
|
||||||
color: reaction.hovered ? colors.highlight : colors.text
|
color: reaction.hovered ? colors.highlight : colors.text
|
||||||
maximumLineCount: 1
|
maximumLineCount: 1
|
||||||
|
@ -65,13 +65,13 @@ Flow {
|
||||||
id: divider
|
id: divider
|
||||||
height: Math.floor(reactionCounter.implicitHeight * 1.4)
|
height: Math.floor(reactionCounter.implicitHeight * 1.4)
|
||||||
width: 1
|
width: 1
|
||||||
color: (reaction.hovered || model.selfReactedEvent !== '') ? colors.highlight : colors.text
|
color: (reaction.hovered || modelData.selfReactedEvent !== '') ? colors.highlight : colors.text
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
anchors.verticalCenter: divider.verticalCenter
|
anchors.verticalCenter: divider.verticalCenter
|
||||||
id: reactionCounter
|
id: reactionCounter
|
||||||
text: model.counter
|
text: modelData.count
|
||||||
font: reaction.font
|
font: reaction.font
|
||||||
color: reaction.hovered ? colors.highlight : colors.text
|
color: reaction.hovered ? colors.highlight : colors.text
|
||||||
}
|
}
|
||||||
|
@ -82,8 +82,8 @@ Flow {
|
||||||
|
|
||||||
implicitWidth: reaction.implicitWidth
|
implicitWidth: reaction.implicitWidth
|
||||||
implicitHeight: reaction.implicitHeight
|
implicitHeight: reaction.implicitHeight
|
||||||
border.color: (reaction.hovered || model.selfReactedEvent !== '') ? colors.highlight : colors.text
|
border.color: (reaction.hovered || modelData.selfReactedEvent !== '') ? colors.highlight : colors.text
|
||||||
color: model.selfReactedEvent !== '' ? Qt.hsla(highlightHue, highlightSat, highlightLight, 0.20) : colors.base
|
color: modelData.selfReactedEvent !== '' ? Qt.hsla(highlightHue, highlightSat, highlightLight, 0.20) : colors.base
|
||||||
border.width: 1
|
border.width: 1
|
||||||
radius: reaction.height / 2.0
|
radius: reaction.height / 2.0
|
||||||
}
|
}
|
||||||
|
|
|
@ -1353,6 +1353,37 @@ Cache::storeEvent(const std::string &room_id,
|
||||||
txn.commit();
|
txn.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<std::string>
|
||||||
|
Cache::relatedEvents(const std::string &room_id, const std::string &event_id)
|
||||||
|
{
|
||||||
|
auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
|
||||||
|
auto relationsDb = getRelationsDb(txn, room_id);
|
||||||
|
|
||||||
|
std::vector<std::string> related_ids;
|
||||||
|
|
||||||
|
auto related_cursor = lmdb::cursor::open(txn, relationsDb);
|
||||||
|
lmdb::val related_to = event_id, related_event;
|
||||||
|
bool first = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!related_cursor.get(related_to, related_event, MDB_SET))
|
||||||
|
return {};
|
||||||
|
|
||||||
|
while (related_cursor.get(
|
||||||
|
related_to, related_event, first ? MDB_FIRST_DUP : MDB_NEXT_DUP)) {
|
||||||
|
first = false;
|
||||||
|
if (event_id != std::string_view(related_to.data(), related_to.size()))
|
||||||
|
break;
|
||||||
|
|
||||||
|
related_ids.emplace_back(related_event.data(), related_event.size());
|
||||||
|
}
|
||||||
|
} catch (const lmdb::error &e) {
|
||||||
|
nhlog::db()->error("related events error: {}", e.what());
|
||||||
|
}
|
||||||
|
|
||||||
|
return related_ids;
|
||||||
|
}
|
||||||
|
|
||||||
QMap<QString, RoomInfo>
|
QMap<QString, RoomInfo>
|
||||||
Cache::roomInfo(bool withInvites)
|
Cache::roomInfo(bool withInvites)
|
||||||
{
|
{
|
||||||
|
@ -2354,6 +2385,10 @@ Cache::saveOldMessages(const std::string &room_id, const mtx::responses::Message
|
||||||
|
|
||||||
std::string event_id_val;
|
std::string event_id_val;
|
||||||
for (const auto &e : res.chunk) {
|
for (const auto &e : res.chunk) {
|
||||||
|
if (std::holds_alternative<
|
||||||
|
mtx::events::RedactionEvent<mtx::events::msg::Redaction>>(e))
|
||||||
|
continue;
|
||||||
|
|
||||||
auto event = mtx::accessors::serialize_event(e);
|
auto event = mtx::accessors::serialize_event(e);
|
||||||
event_id_val = event["event_id"].get<std::string>();
|
event_id_val = event["event_id"].get<std::string>();
|
||||||
lmdb::val event_id = event_id_val;
|
lmdb::val event_id = event_id_val;
|
||||||
|
|
|
@ -188,6 +188,9 @@ public:
|
||||||
void storeEvent(const std::string &room_id,
|
void storeEvent(const std::string &room_id,
|
||||||
const std::string &event_id,
|
const std::string &event_id,
|
||||||
const mtx::events::collections::TimelineEvent &event);
|
const mtx::events::collections::TimelineEvent &event);
|
||||||
|
std::vector<std::string> relatedEvents(const std::string &room_id,
|
||||||
|
const std::string &event_id);
|
||||||
|
|
||||||
struct TimelineRange
|
struct TimelineRange
|
||||||
{
|
{
|
||||||
uint64_t first, last;
|
uint64_t first, last;
|
||||||
|
|
|
@ -3,12 +3,15 @@
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
|
#include "Cache.h"
|
||||||
#include "Cache_p.h"
|
#include "Cache_p.h"
|
||||||
#include "EventAccessors.h"
|
#include "EventAccessors.h"
|
||||||
#include "Logging.h"
|
#include "Logging.h"
|
||||||
#include "MatrixClient.h"
|
#include "MatrixClient.h"
|
||||||
#include "Olm.h"
|
#include "Olm.h"
|
||||||
|
|
||||||
|
Q_DECLARE_METATYPE(Reaction)
|
||||||
|
|
||||||
QCache<EventStore::IdIndex, mtx::events::collections::TimelineEvents> EventStore::decryptedEvents_{
|
QCache<EventStore::IdIndex, mtx::events::collections::TimelineEvents> EventStore::decryptedEvents_{
|
||||||
1000};
|
1000};
|
||||||
QCache<EventStore::IdIndex, mtx::events::collections::TimelineEvents> EventStore::events_by_id_{
|
QCache<EventStore::IdIndex, mtx::events::collections::TimelineEvents> EventStore::events_by_id_{
|
||||||
|
@ -18,6 +21,9 @@ QCache<EventStore::Index, mtx::events::collections::TimelineEvents> EventStore::
|
||||||
EventStore::EventStore(std::string room_id, QObject *)
|
EventStore::EventStore(std::string room_id, QObject *)
|
||||||
: room_id_(std::move(room_id))
|
: room_id_(std::move(room_id))
|
||||||
{
|
{
|
||||||
|
static auto reactionType = qRegisterMetaType<Reaction>();
|
||||||
|
(void)reactionType;
|
||||||
|
|
||||||
auto range = cache::client()->getTimelineRange(room_id_);
|
auto range = cache::client()->getTimelineRange(room_id_);
|
||||||
|
|
||||||
if (range) {
|
if (range) {
|
||||||
|
@ -223,6 +229,70 @@ EventStore::handleSync(const mtx::responses::Timeline &events)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QVariantList
|
||||||
|
EventStore::reactions(const std::string &event_id)
|
||||||
|
{
|
||||||
|
auto event_ids = cache::client()->relatedEvents(room_id_, event_id);
|
||||||
|
|
||||||
|
struct TempReaction
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
std::vector<std::string> users;
|
||||||
|
std::string reactedBySelf;
|
||||||
|
};
|
||||||
|
std::map<std::string, TempReaction> aggregation;
|
||||||
|
std::vector<Reaction> reactions;
|
||||||
|
|
||||||
|
auto self = http::client()->user_id().to_string();
|
||||||
|
for (const auto &id : event_ids) {
|
||||||
|
auto related_event = event(id, event_id);
|
||||||
|
if (!related_event)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (auto reaction = std::get_if<mtx::events::RoomEvent<mtx::events::msg::Reaction>>(
|
||||||
|
related_event)) {
|
||||||
|
auto &agg = aggregation[reaction->content.relates_to.key];
|
||||||
|
|
||||||
|
if (agg.count == 0) {
|
||||||
|
Reaction temp{};
|
||||||
|
temp.key_ =
|
||||||
|
QString::fromStdString(reaction->content.relates_to.key);
|
||||||
|
reactions.push_back(temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
agg.count++;
|
||||||
|
agg.users.push_back(cache::displayName(room_id_, reaction->sender));
|
||||||
|
if (reaction->sender == self)
|
||||||
|
agg.reactedBySelf = reaction->event_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariantList temp;
|
||||||
|
for (auto &reaction : reactions) {
|
||||||
|
const auto &agg = aggregation[reaction.key_.toStdString()];
|
||||||
|
reaction.count_ = agg.count;
|
||||||
|
reaction.selfReactedEvent_ = QString::fromStdString(agg.reactedBySelf);
|
||||||
|
|
||||||
|
bool first = true;
|
||||||
|
for (const auto &user : agg.users) {
|
||||||
|
if (first)
|
||||||
|
first = false;
|
||||||
|
else
|
||||||
|
reaction.users_ += ", ";
|
||||||
|
|
||||||
|
reaction.users_ += QString::fromStdString(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
nhlog::db()->debug("key: {}, count: {}, users: {}",
|
||||||
|
reaction.key_.toStdString(),
|
||||||
|
reaction.count_,
|
||||||
|
reaction.users_.toStdString());
|
||||||
|
temp.append(QVariant::fromValue(reaction));
|
||||||
|
}
|
||||||
|
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
|
||||||
mtx::events::collections::TimelineEvents *
|
mtx::events::collections::TimelineEvents *
|
||||||
EventStore::event(int idx, bool decrypt)
|
EventStore::event(int idx, bool decrypt)
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,12 +5,15 @@
|
||||||
|
|
||||||
#include <QCache>
|
#include <QCache>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QVariant>
|
||||||
#include <qhashfunctions.h>
|
#include <qhashfunctions.h>
|
||||||
|
|
||||||
#include <mtx/events/collections.hpp>
|
#include <mtx/events/collections.hpp>
|
||||||
#include <mtx/responses/messages.hpp>
|
#include <mtx/responses/messages.hpp>
|
||||||
#include <mtx/responses/sync.hpp>
|
#include <mtx/responses/sync.hpp>
|
||||||
|
|
||||||
|
#include "Reaction.h"
|
||||||
|
|
||||||
class EventStore : public QObject
|
class EventStore : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -65,6 +68,8 @@ public:
|
||||||
// always returns a proper event as long as the idx is valid
|
// always returns a proper event as long as the idx is valid
|
||||||
mtx::events::collections::TimelineEvents *event(int idx, bool decrypt = true);
|
mtx::events::collections::TimelineEvents *event(int idx, bool decrypt = true);
|
||||||
|
|
||||||
|
QVariantList reactions(const std::string &event_id);
|
||||||
|
|
||||||
int size() const
|
int size() const
|
||||||
{
|
{
|
||||||
return last != std::numeric_limits<uint64_t>::max()
|
return last != std::numeric_limits<uint64_t>::max()
|
||||||
|
|
1
src/timeline/Reaction.cpp
Normal file
1
src/timeline/Reaction.cpp
Normal file
|
@ -0,0 +1 @@
|
||||||
|
#include "Reaction.h"
|
24
src/timeline/Reaction.h
Normal file
24
src/timeline/Reaction.h
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
struct Reaction
|
||||||
|
{
|
||||||
|
Q_GADGET
|
||||||
|
Q_PROPERTY(QString key READ key)
|
||||||
|
Q_PROPERTY(QString users READ users)
|
||||||
|
Q_PROPERTY(QString selfReactedEvent READ selfReactedEvent)
|
||||||
|
Q_PROPERTY(int count READ count)
|
||||||
|
|
||||||
|
public:
|
||||||
|
QString key() const { return key_; }
|
||||||
|
QString users() const { return users_; }
|
||||||
|
QString selfReactedEvent() const { return selfReactedEvent_; }
|
||||||
|
int count() const { return count_; }
|
||||||
|
|
||||||
|
QString key_;
|
||||||
|
QString users_;
|
||||||
|
QString selfReactedEvent_;
|
||||||
|
int count_;
|
||||||
|
};
|
|
@ -1,98 +0,0 @@
|
||||||
#include "ReactionsModel.h"
|
|
||||||
|
|
||||||
#include <Cache.h>
|
|
||||||
#include <MatrixClient.h>
|
|
||||||
|
|
||||||
QHash<int, QByteArray>
|
|
||||||
ReactionsModel::roleNames() const
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
{Key, "key"},
|
|
||||||
{Count, "counter"},
|
|
||||||
{Users, "users"},
|
|
||||||
{SelfReactedEvent, "selfReactedEvent"},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
ReactionsModel::rowCount(const QModelIndex &) const
|
|
||||||
{
|
|
||||||
return static_cast<int>(reactions.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant
|
|
||||||
ReactionsModel::data(const QModelIndex &index, int role) const
|
|
||||||
{
|
|
||||||
const int i = index.row();
|
|
||||||
if (i < 0 || i >= static_cast<int>(reactions.size()))
|
|
||||||
return {};
|
|
||||||
|
|
||||||
switch (role) {
|
|
||||||
case Key:
|
|
||||||
return QString::fromStdString(reactions[i].key);
|
|
||||||
case Count:
|
|
||||||
return static_cast<int>(reactions[i].reactions.size());
|
|
||||||
case Users: {
|
|
||||||
QString users;
|
|
||||||
bool first = true;
|
|
||||||
for (const auto &reaction : reactions[i].reactions) {
|
|
||||||
if (!first)
|
|
||||||
users += ", ";
|
|
||||||
else
|
|
||||||
first = false;
|
|
||||||
users += QString::fromStdString(
|
|
||||||
cache::displayName(room_id_, reaction.second.sender));
|
|
||||||
}
|
|
||||||
return users;
|
|
||||||
}
|
|
||||||
case SelfReactedEvent:
|
|
||||||
for (const auto &reaction : reactions[i].reactions)
|
|
||||||
if (reaction.second.sender == http::client()->user_id().to_string())
|
|
||||||
return QString::fromStdString(reaction.second.event_id);
|
|
||||||
return QStringLiteral("");
|
|
||||||
default:
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
ReactionsModel::addReaction(const std::string &room_id,
|
|
||||||
const mtx::events::RoomEvent<mtx::events::msg::Reaction> &reaction)
|
|
||||||
{
|
|
||||||
room_id_ = room_id;
|
|
||||||
|
|
||||||
int idx = 0;
|
|
||||||
for (auto &storedReactions : reactions) {
|
|
||||||
if (storedReactions.key == reaction.content.relates_to.key) {
|
|
||||||
storedReactions.reactions[reaction.event_id] = reaction;
|
|
||||||
emit dataChanged(index(idx, 0), index(idx, 0));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
idx++;
|
|
||||||
}
|
|
||||||
|
|
||||||
beginInsertRows(QModelIndex(), idx, idx);
|
|
||||||
reactions.push_back(
|
|
||||||
KeyReaction{reaction.content.relates_to.key, {{reaction.event_id, reaction}}});
|
|
||||||
endInsertRows();
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
ReactionsModel::removeReaction(const mtx::events::RoomEvent<mtx::events::msg::Reaction> &reaction)
|
|
||||||
{
|
|
||||||
int idx = 0;
|
|
||||||
for (auto &storedReactions : reactions) {
|
|
||||||
if (storedReactions.key == reaction.content.relates_to.key) {
|
|
||||||
storedReactions.reactions.erase(reaction.event_id);
|
|
||||||
|
|
||||||
if (storedReactions.reactions.size() == 0) {
|
|
||||||
beginRemoveRows(QModelIndex(), idx, idx);
|
|
||||||
reactions.erase(reactions.begin() + idx);
|
|
||||||
endRemoveRows();
|
|
||||||
} else
|
|
||||||
emit dataChanged(index(idx, 0), index(idx, 0));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
idx++;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QAbstractListModel>
|
|
||||||
#include <QHash>
|
|
||||||
|
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <mtx/events/collections.hpp>
|
|
||||||
|
|
||||||
class ReactionsModel : public QAbstractListModel
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit ReactionsModel(QObject *parent = nullptr) { Q_UNUSED(parent); }
|
|
||||||
enum Roles
|
|
||||||
{
|
|
||||||
Key,
|
|
||||||
Count,
|
|
||||||
Users,
|
|
||||||
SelfReactedEvent,
|
|
||||||
};
|
|
||||||
|
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
|
||||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
|
||||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void addReaction(const std::string &room_id,
|
|
||||||
const mtx::events::RoomEvent<mtx::events::msg::Reaction> &reaction);
|
|
||||||
void removeReaction(const mtx::events::RoomEvent<mtx::events::msg::Reaction> &reaction);
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct KeyReaction
|
|
||||||
{
|
|
||||||
std::string key;
|
|
||||||
std::map<std::string, mtx::events::RoomEvent<mtx::events::msg::Reaction>> reactions;
|
|
||||||
};
|
|
||||||
std::string room_id_;
|
|
||||||
std::vector<KeyReaction> reactions;
|
|
||||||
};
|
|
|
@ -366,7 +366,8 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r
|
||||||
case ReplyTo:
|
case ReplyTo:
|
||||||
return QVariant(QString::fromStdString(in_reply_to_event(event)));
|
return QVariant(QString::fromStdString(in_reply_to_event(event)));
|
||||||
case Reactions: {
|
case Reactions: {
|
||||||
return {};
|
auto id = event_id(event);
|
||||||
|
return QVariant::fromValue(events.reactions(id));
|
||||||
}
|
}
|
||||||
case RoomId:
|
case RoomId:
|
||||||
return QVariant(room_id_);
|
return QVariant(room_id_);
|
||||||
|
|
|
@ -10,7 +10,6 @@
|
||||||
|
|
||||||
#include "CacheCryptoStructs.h"
|
#include "CacheCryptoStructs.h"
|
||||||
#include "EventStore.h"
|
#include "EventStore.h"
|
||||||
#include "ReactionsModel.h"
|
|
||||||
|
|
||||||
namespace mtx::http {
|
namespace mtx::http {
|
||||||
using RequestErr = const std::optional<mtx::http::ClientError> &;
|
using RequestErr = const std::optional<mtx::http::ClientError> &;
|
||||||
|
|
Loading…
Reference in a new issue