Devtools: Implement changing room state

This commit is contained in:
Tobias Fella
2024-03-03 00:19:19 +01:00
parent d2695947ed
commit c344a3ee55
13 changed files with 236 additions and 33 deletions

View File

@@ -277,6 +277,7 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
qml/AccountSwitchDialog.qml
qml/ConfirmLeaveDialog.qml
qml/CodeMaximizeComponent.qml
qml/EditStateDialog.qml
RESOURCES
qml/confetti.png
qml/glowdot.png

View File

@@ -74,14 +74,9 @@ ColumnLayout {
description: i18ncp("'Event' being some JSON data, not something physically happening.", "%1 event of this type", "%1 events of this type", model.eventCount)
onClicked: {
if (model.eventCount === 1) {
onClicked: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
sourceText: stateModel.stateEventJson(stateModel.index(model.index, 0))
}, {
title: i18n("Event Source"),
width: Kirigami.Units.gridUnit * 25
})
openEventSource(model.type, model.stateKey);
} else {
pageStack.pushDialogLayer(stateKeysComponent, {
pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.devtools', 'StateKeys'), {
room: root.room,
eventType: model.type
}, {
@@ -91,9 +86,17 @@ ColumnLayout {
}
}
}
Component {
id: stateKeysComponent
StateKeys {}
}
}
function openEventSource(type: string, stateKey: string): void {
onClicked: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
model: stateModel,
allowEdit: true,
room: root.room,
type: type,
stateKey: stateKey,
}, {
title: i18n("Event Source"),
width: Kirigami.Units.gridUnit * 25
});
}
}

View File

@@ -31,13 +31,21 @@ FormCard.FormCardPage {
delegate: FormCard.FormButtonDelegate {
text: model.stateKey
onClicked: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
sourceText: stateKeysModel.stateEventJson(stateKeysModel.index(model.index, 0))
}, {
title: i18nc("@title:window", "Event Source"),
width: Kirigami.Units.gridUnit * 25
})
onClicked: openEventSource(model.stateKey)
}
}
}
function openEventSource(stateKey: string): void {
applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
model: stateKeysModel,
allowEdit: true,
room: root.room,
type: root.eventType,
stateKey: stateKey
}, {
title: i18nc("@title:window", "Event Source"),
width: Kirigami.Units.gridUnit * 25
});
}
}

View File

@@ -77,12 +77,14 @@ void StateKeysModel::setEventType(const QString &eventType)
loadState();
}
QByteArray StateKeysModel::stateEventJson(const QModelIndex &index)
QByteArray StateKeysModel::stateEventJson(const QString &type, const QString &stateKey)
{
const auto row = index.row();
const auto event = m_stateKeys[row];
const auto json = event->fullJson();
return QJsonDocument(json).toJson();
return QJsonDocument(m_room->currentState().get(type, stateKey)->fullJson()).toJson();
}
QByteArray StateKeysModel::stateEventContentJson(const QString &type, const QString &stateKey)
{
return QJsonDocument(m_room->currentState().get(type, stateKey)->contentJson()).toJson();
}
#include "moc_statekeysmodel.cpp"

View File

@@ -69,7 +69,12 @@ public:
/**
* @brief Get the full JSON for an event.
*/
Q_INVOKABLE QByteArray stateEventJson(const QModelIndex &index);
Q_INVOKABLE QByteArray stateEventJson(const QString &type, const QString &stateKey);
/**
* @brief Get the content JSON for an event.
*/
Q_INVOKABLE QByteArray stateEventContentJson(const QString &type, const QString &stateKey);
Q_SIGNALS:
void roomChanged();

View File

@@ -10,7 +10,11 @@ StateModel::StateModel(QObject *parent)
QHash<int, QByteArray> StateModel::roleNames() const
{
return {{TypeRole, "type"}, {EventCountRole, "eventCount"}};
return {
{TypeRole, "type"},
{EventCountRole, "eventCount"},
{StateKeyRole, "stateKey"},
};
}
QVariant StateModel::data(const QModelIndex &index, int role) const
{
@@ -20,6 +24,8 @@ QVariant StateModel::data(const QModelIndex &index, int role) const
return m_stateEvents.keys()[row];
case EventCountRole:
return m_stateEvents.values()[row].count();
case StateKeyRole:
return m_stateEvents.values()[row][0];
}
return {};
}
@@ -63,14 +69,14 @@ void StateModel::setRoom(NeoChatRoom *room)
});
}
QByteArray StateModel::stateEventJson(const QModelIndex &index)
QByteArray StateModel::stateEventJson(const QString &type, const QString &stateKey)
{
auto row = index.row();
const auto type = m_stateEvents.keys()[row];
const auto stateKey = m_stateEvents.values()[row][0];
const auto event = m_room->currentState().get(type, stateKey);
return QJsonDocument(m_room->currentState().get(type, stateKey)->fullJson()).toJson();
}
return QJsonDocument(event->fullJson()).toJson();
QByteArray StateModel::stateEventContentJson(const QString &type, const QString &stateKey)
{
return QJsonDocument(m_room->currentState().get(type, stateKey)->contentJson()).toJson();
}
#include "moc_statemodel.cpp"

View File

@@ -30,6 +30,7 @@ public:
enum Roles {
TypeRole = 0, /**< The type of the state event. */
EventCountRole, /**< Number of events of this type. */
StateKeyRole, /**<State key. Only valid if there's exactly one event of this type. */
};
Q_ENUM(Roles)
@@ -62,7 +63,12 @@ public:
/**
* @brief Get the full JSON for an event.
*/
Q_INVOKABLE QByteArray stateEventJson(const QModelIndex &index);
Q_INVOKABLE QByteArray stateEventJson(const QString &type, const QString &stateKey);
/**
* @brief Get the content JSON for an event.
*/
Q_INVOKABLE QByteArray stateEventContentJson(const QString &type, const QString &stateKey);
Q_SIGNALS:
void roomChanged();
@@ -71,7 +77,7 @@ private:
QPointer<NeoChatRoom> m_room;
/**
* @brief A map from state event type to number of events of that type
* @brief A map from state event type to state keys
*/
QMap<QString, QList<QString>> m_stateEvents;
void loadState();

View File

@@ -1979,4 +1979,9 @@ User *NeoChatRoom::invitingUser() const
return connection()->user(currentState().get<RoomMemberEvent>(connection()->userId())->senderId());
}
void NeoChatRoom::setRoomState(const QString &type, const QString &stateKey, const QByteArray &content)
{
setState(type, stateKey, QJsonDocument::fromJson(content).object());
}
#include "moc_neochatroom.cpp"

View File

@@ -659,6 +659,8 @@ public:
* */
Q_INVOKABLE void setCanonicalAlias(const QString &newAlias);
Q_INVOKABLE void setRoomState(const QString &type, const QString &stateKey, const QByteArray &content);
PushNotificationState::State pushNotificationState() const;
void setPushNotificationState(PushNotificationState::State state);

120
src/qml/EditStateDialog.qml Normal file
View File

@@ -0,0 +1,120 @@
// SPDX-FileCopyrightText: 2024 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.syntaxhighlighting
import org.kde.neochat
Kirigami.Page {
id: root
required property string sourceText
property bool allowEdit: false
property NeoChatRoom room
property string type
property string stateKey
topPadding: 0
leftPadding: 0
rightPadding: 0
bottomPadding: 0
title: i18nc("@title As in 'edit the state of this rooms'", "Edit State")
actions: [
Kirigami.Action {
text: i18nc("@action", "Revert changes")
icon.name: "document-revert"
onTriggered: sourceTextArea.text = root.sourceText
enabled: sourceTextArea.text !== root.sourceText
},
Kirigami.Action {
text: i18nc("@action As in 'Apply the changes'", "Apply")
icon.name: "document-edit"
onTriggered: {
root.room.setRoomState(root.type, root.stateKey, sourceTextArea.text);
root.closeDialog();
}
enabled: QmlUtils.isValidJson(sourceTextArea.text)
}
]
QQC2.ScrollView {
id: scrollView
anchors.fill: parent
contentWidth: availableWidth
QQC2.TextArea {
id: sourceTextArea
Layout.fillWidth: true
leftPadding: lineNumberColumn.width + lineNumberColumn.anchors.leftMargin + Kirigami.Units.smallSpacing * 2
text: root.sourceText
textFormat: TextEdit.PlainText
wrapMode: TextEdit.Wrap
Kirigami.SpellCheck.enabled: false
onWidthChanged: lineModel.resetModel()
onHeightChanged: lineModel.resetModel()
SyntaxHighlighter {
textEdit: sourceTextArea
definition: "JSON"
repository: Repository
}
ColumnLayout {
id: lineNumberColumn
anchors {
top: sourceTextArea.top
topMargin: sourceTextArea.topPadding
left: sourceTextArea.left
leftMargin: Kirigami.Units.smallSpacing
}
spacing: 0
Repeater {
id: repeater
model: LineModel {
id: lineModel
document: sourceTextArea.textDocument
}
delegate: QQC2.Label {
id: label
required property int index
required property int docLineHeight
Layout.fillWidth: true
Layout.preferredHeight: docLineHeight
topPadding: 1
horizontalAlignment: Text.AlignRight
text: index + 1
color: Kirigami.Theme.disabledTextColor
}
}
}
background: Rectangle {
Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Theme.inherit: false
color: Kirigami.Theme.backgroundColor
}
}
}
Kirigami.Separator {
anchors {
top: parent.top
bottom: parent.bottom
left: parent.left
leftMargin: lineNumberColumn.width + lineNumberColumn.anchors.leftMargin + Kirigami.Units.smallSpacing
}
}
}

View File

@@ -13,15 +13,51 @@ import org.kde.neochat
Kirigami.Page {
id: root
property string sourceText
property var model
property NeoChatRoom room
property string type
property string stateKey
property bool allowEdit: false
property string contentJson: model.stateEventContentJson(root.type, root.stateKey)
property string sourceText: model.stateEventJson(root.type, root.stateKey)
topPadding: 0
leftPadding: 0
rightPadding: 0
bottomPadding: 0
Connections {
enabled: root.model
target: root.room
function onChanged(): void {
root.contentJson = model.stateEventContentJson(root.type, root.stateKey);
root.sourceText = model.stateEventJson(root.type, root.stateKey);
}
}
title: i18n("Event Source")
actions: [
Kirigami.Action {
text: i18nc("@action As in 'edit the state of this rooms'", "Edit state")
icon.name: "document-edit"
visible: root.allowEdit
enabled: room.canSendState(root.type) && (!root.stateKey.startsWith("@") || root.stateKey === root.room.connection.localUserId) && root.type !== "m.room.create"
onTriggered: pageStack.pushDialogLayer(Qt.createComponent("org.kde.neochat", "EditStateDialog.qml"), {
room: root.room,
type: root.type,
stateKey: root.stateKey,
sourceText: root.contentJson,
}, {
title: i18nc("@title As in 'edit the state of this rooms'", "Edit State")
})
}
]
QQC2.ScrollView {
id: scrollView
anchors.fill: parent

View File

@@ -3,6 +3,8 @@
#include "utils.h"
#include <QJsonDocument>
using namespace Quotient;
static const QVariantMap emptyUser = {
@@ -41,3 +43,8 @@ QVariantMap QmlUtils::getUser(User *user) const
{QStringLiteral("object"), QVariant::fromValue(user)},
};
}
bool QmlUtils::isValidJson(const QByteArray &json)
{
return !QJsonDocument::fromJson(json).isNull();
}

View File

@@ -30,11 +30,13 @@ public:
}
Q_INVOKABLE QVariantMap getUser(Quotient::User *user) const;
Q_INVOKABLE bool isValidJson(const QByteArray &json);
private:
QmlUtils() = default;
};
namespace Utils
{