fix: improve image type detection
- use the same approach as status-go to detect the image type, relying on "magic" type matching instead of looking at the file extension (now using C++ and QMime*) - add a little error popup when the user tries to upload an unsupported image type while creating/editing a community - expose all the image related properties from the C++ backend instead of constructing and duplicating them in QML - cleanup some unused/dead code Fixes #16668
This commit is contained in:
parent
e3238b3fd2
commit
623333ab8c
|
@ -1,18 +1,38 @@
|
|||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class QQmlEngine;
|
||||
class QJSEngine;
|
||||
#include <QMimeDatabase>
|
||||
|
||||
class UrlUtils : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString validImageNameFilters READ validImageNameFilters FINAL CONSTANT) // "*.jpg *.jpe *.jp *.jpeg *.png *.webp *.gif *.svg"
|
||||
Q_PROPERTY(QStringList validPreferredImageExtensions READ validPreferredImageExtensions FINAL CONSTANT) // ["jpg", "png", "webp", "gif", "svg"]
|
||||
Q_PROPERTY(QStringList allValidImageExtensions READ allValidImageExtensions FINAL CONSTANT) // ["jpg", "jpe", "jp", "jpeg", "png", "webp", "gif", "svg"]
|
||||
|
||||
public:
|
||||
static QObject* qmlInstance(QQmlEngine* engine, QJSEngine* scriptEngine);
|
||||
explicit UrlUtils(QObject* parent = nullptr);
|
||||
|
||||
Q_INVOKABLE static bool isValidImageUrl(const QUrl &url,
|
||||
const QStringList &acceptedExtensions);
|
||||
Q_INVOKABLE bool isValidImageUrl(const QUrl &url) const;
|
||||
Q_INVOKABLE static qint64 getFileSize(const QUrl &url);
|
||||
|
||||
private:
|
||||
QMimeDatabase m_mimeDb;
|
||||
|
||||
QStringList m_validImageMimeTypes{QStringLiteral("image/jpeg"),
|
||||
QStringLiteral("image/png"),
|
||||
QStringLiteral("image/gif"),
|
||||
QStringLiteral("image/svg")};
|
||||
|
||||
// "*.jpg *.jpe *.jp *.jpeg *.png *.webp *.gif *.svg"
|
||||
QString m_imgFilters;
|
||||
QString validImageNameFilters() const { return m_imgFilters; }
|
||||
|
||||
// ["jpg", "png", "webp", "gif", "svg"]
|
||||
QStringList m_imgExtensions;
|
||||
QStringList validPreferredImageExtensions() const { return m_imgExtensions; }
|
||||
|
||||
// ["jpg", "jpe", "jp", "jpeg", "png", "webp", "gif", "svg"]
|
||||
QStringList m_allImgExtensions;
|
||||
QStringList allValidImageExtensions() const { return m_allImgExtensions; }
|
||||
};
|
||||
|
|
|
@ -71,7 +71,9 @@ void registerStatusQTypes() {
|
|||
qmlRegisterType<FormattedDoubleProperty>("StatusQ", 0, 1, "FormattedDoubleProperty");
|
||||
|
||||
qmlRegisterSingletonType<ClipboardUtils>("StatusQ", 0, 1, "ClipboardUtils", &ClipboardUtils::qmlInstance);
|
||||
qmlRegisterSingletonType<UrlUtils>("StatusQ", 0, 1, "UrlUtils", &UrlUtils::qmlInstance);
|
||||
qmlRegisterSingletonType<UrlUtils>("StatusQ", 0, 1, "UrlUtils", [](QQmlEngine* engine, QJSEngine*) {
|
||||
return new UrlUtils(engine);
|
||||
});
|
||||
|
||||
qmlRegisterType<ModelEntry>("StatusQ", 0, 1, "ModelEntry");
|
||||
qmlRegisterType<SnapshotObject>("StatusQ", 0, 1, "SnapshotObject");
|
||||
|
|
|
@ -1,25 +1,40 @@
|
|||
#include "StatusQ/urlutils.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QImageReader>
|
||||
#include <QUrl>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
QObject* UrlUtils::qmlInstance(QQmlEngine*, QJSEngine*)
|
||||
{
|
||||
return new UrlUtils;
|
||||
namespace {
|
||||
constexpr auto webpMime = "image/webp";
|
||||
}
|
||||
|
||||
bool UrlUtils::isValidImageUrl(const QUrl& url, const QStringList& acceptedExtensions)
|
||||
{
|
||||
const auto strippedUrl = url.url(
|
||||
QUrl::RemoveAuthority | QUrl::RemoveFragment | QUrl::RemoveQuery);
|
||||
UrlUtils::UrlUtils(QObject *parent): QObject(parent) {
|
||||
const auto webpSupported = QImageReader::supportedMimeTypes().contains(webpMime);
|
||||
if (webpSupported)
|
||||
m_validImageMimeTypes.append(webpMime);
|
||||
|
||||
return std::any_of(acceptedExtensions.constBegin(),
|
||||
acceptedExtensions.constEnd(),
|
||||
[strippedUrl](const auto & ext) {
|
||||
return strippedUrl.endsWith(ext, Qt::CaseInsensitive);
|
||||
});
|
||||
QStringList imgFilters;
|
||||
for (const auto& mime: std::as_const(m_validImageMimeTypes)) {
|
||||
const auto mimeData = m_mimeDb.mimeTypeForName(mime);
|
||||
imgFilters.append(mimeData.globPatterns());
|
||||
m_imgExtensions.append(mimeData.preferredSuffix());
|
||||
m_allImgExtensions.append(mimeData.suffixes());
|
||||
}
|
||||
|
||||
m_imgFilters = imgFilters.join(' ');
|
||||
m_imgFilters.append(QStringLiteral(" "));
|
||||
m_imgFilters.append(m_imgFilters.toUpper()); // include the uppercase extensions too for case sensitive file systems
|
||||
}
|
||||
|
||||
bool UrlUtils::isValidImageUrl(const QUrl &url) const
|
||||
{
|
||||
QString mimeType;
|
||||
if (url.isLocalFile())
|
||||
mimeType = m_mimeDb.mimeTypeForFile(url.toLocalFile(), QMimeDatabase::MatchContent).name();
|
||||
else
|
||||
mimeType = m_mimeDb.mimeTypeForUrl(url).name();
|
||||
|
||||
return m_validImageMimeTypes.contains(mimeType);
|
||||
}
|
||||
|
||||
qint64 UrlUtils::getFileSize(const QUrl& url)
|
||||
|
|
|
@ -38,27 +38,6 @@ QtObject {
|
|||
readonly property bool createCommunityEnabled: localAppSettings.createCommunityEnabled ?? false
|
||||
readonly property bool testEnvironment: localAppSettings.testEnvironment ?? false
|
||||
|
||||
// TODO: Could the backend provide directly 2 filtered models??
|
||||
//property var featuredCommunitiesModel: root.communitiesModuleInst.curatedFeaturedCommunities
|
||||
//property var popularCommunitiesModel: root.communitiesModuleInst.curatedPopularCommunities
|
||||
property ListModel tagsModel: ListModel {}//root.notionsTagsModel
|
||||
|
||||
// TO DO: Complete list can be added in backend or here: https://www.notion.so/Category-tags-339b2e699e7c4d36ab0608ab00b99111
|
||||
property ListModel notionsTagsModel : ListModel {
|
||||
ListElement { name: "gaming"; emoji: "🎮"}
|
||||
ListElement { name: "art"; emoji: "🖼️️"}
|
||||
ListElement { name: "crypto"; emoji: "💸"}
|
||||
ListElement { name: "nsfw"; emoji: "🍆"}
|
||||
ListElement { name: "markets"; emoji: "💎"}
|
||||
ListElement { name: "defi"; emoji: "📈"}
|
||||
ListElement { name: "travel"; emoji: "🚁"}
|
||||
ListElement { name: "web3"; emoji: "🗺"}
|
||||
ListElement { name: "sport"; emoji: "🎾"}
|
||||
ListElement { name: "food"; emoji: "🥑"}
|
||||
ListElement { name: "enviroment"; emoji: "☠️"}
|
||||
ListElement { name: "privacy"; emoji: "👻"}
|
||||
}
|
||||
|
||||
property string communityTags: communitiesModuleInst.tags
|
||||
|
||||
signal importingCommunityStateChanged(string communityId, int state, string errorMsg)
|
||||
|
|
|
@ -147,11 +147,12 @@ SettingsContentBase {
|
|||
|
||||
Panel {
|
||||
id: panelMembers
|
||||
filters: ExpressionFilter {
|
||||
filters: FastExpressionFilter {
|
||||
readonly property int ownerRole: Constants.memberRole.owner
|
||||
readonly property int adminRole: Constants.memberRole.admin
|
||||
readonly property int tokenMasterRole: Constants.memberRole.tokenMaster
|
||||
expression: model.joined && model.memberRole !== ownerRole && model.memberRole !== adminRole && model.memberRole !== tokenMasterRole
|
||||
expectedRoles: ["joined", "memberRole"]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -162,8 +163,9 @@ SettingsContentBase {
|
|||
|
||||
Panel {
|
||||
id: panelPendingRequests
|
||||
filters: ExpressionFilter {
|
||||
filters: FastExpressionFilter {
|
||||
expression: model.spectated && !model.joined
|
||||
expectedRoles: ["joined", "spectated"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQuick.Dialogs 1.3
|
||||
|
||||
import StatusQ 0.1
|
||||
import StatusQ.Components 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Popups 0.1
|
||||
import StatusQ.Popups.Dialog 0.1
|
||||
|
||||
import utils 1.0
|
||||
|
||||
|
@ -39,14 +42,37 @@ Item {
|
|||
|
||||
title: root.imageFileDialogTitle
|
||||
folder: root.userSelectedImage ? imageCropper.source.substr(0, imageCropper.source.lastIndexOf("/")) : shortcuts.pictures
|
||||
nameFilters: [qsTr("Supported image formats (%1)").arg(Constants.acceptedDragNDropImageExtensions.map(img => "*" + img).join(" "))]
|
||||
nameFilters: [qsTr("Supported image formats (%1)").arg(UrlUtils.validImageNameFilters)]
|
||||
onAccepted: {
|
||||
if (fileDialog.fileUrls.length > 0) {
|
||||
cropImage(fileDialog.fileUrls[0])
|
||||
const url = fileDialog.fileUrls[0]
|
||||
if (Utils.isValidDragNDropImage(url))
|
||||
cropImage(url)
|
||||
else
|
||||
errorDialog.open()
|
||||
}
|
||||
}
|
||||
} // FileDialog
|
||||
|
||||
StatusDialog {
|
||||
id: errorDialog
|
||||
title: qsTr("Image format not supported")
|
||||
width: 480
|
||||
contentItem: ColumnLayout {
|
||||
StatusBaseText {
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.WordWrap
|
||||
text: qsTr("Format of the image you chose is not supported. Most probably you picked a file that is invalid, corrupted or has a wrong file extension.")
|
||||
}
|
||||
StatusBaseText {
|
||||
Layout.fillWidth: true
|
||||
font.pixelSize: Theme.additionalTextSize
|
||||
text: qsTr("Supported image extensions: %1").arg(UrlUtils.allValidImageExtensions)
|
||||
}
|
||||
}
|
||||
standardButtons: Dialog.Ok
|
||||
} // StatusDialog
|
||||
|
||||
StatusModal {
|
||||
id: imageCropperModal
|
||||
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import QtQuick 2.15
|
||||
|
||||
import StatusQ 0.1
|
||||
|
||||
import utils 1.0
|
||||
|
||||
StatusChatImageValidator {
|
||||
id: root
|
||||
|
||||
errorMessage: qsTr("Format not supported.")
|
||||
secondaryErrorMessage: qsTr("Upload %1 only").arg(Constants.acceptedDragNDropImageExtensions.map(ext => ext.replace(".", "").toUpperCase() + "s").join(", "))
|
||||
secondaryErrorMessage: qsTr("Upload %1 only").arg(UrlUtils.validPreferredImageExtensions.map(ext => ext.toUpperCase() + "s").join(", "))
|
||||
|
||||
onImagesChanged: {
|
||||
let isValid = true
|
||||
|
|
|
@ -982,7 +982,7 @@ Rectangle {
|
|||
folder: shortcuts.pictures
|
||||
selectMultiple: true
|
||||
nameFilters: [
|
||||
qsTr("Image files (%1)").arg(Constants.acceptedDragNDropImageExtensions.map(img => "*" + img).join(" "))
|
||||
qsTr("Image files (%1)").arg(UrlUtils.validImageNameFilters)
|
||||
]
|
||||
onAccepted: {
|
||||
imageBtn.highlighted = false
|
||||
|
|
|
@ -955,7 +955,6 @@ QtObject {
|
|||
readonly property int maxNumberOfPins: 3
|
||||
|
||||
readonly property string dataImagePrefix: "data:image"
|
||||
readonly property var acceptedDragNDropImageExtensions: [".png", ".jpg", ".jpeg"]
|
||||
|
||||
readonly property string mentionSpanTag: `<span style="background-color: ${Theme.palette.mentionColor2};"><a style="color:${Theme.palette.mentionColor1};text-decoration:none" href='http://'>`
|
||||
|
||||
|
|
|
@ -276,8 +276,7 @@ QtObject {
|
|||
}
|
||||
|
||||
function isValidDragNDropImage(url) {
|
||||
return url.startsWith(Constants.dataImagePrefix) ||
|
||||
UrlUtils.isValidImageUrl(url, Constants.acceptedDragNDropImageExtensions)
|
||||
return url.startsWith(Constants.dataImagePrefix) || UrlUtils.isValidImageUrl(url)
|
||||
}
|
||||
|
||||
function isFilesizeValid(img) {
|
||||
|
|
Loading…
Reference in New Issue