first compiling prototype, doesnt work yet
This commit is contained in:
parent
ef1a9846bf
commit
38159eafeb
21 changed files with 662 additions and 734 deletions
|
@ -28,6 +28,8 @@ Chat::Chat(Models::Account* acc, Models::Contact* p_contact, QWidget* parent):
|
|||
setAvatar(p_contact->getAvatarPath());
|
||||
|
||||
connect(contact, &Models::Contact::childChanged, this, &Chat::onContactChanged);
|
||||
|
||||
feed->setModel(p_contact->feed);
|
||||
}
|
||||
|
||||
Chat::~Chat()
|
||||
|
@ -71,31 +73,15 @@ Shared::Message Chat::createMessage() const
|
|||
return msg;
|
||||
}
|
||||
|
||||
void Chat::addMessage(const Shared::Message& data)
|
||||
{
|
||||
Conversation::addMessage(data);
|
||||
|
||||
if (!data.getOutgoing()) { //TODO need to check if that was the last message
|
||||
const QString& res = data.getPenPalResource();
|
||||
if (res.size() > 0) {
|
||||
setPalResource(res);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Chat::setName(const QString& name)
|
||||
{
|
||||
Conversation::setName(name);
|
||||
line->setPalName(getJid(), name);
|
||||
}
|
||||
|
||||
void Chat::setAvatar(const QString& path)
|
||||
{
|
||||
Conversation::setAvatar(path);
|
||||
|
||||
if (path.size() == 0) {
|
||||
line->dropPalAvatar(contact->getJid());
|
||||
} else {
|
||||
line->setPalAvatar(contact->getJid(), path);
|
||||
}
|
||||
}
|
||||
// TODO
|
||||
// void Chat::addMessage(const Shared::Message& data)
|
||||
// {
|
||||
// Conversation::addMessage(data);
|
||||
//
|
||||
// if (!data.getOutgoing()) { //TODO need to check if that was the last message
|
||||
// const QString& res = data.getPenPalResource();
|
||||
// if (res.size() > 0) {
|
||||
// setPalResource(res);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -35,14 +35,12 @@ public:
|
|||
Chat(Models::Account* acc, Models::Contact* p_contact, QWidget* parent = 0);
|
||||
~Chat();
|
||||
|
||||
void addMessage(const Shared::Message & data) override;
|
||||
void setAvatar(const QString& path) override;
|
||||
//void addMessage(const Shared::Message & data) override;
|
||||
|
||||
protected slots:
|
||||
void onContactChanged(Models::Item* item, int row, int col);
|
||||
|
||||
protected:
|
||||
void setName(const QString & name) override;
|
||||
Shared::Message createMessage() const override;
|
||||
|
||||
private:
|
||||
|
|
|
@ -35,7 +35,6 @@ Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, c
|
|||
account(acc),
|
||||
palJid(pJid),
|
||||
activePalResource(pRes),
|
||||
line(new MessageLine(muc)),
|
||||
m_ui(new Ui::Conversation()),
|
||||
ker(),
|
||||
scrollResizeCatcher(),
|
||||
|
@ -46,6 +45,7 @@ Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, c
|
|||
filesLayout(0),
|
||||
overlay(new QWidget()),
|
||||
filesToAttach(),
|
||||
feed(0),
|
||||
scroll(down),
|
||||
manualSliderChange(false),
|
||||
requestingHistory(false),
|
||||
|
@ -53,6 +53,7 @@ Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, c
|
|||
tsb(QApplication::style()->styleHint(QStyle::SH_ScrollBar_Transient) == 1)
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
feed = m_ui->feed;
|
||||
|
||||
connect(acc, &Models::Account::childChanged, this, &Conversation::onAccountChanged);
|
||||
|
||||
|
@ -67,10 +68,10 @@ Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, c
|
|||
connect(&vis, &VisibilityCatcher::shown, this, &Conversation::onScrollResize);
|
||||
connect(&vis, &VisibilityCatcher::hidden, this, &Conversation::onScrollResize);
|
||||
connect(m_ui->sendButton, &QPushButton::clicked, this, &Conversation::onEnterPressed);
|
||||
connect(line, &MessageLine::resize, this, &Conversation::onMessagesResize);
|
||||
connect(line, &MessageLine::downloadFile, this, &Conversation::downloadFile);
|
||||
connect(line, &MessageLine::uploadFile, this, qOverload<const Shared::Message&, const QString&>(&Conversation::sendMessage));
|
||||
connect(line, &MessageLine::requestLocalFile, this, &Conversation::requestLocalFile);
|
||||
//connect(line, &MessageLine::resize, this, &Conversation::onMessagesResize);
|
||||
//connect(line, &MessageLine::downloadFile, this, &Conversation::downloadFile);
|
||||
//connect(line, &MessageLine::uploadFile, this, qOverload<const Shared::Message&, const QString&>(&Conversation::sendMessage));
|
||||
//connect(line, &MessageLine::requestLocalFile, this, &Conversation::requestLocalFile);
|
||||
connect(m_ui->attachButton, &QPushButton::clicked, this, &Conversation::onAttach);
|
||||
connect(m_ui->clearButton, &QPushButton::clicked, this, &Conversation::onClearButton);
|
||||
connect(m_ui->messageEditor->document()->documentLayout(), &QAbstractTextDocumentLayout::documentSizeChanged,
|
||||
|
@ -78,22 +79,22 @@ Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, c
|
|||
|
||||
m_ui->messageEditor->installEventFilter(&ker);
|
||||
|
||||
QScrollBar* vs = m_ui->scrollArea->verticalScrollBar();
|
||||
m_ui->scrollArea->setWidget(line);
|
||||
vs->installEventFilter(&vis);
|
||||
//QScrollBar* vs = m_ui->scrollArea->verticalScrollBar();
|
||||
//m_ui->scrollArea->setWidget(line);
|
||||
//vs->installEventFilter(&vis);
|
||||
|
||||
line->setAutoFillBackground(false);
|
||||
if (testAttribute(Qt::WA_TranslucentBackground)) {
|
||||
m_ui->scrollArea->setAutoFillBackground(false);
|
||||
} else {
|
||||
m_ui->scrollArea->setBackgroundRole(QPalette::Base);
|
||||
}
|
||||
//line->setAutoFillBackground(false);
|
||||
//if (testAttribute(Qt::WA_TranslucentBackground)) {
|
||||
//m_ui->scrollArea->setAutoFillBackground(false);
|
||||
//} else {
|
||||
//m_ui->scrollArea->setBackgroundRole(QPalette::Base);
|
||||
//}
|
||||
|
||||
connect(vs, &QScrollBar::valueChanged, this, &Conversation::onSliderValueChanged);
|
||||
m_ui->scrollArea->installEventFilter(&scrollResizeCatcher);
|
||||
//connect(vs, &QScrollBar::valueChanged, this, &Conversation::onSliderValueChanged);
|
||||
//m_ui->scrollArea->installEventFilter(&scrollResizeCatcher);
|
||||
|
||||
line->setMyAvatarPath(acc->getAvatarPath());
|
||||
line->setMyName(acc->getName());
|
||||
//line->setMyAvatarPath(acc->getAvatarPath());
|
||||
//line->setMyName(acc->getName());
|
||||
|
||||
QGridLayout* gr = static_cast<QGridLayout*>(layout());
|
||||
QLabel* progressLabel = new QLabel(tr("Drop files here to attach them to your message"));
|
||||
|
@ -129,9 +130,9 @@ void Conversation::onAccountChanged(Models::Item* item, int row, int col)
|
|||
if (col == 2 && account->getState() == Shared::ConnectionState::connected) {
|
||||
if (!requestingHistory) {
|
||||
requestingHistory = true;
|
||||
line->showBusyIndicator();
|
||||
emit requestArchive("");
|
||||
scroll = down;
|
||||
//line->showBusyIndicator();
|
||||
//emit requestArchive("");
|
||||
//scroll = down;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -139,12 +140,12 @@ void Conversation::onAccountChanged(Models::Item* item, int row, int col)
|
|||
|
||||
void Conversation::applyVisualEffects()
|
||||
{
|
||||
DropShadowEffect *e1 = new DropShadowEffect;
|
||||
e1->setBlurRadius(10);
|
||||
e1->setColor(Qt::black);
|
||||
e1->setThickness(1);
|
||||
e1->setFrame(true, false, true, false);
|
||||
m_ui->scrollArea->setGraphicsEffect(e1);
|
||||
// DropShadowEffect *e1 = new DropShadowEffect;
|
||||
// e1->setBlurRadius(10);
|
||||
// e1->setColor(Qt::black);
|
||||
// e1->setThickness(1);
|
||||
// e1->setFrame(true, false, true, false);
|
||||
// m_ui->scrollArea->setGraphicsEffect(e1);
|
||||
}
|
||||
|
||||
void Conversation::setName(const QString& name)
|
||||
|
@ -163,20 +164,9 @@ QString Conversation::getJid() const
|
|||
return palJid;
|
||||
}
|
||||
|
||||
void Conversation::addMessage(const Shared::Message& data)
|
||||
{
|
||||
int pos = m_ui->scrollArea->verticalScrollBar()->sliderPosition();
|
||||
int max = m_ui->scrollArea->verticalScrollBar()->maximum();
|
||||
|
||||
MessageLine::Position place = line->message(data);
|
||||
if (place == MessageLine::invalid) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Conversation::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
|
||||
{
|
||||
line->changeMessage(id, data);
|
||||
// line->changeMessage(id, data);
|
||||
}
|
||||
|
||||
KeyEnterReceiver::KeyEnterReceiver(QObject* parent): QObject(parent), ownEvent(false) {}
|
||||
|
@ -226,86 +216,86 @@ void Conversation::onEnterPressed()
|
|||
m_ui->messageEditor->clear();
|
||||
Shared::Message msg = createMessage();
|
||||
msg.setBody(body);
|
||||
addMessage(msg);
|
||||
//addMessage(msg);
|
||||
emit sendMessage(msg);
|
||||
}
|
||||
if (filesToAttach.size() > 0) {
|
||||
for (Badge* badge : filesToAttach) {
|
||||
Shared::Message msg = createMessage();
|
||||
line->appendMessageWithUpload(msg, badge->id);
|
||||
usleep(1000); //this is required for the messages not to have equal time when appending into messageline
|
||||
}
|
||||
clearAttachedFiles();
|
||||
// for (Badge* badge : filesToAttach) {
|
||||
// Shared::Message msg = createMessage();
|
||||
// line->appendMessageWithUpload(msg, badge->id);
|
||||
// usleep(1000); //this is required for the messages not to have equal time when appending into messageline
|
||||
// }
|
||||
// clearAttachedFiles();
|
||||
}
|
||||
}
|
||||
|
||||
void Conversation::appendMessageWithUpload(const Shared::Message& data, const QString& path)
|
||||
{
|
||||
line->appendMessageWithUploadNoSiganl(data, path);
|
||||
// line->appendMessageWithUploadNoSiganl(data, path);
|
||||
}
|
||||
|
||||
void Conversation::onMessagesResize(int amount)
|
||||
{
|
||||
manualSliderChange = true;
|
||||
switch (scroll) {
|
||||
case down:
|
||||
m_ui->scrollArea->verticalScrollBar()->setValue(m_ui->scrollArea->verticalScrollBar()->maximum());
|
||||
break;
|
||||
case keep: {
|
||||
int max = m_ui->scrollArea->verticalScrollBar()->maximum();
|
||||
int value = m_ui->scrollArea->verticalScrollBar()->value() + amount;
|
||||
m_ui->scrollArea->verticalScrollBar()->setValue(value);
|
||||
|
||||
if (value == max) {
|
||||
scroll = down;
|
||||
} else {
|
||||
scroll = nothing;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
manualSliderChange = false;
|
||||
// manualSliderChange = true;
|
||||
// switch (scroll) {
|
||||
// case down:
|
||||
// m_ui->scrollArea->verticalScrollBar()->setValue(m_ui->scrollArea->verticalScrollBar()->maximum());
|
||||
// break;
|
||||
// case keep: {
|
||||
// int max = m_ui->scrollArea->verticalScrollBar()->maximum();
|
||||
// int value = m_ui->scrollArea->verticalScrollBar()->value() + amount;
|
||||
// m_ui->scrollArea->verticalScrollBar()->setValue(value);
|
||||
//
|
||||
// if (value == max) {
|
||||
// scroll = down;
|
||||
// } else {
|
||||
// scroll = nothing;
|
||||
// }
|
||||
// }
|
||||
// break;
|
||||
// default:
|
||||
// break;
|
||||
// }
|
||||
// manualSliderChange = false;
|
||||
}
|
||||
|
||||
void Conversation::onSliderValueChanged(int value)
|
||||
{
|
||||
if (!manualSliderChange) {
|
||||
if (value == m_ui->scrollArea->verticalScrollBar()->maximum()) {
|
||||
scroll = down;
|
||||
} else {
|
||||
if (!requestingHistory && value == 0) {
|
||||
requestingHistory = true;
|
||||
line->showBusyIndicator();
|
||||
emit requestArchive(line->firstMessageId());
|
||||
scroll = keep;
|
||||
} else {
|
||||
scroll = nothing;
|
||||
}
|
||||
}
|
||||
}
|
||||
// if (!manualSliderChange) {
|
||||
// if (value == m_ui->scrollArea->verticalScrollBar()->maximum()) {
|
||||
// scroll = down;
|
||||
// } else {
|
||||
// if (!requestingHistory && value == 0) {
|
||||
// requestingHistory = true;
|
||||
// line->showBusyIndicator();
|
||||
// emit requestArchive(line->firstMessageId());
|
||||
// scroll = keep;
|
||||
// } else {
|
||||
// scroll = nothing;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
void Conversation::responseArchive(const std::list<Shared::Message> list)
|
||||
{
|
||||
requestingHistory = false;
|
||||
scroll = keep;
|
||||
|
||||
line->hideBusyIndicator();
|
||||
for (std::list<Shared::Message>::const_iterator itr = list.begin(), end = list.end(); itr != end; ++itr) {
|
||||
addMessage(*itr);
|
||||
}
|
||||
// requestingHistory = false;
|
||||
// scroll = keep;
|
||||
//
|
||||
// line->hideBusyIndicator();
|
||||
// for (std::list<Shared::Message>::const_iterator itr = list.begin(), end = list.end(); itr != end; ++itr) {
|
||||
// addMessage(*itr);
|
||||
// }
|
||||
}
|
||||
|
||||
void Conversation::showEvent(QShowEvent* event)
|
||||
{
|
||||
if (!everShown) {
|
||||
everShown = true;
|
||||
line->showBusyIndicator();
|
||||
// line->showBusyIndicator();
|
||||
requestingHistory = true;
|
||||
scroll = keep;
|
||||
emit requestArchive(line->firstMessageId());
|
||||
emit requestArchive("");
|
||||
}
|
||||
emit shown();
|
||||
|
||||
|
@ -342,30 +332,30 @@ void Conversation::setStatus(const QString& status)
|
|||
|
||||
void Conversation::onScrollResize()
|
||||
{
|
||||
if (everShown) {
|
||||
int size = m_ui->scrollArea->width();
|
||||
QScrollBar* bar = m_ui->scrollArea->verticalScrollBar();
|
||||
if (bar->isVisible() && !tsb) {
|
||||
size -= bar->width();
|
||||
|
||||
}
|
||||
line->setMaximumWidth(size);
|
||||
}
|
||||
// if (everShown) {
|
||||
// int size = m_ui->scrollArea->width();
|
||||
// QScrollBar* bar = m_ui->scrollArea->verticalScrollBar();
|
||||
// if (bar->isVisible() && !tsb) {
|
||||
// size -= bar->width();
|
||||
//
|
||||
// }
|
||||
// line->setMaximumWidth(size);
|
||||
// }
|
||||
}
|
||||
|
||||
void Conversation::responseFileProgress(const QString& messageId, qreal progress)
|
||||
{
|
||||
line->fileProgress(messageId, progress);
|
||||
// line->fileProgress(messageId, progress);
|
||||
}
|
||||
|
||||
void Conversation::fileError(const QString& messageId, const QString& error)
|
||||
{
|
||||
line->fileError(messageId, error);
|
||||
// line->fileError(messageId, error);
|
||||
}
|
||||
|
||||
void Conversation::responseLocalFile(const QString& messageId, const QString& path)
|
||||
{
|
||||
line->responseLocalFile(messageId, path);
|
||||
// line->responseLocalFile(messageId, path);
|
||||
}
|
||||
|
||||
Models::Roster::ElId Conversation::getId() const
|
||||
|
@ -444,7 +434,7 @@ void Conversation::onTextEditDocSizeChanged(const QSizeF& size)
|
|||
|
||||
void Conversation::setFeedFrames(bool top, bool right, bool bottom, bool left)
|
||||
{
|
||||
static_cast<DropShadowEffect*>(m_ui->scrollArea->graphicsEffect())->setFrame(top, right, bottom, left);
|
||||
//static_cast<DropShadowEffect*>(m_ui->scrollArea->graphicsEffect())->setFrame(top, right, bottom, left);
|
||||
}
|
||||
|
||||
void Conversation::dragEnterEvent(QDragEnterEvent* event)
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include <QMap>
|
||||
#include <QMimeData>
|
||||
#include <QFileInfo>
|
||||
#include <QListView>
|
||||
|
||||
#include "shared/message.h"
|
||||
#include "order.h"
|
||||
|
@ -78,7 +79,6 @@ public:
|
|||
QString getAccount() const;
|
||||
QString getPalResource() const;
|
||||
Models::Roster::ElId getId() const;
|
||||
virtual void addMessage(const Shared::Message& data);
|
||||
|
||||
void setPalResource(const QString& res);
|
||||
void responseArchive(const std::list<Shared::Message> list);
|
||||
|
@ -135,7 +135,6 @@ protected:
|
|||
Models::Account* account;
|
||||
QString palJid;
|
||||
QString activePalResource;
|
||||
MessageLine* line;
|
||||
QScopedPointer<Ui::Conversation> m_ui;
|
||||
KeyEnterReceiver ker;
|
||||
Resizer scrollResizeCatcher;
|
||||
|
@ -146,6 +145,7 @@ protected:
|
|||
FlowLayout* filesLayout;
|
||||
QWidget* overlay;
|
||||
W::Order<Badge*, Badge::Comparator> filesToAttach;
|
||||
QListView* feed;
|
||||
Scroll scroll;
|
||||
bool manualSliderChange;
|
||||
bool requestingHistory;
|
||||
|
|
|
@ -215,60 +215,37 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QScrollArea" name="scrollArea">
|
||||
<property name="autoFillBackground">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QListView" name="feed">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="lineWidth">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="midLineWidth">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="horizontalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QAbstractScrollArea::AdjustIgnored</enum>
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::NoEditTriggers</set>
|
||||
</property>
|
||||
<property name="widgetResizable">
|
||||
<property name="showDropIndicator" stdset="0">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::NoSelection</enum>
|
||||
</property>
|
||||
<property name="verticalScrollMode">
|
||||
<enum>QAbstractItemView::ScrollPerPixel</enum>
|
||||
</property>
|
||||
<property name="horizontalScrollMode">
|
||||
<enum>QAbstractItemView::ScrollPerItem</enum>
|
||||
</property>
|
||||
<property name="isWrapping" stdset="0">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="scrollAreaWidgetContents">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>520</width>
|
||||
<height>385</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<zorder>scrollArea</zorder>
|
||||
<zorder>widget_3</zorder>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
|
|
|
@ -23,7 +23,6 @@ Room::Room(Models::Account* acc, Models::Room* p_room, QWidget* parent):
|
|||
room(p_room)
|
||||
{
|
||||
setName(p_room->getName());
|
||||
line->setMyName(room->getNick());
|
||||
setStatus(room->getSubject());
|
||||
setAvatar(room->getAvatarPath());
|
||||
|
||||
|
@ -31,15 +30,7 @@ Room::Room(Models::Account* acc, Models::Room* p_room, QWidget* parent):
|
|||
connect(room, &Models::Room::participantJoined, this, &Room::onParticipantJoined);
|
||||
connect(room, &Models::Room::participantLeft, this, &Room::onParticipantLeft);
|
||||
|
||||
std::map<QString, const Models::Participant&> members = room->getParticipants();
|
||||
for (std::pair<QString, const Models::Participant&> pair : members) {
|
||||
QString aPath = pair.second.getAvatarPath();
|
||||
if (aPath.size() > 0) {
|
||||
line->setPalAvatar(pair.first, aPath);
|
||||
}
|
||||
}
|
||||
|
||||
line->setExPalAvatars(room->getExParticipantAvatars());
|
||||
feed->setModel(p_room->feed);
|
||||
}
|
||||
|
||||
Room::~Room()
|
||||
|
@ -75,30 +66,14 @@ void Room::onRoomChanged(Models::Item* item, int row, int col)
|
|||
setAvatar(room->getAvatarPath());
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (col) {
|
||||
case 7: {
|
||||
Models::Participant* mem = static_cast<Models::Participant*>(item);
|
||||
QString aPath = mem->getAvatarPath();
|
||||
if (aPath.size() > 0) {
|
||||
line->setPalAvatar(mem->getName(), aPath);
|
||||
} else {
|
||||
line->dropPalAvatar(mem->getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Room::onParticipantJoined(const Models::Participant& participant)
|
||||
{
|
||||
QString aPath = participant.getAvatarPath();
|
||||
if (aPath.size() > 0) {
|
||||
line->setPalAvatar(participant.getName(), aPath);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Room::onParticipantLeft(const QString& name)
|
||||
{
|
||||
line->movePalAvatarToEx(name);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue