Re-order spaces by dragging and dropping

Title
This commit is contained in:
James Graham
2024-03-10 11:18:28 +00:00
parent 7654b83339
commit cc058a7cd3
6 changed files with 152 additions and 8 deletions

View File

@@ -8,6 +8,7 @@
#include <Quotient/room.h> #include <Quotient/room.h>
#include "neochatconnection.h" #include "neochatconnection.h"
#include "neochatroom.h"
SpaceChildrenModel::SpaceChildrenModel(QObject *parent) SpaceChildrenModel::SpaceChildrenModel(QObject *parent)
: QAbstractItemModel(parent) : QAbstractItemModel(parent)
@@ -32,7 +33,8 @@ void SpaceChildrenModel::setSpace(NeoChatRoom *space)
} }
// disconnect the new room signal from the old connection in case it is different. // disconnect the new room signal from the old connection in case it is different.
if (m_space != nullptr) { if (m_space != nullptr) {
disconnect(m_space->connection(), &Quotient::Connection::loadedRoomState, this, nullptr); m_space->connection()->disconnect(this);
m_space->disconnect(this);
} }
m_space = space; m_space = space;
@@ -116,6 +118,11 @@ void SpaceChildrenModel::insertChildren(std::vector<Quotient::GetSpaceHierarchyJ
if (!successorId.isEmpty()) { if (!successorId.isEmpty()) {
m_replacedRooms += successorId; m_replacedRooms += successorId;
} }
if (dynamic_cast<NeoChatRoom *>(room)->isSpace()) {
connect(room, &Quotient::Room::changed, this, [this]() {
refreshModel();
});
}
} }
if (children[i].childrenState.size() > 0) { if (children[i].childrenState.size() > 0) {
auto job = m_space->connection()->callApi<Quotient::GetSpaceHierarchyJob>(children[i].roomId, Quotient::none, Quotient::none, 1); auto job = m_space->connection()->callApi<Quotient::GetSpaceHierarchyJob>(children[i].roomId, Quotient::none, Quotient::none, 1);

View File

@@ -60,4 +60,65 @@ bool SpaceChildSortFilterModel::filterAcceptsRow(int sourceRow, const QModelInde
return true; return true;
} }
void SpaceChildSortFilterModel::move(const QModelIndex &currentIndex, const QModelIndex &targetIndex)
{
const auto rootSpace = dynamic_cast<SpaceChildrenModel *>(sourceModel())->space();
if (rootSpace == nullptr) {
return;
}
const auto connection = rootSpace->connection();
const auto currentParent = currentIndex.parent();
auto targetParent = targetIndex.parent();
NeoChatRoom *currentParentSpace = nullptr;
if (!currentParent.isValid()) {
currentParentSpace = rootSpace;
} else {
currentParentSpace = static_cast<NeoChatRoom *>(connection->room(currentParent.data(SpaceChildrenModel::RoomIDRole).toString()));
}
NeoChatRoom *targetParentSpace = nullptr;
if (!targetParent.isValid()) {
targetParentSpace = rootSpace;
} else {
targetParentSpace = static_cast<NeoChatRoom *>(connection->room(targetParent.data(SpaceChildrenModel::RoomIDRole).toString()));
}
// If both parents are not resolvable to a room object we don't have the permissions
// required for this action.
if (currentParentSpace == nullptr || targetParentSpace == nullptr) {
return;
}
const auto currentRow = currentIndex.row();
auto targetRow = targetIndex.row();
const auto moveRoomId = currentIndex.data(SpaceChildrenModel::RoomIDRole).toString();
auto targetRoom = static_cast<NeoChatRoom *>(connection->room(targetIndex.data(SpaceChildrenModel::RoomIDRole).toString()));
// If the target room is a space, assume we want to drop the room into it.
if (targetRoom != nullptr && targetRoom->isSpace()) {
targetParent = targetIndex;
targetParentSpace = targetRoom;
targetRow = rowCount(targetParent);
}
const auto newRowCount = rowCount(targetParent) + (currentParentSpace != targetParentSpace ? 1 : 0);
for (int i = 0; i < newRowCount; i++) {
if (currentParentSpace == targetParentSpace && i == currentRow) {
continue;
}
targetParentSpace->setChildOrder(index(i, 0, targetParent).data(SpaceChildrenModel::RoomIDRole).toString(),
QString::number(i > targetRow ? i + 1 : i, 36));
if (i == targetRow) {
if (currentParentSpace != targetParentSpace) {
currentParentSpace->removeChild(moveRoomId, true);
targetParentSpace->addChild(moveRoomId, true, false, false, QString::number(i + 1, 36));
} else {
targetParentSpace->setChildOrder(currentIndex.data(SpaceChildrenModel::RoomIDRole).toString(), QString::number(i + 1, 36));
}
}
}
}
#include "moc_spacechildsortfiltermodel.cpp" #include "moc_spacechildsortfiltermodel.cpp"

View File

@@ -46,6 +46,8 @@ protected:
*/ */
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
Q_INVOKABLE void move(const QModelIndex &currentIndex, const QModelIndex &targetIndex);
Q_SIGNALS: Q_SIGNALS:
void filterTextChanged(); void filterTextChanged();

View File

@@ -1329,7 +1329,7 @@ bool NeoChatRoom::childrenHaveHighlightNotifications() const
return SpaceHierarchyCache::instance().spaceHasHighlightNotifications(id()); return SpaceHierarchyCache::instance().spaceHasHighlightNotifications(id());
} }
void NeoChatRoom::addChild(const QString &childId, bool setChildParent, bool canonical, bool suggested) void NeoChatRoom::addChild(const QString &childId, bool setChildParent, bool canonical, bool suggested, const QString &order)
{ {
if (!isSpace()) { if (!isSpace()) {
return; return;
@@ -1337,7 +1337,9 @@ void NeoChatRoom::addChild(const QString &childId, bool setChildParent, bool can
if (!canSendEvent("m.space.child"_ls)) { if (!canSendEvent("m.space.child"_ls)) {
return; return;
} }
setState("m.space.child"_ls, childId, QJsonObject{{QLatin1String("via"), QJsonArray{connection()->domain()}}, {"suggested"_ls, suggested}}); setState("m.space.child"_ls,
childId,
QJsonObject{{QLatin1String("via"), QJsonArray{connection()->domain()}}, {"suggested"_ls, suggested}, {"order"_ls, order}});
if (setChildParent) { if (setChildParent) {
if (auto child = static_cast<NeoChatRoom *>(connection()->room(childId))) { if (auto child = static_cast<NeoChatRoom *>(connection()->room(childId))) {
@@ -1403,6 +1405,28 @@ void NeoChatRoom::toggleChildSuggested(const QString &childId)
} }
} }
void NeoChatRoom::setChildOrder(const QString &childId, const QString &order)
{
if (!isSpace()) {
return;
}
if (!canSendEvent("m.space.child"_ls)) {
return;
}
if (const auto childEvent = currentState().get("m.space.child"_ls, childId)) {
auto content = childEvent->contentJson();
if (!content.contains("via"_ls)) {
return;
}
if (content.value("order"_ls).toString() == order) {
return;
}
content.insert("order"_ls, order);
setState("m.space.child"_ls, childId, content);
}
}
PushNotificationState::State NeoChatRoom::pushNotificationState() const PushNotificationState::State NeoChatRoom::pushNotificationState() const
{ {
return m_currentPushNotificationState; return m_currentPushNotificationState;

View File

@@ -560,7 +560,7 @@ public:
* Will fail if the user doesn't have the required privileges or this room is * Will fail if the user doesn't have the required privileges or this room is
* not a space. * not a space.
*/ */
Q_INVOKABLE void addChild(const QString &childId, bool setChildParent = false, bool canonical = false, bool suggested = false); Q_INVOKABLE void addChild(const QString &childId, bool setChildParent = false, bool canonical = false, bool suggested = false, const QString &order = {});
/** /**
* @brief Remove the given room as a child. * @brief Remove the given room as a child.
@@ -583,6 +583,8 @@ public:
*/ */
Q_INVOKABLE void toggleChildSuggested(const QString &childId); Q_INVOKABLE void toggleChildSuggested(const QString &childId);
void setChildOrder(const QString &childId, const QString &order = {});
bool isInvite() const; bool isInvite() const;
bool readOnly() const; bool readOnly() const;

View File

@@ -18,6 +18,7 @@ Item {
required property bool expanded required property bool expanded
required property int hasChildren required property int hasChildren
required property int depth required property int depth
required property int row
required property string roomId required property string roomId
required property string displayName required property string displayName
required property url avatarUrl required property url avatarUrl
@@ -36,10 +37,17 @@ Item {
signal createRoom signal createRoom
Delegates.RoundedItemDelegate { Delegates.RoundedItemDelegate {
anchors.centerIn: root id: mainDelegate
property int row: root.row
anchors.horizontalCenter: root.horizontalCenter
anchors.verticalCenter: root.verticalCenter
width: sizeHelper.currentWidth width: sizeHelper.currentWidth
highlighted: dropArea.containsDrag
contentItem: RowLayout { contentItem: RowLayout {
z: 1
spacing: Kirigami.Units.largeSpacing spacing: Kirigami.Units.largeSpacing
RowLayout { RowLayout {
@@ -140,15 +148,55 @@ Item {
} }
} }
TapHandler { MouseArea {
onTapped: { id: dragArea
anchors.fill: parent
drag.target: mainDelegate
drag.axis: Drag.YAxis
drag.onActiveChanged: {
if (!dragArea.drag.active) {
mainDelegate.Drag.drop();
}
}
onClicked: {
if (root.isSpace) { if (root.isSpace) {
root.treeView.toggleExpanded(row); root.treeView.toggleExpanded(root.row);
} else { } else {
RoomManager.resolveResource(root.roomId, root.isJoined ? "" : "join"); RoomManager.resolveResource(root.roomId, root.isJoined ? "" : "join");
} }
} }
} }
states: [
State {
when: mainDelegate.Drag.active && root.parentRoom.canSendState("m.space.child")
ParentChange {
target: mainDelegate
parent: root.treeView
}
AnchorChanges {
target: mainDelegate
anchors.horizontalCenter: undefined
anchors.verticalCenter: undefined
}
}
]
Drag.active: dragArea.drag.active
Drag.hotSpot.x: mainDelegate.width / 2
Drag.hotSpot.y: Kirigami.Units.smallSpacing
}
DropArea {
id: dropArea
anchors.fill: parent
onDropped: (drag) => {
root.treeView.model.move(root.treeView.index(drag.source.row, 0), root.treeView.index(root.row, 0))
}
} }
DelegateSizeHelper { DelegateSizeHelper {