Brings the Qt uploader to parity with the web /manage page and the Android
app. Adds a stacked Manage page (reached from the post page) that lists
posts via GET /api/photos with thumbnails from GET /api/thumb/<slug>, edits
caption/date/tags in a QDialog via POST /api/edit, and deletes to trash via
POST /api/delete with a QMessageBox confirm. As a non-browser client it sends
no Origin and so passes the server's same-origin CSRF check.
Tag autocomplete now backs every tags field (post cards and the edit dialog)
via a per-token TagCompleter (QCompleter subclass, MatchContains) fed from
the unguarded GET /api/tags.json.
New files: managepostwidget.{h,cpp}, tagcompleter.{h,cpp}; network methods
and a RemotePhoto struct added to uploadclient. Built and linked clean; not
yet runtime-tested on a real display.
121 lines
3.5 KiB
C++
121 lines
3.5 KiB
C++
#include "photoitemwidget.h"
|
|
|
|
#include "tagcompleter.h"
|
|
|
|
#include <QDate>
|
|
#include <QDateEdit>
|
|
#include <QHBoxLayout>
|
|
#include <QImageReader>
|
|
#include <QLabel>
|
|
#include <QLineEdit>
|
|
#include <QPixmap>
|
|
#include <QPushButton>
|
|
#include <QVBoxLayout>
|
|
|
|
namespace {
|
|
constexpr int kThumbSize = 72;
|
|
}
|
|
|
|
PhotoItemWidget::PhotoItemWidget(QString sourcePath, QWidget *parent)
|
|
: QWidget(parent)
|
|
, m_sourcePath(std::move(sourcePath))
|
|
{
|
|
auto *row = new QHBoxLayout(this);
|
|
row->setContentsMargins(0, 4, 0, 4);
|
|
|
|
m_thumb = new QLabel;
|
|
m_thumb->setFixedSize(kThumbSize, kThumbSize);
|
|
m_thumb->setAlignment(Qt::AlignCenter);
|
|
m_thumb->setFrameShape(QFrame::Box);
|
|
{
|
|
QImageReader reader(m_sourcePath);
|
|
reader.setAutoTransform(true);
|
|
const QImage image = reader.read();
|
|
if (!image.isNull())
|
|
m_thumb->setPixmap(QPixmap::fromImage(image).scaled(
|
|
kThumbSize, kThumbSize, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation));
|
|
else
|
|
m_thumb->setText(QStringLiteral("?"));
|
|
}
|
|
row->addWidget(m_thumb);
|
|
|
|
auto *fields = new QVBoxLayout;
|
|
m_caption = new QLineEdit;
|
|
m_caption->setPlaceholderText(QStringLiteral("Caption (defaults to the filename)"));
|
|
fields->addWidget(m_caption);
|
|
|
|
m_date = new QDateEdit(QDate::currentDate());
|
|
m_date->setCalendarPopup(true);
|
|
m_date->setDisplayFormat(QStringLiteral("yyyy-MM-dd"));
|
|
fields->addWidget(m_date);
|
|
|
|
m_tags = new QLineEdit;
|
|
m_tags->setPlaceholderText(QStringLiteral("tags (comma-separated)"));
|
|
m_tagCompleter = new TagCompleter(this);
|
|
m_tags->setCompleter(m_tagCompleter);
|
|
fields->addWidget(m_tags);
|
|
|
|
m_statusLabel = new QLabel;
|
|
m_statusLabel->setOpenExternalLinks(true);
|
|
m_statusLabel->setWordWrap(true);
|
|
fields->addWidget(m_statusLabel);
|
|
row->addLayout(fields, 1);
|
|
|
|
m_remove = new QPushButton(QStringLiteral("✕"));
|
|
m_remove->setFlat(true);
|
|
m_remove->setFixedWidth(32);
|
|
connect(m_remove, &QPushButton::clicked, this, [this]() { emit removeRequested(this); });
|
|
row->addWidget(m_remove, 0, Qt::AlignTop);
|
|
}
|
|
|
|
QString PhotoItemWidget::caption() const
|
|
{
|
|
return m_caption->text();
|
|
}
|
|
|
|
QString PhotoItemWidget::date() const
|
|
{
|
|
return m_date->date().toString(QStringLiteral("yyyy-MM-dd"));
|
|
}
|
|
|
|
QString PhotoItemWidget::tags() const
|
|
{
|
|
return m_tags->text();
|
|
}
|
|
|
|
void PhotoItemWidget::setTagSuggestions(const QStringList &tags)
|
|
{
|
|
m_tagCompleter->setSuggestions(tags);
|
|
}
|
|
|
|
void PhotoItemWidget::setStatus(Status status, const QString &message, const QString &url)
|
|
{
|
|
m_status = status;
|
|
|
|
// Inputs are frozen once the photo is in flight; the remove button stays
|
|
// usable except mid-transfer.
|
|
const bool editable = status == Status::Pending || status == Status::Error;
|
|
m_caption->setEnabled(editable);
|
|
m_date->setEnabled(editable);
|
|
m_tags->setEnabled(editable);
|
|
m_remove->setEnabled(status != Status::Preparing && status != Status::Uploading);
|
|
|
|
switch (status) {
|
|
case Status::Pending:
|
|
m_statusLabel->clear();
|
|
break;
|
|
case Status::Preparing:
|
|
m_statusLabel->setText(QStringLiteral("Preparing…"));
|
|
break;
|
|
case Status::Uploading:
|
|
m_statusLabel->setText(QStringLiteral("Uploading…"));
|
|
break;
|
|
case Status::Success:
|
|
m_statusLabel->setText(QStringLiteral("Posted — <a href=\"%1\">View</a>").arg(url));
|
|
break;
|
|
case Status::Error:
|
|
m_statusLabel->setText(QStringLiteral("<span style=\"color:#fb4934\">%1</span>").arg(message));
|
|
break;
|
|
}
|
|
}
|