From 8b0698c670853c05ccb32cf54a39b228a0e19478 Mon Sep 17 00:00:00 2001 From: Arno Rehn Date: Sat, 4 Oct 2025 20:37:55 +0200 Subject: [PATCH] Support adding Jitsi and removing widgets --- src/app/qml/RoomPage.qml | 10 ++++--- src/libneochat/models/widgetmodel.cpp | 38 +++++++++++++++++++++++++++ src/libneochat/models/widgetmodel.h | 11 ++++++++ src/roominfo/WidgetsPage.qml | 27 +++++++++++++++---- 4 files changed, 78 insertions(+), 8 deletions(-) diff --git a/src/app/qml/RoomPage.qml b/src/app/qml/RoomPage.qml index 5739a9969..22eda1d4b 100644 --- a/src/app/qml/RoomPage.qml +++ b/src/app/qml/RoomPage.qml @@ -79,10 +79,14 @@ Kirigami.Page { Kirigami.Action { tooltip: i18nc("@action:button", "Open Jitsi Meet in browser") icon.name: "camera-video-symbolic" - visible: root.widgetModel.jitsiIndex >= 0 onTriggered: { - let idx = root.widgetModel.index(root.widgetModel.jitsiIndex, 0); - let url = root.widgetModel.data(idx, WidgetModel.UrlRole); + let url + if (root.widgetModel.jitsiIndex < 0) { + url = root.widgetModel.addJitsiConference(); + } else { + let idx = root.widgetModel.index(root.widgetModel.jitsiIndex, 0); + url = root.widgetModel.data(idx, WidgetModel.UrlRole); + } Qt.openUrlExternally(url); } }, diff --git a/src/libneochat/models/widgetmodel.cpp b/src/libneochat/models/widgetmodel.cpp index dc11f2ddc..cfd71dc92 100644 --- a/src/libneochat/models/widgetmodel.cpp +++ b/src/libneochat/models/widgetmodel.cpp @@ -316,3 +316,41 @@ int WidgetModel::jitsiIndex() const return d->jitsiIndex; } + +QUrl WidgetModel::addJitsiConference() +{ + // URL is not spec-compliant, but this is what Element does as well. + const auto conferenceId = QUuid::createUuid().toString(QUuid::WithoutBraces); + // clang-format off + const QJsonObject content{ + {"name"_L1, "Jitsi Meet"_L1}, + {"type"_L1, "jitsi"_L1}, + {"url"_L1, "https://scalar.vector.im/api/widgets/jitsi.html"_L1}, + {"data"_L1, QJsonObject{ + {"conferenceId"_L1, conferenceId}, + {"domain"_L1, "meet.element.io"_L1}, // TODO: make domain configurable + {"isAudioOnly"_L1, false}, + {"roomName"_L1, room()->displayName()}, + }}, + }; + // clang-format on + + // Re-use conferenceId as state_key + room()->setState(WidgetEvent::MetaType.matrixId, conferenceId, content); + + return buildWidgetUrl(JitsiMeetUrlTemplate, room(), content["data"_L1].toObject()); +} + +bool WidgetModel::removeWidget(int index) +{ + Q_D(const WidgetModel); + + if (index < 0 || index >= rowCount()) { + return false; + } + + const auto stateKey = std::next(d->state.begin(), index).value()->stateKey(); + room()->setState(WidgetEvent::MetaType.matrixId, stateKey, QJsonObject{}); + + return true; +} diff --git a/src/libneochat/models/widgetmodel.h b/src/libneochat/models/widgetmodel.h index bfc19c23f..b65d2ede8 100644 --- a/src/libneochat/models/widgetmodel.h +++ b/src/libneochat/models/widgetmodel.h @@ -51,6 +51,17 @@ public: int jitsiIndex() const; + /** + * @brief Adds a new Jitsi widget + * @return The URL of the newly added Jitsi conference + */ + Q_INVOKABLE QUrl addJitsiConference(); + /** + * @brief Removes the widget at @p index + * @return `true` on success, `false` otherwise + */ + Q_INVOKABLE bool removeWidget(int index); + Q_SIGNALS: void roomChanged(); void jitsiIndexChanged(); diff --git a/src/roominfo/WidgetsPage.qml b/src/roominfo/WidgetsPage.qml index ee013a583..96bf68af5 100644 --- a/src/roominfo/WidgetsPage.qml +++ b/src/roominfo/WidgetsPage.qml @@ -24,6 +24,7 @@ Kirigami.ScrollablePage { currentIndex: -1 model: WidgetModel { + id: widgetModel room: root.room } @@ -33,18 +34,34 @@ Kirigami.ScrollablePage { required text required property url url required property string type + required property int index // Can we actually use the jitsi logo without being infringing any // trademarks? icon.name: type === "jitsi" ? "meeting-attending" : type === "m.etherpad" ? "document-share" : "" + icon.width: Kirigami.Units.iconSizes.smallMedium + icon.height: Kirigami.Units.iconSizes.smallMedium - contentItem: Delegates.SubtitleContentItem { - iconItem.visible: true - itemDelegate: del - subtitle: del.url - labelItem.textFormat: Text.PlainText + contentItem: RowLayout { + spacing: Kirigami.Units.smallSpacing + Delegates.SubtitleContentItem { + Layout.fillWidth: true + + iconItem.visible: true + itemDelegate: del + subtitle: del.url + labelItem.textFormat: Text.PlainText + } + + QQC2.ToolButton { + action: Kirigami.Action { + icon.name: "delete-symbolic" + tooltip: i18nc("@action:button", "Remove widget") + onTriggered: widgetModel.removeWidget(del.index) + } + } } onClicked: Qt.openUrlExternally(url)