Implement inviting to room
This commit is contained in:
parent
fd2f8c9828
commit
001c643406
17
TODO.md
17
TODO.md
@ -1,4 +1,4 @@
|
|||||||
- scale vs current zoom methods?
|
- binding on ...
|
||||||
- better cancel for all boxes
|
- better cancel for all boxes
|
||||||
|
|
||||||
- Media
|
- Media
|
||||||
@ -56,6 +56,7 @@
|
|||||||
- Quote links color in room subtitles (e.g. "> http://foo.orgA)" )
|
- Quote links color in room subtitles (e.g. "> http://foo.orgA)" )
|
||||||
|
|
||||||
- UI
|
- UI
|
||||||
|
- Scrollable popup
|
||||||
- Make theme error/etc text colors more like name colors
|
- Make theme error/etc text colors more like name colors
|
||||||
- In account settings, display name field text should be colored
|
- In account settings, display name field text should be colored
|
||||||
- Way to open context menus without a right mouse button
|
- Way to open context menus without a right mouse button
|
||||||
@ -144,15 +145,9 @@
|
|||||||
- Client improvements
|
- Client improvements
|
||||||
- Refetch profile after manual profile change, don't wait for a room event
|
- Refetch profile after manual profile change, don't wait for a room event
|
||||||
|
|
||||||
- Prevent starting multiple instances, causes problems with E2E DB
|
- Prevent starting multiple client instances, causes problems with E2E DB
|
||||||
(sending new messages from second instances makes them undecryptable to
|
|
||||||
first instance until it's restarted)
|
|
||||||
- Could be fixed by "listening for a `RoomKeyEvent`that has the same
|
|
||||||
session id as the undecryptable `MegolmEvent`, then retry decrypting
|
|
||||||
the message with `decrypt_event()`" - poljar
|
|
||||||
|
|
||||||
- [Soft logouts](https://github.com/poljar/matrix-nio/commit/aba10)
|
|
||||||
- Check if username exists on login screen
|
- Check if username exists on login screen
|
||||||
|
- [Soft logouts](https://github.com/poljar/matrix-nio/commit/aba10)
|
||||||
- `pyotherside.atexit()`
|
- `pyotherside.atexit()`
|
||||||
- Logout previous session if adding an account that's already connected
|
- Logout previous session if adding an account that's already connected
|
||||||
- Config file format
|
- Config file format
|
||||||
@ -166,7 +161,6 @@
|
|||||||
- Fetch all members when using the filter members bar
|
- Fetch all members when using the filter members bar
|
||||||
|
|
||||||
- Direct chats category
|
- Direct chats category
|
||||||
it should be the peer's display name instead.
|
|
||||||
- Animate RoomEventDelegate DayBreak apparition
|
- Animate RoomEventDelegate DayBreak apparition
|
||||||
- Live-reloading accounts.json
|
- Live-reloading accounts.json
|
||||||
|
|
||||||
@ -178,7 +172,8 @@
|
|||||||
- RoomMessageMedia and RoomAvatarEvent info attributes
|
- RoomMessageMedia and RoomAvatarEvent info attributes
|
||||||
- `m.room.aliases` events
|
- `m.room.aliases` events
|
||||||
- MatrixRoom invited members list
|
- MatrixRoom invited members list
|
||||||
- When inviting someone to direct chat, room is "Empty room" until accepted,
|
- Verify room has correct name when creating direct chat and peer hasn't
|
||||||
|
accepted the invite yet
|
||||||
- Left room events after client reboot
|
- Left room events after client reboot
|
||||||
- `org.matrix.room.preview_urls` events
|
- `org.matrix.room.preview_urls` events
|
||||||
- Support "Empty room (was ...)" after peer left
|
- Support "Empty room (was ...)" after peer left
|
||||||
|
@ -3,6 +3,8 @@ from dataclasses import dataclass, field
|
|||||||
import nio
|
import nio
|
||||||
|
|
||||||
|
|
||||||
|
# Matrix Errors
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class MatrixError(Exception):
|
class MatrixError(Exception):
|
||||||
http_code: int = 400
|
http_code: int = 400
|
||||||
@ -29,6 +31,18 @@ class MatrixForbidden(MatrixError):
|
|||||||
m_code: str = "M_FORBIDDEN"
|
m_code: str = "M_FORBIDDEN"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MatrixBadJson(MatrixError):
|
||||||
|
http_code: int = 403
|
||||||
|
m_code: str = "M_BAD_JSON"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MatrixNotJson(MatrixError):
|
||||||
|
http_code: int = 403
|
||||||
|
m_code: str = "M_NOT_JSON"
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class MatrixUserDeactivated(MatrixError):
|
class MatrixUserDeactivated(MatrixError):
|
||||||
http_code: int = 403
|
http_code: int = 403
|
||||||
@ -47,6 +61,8 @@ class MatrixTooLarge(MatrixError):
|
|||||||
m_code: str = "M_TOO_LARGE"
|
m_code: str = "M_TOO_LARGE"
|
||||||
|
|
||||||
|
|
||||||
|
# Client errors
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class UserNotFound(Exception):
|
class UserNotFound(Exception):
|
||||||
user_id: str = field()
|
user_id: str = field()
|
||||||
|
@ -9,7 +9,8 @@ from contextlib import suppress
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import (
|
from typing import (
|
||||||
Any, DefaultDict, Dict, NamedTuple, Optional, Set, Tuple, Type, Union,
|
Any, DefaultDict, Dict, List, NamedTuple, Optional, Set, Tuple, Type,
|
||||||
|
Union,
|
||||||
)
|
)
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
from uuid import UUID, uuid4
|
from uuid import UUID, uuid4
|
||||||
@ -51,6 +52,10 @@ class MatrixImageInfo(NamedTuple):
|
|||||||
|
|
||||||
|
|
||||||
class MatrixClient(nio.AsyncClient):
|
class MatrixClient(nio.AsyncClient):
|
||||||
|
user_id_regex = re.compile(r"^@.+:.+")
|
||||||
|
room_id_or_alias_regex = re.compile(r"^[#!].+:.+")
|
||||||
|
http_s_url = re.compile(r"^https?://")
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
backend,
|
backend,
|
||||||
user: str,
|
user: str,
|
||||||
@ -500,7 +505,7 @@ class MatrixClient(nio.AsyncClient):
|
|||||||
if invite == self.user_id:
|
if invite == self.user_id:
|
||||||
raise InvalidUserInContext(invite)
|
raise InvalidUserInContext(invite)
|
||||||
|
|
||||||
if not re.match(r"^@.+:.+", invite):
|
if not self.user_id_regex.match(invite):
|
||||||
raise InvalidUserId(invite)
|
raise InvalidUserId(invite)
|
||||||
|
|
||||||
if isinstance(await self.get_profile(invite), nio.ProfileGetError):
|
if isinstance(await self.get_profile(invite), nio.ProfileGetError):
|
||||||
@ -548,15 +553,15 @@ class MatrixClient(nio.AsyncClient):
|
|||||||
async def room_join(self, alias_or_id_or_url: str) -> str:
|
async def room_join(self, alias_or_id_or_url: str) -> str:
|
||||||
string = alias_or_id_or_url.strip()
|
string = alias_or_id_or_url.strip()
|
||||||
|
|
||||||
if re.match(r"^https?://", string):
|
if self.http_s_url.match(string):
|
||||||
for part in urlparse(string).fragment.split("/"):
|
for part in urlparse(string).fragment.split("/"):
|
||||||
if re.match(r"^[#!].+:.+", part):
|
if self.room_id_or_alias_regex.match(part):
|
||||||
string = part
|
string = part
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"No alias or room id found in url {string}")
|
raise ValueError(f"No alias or room id found in url {string}")
|
||||||
|
|
||||||
if not re.match(r"^[#!].+:.+", string):
|
if not self.room_id_or_alias_regex.match(string):
|
||||||
raise ValueError("Not an alias or room id")
|
raise ValueError("Not an alias or room id")
|
||||||
|
|
||||||
response = await super().join(string)
|
response = await super().join(string)
|
||||||
@ -575,6 +580,43 @@ class MatrixClient(nio.AsyncClient):
|
|||||||
self.models.pop((Member, room_id), None)
|
self.models.pop((Member, room_id), None)
|
||||||
|
|
||||||
|
|
||||||
|
async def room_mass_invite(
|
||||||
|
self, room_id: str, *user_ids: str,
|
||||||
|
) -> Tuple[List[str], List[Tuple[str, Exception]]]:
|
||||||
|
|
||||||
|
user_ids = tuple(
|
||||||
|
uid for uid in user_ids
|
||||||
|
# Server would return a 403 forbidden for users already in the room
|
||||||
|
if uid not in self.all_rooms[room_id].users
|
||||||
|
)
|
||||||
|
|
||||||
|
async def invite(user_id):
|
||||||
|
if not self.user_id_regex.match(user_id):
|
||||||
|
return InvalidUserId(user_id)
|
||||||
|
|
||||||
|
if isinstance(await self.get_profile(invite), nio.ProfileGetError):
|
||||||
|
return UserNotFound(user_id)
|
||||||
|
|
||||||
|
return await self.room_invite(room_id, user_id)
|
||||||
|
|
||||||
|
coros = [invite(uid) for uid in user_ids]
|
||||||
|
successes = []
|
||||||
|
errors: list = []
|
||||||
|
responses = await asyncio.gather(*coros)
|
||||||
|
|
||||||
|
for user_id, response in zip(user_ids, responses):
|
||||||
|
if isinstance(response, nio.RoomInviteError):
|
||||||
|
errors.append((user_id, MatrixError.from_nio(response)))
|
||||||
|
|
||||||
|
elif isinstance(response, Exception):
|
||||||
|
errors.append((user_id, response))
|
||||||
|
|
||||||
|
else:
|
||||||
|
successes.append(user_id)
|
||||||
|
|
||||||
|
return (successes, errors)
|
||||||
|
|
||||||
|
|
||||||
async def generate_thumbnail(
|
async def generate_thumbnail(
|
||||||
self, data: UploadData, is_svg: bool = False,
|
self, data: UploadData, is_svg: bool = False,
|
||||||
) -> Tuple[bytes, MatrixImageInfo]:
|
) -> Tuple[bytes, MatrixImageInfo]:
|
||||||
@ -760,6 +802,7 @@ class MatrixClient(nio.AsyncClient):
|
|||||||
last_ev = None
|
last_ev = None
|
||||||
|
|
||||||
inviter = getattr(room, "inviter", "") or ""
|
inviter = getattr(room, "inviter", "") or ""
|
||||||
|
levels = room.power_levels
|
||||||
|
|
||||||
self.models[Room, self.user_id][room.room_id] = Room(
|
self.models[Room, self.user_id][room.room_id] = Room(
|
||||||
room_id = room.room_id,
|
room_id = room.room_id,
|
||||||
@ -771,7 +814,11 @@ class MatrixClient(nio.AsyncClient):
|
|||||||
inviter_avatar =
|
inviter_avatar =
|
||||||
(room.avatar_url(inviter) or "") if inviter else "",
|
(room.avatar_url(inviter) or "") if inviter else "",
|
||||||
left = left,
|
left = left,
|
||||||
last_event = last_ev,
|
|
||||||
|
can_invite =
|
||||||
|
levels.users.get(self.user_id, 0) >= levels.defaults.invite,
|
||||||
|
|
||||||
|
last_event = last_ev,
|
||||||
)
|
)
|
||||||
|
|
||||||
# List members that left the room, then remove them from our model
|
# List members that left the room, then remove them from our model
|
||||||
|
@ -47,6 +47,8 @@ class Room(ModelItem):
|
|||||||
left: bool = False
|
left: bool = False
|
||||||
typing_members: List[str] = field(default_factory=list)
|
typing_members: List[str] = field(default_factory=list)
|
||||||
|
|
||||||
|
can_invite: bool = True
|
||||||
|
|
||||||
# Event.serialized
|
# Event.serialized
|
||||||
last_event: Optional[Dict[str, Any]] = field(default=None, repr=False)
|
last_event: Optional[Dict[str, Any]] = field(default=None, repr=False)
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ import QtQuick.Layouts 1.12
|
|||||||
import "../utils.js" as Utils
|
import "../utils.js" as Utils
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: interfaceBox
|
id: box
|
||||||
color: theme.controls.box.background
|
color: theme.controls.box.background
|
||||||
implicitWidth: theme.controls.box.defaultWidth
|
implicitWidth: theme.controls.box.defaultWidth
|
||||||
implicitHeight: childrenRect.height
|
implicitHeight: childrenRect.height
|
||||||
@ -17,6 +17,10 @@ Rectangle {
|
|||||||
property string focusButton: ""
|
property string focusButton: ""
|
||||||
property string clickButtonOnEnter: ""
|
property string clickButtonOnEnter: ""
|
||||||
|
|
||||||
|
property bool fillAvailableHeight: false
|
||||||
|
|
||||||
|
property HButton firstButton: null
|
||||||
|
|
||||||
default property alias body: interfaceBody.data
|
default property alias body: interfaceBody.data
|
||||||
|
|
||||||
|
|
||||||
@ -39,6 +43,11 @@ Rectangle {
|
|||||||
id: mainColumn
|
id: mainColumn
|
||||||
width: parent.width
|
width: parent.width
|
||||||
|
|
||||||
|
Binding on height {
|
||||||
|
value: box.height
|
||||||
|
when: box.fillAvailableHeight
|
||||||
|
}
|
||||||
|
|
||||||
HColumnLayout {
|
HColumnLayout {
|
||||||
id: interfaceBody
|
id: interfaceBody
|
||||||
spacing: theme.spacing * 1.5
|
spacing: theme.spacing * 1.5
|
||||||
@ -56,6 +65,12 @@ Rectangle {
|
|||||||
id: buttonRepeater
|
id: buttonRepeater
|
||||||
model: []
|
model: []
|
||||||
|
|
||||||
|
onItemAdded:
|
||||||
|
if (index === 0) firstButton = buttonRepeater.itemAt(0)
|
||||||
|
|
||||||
|
onItemRemoved:
|
||||||
|
if (index === 0) firstButton = null
|
||||||
|
|
||||||
HButton {
|
HButton {
|
||||||
id: button
|
id: button
|
||||||
text: modelData.text
|
text: modelData.text
|
||||||
|
@ -9,7 +9,6 @@ Popup {
|
|||||||
padding: 0
|
padding: 0
|
||||||
margins: theme.spacing
|
margins: theme.spacing
|
||||||
|
|
||||||
|
|
||||||
enter: Transition {
|
enter: Transition {
|
||||||
HScaleAnimator { from: 0; to: 1; overshoot: 4 }
|
HScaleAnimator { from: 0; to: 1; overshoot: 4 }
|
||||||
}
|
}
|
||||||
@ -22,4 +21,11 @@ Popup {
|
|||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
color: theme.controls.popup.background
|
color: theme.controls.popup.background
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
readonly property int maximumPreferredWidth:
|
||||||
|
window.width - leftMargin - rightMargin - leftInset - rightInset
|
||||||
|
|
||||||
|
readonly property int maximumPreferredHeight:
|
||||||
|
window.height - topMargin - bottomMargin - topInset - bottomInset
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ ScrollView {
|
|||||||
property alias placeholderText: textArea.placeholderText
|
property alias placeholderText: textArea.placeholderText
|
||||||
property alias text: textArea.text
|
property alias text: textArea.text
|
||||||
property alias area: textArea
|
property alias area: textArea
|
||||||
|
property var focusItemOnTab: null
|
||||||
|
|
||||||
|
|
||||||
TextArea {
|
TextArea {
|
||||||
@ -53,5 +54,8 @@ ScrollView {
|
|||||||
event.modifiers & Qt.AltModifier ||
|
event.modifiers & Qt.AltModifier ||
|
||||||
event.modifiers & Qt.MetaModifier
|
event.modifiers & Qt.MetaModifier
|
||||||
) event.accepted = true
|
) event.accepted = true
|
||||||
|
|
||||||
|
KeyNavigation.priority: KeyNavigation.BeforeItem
|
||||||
|
KeyNavigation.tab: focusItemOnTab
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,12 +64,30 @@ HColumnLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
HButton {
|
HButton {
|
||||||
enabled: false // TODO
|
id: inviteButton
|
||||||
icon.name: "room-send-invite"
|
icon.name: "room-send-invite"
|
||||||
topPadding: 0
|
|
||||||
bottomPadding: 0
|
|
||||||
toolTip.text: qsTr("Invite to this room")
|
|
||||||
backgroundColor: theme.chat.roomPane.inviteButton.background
|
backgroundColor: theme.chat.roomPane.inviteButton.background
|
||||||
|
enabled: chat.ready ? chat.roomInfo.can_invite : false
|
||||||
|
|
||||||
|
toolTip.text:
|
||||||
|
enabled ?
|
||||||
|
qsTr("Invite members to this room") :
|
||||||
|
qsTr("No permission to invite members in this room")
|
||||||
|
|
||||||
|
topPadding: 0 // XXX
|
||||||
|
bottomPadding: 0
|
||||||
|
|
||||||
|
onClicked: Utils.makePopup(
|
||||||
|
"Popups/InviteToRoomPopup.qml",
|
||||||
|
chat,
|
||||||
|
{
|
||||||
|
userId: chat.userId,
|
||||||
|
roomId: chat.roomId,
|
||||||
|
invitingAllowed: Qt.binding(() => inviteButton.enabled),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// onEnabledChanged: if (openedPopup && ! enabled)
|
||||||
|
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ HBox {
|
|||||||
let txt = qsTr("Unknown error - %1: %2").arg(type).arg(args)
|
let txt = qsTr("Unknown error - %1: %2").arg(type).arg(args)
|
||||||
|
|
||||||
if (type === "InvalidUserInContext")
|
if (type === "InvalidUserInContext")
|
||||||
txt = qsTr("You can't invite yourself!")
|
txt = qsTr("Can't start chatting with yourself")
|
||||||
|
|
||||||
if (type === "InvalidUserId")
|
if (type === "InvalidUserId")
|
||||||
txt = qsTr("Invalid user ID, expected format is " +
|
txt = qsTr("Invalid user ID, expected format is " +
|
||||||
|
@ -14,22 +14,29 @@ HPopup {
|
|||||||
|
|
||||||
default property alias boxData: box.body
|
default property alias boxData: box.body
|
||||||
property alias box: box
|
property alias box: box
|
||||||
|
property bool fillAvailableHeight: false
|
||||||
|
|
||||||
property alias summary: summary
|
property alias summary: summary
|
||||||
property alias details: details
|
property alias details: details
|
||||||
property bool okClicked: false
|
|
||||||
|
|
||||||
property string okText: qsTr("OK")
|
property string okText: qsTr("OK")
|
||||||
property bool okEnabled: true
|
property bool okEnabled: true
|
||||||
|
property bool okClicked: false
|
||||||
|
|
||||||
|
|
||||||
|
Binding on height {
|
||||||
|
value: popup.maximumPreferredHeight
|
||||||
|
when: popup.fillAvailableHeight
|
||||||
|
}
|
||||||
|
|
||||||
HBox {
|
HBox {
|
||||||
id: box
|
id: box
|
||||||
clickButtonOnEnter: "ok"
|
|
||||||
implicitWidth: Math.min(
|
implicitWidth: Math.min(
|
||||||
window.width - popup.leftMargin - popup.rightMargin,
|
window.width - popup.leftMargin - popup.rightMargin,
|
||||||
theme.controls.popup.defaultWidth,
|
theme.controls.popup.defaultWidth,
|
||||||
)
|
)
|
||||||
|
fillAvailableHeight: popup.fillAvailableHeight
|
||||||
|
clickButtonOnEnter: "ok"
|
||||||
|
|
||||||
buttonModel: [
|
buttonModel: [
|
||||||
{ name: "ok", text: okText, iconName: "ok", enabled: okEnabled},
|
{ name: "ok", text: okText, iconName: "ok", enabled: okEnabled},
|
||||||
@ -44,6 +51,11 @@ HPopup {
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
Binding on height {
|
||||||
|
value: popup.maximumPreferredHeight
|
||||||
|
when: popup.fillAvailableHeight
|
||||||
|
}
|
||||||
|
|
||||||
HLabel {
|
HLabel {
|
||||||
id: summary
|
id: summary
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
|
118
src/qml/Popups/InviteToRoomPopup.qml
Normal file
118
src/qml/Popups/InviteToRoomPopup.qml
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import QtQuick 2.12
|
||||||
|
import QtQuick.Layouts 1.12
|
||||||
|
import "../Base"
|
||||||
|
|
||||||
|
BoxPopup {
|
||||||
|
id: popup
|
||||||
|
// fillAvailableHeight: true
|
||||||
|
summary.text: qsTr("Invite room members")
|
||||||
|
okText: qsTr("Invite")
|
||||||
|
okEnabled: invitingAllowed && Boolean(inviteArea.text.trim())
|
||||||
|
|
||||||
|
onOpened: inviteArea.area.forceActiveFocus()
|
||||||
|
|
||||||
|
onInvitingAllowedChanged:
|
||||||
|
if (! invitingAllowed && inviteFuture) inviteFuture.cancel()
|
||||||
|
|
||||||
|
box.buttonCallbacks: ({
|
||||||
|
ok: button => {
|
||||||
|
button.loading = true
|
||||||
|
|
||||||
|
const inviteesLeft = inviteArea.text.trim().split(/\s+/).filter(
|
||||||
|
user => ! successfulInvites.includes(user)
|
||||||
|
)
|
||||||
|
|
||||||
|
inviteFuture = py.callClientCoro(
|
||||||
|
userId,
|
||||||
|
"room_mass_invite",
|
||||||
|
[roomId, ...inviteesLeft],
|
||||||
|
|
||||||
|
([successes, errors]) => {
|
||||||
|
if (errors.length < 1) {
|
||||||
|
popup.close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
successfulInvites = successes
|
||||||
|
failedInvites = errors
|
||||||
|
button.loading = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
cancel: button => {
|
||||||
|
if (inviteFuture) inviteFuture.cancel()
|
||||||
|
popup.close()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
property string userId
|
||||||
|
property string roomId
|
||||||
|
property bool invitingAllowed: true
|
||||||
|
|
||||||
|
property var inviteFuture: null
|
||||||
|
property var successfulInvites: []
|
||||||
|
property var failedInvites: []
|
||||||
|
|
||||||
|
|
||||||
|
HScrollableTextArea {
|
||||||
|
id: inviteArea
|
||||||
|
focusItemOnTab: box.firstButton
|
||||||
|
area.placeholderText:
|
||||||
|
qsTr("User IDs (e.g. @bob:matrix.org @alice:localhost)")
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
}
|
||||||
|
|
||||||
|
HLabel {
|
||||||
|
id: errorMessage
|
||||||
|
visible: Layout.maximumHeight > 0
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
color: theme.colors.errorText
|
||||||
|
text:
|
||||||
|
invitingAllowed ?
|
||||||
|
allErrors :
|
||||||
|
qsTr("You do not have permission to invite members in this room")
|
||||||
|
|
||||||
|
Layout.maximumHeight: text ? implicitHeight : 0
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
readonly property string allErrors: {
|
||||||
|
// TODO: handle these: real user not found
|
||||||
|
const lines = []
|
||||||
|
|
||||||
|
for (let [user, error] of failedInvites) {
|
||||||
|
const type = py.getattr(
|
||||||
|
py.getattr(error, "__class__"), "__name__",
|
||||||
|
)
|
||||||
|
|
||||||
|
lines.push(
|
||||||
|
type === "InvalidUserId" ?
|
||||||
|
qsTr("%1 is not a valid user ID, expected format is " +
|
||||||
|
"@username:homeserver").arg(user) :
|
||||||
|
|
||||||
|
type === "UserNotFound" ?
|
||||||
|
qsTr("%1 not found, please verify the entered user ID")
|
||||||
|
.arg(user) :
|
||||||
|
|
||||||
|
type === "MatrixUnsupportedRoomVersion" ?
|
||||||
|
qsTr("%1's server does not support this room's version")
|
||||||
|
.arg(user) :
|
||||||
|
|
||||||
|
type === "MatrixForbidden" ?
|
||||||
|
qsTr("%1 is banned from this room")
|
||||||
|
.arg(user) :
|
||||||
|
|
||||||
|
qsTr("Unknown error while inviting %1: %2 - %3")
|
||||||
|
.arg(user).arg(type).arg(py.getattr(error, "args"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines.join("\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on Layout.maximumHeight { HNumberAnimation {} }
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user