summary refs log tree commit diff
path: root/resources
diff options
context:
space:
mode:
authorNicolas Werner <nicolas.werner@hotmail.de>2022-05-27 16:31:54 +0200
committerNicolas Werner <nicolas.werner@hotmail.de>2022-05-27 17:01:58 +0200
commit6c6d43691d98aa02513350b52fe736fff6d6071d (patch)
tree6155225e4005f22c2a613a77912227b05fb48ff7 /resources
parentTranslated using Weblate (Russian) (diff)
downloadnheko-6c6d43691d98aa02513350b52fe736fff6d6071d.tar.xz
Add basic powerlevel editor
Diffstat (limited to 'resources')
-rw-r--r--resources/qml/Completer.qml28
-rw-r--r--resources/qml/Root.qml16
-rw-r--r--resources/qml/components/ReorderableListview.qml126
-rw-r--r--resources/qml/dialogs/PowerLevelEditor.qml347
-rw-r--r--resources/qml/dialogs/RoomSettings.qml12
-rw-r--r--resources/res.qrc6
6 files changed, 523 insertions, 12 deletions
diff --git a/resources/qml/Completer.qml b/resources/qml/Completer.qml
index a16ffa65..7e14e734 100644
--- a/resources/qml/Completer.qml
+++ b/resources/qml/Completer.qml
@@ -13,6 +13,7 @@ Control {
     id: popup
 
     property alias currentIndex: listView.currentIndex
+    property string roomId
     property string completerName
     property var completer
     property bool bottomToTop: true
@@ -24,6 +25,10 @@ Control {
     property int rowSpacing: 5
     property alias count: listView.count
 
+    Component.onCompleted: {
+        console.log("RRRRRRRRRR: " + roomId);
+    }
+
     signal completionClicked(string completion)
     signal completionSelected(string id)
 
@@ -65,18 +70,22 @@ Control {
     function finishCompletion() {
         if (popup.completerName == "room")
             popup.completionSelected(listView.itemAtIndex(currentIndex).modelData.roomid);
+        else if (popup.completerName == "user")
+            popup.completionSelected(listView.itemAtIndex(currentIndex).modelData.userid);
 
     }
 
-    onCompleterNameChanged: {
+    function changeCompleter() {
         if (completerName) {
-            completer = TimelineManager.completerFor(completerName, completerName == "room" ? "" : room.roomId);
+            completer = TimelineManager.completerFor(completerName, completerName == "room" ? "" : (popup.roomId != "" ? popup.roomId :  room.roomId));
             completer.setSearchString("");
         } else {
             completer = undefined;
         }
         currentIndex = -1
     }
+    onCompleterNameChanged: changeCompleter()
+    onRoomIdChanged: changeCompleter()
 
     bottomPadding: 1
     leftPadding: 1
@@ -131,6 +140,8 @@ Control {
                      popup.completionClicked(completer.completionAt(model.index));
                      if (popup.completerName == "room")
                          popup.completionSelected(model.roomid);
+                     else if (popup.completerName == "user")
+                         popup.completionSelected(model.userid);
                 }
             }
             Ripple {
@@ -151,7 +162,7 @@ Control {
                     RowLayout {
                         id: del
 
-                        anchors.centerIn: parent
+                        anchors.centerIn: centerRowContent ? parent : undefined
                         spacing: rowSpacing
 
                         Avatar {
@@ -160,7 +171,7 @@ Control {
                             displayName: model.displayName
                             userid: model.userid
                             url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
-                            onClicked: popup.completionClicked(completer.completionAt(model.index))
+                            enabled: false
                         }
 
                         Label {
@@ -216,7 +227,7 @@ Control {
                             displayName: model.shortcode
                             //userid: model.shortcode
                             url: model.url.replace("mxc://", "image://MxcImage/")
-                            onClicked: popup.completionClicked(completer.completionAt(model.index))
+                            enabled: false
                             crop: false
                         }
 
@@ -249,10 +260,7 @@ Control {
                             displayName: model.roomName
                             roomid: model.roomid
                             url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
-                            onClicked: {
-                                popup.completionClicked(completer.completionAt(model.index));
-                                popup.completionSelected(model.roomid);
-                            }
+                            enabled: false
                         }
 
                         Label {
@@ -281,7 +289,7 @@ Control {
                             displayName: model.roomName
                             roomid: model.roomid
                             url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
-                            onClicked: popup.completionClicked(completer.completionAt(model.index))
+                            enabled: false
                         }
 
                         Label {
diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml
index 00600508..86ddc649 100644
--- a/resources/qml/Root.qml
+++ b/resources/qml/Root.qml
@@ -51,6 +51,22 @@ Pane {
 
     }
 
+        function showPLEditor(settings) {
+            var dialog = plEditor.createObject(timelineRoot, {
+                "roomSettings": settings
+            });
+            dialog.show();
+            destroyOnClose(dialog);
+        }
+
+    Component {
+        id: plEditor
+
+        PowerLevelEditor {
+        }
+    }
+
+
     Component {
         id: roomSettingsComponent
 
diff --git a/resources/qml/components/ReorderableListview.qml b/resources/qml/components/ReorderableListview.qml
new file mode 100644
index 00000000..7e9ae05d
--- /dev/null
+++ b/resources/qml/components/ReorderableListview.qml
@@ -0,0 +1,126 @@
+// SPDX-FileCopyrightText: 2022 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import QtQuick 2.15
+import QtQml.Models 2.1
+import im.nheko 1.0
+import ".."
+
+Item {
+    id: root
+
+    property alias model: visualModel.model
+    property Component delegate
+
+    Component {
+        id: dragDelegate
+
+        MouseArea {
+            id: dragArea
+
+            required property var model
+            required property int index
+
+            enabled: model.moveable == undefined || model.moveable
+
+            property bool held: false
+
+            anchors { left: parent.left; right: parent.right }
+            height: content.height
+
+            drag.target: held ? content : undefined
+            drag.axis: Drag.YAxis
+
+            onPressAndHold: held = true
+            onPressed: if (mouse.source !== Qt.MouseEventNotSynthesized) { held = true }
+            onReleased: held = false
+            onHeldChanged: if (held) ListView.view.currentIndex = dragArea.index; else ListView.view.currentIndex = -1
+
+            Rectangle {
+                id: content
+
+                anchors {
+                    horizontalCenter: parent.horizontalCenter
+                    verticalCenter: parent.verticalCenter
+                }
+                width: dragArea.width; height: actualDelegate.implicitHeight + 4
+
+                border.width: dragArea.enabled ? 1 : 0
+                border.color: Nheko.colors.highlight
+
+                color: dragArea.held ? Nheko.colors.highlight : Nheko.colors.base
+                Behavior on color { ColorAnimation { duration: 100 } }
+
+                radius: 2
+
+                Drag.active: dragArea.held
+                Drag.source: dragArea
+                Drag.hotSpot.x: width / 2
+                Drag.hotSpot.y: height / 2
+
+                states: State {
+                    when: dragArea.held
+
+                    ParentChange { target: content; parent: root }
+                    AnchorChanges {
+                        target: content
+                        anchors { horizontalCenter: undefined; verticalCenter: undefined }
+                    }
+                }
+
+                Loader {
+                    id: actualDelegate
+                    sourceComponent: root.delegate
+                    property var model: dragArea.model
+                    property int index: dragArea.index
+                    property int offset: -view.contentY + dragArea.y
+                    anchors { fill: parent; margins: 2 }
+                }
+
+            }
+
+            DropArea {
+                enabled: index != 0 || model.moveable == undefined || model.moveable
+                anchors { fill: parent; margins: 8 }
+
+                onEntered: (drag)=> {
+                    visualModel.model.move(drag.source.index, dragArea.index)
+                    }
+                }
+
+            }
+        }
+
+
+        DelegateModel {
+            id: visualModel
+
+            delegate: dragDelegate
+        }
+
+        ListView {
+            id: view
+
+            clip: true
+
+            anchors { fill: parent; margins: 2 }
+        ScrollHelper {
+            flickable: parent
+            anchors.fill: parent
+        }
+
+            model: visualModel
+
+            highlightRangeMode: ListView.ApplyRange
+            preferredHighlightBegin: 0.2 * height
+            preferredHighlightEnd: 0.8 * height
+
+            spacing: 4
+            cacheBuffer: 50
+        }
+
+
+    }
+
+
diff --git a/resources/qml/dialogs/PowerLevelEditor.qml b/resources/qml/dialogs/PowerLevelEditor.qml
new file mode 100644
index 00000000..241585f9
--- /dev/null
+++ b/resources/qml/dialogs/PowerLevelEditor.qml
@@ -0,0 +1,347 @@
+// SPDX-FileCopyrightText: 2022 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import ".."
+import "../components"
+import QtQuick 2.12
+import QtQuick.Controls 2.5
+import QtQuick.Layouts 1.3
+import im.nheko 1.0
+
+
+ApplicationWindow {
+    id: plEditorW
+
+    property var roomSettings
+    property var editingModel: Nheko.editPowerlevels(roomSettings.roomId)
+
+    modality: Qt.NonModal
+    flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
+    minimumWidth: 300
+    minimumHeight: 400
+
+    title: qsTr("Permissions in %1").arg(roomSettings.roomName);
+
+//    Shortcut {
+//        sequence: StandardKey.Cancel
+//        onActivated: dbb.rejected()
+//    }
+
+    ColumnLayout {
+        anchors.margins: Nheko.paddingMedium
+        anchors.fill: parent
+        spacing: 0
+
+
+        MatrixText {
+            text: qsTr("Be careful when editing permissions. You can't lower the permissions of people with a same or higher level than you. Be careful when promoting others.")
+            font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1)
+            Layout.fillWidth: true
+            Layout.fillHeight: false
+            color: Nheko.colors.text
+            Layout.bottomMargin: Nheko.paddingMedium
+        }
+
+        TabBar {
+            id: bar
+            width: parent.width
+            palette: Nheko.colors
+
+            component TabB : TabButton {
+                id: control
+
+                contentItem: Text {
+                    text: control.text
+                    font: control.font
+                    opacity: enabled ? 1.0 : 0.3
+                    color: control.down ? Nheko.colors.highlightedText : Nheko.colors.text
+                    horizontalAlignment: Text.AlignHCenter
+                    verticalAlignment: Text.AlignVCenter
+                    elide: Text.ElideRight
+                }
+
+                background: Rectangle {
+                    border.color: control.down ? Nheko.colors.highlight : Nheko.theme.separator
+                    color: control.checked ? Nheko.colors.highlight : Nheko.colors.base
+                    border.width: 1
+                    radius: 2
+                }
+            }
+            TabB {
+                text: qsTr("Roles")
+            }
+            TabB {
+                text: qsTr("Users")
+            }
+        }
+        Rectangle {
+            Layout.fillWidth: true
+            Layout.fillHeight: true
+            color: Nheko.colors.alternateBase
+            border.width: 1
+            border.color: Nheko.theme.separator
+
+            StackLayout {
+                anchors.fill: parent
+                anchors.margins: Nheko.paddingMedium
+                currentIndex: bar.currentIndex
+
+
+                ColumnLayout {
+                    spacing: Nheko.paddingMedium
+
+                    MatrixText {
+                        text: qsTr("Move permissions between roles to change them")
+                        font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1)
+                        Layout.fillWidth: true
+                        Layout.fillHeight: false
+                        color: Nheko.colors.text
+                    }
+
+                    ReorderableListview {
+                        Layout.fillWidth: true
+                        Layout.fillHeight: true
+
+                        model: editingModel.types
+
+                        delegate: RowLayout {
+                            Column {
+                                Layout.fillWidth: true
+
+                                Text { visible: model.isType; text: model.displayName; color: Nheko.colors.text}
+                                Text {
+                                    visible: !model.isType;
+                                    text: {
+                                        if (editingModel.adminLevel == model.powerlevel)
+                                        return qsTr("Administrator (%1)").arg(model.powerlevel)
+                                        else if (editingModel.moderatorLevel == model.powerlevel)
+                                        return qsTr("Moderator (%1)").arg(model.powerlevel)
+                                        else
+                                        return qsTr("Custom (%1)").arg(model.powerlevel)
+                                    }
+                                    color: Nheko.colors.text
+                                }
+                            }
+
+                            ImageButton {
+                                Layout.alignment: Qt.AlignRight
+                                Layout.rightMargin: 2
+                                image: model.isType ? ":/icons/icons/ui/dismiss.svg" : ":/icons/icons/ui/add-square-button.svg" 
+                                visible: !model.isType || model.removeable
+                                hoverEnabled: true
+                                ToolTip.visible: hovered
+                                ToolTip.text: model.isType ? qsTr("Remove event type") : qsTr("Add event type")
+                                onClicked: {
+                                    if (model.isType) {
+                                        editingModel.types.remove(index);
+                                    } else {
+                                        typeEntry.y = offset
+                                        typeEntry.visible = true
+                                        typeEntry.index = index;
+                                        typeEntry.forceActiveFocus()
+                                    }
+                                }
+                            }
+                        }
+                            MatrixTextField {
+                                id: typeEntry
+
+                                property int index
+
+                                width: parent.width
+                                z: 5
+                                visible: false
+
+                                color: Nheko.colors.text
+
+                                Keys.onPressed: {
+                                    if (typeEntry.text.includes('.') && event.matches(StandardKey.InsertParagraphSeparator)) {
+                                        editingModel.types.add(typeEntry.index, typeEntry.text)
+                                        typeEntry.visible = false;
+                                        typeEntry.clear();
+                                        event.accepted = true;
+                                    }
+                                    else if (event.matches(StandardKey.Cancel)) {
+                                        typeEntry.visible = false;
+                                        typeEntry.clear();
+                                        event.accepted = true;
+                                    }
+                                }
+                            }
+                    }
+
+                }
+                ColumnLayout {
+                    spacing: Nheko.paddingMedium
+
+                    MatrixText {
+                        text: qsTr("Move users up or down to change their permissions")
+                        font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1)
+                        Layout.fillWidth: true
+                        Layout.fillHeight: false
+                    }
+
+                    ReorderableListview {
+                        Layout.fillWidth: true
+                        Layout.fillHeight: true
+
+                        model: editingModel.users
+
+                            Column{
+                                id: userEntryCompleter
+
+                                property int index: 0
+
+                                visible: false
+
+                                width: parent.width
+                                spacing: 1
+                                z: 5
+                            MatrixTextField {
+                                id: userEntry
+
+                                width: parent.width
+                                //font.pixelSize: Math.ceil(quickSwitcher.textHeight * 0.6)
+                                color: Nheko.colors.text
+                                onTextEdited: {
+                                    userCompleter.completer.searchString = text;
+                                }
+                                Keys.onPressed: {
+                                    if (event.key == Qt.Key_Up || event.key == Qt.Key_Backtab) {
+                                        event.accepted = true;
+                                        userCompleter.up();
+                                    } else if (event.key == Qt.Key_Down || event.key == Qt.Key_Tab) {
+                                        event.accepted = true;
+                                        if (event.key == Qt.Key_Tab && (event.modifiers & Qt.ShiftModifier))
+                                        userCompleter.up();
+                                        else
+                                        userCompleter.down();
+                                    } else if (event.matches(StandardKey.InsertParagraphSeparator)) {
+                                        userCompleter.finishCompletion();
+                                        event.accepted = true;
+                                    } else if (event.matches(StandardKey.Cancel)) {
+                                        typeEntry.visible = false;
+                                        typeEntry.clear();
+                                        event.accepted = true;
+                                    }
+                                }
+                            }
+
+
+                                Completer {
+                                    id: userCompleter
+
+                                    visible: userEntry.text.length > 0
+                                    width: parent.width
+                                    roomId: plEditorW.roomSettings.roomId
+                                    completerName: "user"
+                                    bottomToTop: false
+                                    fullWidth: true
+                                    avatarHeight: Nheko.avatarSize / 2
+                                    avatarWidth: Nheko.avatarSize / 2
+                                    centerRowContent: false
+                                    rowMargin: 2
+                                    rowSpacing: 2
+                                }
+                            }
+
+                            Connections {
+                                function onCompletionSelected(id) {
+                                    console.log("selected: " + id);
+                                    editingModel.users.add(userEntryCompleter.index, id);
+                                    userEntry.clear();
+                                    userEntryCompleter.visible = false;
+                                }
+
+                                function onCountChanged() {
+                                    if (userCompleter.count > 0 && (userCompleter.currentIndex < 0 || userCompleter.currentIndex >= userCompleter.count))
+                                    userCompleter.currentIndex = 0;
+
+                                }
+
+                                target: userCompleter
+                            }
+
+                            delegate: RowLayout {
+                                //anchors { fill: parent; margins: 2 }
+                                id: row
+
+                                Avatar {
+                                    id: avatar
+
+                                    Layout.preferredHeight: Nheko.avatarSize / 2
+                                    Layout.preferredWidth: Nheko.avatarSize / 2
+                                    Layout.leftMargin: 2
+                                    userid: model.mxid
+                                    url: {
+                                        if (model.isUser)
+                                        return model.avatarUrl.replace("mxc://", "image://MxcImage/")
+                                        else if (editingModel.adminLevel >= model.powerlevel)
+                                        return "image://colorimage/:/icons/icons/ui/ribbon_star.svg?" + Nheko.colors.buttonText;
+                                        else if (editingModel.moderatorLevel >= model.powerlevel)
+                                        return "image://colorimage/:/icons/icons/ui/ribbon.svg?" + Nheko.colors.buttonText;
+                                        else
+                                        return "image://colorimage/:/icons/icons/ui/person.svg?" + Nheko.colors.buttonText;
+                                    }
+                                    displayName: model.displayName
+                                    enabled: false
+                                }
+                                Column {
+                                    Layout.fillWidth: true
+
+                                    Text { visible: model.isUser; text: model.displayName; color: Nheko.colors.text}
+                                    Text { visible: model.isUser; text: model.mxid; color: Nheko.colors.text}
+                                    Text {
+                                        visible: !model.isUser;
+                                        text: {
+                                            if (editingModel.adminLevel == model.powerlevel)
+                                            return qsTr("Administrator (%1)").arg(model.powerlevel)
+                                            else if (editingModel.moderatorLevel == model.powerlevel)
+                                            return qsTr("Moderator (%1)").arg(model.powerlevel)
+                                            else
+                                            return qsTr("Custom (%1)").arg(model.powerlevel)
+                                        }
+                                        color: Nheko.colors.text
+                                    }
+                                }
+
+                                ImageButton {
+                                    Layout.alignment: Qt.AlignRight
+                                    Layout.rightMargin: 2
+                                    image: model.isUser ? ":/icons/icons/ui/dismiss.svg" : ":/icons/icons/ui/add-square-button.svg" 
+                                    visible: !model.isUser || model.removeable
+                                    hoverEnabled: true
+                                    ToolTip.visible: hovered
+                                    ToolTip.text: model.isUser ? qsTr("Remove user") : qsTr("Add user")
+                                    onClicked: {
+                                        if (model.isUser) {
+                                            editingModel.users.remove(index);
+                                        } else {
+                                            userEntryCompleter.y = offset
+                                            userEntryCompleter.visible = true
+                                            userEntryCompleter.index = index;
+                                            userEntry.forceActiveFocus()
+                                        }
+                                    }
+                                }
+                            }
+                        }
+
+                    }
+                }
+            }
+        }
+
+        footer: DialogButtonBox {
+            id: dbb
+
+            standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
+            onAccepted: {
+                editingModel.commit();
+                plEditorW.close();
+            }
+            onRejected: plEditorW.close();
+        }
+
+    }
diff --git a/resources/qml/dialogs/RoomSettings.qml b/resources/qml/dialogs/RoomSettings.qml
index 4a7b24fe..332a7b09 100644
--- a/resources/qml/dialogs/RoomSettings.qml
+++ b/resources/qml/dialogs/RoomSettings.qml
@@ -336,6 +336,18 @@ ApplicationWindow {
                 }
 
                 Label {
+                    text: qsTr("Permission")
+                    color: Nheko.colors.text
+                }
+
+                Button {
+                    text: qsTr("Configure")
+                    ToolTip.text: qsTr("View and change the permissions in this room")
+                    onClicked: timelineRoot.showPLEditor(roomSettings)
+                    Layout.alignment: Qt.AlignRight
+                }
+
+                Label {
                     text: qsTr("Sticker & Emote Settings")
                     color: Nheko.colors.text
                 }
diff --git a/resources/res.qrc b/resources/res.qrc
index 35b06704..6e3023ea 100644
--- a/resources/res.qrc
+++ b/resources/res.qrc
@@ -127,6 +127,7 @@
         <file>qml/components/AvatarListTile.qml</file>
         <file>qml/components/FlatButton.qml</file>
         <file>qml/components/MainWindowDialog.qml</file>
+        <file>qml/components/ReorderableListview.qml</file>
         <file>qml/components/TextButton.qml</file>
         <file>qml/delegates/Encrypted.qml</file>
         <file>qml/delegates/FileMessage.qml</file>
@@ -148,22 +149,23 @@
         <file>qml/device-verification/Waiting.qml</file>
         <file>qml/dialogs/CreateDirect.qml</file>
         <file>qml/dialogs/CreateRoom.qml</file>
+        <file>qml/dialogs/HiddenEventsDialog.qml</file>
         <file>qml/dialogs/ImageOverlay.qml</file>
         <file>qml/dialogs/ImagePackEditorDialog.qml</file>
         <file>qml/dialogs/ImagePackSettingsDialog.qml</file>
-        <file>qml/dialogs/PhoneNumberInputDialog.qml</file>
         <file>qml/dialogs/InputDialog.qml</file>
         <file>qml/dialogs/InviteDialog.qml</file>
         <file>qml/dialogs/JoinRoomDialog.qml</file>
         <file>qml/dialogs/LeaveRoomDialog.qml</file>
         <file>qml/dialogs/LogoutDialog.qml</file>
+        <file>qml/dialogs/PhoneNumberInputDialog.qml</file>
+        <file>qml/dialogs/PowerLevelEditor.qml</file>
         <file>qml/dialogs/RawMessageDialog.qml</file>
         <file>qml/dialogs/ReadReceipts.qml</file>
         <file>qml/dialogs/RoomDirectory.qml</file>
         <file>qml/dialogs/RoomMembers.qml</file>
         <file>qml/dialogs/RoomSettings.qml</file>
         <file>qml/dialogs/UserProfile.qml</file>
-        <file>qml/dialogs/HiddenEventsDialog.qml</file>
         <file>qml/emoji/EmojiPicker.qml</file>
         <file>qml/emoji/StickerPicker.qml</file>
         <file>qml/ui/NhekoSlider.qml</file>