summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMikolaj Boc <mikolaj.boc@qt.io>2022-09-23 15:27:50 +0200
committerMikolaj Boc <mikolaj.boc@qt.io>2023-03-07 12:50:41 +0100
commit79cc3ae201483f42b8b333b29a7924ec1d4e2acd (patch)
tree79568425e40efa1c870dbf18ca88b8345ff39051
parentc6183cfc7b971160b293d7f619bb41045837c0e6 (diff)
Support filter list for file input when opening a file on WASM
The polyfill for file input on WASM now makes use of the supplied filter list. Some changes were introduced in the abstraction for filters so that they are usable both for the new file API and the legacy file input. Change-Id: Id6341be4d6a1647e17382d13da7be42491cfaf80 Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
-rw-r--r--src/gui/platform/wasm/qlocalfileapi.cpp108
-rw-r--r--src/gui/platform/wasm/qlocalfileapi_p.h22
-rw-r--r--src/gui/platform/wasm/qwasmlocalfileaccess.cpp2
-rw-r--r--tests/auto/wasm/tst_localfileapi.cpp113
4 files changed, 157 insertions, 88 deletions
diff --git a/src/gui/platform/wasm/qlocalfileapi.cpp b/src/gui/platform/wasm/qlocalfileapi.cpp
index b56490eba4..bfcb5a5866 100644
--- a/src/gui/platform/wasm/qlocalfileapi.cpp
+++ b/src/gui/platform/wasm/qlocalfileapi.cpp
@@ -8,6 +8,33 @@
QT_BEGIN_NAMESPACE
namespace LocalFileApi {
namespace {
+std::string qtFilterListToFileInputAccept(const QStringList &filterList)
+{
+ QStringList transformed;
+ for (const auto &filter : filterList) {
+
+ emscripten::val::global("console").call<void>("log", filter.toStdString());
+
+ const auto type = Type::fromQt(filter);
+ if (type && type->accept()) {
+ const auto &extensions = type->accept()->mimeType().extensions();
+ for (const auto &ext : extensions) {
+ emscripten::val::global("console").call<void>("log",
+ ext.value().toString().toStdString());
+ }
+
+ std::transform(extensions.begin(), extensions.end(), std::back_inserter(transformed),
+ [](const Type::Accept::MimeType::Extension &extension) {
+ return extension.value().toString();
+ });
+ }
+ }
+ for (const QString &tran : transformed) {
+ emscripten::val::global("console").call<void>("log", tran.toStdString());
+ }
+ return transformed.join(QStringLiteral(",")).toStdString();
+}
+
std::optional<emscripten::val> qtFilterListToTypes(const QStringList &filterList)
{
using namespace qstdweb;
@@ -17,20 +44,38 @@ std::optional<emscripten::val> qtFilterListToTypes(const QStringList &filterList
for (const auto &fileFilter : filterList) {
auto type = Type::fromQt(fileFilter);
- if (type)
- types.call<void>("push", type->asVal());
+ if (type) {
+ auto jsType = val::object();
+ jsType.set("description", type->description().toString().toStdString());
+ if (type->accept()) {
+ jsType.set("accept", ([&mimeType = type->accept()->mimeType()]() {
+ val acceptDict = val::object();
+
+ QList<emscripten::val> extensions;
+ extensions.reserve(mimeType.extensions().size());
+ std::transform(
+ mimeType.extensions().begin(), mimeType.extensions().end(),
+ std::back_inserter(extensions),
+ [](const Type::Accept::MimeType::Extension &extension) {
+ return val(extension.value().toString().toStdString());
+ });
+ acceptDict.set("application/octet-stream",
+ emscripten::val::array(extensions.begin(),
+ extensions.end()));
+ return acceptDict;
+ })());
+ }
+ types.call<void>("push", std::move(jsType));
+ }
}
return types["length"].as<int>() == 0 ? std::optional<emscripten::val>() : types;
}
-}
+} // namespace
Type::Type(QStringView description, std::optional<Accept> accept)
- : m_storage(emscripten::val::object())
+ : m_description(description.trimmed()), m_accept(std::move(accept))
{
- m_storage.set("description", description.trimmed().toString().toStdString());
- if (accept)
- m_storage.set("accept", accept->asVal());
}
Type::~Type() = default;
@@ -69,12 +114,7 @@ std::optional<Type> Type::fromQt(QStringView type)
return Type(description, std::move(*accept));
}
-emscripten::val Type::asVal() const
-{
- return m_storage;
-}
-
-Type::Accept::Accept() : m_storage(emscripten::val::object()) { }
+Type::Accept::Accept() = default;
Type::Accept::~Accept() = default;
@@ -100,39 +140,25 @@ std::optional<Type::Accept> Type::Accept::fromQt(QStringView qtRepresentation)
internalMatch = internalRegex.matchView(qtRepresentation, internalMatch.capturedEnd());
}
- accept.addMimeType(mimeType);
+ accept.setMimeType(mimeType);
return accept;
}
-void Type::Accept::addMimeType(MimeType mimeType)
-{
- // The mime type provided here does not seem to have any effect at the result at all.
- m_storage.set("application/octet-stream", mimeType.asVal());
-}
-
-emscripten::val Type::Accept::asVal() const
+void Type::Accept::setMimeType(MimeType mimeType)
{
- return m_storage;
+ m_mimeType = std::move(mimeType);
}
-Type::Accept::MimeType::MimeType() : m_storage(emscripten::val::array()) { }
+Type::Accept::MimeType::MimeType() = default;
Type::Accept::MimeType::~MimeType() = default;
void Type::Accept::MimeType::addExtension(Extension extension)
{
- m_storage.call<void>("push", extension.asVal());
+ m_extensions.push_back(std::move(extension));
}
-emscripten::val Type::Accept::MimeType::asVal() const
-{
- return m_storage;
-}
-
-Type::Accept::MimeType::Extension::Extension(QStringView extension)
- : m_storage(extension.toString().toStdString())
-{
-}
+Type::Accept::MimeType::Extension::Extension(QStringView extension) : m_value(extension) { }
Type::Accept::MimeType::Extension::~Extension() = default;
@@ -161,15 +187,10 @@ Type::Accept::MimeType::Extension::fromQt(QStringView qtRepresentation)
return std::nullopt;
}
-emscripten::val Type::Accept::MimeType::Extension::asVal() const
-{
- return m_storage;
-}
-
emscripten::val makeOpenFileOptions(const QStringList &filterList, bool acceptMultiple)
{
auto options = emscripten::val::object();
- if (auto typeList = LocalFileApi::qtFilterListToTypes(filterList)) {
+ if (auto typeList = qtFilterListToTypes(filterList)) {
options.set("types", std::move(*typeList));
options.set("excludeAcceptAllOption", true);
}
@@ -186,12 +207,17 @@ emscripten::val makeSaveFileOptions(const QStringList &filterList, const std::st
if (!suggestedName.empty())
options.set("suggestedName", emscripten::val(suggestedName));
- if (auto typeList = LocalFileApi::qtFilterListToTypes(filterList))
+ if (auto typeList = qtFilterListToTypes(filterList))
options.set("types", emscripten::val(std::move(*typeList)));
return options;
}
-} // namespace LocalFileApi
+std::string makeFileInputAccept(const QStringList &filterList)
+{
+ return qtFilterListToFileInputAccept(filterList);
+}
+
+} // namespace LocalFileApi
QT_END_NAMESPACE
diff --git a/src/gui/platform/wasm/qlocalfileapi_p.h b/src/gui/platform/wasm/qlocalfileapi_p.h
index a8e7f666f9..b8fa6adb99 100644
--- a/src/gui/platform/wasm/qlocalfileapi_p.h
+++ b/src/gui/platform/wasm/qlocalfileapi_p.h
@@ -36,12 +36,12 @@ public:
~Extension();
- emscripten::val asVal() const;
+ const QStringView &value() const { return m_value; }
private:
explicit Extension(QStringView extension);
- emscripten::val m_storage;
+ QStringView m_value;
};
MimeType();
@@ -49,38 +49,42 @@ public:
void addExtension(Extension type);
- emscripten::val asVal() const;
+ const std::vector<Extension> &extensions() const { return m_extensions; }
private:
- emscripten::val m_storage;
+ std::vector<Extension> m_extensions;
};
static std::optional<Accept> fromQt(QStringView type);
~Accept();
- void addMimeType(MimeType mimeType);
+ void setMimeType(MimeType mimeType);
- emscripten::val asVal() const;
+ const MimeType &mimeType() const { return m_mimeType; }
private:
Accept();
- emscripten::val m_storage;
+ MimeType m_mimeType;
};
Type(QStringView description, std::optional<Accept> accept);
~Type();
static std::optional<Type> fromQt(QStringView type);
- emscripten::val asVal() const;
+ const QStringView &description() const { return m_description; }
+ const std::optional<Accept> &accept() const { return m_accept; }
private:
- emscripten::val m_storage;
+ QStringView m_description;
+ std::optional<Accept> m_accept;
};
Q_CORE_EXPORT emscripten::val makeOpenFileOptions(const QStringList &filterList, bool acceptMultiple);
Q_CORE_EXPORT emscripten::val makeSaveFileOptions(const QStringList &filterList, const std::string& suggestedName);
+Q_AUTOTEST_EXPORT std::string makeFileInputAccept(const QStringList &filterList);
+
} // namespace LocalFileApi
QT_END_NAMESPACE
diff --git a/src/gui/platform/wasm/qwasmlocalfileaccess.cpp b/src/gui/platform/wasm/qwasmlocalfileaccess.cpp
index 72f1dcc339..762d5e1a40 100644
--- a/src/gui/platform/wasm/qwasmlocalfileaccess.cpp
+++ b/src/gui/platform/wasm/qwasmlocalfileaccess.cpp
@@ -31,7 +31,7 @@ void showOpenViaHTMLPolyfill(const QStringList &accept, FileSelectMode fileSelec
emscripten::val input = document.call<emscripten::val>("createElement", std::string("input"));
input.set("type", "file");
input.set("style", "display:none");
- // input.set("accept", emscripten::val(accept));
+ input.set("accept", LocalFileApi::makeFileInputAccept(accept));
Q_UNUSED(accept);
input.set("multiple", emscripten::val(fileSelectMode == FileSelectMode::MultipleFiles));
diff --git a/tests/auto/wasm/tst_localfileapi.cpp b/tests/auto/wasm/tst_localfileapi.cpp
index 0d17202d4b..95bdbcffb2 100644
--- a/tests/auto/wasm/tst_localfileapi.cpp
+++ b/tests/auto/wasm/tst_localfileapi.cpp
@@ -11,14 +11,16 @@ class tst_LocalFileApi : public QObject
Q_OBJECT
private:
- emscripten::val makeAccept(std::vector<emscripten::val> types) {
+ emscripten::val makeAccept(std::vector<emscripten::val> types)
+ {
auto accept = emscripten::val::object();
accept.set("application/octet-stream",
emscripten::val::array(std::move(types)));
return accept;
}
- emscripten::val makeType(QString description, std::vector<emscripten::val> acceptExtensions) {
+ emscripten::val makeType(QString description, std::vector<emscripten::val> acceptExtensions)
+ {
using namespace emscripten;
auto type = val::object();
@@ -65,6 +67,8 @@ private Q_SLOTS:
void openFileOptions();
void saveFileOptions_data();
void saveFileOptions();
+ void fileInputAccept_data();
+ void fileInputAccept();
};
bool valDeepEquals(emscripten::val lhs, emscripten::val rhs)
@@ -79,24 +83,27 @@ bool valDeepEquals(emscripten::val lhs, emscripten::val rhs)
void tst_LocalFileApi::fileExtensionFilterTransformation_data()
{
QTest::addColumn<QString>("qtFileFilter");
- QTest::addColumn<std::optional<std::string>>("expectedWebExtensionFilter");
-
- QTest::newRow("PNG extension with an asterisk") << QString("*.png") << std::make_optional<std::string>(".png");
- QTest::newRow("Long extension with an asterisk") << QString("*.someotherfile") << std::make_optional<std::string>(".someotherfile");
- QTest::newRow(".dat with no asterisk") << QString(".dat") << std::make_optional<std::string>(".dat");
- QTest::newRow("Multiple asterisks") << QString("*ot*.abc") << std::optional<std::string>();
- QTest::newRow("Filename") << QString("abcd.abc") << std::optional<std::string>();
- QTest::newRow("match all") << QString("*.*") << std::optional<std::string>();
+ QTest::addColumn<std::optional<QString>>("expectedWebExtensionFilter");
+
+ QTest::newRow("PNG extension with an asterisk")
+ << QString("*.png") << std::make_optional<QString>(".png");
+ QTest::newRow("Long extension with an asterisk")
+ << QString("*.someotherfile") << std::make_optional<QString>(".someotherfile");
+ QTest::newRow(".dat with no asterisk")
+ << QString(".dat") << std::make_optional<QString>(".dat");
+ QTest::newRow("Multiple asterisks") << QString("*ot*.abc") << std::optional<QString>();
+ QTest::newRow("Filename") << QString("abcd.abc") << std::optional<QString>();
+ QTest::newRow("match all") << QString("*.*") << std::optional<QString>();
}
void tst_LocalFileApi::fileExtensionFilterTransformation()
{
QFETCH(QString, qtFileFilter);
- QFETCH(std::optional<std::string>, expectedWebExtensionFilter);
+ QFETCH(std::optional<QString>, expectedWebExtensionFilter);
auto result = LocalFileApi::Type::Accept::MimeType::Extension::fromQt(qtFileFilter);
if (expectedWebExtensionFilter) {
- QCOMPARE_EQ(expectedWebExtensionFilter, result->asVal().as<std::string>());
+ QCOMPARE_EQ(expectedWebExtensionFilter, result->value());
} else {
QVERIFY(!result.has_value());
}
@@ -107,34 +114,37 @@ void tst_LocalFileApi::acceptTransformation_data()
using namespace emscripten;
QTest::addColumn<QString>("qtFilterList");
- QTest::addColumn<emscripten::val>("expectedWebType");
+ QTest::addColumn<QStringList>("expectedExtensionList");
- QTest::newRow("Multiple types") << QString("*.png *.other *.txt")
- << makeAccept(std::vector<val> { val(".png"), val(".other"), val(".txt") });
+ QTest::newRow("Multiple types")
+ << QString("*.png *.other *.txt") << QStringList{ ".png", ".other", ".txt" };
- QTest::newRow("Single type") << QString("*.png")
- << makeAccept(std::vector<val> { val(".png") });
+ QTest::newRow("Single type") << QString("*.png") << QStringList{ ".png" };
- QTest::newRow("No filter when accepts all") << QString("*.*")
- << val::undefined();
+ QTest::newRow("No filter when accepts all") << QString("*.*") << QStringList();
- QTest::newRow("No filter when one filter accepts all") << QString("*.* *.jpg")
- << val::undefined();
+ QTest::newRow("No filter when one filter accepts all") << QString("*.* *.jpg") << QStringList();
QTest::newRow("Weird spaces") << QString(" *.jpg *.png *.icon ")
- << makeAccept(std::vector<val> { val(".jpg"), val(".png"), val(".icon") });
+ << QStringList{ ".jpg", ".png", ".icon" };
}
void tst_LocalFileApi::acceptTransformation()
{
QFETCH(QString, qtFilterList);
- QFETCH(emscripten::val, expectedWebType);
+ QFETCH(QStringList, expectedExtensionList);
auto result = LocalFileApi::Type::Accept::fromQt(qtFilterList);
- if (!expectedWebType.isUndefined()) {
- QVERIFY(valDeepEquals(result->asVal(), expectedWebType));
- } else {
+ if (expectedExtensionList.isEmpty()) {
QVERIFY(!result.has_value());
+ } else {
+ QStringList transformed;
+ std::transform(result->mimeType().extensions().begin(),
+ result->mimeType().extensions().end(), std::back_inserter(transformed),
+ [](const LocalFileApi::Type::Accept::MimeType::Extension &extension) {
+ return extension.value().toString();
+ });
+ QCOMPARE_EQ(expectedExtensionList, transformed);
}
}
@@ -143,26 +153,31 @@ void tst_LocalFileApi::typeTransformation_data()
using namespace emscripten;
QTest::addColumn<QString>("qtFilterList");
- QTest::addColumn<emscripten::val>("expectedWebType");
+ QTest::addColumn<QString>("expectedDescription");
+ QTest::addColumn<QStringList>("expectedExtensions");
- QTest::newRow("With description") << QString("Text files (*.txt)")
- << makeType("Text files", std::vector<val> { val(".txt") });
+ QTest::newRow("With description")
+ << QString("Text files (*.txt)") << QString("Text files") << QStringList{ ".txt" };
- QTest::newRow("No description") << QString("*.jpg")
- << makeType("", std::vector<val> { val(".jpg") });
+ QTest::newRow("No description") << QString("*.jpg") << QString("") << QStringList{ ".jpg" };
}
void tst_LocalFileApi::typeTransformation()
{
QFETCH(QString, qtFilterList);
- QFETCH(emscripten::val, expectedWebType);
+ QFETCH(QString, expectedDescription);
+ QFETCH(QStringList, expectedExtensions);
auto result = LocalFileApi::Type::fromQt(qtFilterList);
- if (!expectedWebType.isUndefined()) {
- QVERIFY(valDeepEquals(result->asVal(), expectedWebType));
- } else {
- QVERIFY(!result.has_value());
- }
+ QCOMPARE_EQ(result->description(), expectedDescription);
+
+ QStringList transformed;
+ std::transform(result->accept()->mimeType().extensions().begin(),
+ result->accept()->mimeType().extensions().end(), std::back_inserter(transformed),
+ [](const LocalFileApi::Type::Accept::MimeType::Extension &extension) {
+ return extension.value().toString();
+ });
+ QCOMPARE_EQ(expectedExtensions, transformed);
}
void tst_LocalFileApi::openFileOptions_data()
@@ -217,5 +232,29 @@ void tst_LocalFileApi::saveFileOptions()
QVERIFY(valDeepEquals(result, expectedWebType));
}
+void tst_LocalFileApi::fileInputAccept_data()
+{
+ using namespace emscripten;
+
+ QTest::addColumn<QStringList>("qtFilterList");
+ QTest::addColumn<QString>("expectedAccept");
+
+ QTest::newRow("Multiple files")
+ << QStringList{ "Text files (*.txt)", "Images (*.jpg *.png)", "*.bat" }
+ << ".txt,.jpg,.png,.bat";
+ QTest::newRow("Spaces") << QStringList{ " Documents (*.doc)", "Everything (*.*)",
+ " Stuff ( *.stf *.tng)", " *.exe" }
+ << ".doc,.stf,.tng,.exe";
+}
+
+void tst_LocalFileApi::fileInputAccept()
+{
+ QFETCH(QStringList, qtFilterList);
+ QFETCH(QString, expectedAccept);
+
+ auto result = LocalFileApi::makeFileInputAccept(qtFilterList);
+ QCOMPARE_EQ(expectedAccept, QString::fromStdString(result));
+}
+
QTEST_APPLESS_MAIN(tst_LocalFileApi)
#include "tst_localfileapi.moc"