Skip to content

Commit b3351d9

Browse files
committed
feat: [WIP] Form.TagPicker support
1 parent 9c7d53c commit b3351d9

20 files changed

Lines changed: 10020 additions & 9460 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ __cmake*
1616
*.tgz
1717

1818
/qt6
19+
**/.qmlls.ini
1920

2021
/AppDir
2122
*AppImage

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
cmake_minimum_required(VERSION 3.16)
22
set(PROJECT_NAME vicinae)
33
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
4+
set(QT_QML_GENERATE_QMLLS_INI ON)
45
project(${PROJECT_NAME} VERSION 1.0.0 LANGUAGES C CXX)
56

67
set(LIB_NAMESPACE "vicinae")

flake.nix

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,12 @@
6868
export CXX=${pkgs.gcc15}/bin/g++
6969
export CMAKE_C_COMPILER=$CC
7070
export CMAKE_CXX_COMPILER=$CXX
71+
72+
export QML2_IMPORT_PATH=${pkgs.qt6.qtdeclarative}/lib/qt-6/qml
73+
export QML_IMPORT_PATH=${pkgs.qt6.qtdeclarative}/lib/qt-6/qml
7174
'';
72-
};
73-
}
74-
);
75+
};
76+
});
7577
overlays.default = final: prev: {
7678
vicinae = final.callPackage ./nix/vicinae.nix { };
7779
mkVicinaeExtension = prev.callPackage ./nix/mkVicinaeExtension.nix { };

src/server/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,7 @@ set(SRCS
517517
src/qml/extension-grid-model.hpp
518518
src/qml/extension-grid-model.cpp
519519
src/qml/source-blend-rect.hpp
520+
src/qml/tag-search.hpp
520521
src/qml/image-url.hpp
521522
src/qml/image-url.cpp
522523
src/qml/image-source.hpp
@@ -719,6 +720,7 @@ set(VICINAE_QML_FILES
719720
src/qml/qml/FormTextArea.qml
720721
src/qml/qml/FormCheckbox.qml
721722
src/qml/qml/FormSeparator.qml
723+
src/qml/qml/FormTagPicker.qml
722724
src/qml/qml/SnippetFormView.qml
723725
src/qml/qml/AliasFormView.qml
724726
src/qml/qml/ShortcutFormView.qml
@@ -772,6 +774,7 @@ set(VICINAE_QML_FILES
772774
src/qml/qml/MissingPreferenceView.qml
773775
src/qml/qml/OAuthOverlayView.qml
774776
src/qml/qml/HudWindow.qml
777+
src/qml/qml/TagPickerTag.qml
775778
)
776779

777780
if(CMAKE_BUILD_TYPE MATCHES "Debug")

src/server/include/extend/form-model.hpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include "extend/action-model.hpp"
33
#include "extend/list-model.hpp"
44
#include "extend/model.hpp"
5+
#include "extend/tag-model.hpp"
56
#include <memory>
67
#include <qcoreevent.h>
78
#include <qjsonobject.h>
@@ -97,6 +98,14 @@ struct FormModel {
9798
public:
9899
DatePickerField(const FieldBase &base) : IField(base) {}
99100
};
101+
102+
struct TagPickerField : public IField {
103+
std::vector<TagPickerItemModel> m_items;
104+
105+
public:
106+
TagPickerField(const FieldBase &base) : IField(base) {}
107+
};
108+
100109
struct InvalidField : public FieldBase {};
101110

102111
struct Separator {};

src/server/include/extend/tag-model.hpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#pragma once
22
#include "extend/color-model.hpp"
33
#include "extend/image-model.hpp"
4+
#include "lib/fuzzy/fuzzy-searchable.hpp"
45
#include <qjsonobject.h>
56
#include <qstring.h>
67

@@ -24,3 +25,19 @@ class TagListParser {
2425

2526
TagListModel parse(const QJsonObject &instance);
2627
};
28+
29+
struct TagPickerItemModel {
30+
QString title;
31+
QString value;
32+
std::optional<ImageLikeModel> icon;
33+
34+
public:
35+
static TagPickerItemModel fromJson(const QJsonObject &instance);
36+
};
37+
38+
template <> struct fuzzy::FuzzySearchable<TagPickerItemModel> {
39+
static int score(const TagPickerItemModel &item, std::string_view query) {
40+
auto name = item.title.toStdString();
41+
return fuzzy::scoreWeighted({{name, 1.0}}, query);
42+
}
43+
};

src/server/src/extend/form-model.cpp

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33

44
#include <algorithm>
55

6-
const static std::vector<QString> fieldTypes = {"dropdown-field", "password-field", "text-field",
7-
"checkbox-field", "date-picker-field", "text-area-field",
8-
"file-picker-field"};
6+
const static std::vector<QString> fieldTypes = {"dropdown-field", "password-field", "text-field",
7+
"checkbox-field", "date-picker-field", "text-area-field",
8+
"file-picker-field", "tag-picker-field"};
99

1010
FormModel FormModel::fromJson(const QJsonObject &json) {
1111
FormModel model;
@@ -123,6 +123,15 @@ FormModel FormModel::fromJson(const QJsonObject &json) {
123123
filePicker->showHiddenFiles = props.value("showHiddenFiles").toBool();
124124

125125
model.items.emplace_back(filePicker);
126+
} else if (*it == "tag-picker-field") {
127+
auto tagPicker = std::make_shared<TagPickerField>(base);
128+
tagPicker->m_items.reserve(children.size());
129+
130+
for (const auto &child : children) {
131+
tagPicker->m_items.emplace_back(TagPickerItemModel::fromJson(child.toObject()));
132+
}
133+
134+
model.items.emplace_back(tagPicker);
126135
}
127136
} else {
128137
qWarning() << "Unknown form children of type" << type;

src/server/src/extend/tag-list.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
#include "extend/color-model.hpp"
22
#include "extend/image-model.hpp"
33
#include "extend/tag-model.hpp"
4+
#include <exception>
45
#include <qjsonarray.h>
56
#include <qjsonobject.h>
7+
#include <qlogging.h>
8+
#include <stdexcept>
69

710
TagListParser::TagListParser() = default;
811

@@ -32,3 +35,15 @@ TagListModel TagListParser::parse(const QJsonObject &instance) {
3235

3336
return model;
3437
}
38+
39+
TagPickerItemModel TagPickerItemModel::fromJson(const QJsonObject &instance) {
40+
TagPickerItemModel model;
41+
auto props = instance.value("props").toObject();
42+
43+
model.title = props.value("title").toString();
44+
model.value = props.value("value").toString();
45+
46+
if (props.contains("icon")) { model.icon = ImageModelParser().parse(props.value("icon")); }
47+
48+
return model;
49+
}

src/server/src/qml/extension-form-model.cpp

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
#include "view-utils.hpp"
33
#include <QJsonArray>
44
#include <QJsonObject>
5+
#include <QJSValue>
6+
#include <qcontainerfwd.h>
7+
#include <qstringliteral.h>
58
#include <utility>
69

710
ExtensionFormModel::ExtensionFormModel(NotifyFn notify, QObject *parent)
@@ -54,7 +57,14 @@ void ExtensionFormModel::setFieldValue(int index, const QVariant &value) {
5457
auto &item = m_items[index];
5558
if (!item.isField()) return;
5659

57-
item.userValue = QJsonValue::fromVariant(value);
60+
// TODO: find out why Form.TagPicker tags would break without that
61+
QVariant normalizedValue = value;
62+
63+
if (value.metaType().id() == qMetaTypeId<QJSValue>()) {
64+
normalizedValue = value.value<QJSValue>().toVariant();
65+
}
66+
67+
item.userValue = QJsonValue::fromVariant(normalizedValue);
5868
item.hasUserValue = true;
5969
emit dataChanged(createIndex(index, 0), createIndex(index, 0), {ValueRole});
6070

@@ -99,6 +109,23 @@ void ExtensionFormModel::setFilePaths(int index, const QVariantList &paths) {
99109
if (item.onChange) { m_notify(*item.onChange, QJsonArray{arr}); }
100110
}
101111

112+
void ExtensionFormModel::setPickedItems(int index, const QVariantList &pickedItems) {
113+
if (index < 0 || std::cmp_greater_equal(index, m_items.size())) return;
114+
auto &item = m_items[index];
115+
if (item.type != FormItemData::Type::TagPicker) return;
116+
117+
QJsonArray arr;
118+
for (const auto &t : pickedItems) {
119+
arr.append(t.toString());
120+
}
121+
122+
item.userValue = arr;
123+
item.hasUserValue = true;
124+
emit dataChanged(createIndex(index, 0), createIndex(index, 0), {ValueRole});
125+
126+
if (item.onChange) { m_notify(*item.onChange, QJsonArray{arr}); }
127+
}
128+
102129
void ExtensionFormModel::dropdownSearchTextChanged(int index, const QString &text) {
103130
if (index < 0 || std::cmp_greater_equal(index, m_items.size())) return;
104131
const auto &item = m_items[index];
@@ -255,6 +282,7 @@ ExtensionFormModel::FormItemData::Type ExtensionFormModel::fieldType(const FormM
255282
if (dynamic_cast<const FormModel::TextAreaField *>(&field)) return FormItemData::Type::TextArea;
256283
if (dynamic_cast<const FormModel::FilePickerField *>(&field)) return FormItemData::Type::FilePicker;
257284
if (dynamic_cast<const FormModel::DatePickerField *>(&field)) return FormItemData::Type::DatePicker;
285+
if (dynamic_cast<const FormModel::TagPickerField *>(&field)) return FormItemData::Type::TagPicker;
258286
return FormItemData::Type::Text;
259287
}
260288

@@ -280,6 +308,8 @@ QVariantMap ExtensionFormModel::buildFieldData(const FormModel::IField &field) {
280308
if (f->min) data["min"] = *f->min;
281309
if (f->max) data["max"] = *f->max;
282310
data["includeTime"] = f->type.value_or("date") == "dateTime";
311+
} else if (auto *f = dynamic_cast<const FormModel::TagPickerField *>(&field)) {
312+
data["items"] = qml::convertTagPickerItems(f->m_items);
283313
}
284314

285315
return data;
@@ -305,6 +335,8 @@ QString ExtensionFormModel::FormItemData::typeString() const {
305335
return QStringLiteral("description");
306336
case Type::Separator:
307337
return QStringLiteral("separator");
338+
case Type::TagPicker:
339+
return QStringLiteral("tagpicker");
308340
}
309341
return QStringLiteral("text");
310342
}

src/server/src/qml/extension-form-model.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
#include <expected>
88
#include <functional>
99
#include <optional>
10+
#include <qcontainerfwd.h>
11+
#include <qtmetamacros.h>
1012
#include <vector>
1113

1214
class ExtensionFormModel : public QAbstractListModel {
@@ -40,6 +42,7 @@ class ExtensionFormModel : public QAbstractListModel {
4042
Q_INVOKABLE void fieldFocused(int index);
4143
Q_INVOKABLE void fieldBlurred(int index);
4244
Q_INVOKABLE void setFilePaths(int index, const QVariantList &paths);
45+
Q_INVOKABLE void setPickedItems(int index, const QVariantList &pickedItems);
4346
Q_INVOKABLE void dropdownSearchTextChanged(int index, const QString &text);
4447

4548
std::optional<FileChooserOptions> filePickerOptions(int index) const;
@@ -58,6 +61,7 @@ class ExtensionFormModel : public QAbstractListModel {
5861
TextArea,
5962
FilePicker,
6063
DatePicker,
64+
TagPicker,
6165
Description,
6266
Separator
6367
};

0 commit comments

Comments
 (0)