Capitalize all component folders

This commit is contained in:
miruka
2019-04-28 12:45:12 -04:00
parent e64b233427
commit c5794424ed
42 changed files with 27 additions and 27 deletions

View File

@@ -0,0 +1,118 @@
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.4
import "../Base" as Base
Base.HGlassRectangle {
id: banner
Layout.fillWidth: true
Layout.preferredHeight: 32
signal buttonClicked(string signalId)
property alias avatarName: bannerAvatar.name
property alias avatarSource: bannerAvatar.imageSource
property alias labelText: bannerLabel.text
property alias buttonModel: bannerRepeater.model
Base.HRowLayout {
id: bannerRow
anchors.fill: parent
Base.HAvatar {
id: bannerAvatar
dimension: banner.Layout.preferredHeight
}
Base.HLabel {
id: bannerLabel
textFormat: Text.StyledText
maximumLineCount: 1
elide: Text.ElideRight
visible:
bannerRow.width - bannerAvatar.width - bannerButtons.width > 30
Layout.maximumWidth:
bannerRow.width -
bannerAvatar.width - bannerButtons.width -
Layout.leftMargin - Layout.rightMargin
Layout.leftMargin: 10
Layout.rightMargin: Layout.leftMargin
}
Item { Layout.fillWidth: true }
Base.HRowLayout {
id: bannerButtons
spacing: 0
function getButtonsWidth() {
var total = 0
for (var i = 0; i < bannerRepeater.count; i++) {
total += bannerRepeater.itemAt(i).implicitWidth
}
return total
}
property bool compact:
bannerRow.width <
bannerAvatar.width +
bannerLabel.implicitWidth +
bannerLabel.Layout.leftMargin +
bannerLabel.Layout.rightMargin +
getButtonsWidth()
property int displayMode:
compact ? Button.IconOnly : Button.TextBesideIcon
Repeater {
id: bannerRepeater
model: []
Base.HButton {
property bool alreadyClicked: false
text: modelData.text
iconName: modelData.iconName
display: bannerButtons.displayMode
MouseArea {
anchors.fill: parent
propagateComposedEvents: true
onClicked: {
if (alreadyClicked) { return }
iconName = "hourglass"
alreadyClicked = true
// modelData might be undefined after Backend call
var signalId = modelData.signalId
var isForget =
modelData.clientFunction === "forgetRoom"
var future =
Backend.clientManager.clients[chatPage.userId].
call(modelData.clientFunction,
modelData.clientArgs)
if (! isForget) {
future.onGotResult.connect(function() {
iconName = modelData.iconName
})
}
if (signalId) { buttonClicked(signalId) }
}
}
Layout.maximumWidth: bannerButtons.compact ? height : -1
Layout.fillHeight: true
}
}
}
}
}

View File

@@ -0,0 +1,14 @@
import QtQuick 2.7
import "../Base" as Base
Base.HNoticeLabel {
text: dateTime.toLocaleDateString()
color: Base.HStyle.chat.daybreak.foreground
backgroundColor: Base.HStyle.chat.daybreak.background
radius: Base.HStyle.chat.daybreak.radius
width: messageDelegate.width
//topPadding: messageDelegate.isFirstMessage ?
//0 : messageDelegate.standardSpacing
//bottomPadding: messageDelegate.standardSpacing
}

View File

@@ -0,0 +1,54 @@
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.4
import "../Base" as Base
import "utils.js" as ChatJS
RowLayout {
id: row
spacing: standardSpacing / 2
layoutDirection: isOwn ? Qt.RightToLeft : Qt.LeftToRight
anchors.right: isOwn ? parent.right : undefined
readonly property string contentText:
isMessage ? "" : ChatJS.getEventText(type, dict)
Base.HAvatar {
id: avatar
name: displayName
hidden: combine
dimension: 28
}
Base.HLabel {
id: contentLabel
text: "<font color='" +
Qt.hsla(Backend.hueFromString(displayName.value || dict.sender),
Base.HStyle.chat.event.saturation,
Base.HStyle.chat.event.lightness,
1) +
"'>" +
(displayName.value || dict.sender) + " " +
contentText +
"&nbsp;&nbsp;" +
"<font size=" + Base.HStyle.fontSize.small + "px " +
"color=" + Base.HStyle.chat.event.date + ">" +
Qt.formatDateTime(dateTime, "hh:mm:ss") +
"</font> " +
"</font>"
textFormat: Text.RichText
background: Rectangle {color: Base.HStyle.chat.event.background}
wrapMode: Text.Wrap
leftPadding: horizontalPadding
rightPadding: horizontalPadding
topPadding: verticalPadding
bottomPadding: verticalPadding
Layout.maximumWidth: Math.min(
600, messageListView.width - avatar.width - row.spacing
)
}
}

View File

@@ -0,0 +1,35 @@
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.4
import "../Base" as Base
Banner {
property var inviter: null
color: Base.HStyle.chat.inviteBanner.background
avatarName: inviter ? inviter.displayname : ""
//avatarSource: inviter ? inviter.avatar_url : ""
labelText:
(inviter ?
("<b>" + inviter.displayname + "</b>") : qsTr("Someone")) +
" " + qsTr("invited you to join the room.")
buttonModel: [
{
text: "Accept",
iconName: "invite_accept",
//iconColor: Qt.hsla(0.45, 0.9, 0.3, 1),
clientFunction: "joinRoom",
clientArgs: [chatPage.roomId],
},
{
text: "Decline",
iconName: "invite_decline",
//iconColor: Qt.hsla(0.95, 0.9, 0.35, 1),
clientFunction: "leaveRoom",
clientArgs: [chatPage.roomId],
}
]
}

View File

@@ -0,0 +1,30 @@
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.4
import "../Base" as Base
import "utils.js" as ChatJS
Banner {
property var leftEvent: null
color: Base.HStyle.chat.leftBanner.background
onButtonClicked: if (signalId === "forget") {
chatPage.canLoadPastEvents = false
pageStack.clear()
}
avatarName: ChatJS.getLeftBannerAvatarName(leftEvent, chatPage.userId)
labelText: ChatJS.getLeftBannerText(leftEvent)
buttonModel: [
{
signalId: "forget",
text: "Forget",
iconName: "forget_room",
//iconColor: Qt.hsla(0.95, 0.9, 0.35, 1),
clientFunction: "forgetRoom",
clientArgs: [chatPage.roomId],
}
]
}

View File

@@ -0,0 +1,64 @@
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.4
import "../Base" as Base
Row {
id: row
spacing: standardSpacing
layoutDirection: isOwn ? Qt.RightToLeft : Qt.LeftToRight
anchors.right: isOwn ? parent.right : undefined
Base.HAvatar { id: avatar; hidden: combine; name: displayName }
ColumnLayout {
spacing: 0
Base.HLabel {
visible: ! combine
id: nameLabel
text: displayName.value || dict.sender
background: Rectangle {color: Base.HStyle.chat.message.background}
color: Qt.hsla(Backend.hueFromString(text),
Base.HStyle.displayName.saturation,
Base.HStyle.displayName.lightness,
1)
elide: Text.ElideRight
maximumLineCount: 1
Layout.preferredWidth: contentLabel.width
horizontalAlignment: isOwn ? Text.AlignRight : Text.AlignLeft
leftPadding: horizontalPadding
rightPadding: horizontalPadding
topPadding: verticalPadding
}
Base.HRichLabel {
id: contentLabel
text: (dict.formatted_body ?
Backend.htmlFilter.filter(dict.formatted_body) :
dict.body) +
"&nbsp;&nbsp;<font size=" + Base.HStyle.fontSize.small +
"px color=" + Base.HStyle.chat.message.date + ">" +
Qt.formatDateTime(dateTime, "hh:mm:ss") +
"</font>" +
(isLocalEcho ?
"&nbsp;<font size=" + Base.HStyle.fontSize.small +
"px>⏳</font>" : "")
textFormat: Text.RichText
background: Rectangle {color: Base.HStyle.chat.message.background}
color: Base.HStyle.chat.message.body
wrapMode: Text.Wrap
leftPadding: horizontalPadding
rightPadding: horizontalPadding
topPadding: nameLabel.visible ? 0 : verticalPadding
bottomPadding: verticalPadding
Layout.minimumWidth: nameLabel.implicitWidth
Layout.maximumWidth: Math.min(
600, messageListView.width - avatar.width - row.spacing
)
}
}
}

View File

@@ -0,0 +1,86 @@
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.4
import "../Base" as Base
import "utils.js" as ChatJS
Column {
id: messageDelegate
function minsBetween(date1, date2) {
return Math.round((((date2 - date1) % 86400000) % 3600000) / 60000)
}
function getIsMessage(type_) { return type_.startsWith("RoomMessage") }
function getPreviousItem() {
return index < messageListView.model.count - 1 ?
messageListView.model.get(index + 1) : null
}
property var previousItem: getPreviousItem()
signal reloadPreviousItem()
onReloadPreviousItem: previousItem = getPreviousItem()
readonly property bool isMessage: getIsMessage(type)
readonly property bool isUndecryptableEvent:
type === "OlmEvent" || type === "MegolmEvent"
readonly property var displayName:
Backend.getUserDisplayName(dict.sender)
readonly property bool isOwn:
chatPage.userId === dict.sender
readonly property bool isFirstEvent: type == "RoomCreateEvent"
readonly property bool combine:
previousItem &&
! talkBreak &&
! dayBreak &&
getIsMessage(previousItem.type) === isMessage &&
previousItem.dict.sender === dict.sender &&
minsBetween(previousItem.dateTime, dateTime) <= 5
readonly property bool dayBreak:
isFirstEvent ||
previousItem &&
dateTime.getDate() != previousItem.dateTime.getDate()
readonly property bool talkBreak:
previousItem &&
! dayBreak &&
minsBetween(previousItem.dateTime, dateTime) >= 20
property int standardSpacing: 16
property int horizontalPadding: 7
property int verticalPadding: 5
ListView.onAdd: {
var nextDelegate = messageListView.contentItem.children[index]
if (nextDelegate) { nextDelegate.reloadPreviousItem() }
}
width: parent.width
topPadding:
isFirstEvent ? 0 :
dayBreak ? standardSpacing * 2 :
talkBreak ? standardSpacing * 3 :
combine ? standardSpacing / 4 :
standardSpacing
Daybreak { visible: dayBreak }
Item {
visible: dayBreak
width: parent.width
height: topPadding
}
MessageContent { visible: isMessage }
EventContent { visible: ! isMessage }
}

View File

@@ -0,0 +1,55 @@
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.4
import "../Base" as Base
Base.HGlassRectangle {
property bool canLoadPastEvents: true
property int space: 8
color: "transparent"
Layout.fillWidth: true
Layout.fillHeight: true
ListView {
id: messageListView
delegate: MessageDelegate {}
model: Backend.models.roomEvents.get(chatPage.roomId)
anchors.fill: parent
anchors.leftMargin: space
anchors.rightMargin: space
clip: true
topMargin: space
bottomMargin: space
verticalLayoutDirection: ListView.BottomToTop
// Keep x scroll pages cached, to limit images having to be
// reloaded from network.
cacheBuffer: height * 6
// Declaring this "alias" provides the on... signal
property real yPos: visibleArea.yPosition
onYPosChanged: {
if (chatPage.canLoadPastEvents && yPos <= 0.1) {
Backend.loadPastEvents(chatPage.roomId)
}
}
}
Base.HLabel {
visible: messageListView.model.count < 1
anchors.centerIn: parent
text: qsTr("Nothing to see here yet…")
padding: 10
topPadding: padding / 3
bottomPadding: topPadding
background: Rectangle {
color: Base.HStyle.chat.messageList.background
radius: 5
}
}
}

View File

@@ -0,0 +1,54 @@
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.4
import "../Base" as Base
Base.HGlassRectangle {
property string displayName: ""
property string topic: ""
id: root
Layout.fillWidth: true
Layout.minimumHeight: 36
Layout.maximumHeight: Layout.minimumHeight
color: Base.HStyle.chat.roomHeader.background
RowLayout {
id: row
spacing: 12
anchors.fill: parent
Base.HAvatar {
id: avatar
Layout.alignment: Qt.AlignTop
dimension: root.Layout.minimumHeight
name: displayName
}
Base.HLabel {
id: roomName
text: displayName
font.pixelSize: Base.HStyle.fontSize.big
elide: Text.ElideRight
maximumLineCount: 1
Layout.maximumWidth:
row.width - row.spacing * (row.children.length - 1) -
avatar.width
}
Base.HLabel {
id: roomTopic
text: topic
font.pixelSize: Base.HStyle.fontSize.small
elide: Text.ElideRight
maximumLineCount: 1
Layout.maximumWidth:
row.width -
row.spacing * (row.children.length - 1) -
avatar.width -
roomName.width
}
Item { Layout.fillWidth: true }
}
}

View File

@@ -0,0 +1,44 @@
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.4
ColumnLayout {
property string userId: ""
property string roomId: ""
readonly property var roomInfo:
Backend.models.rooms.get(userId).getWhere("roomId", roomId)
property bool canLoadPastEvents: true
Component.onCompleted: console.log("replaced")
id: chatPage
spacing: 0
onFocusChanged: sendBox.setFocus()
RoomHeader {
displayName: roomInfo.displayName
topic: roomInfo.topic
}
MessageList {}
TypingUsersBar {}
InviteBanner {
visible: roomInfo.category === "Invites"
inviter: roomInfo.inviter
}
SendBox {
id: sendBox
visible: roomInfo.category === "Rooms"
}
LeftBanner {
visible: roomInfo.category === "Left"
leftEvent: roomInfo.leftEvent
}
}

View File

@@ -0,0 +1,62 @@
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.4
import "../Base" as Base
Base.HGlassRectangle {
function setFocus() { textArea.forceActiveFocus() }
id: root
Layout.fillWidth: true
Layout.minimumHeight: 32
Layout.preferredHeight: textArea.implicitHeight
// parent.height / 2 causes binding loop?
Layout.maximumHeight: pageStack.height / 2
color: Base.HStyle.chat.sendBox.background
Base.HRowLayout {
anchors.fill: parent
spacing: 0
Base.HAvatar {
id: avatar
name: Backend.getUserDisplayName(chatPage.userId)
dimension: root.Layout.minimumHeight
}
Base.HScrollableTextArea {
Layout.fillHeight: true
Layout.fillWidth: true
id: textArea
placeholderText: qsTr("Type a message...")
area.focus: true
function setTyping(typing) {
Backend.clientManager.clients[chatPage.userId]
.setTypingState(chatPage.roomId, typing)
}
onTextChanged: setTyping(Boolean(text))
area.onEditingFinished: setTyping(false) // when lost focus
Keys.onReturnPressed: {
event.accepted = true
if (event.modifiers & Qt.ShiftModifier ||
event.modifiers & Qt.ControlModifier ||
event.modifiers & Qt.AltModifier) {
textArea.insert(textArea.cursorPosition, "\n")
return
}
if (textArea.text === "") { return }
Backend.clientManager.clients[chatPage.userId]
.sendMarkdown(chatPage.roomId, textArea.text)
textArea.clear()
}
Keys.onEnterPressed: Keys.onReturnPressed(event) // numpad enter
}
}
}

View File

@@ -0,0 +1,24 @@
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.4
import "../Base" as Base
import "utils.js" as ChatJS
Rectangle {
id: root
Layout.fillWidth: true
Layout.minimumHeight: usersLabel.text ? usersLabel.implicitHeight : 0
Layout.maximumHeight: Layout.minimumHeight
color: "#BBB"
property var typingUsers: chatPage.roomInfo.typingUsers
Base.HLabel {
id: usersLabel
anchors.fill: parent
text: ChatJS.getTypingUsersText(typingUsers, chatPage.userId)
elide: Text.ElideMiddle
maximumLineCount: 1
}
}

View File

@@ -0,0 +1,212 @@
function getEventText(type, dict) {
switch (type) {
case "RoomCreateEvent":
return (dict.federate ? "allowed" : "blocked") +
" users on other matrix servers " +
(dict.federate ? "to join" : "from joining") +
" this room."
break
case "RoomGuestAccessEvent":
return (dict.guest_access === "can_join" ? "allowed " : "forbad") +
"guests to join the room."
break
case "RoomJoinRulesEvent":
return "made the room " +
(dict.join_rule === "public." ? "public" : "invite only.")
break
case "RoomHistoryVisibilityEvent":
return getHistoryVisibilityEventText(dict)
break
case "PowerLevelsEvent":
return "changed the room's permissions."
case "RoomMemberEvent":
return getMemberEventText(dict)
break
case "RoomAliasEvent":
return "set the room's main address to " +
dict.canonical_alias + "."
break
case "RoomNameEvent":
return "changed the room's name to \"" + dict.name + "\"."
break
case "RoomTopicEvent":
return "changed the room's topic to \"" + dict.topic + "\"."
break
case "RoomEncryptionEvent":
return "turned on encryption for this room."
break
case "OlmEvent":
case "MegolmEvent":
return "hasn't sent your device the keys to decrypt this message."
default:
console.log(type + "\n" + JSON.stringify(dict, null, 4) + "\n")
return "did something this client does not understand."
//case "CallEvent": TODO
}
}
function getHistoryVisibilityEventText(dict) {
switch (dict.history_visibility) {
case "shared":
var end = "all room members."
break
case "world_readable":
var end = "any member or outsider."
break
case "joined":
var end = "all room members, since the point they joined."
break
case "invited":
var end = "all room members, since the point they were invited."
break
}
return "made future history visible to " + end
}
function getStateDisplayName(dict) {
// The dict.content.displayname may be outdated, prefer
// retrieving it fresh
var name = Backend.getUserDisplayName(dict.state_key, false)
return name === dict.state_key ?
dict.content.displayname : name.result()
}
function getMemberEventText(dict) {
var info = dict.content, prev = dict.prev_content
if (! prev || (info.membership != prev.membership)) {
var reason = info.reason ? (" Reason: " + info.reason) : ""
switch (info.membership) {
case "join":
return prev && prev.membership === "invite" ?
"accepted the invitation." : "joined the room."
break
case "invite":
return "invited " + getStateDisplayName(dict) + " to the room."
break
case "leave":
if (dict.state_key === dict.sender) {
return (prev && prev.membership === "invite" ?
"declined the invitation." : "left the room.") +
reason
}
var name = getStateDisplayName(dict)
return (prev && prev.membership === "invite" ?
"withdrew " + name + "'s invitation." :
prev && prev.membership == "ban" ?
"unbanned " + name + " from the room." :
"kicked out " + name + " from the room.") +
reason
break
case "ban":
var name = getStateDisplayName(dict)
return "banned " + name + " from the room." + reason
break
}
}
var changed = []
if (prev && (info.avatar_url != prev.avatar_url)) {
changed.push("profile picture")
}
if (prev && (info.displayname != prev.displayname)) {
changed.push("display name from \"" +
(prev.displayname || dict.state_key) + '" to "' +
(info.displayname || dict.state_key) + '"')
}
if (changed.length > 0) {
return "changed their " + changed.join(" and ") + "."
}
return ""
}
function getLeftBannerText(leftEvent) {
if (! leftEvent) {
return "You are not member of this room."
}
var info = leftEvent.content
var prev = leftEvent.prev_content
var reason = info.reason ? (" Reason: " + info.reason) : ""
if (leftEvent.state_key === leftEvent.sender) {
return (prev && prev.membership === "invite" ?
"You declined to join the room." : "You left the room.") +
reason
}
if (info.membership)
var name = Backend.getUserDisplayName(leftEvent.sender, false).result()
return "<b>" + name + "</b> " +
(info.membership == "ban" ?
"banned you from the room." :
prev && prev.membership === "invite" ?
"canceled your invitation." :
prev && prev.membership == "ban" ?
"unbanned you from the room." :
"kicked you out of the room.") +
reason
}
function getLeftBannerAvatarName(leftEvent, accountId) {
if (! leftEvent || leftEvent.state_key == leftEvent.sender) {
return Backend.getUserDisplayName(accountId, false).result()
}
return Backend.getUserDisplayName(leftEvent.sender, false).result()
}
function getTypingUsersText(users, ourAccountId) {
var names = []
for (var i = 0; i < users.length; i++) {
if (users[i] !== ourAccountId) {
names.push(Backend.getUserDisplayName(users[i], false).result())
}
}
if (names.length < 1) { return "" }
return "🖋 " +
[names.slice(0, -1).join(", "), names.slice(-1)[0]]
.join(names.length < 2 ? "" : " and ") +
(names.length > 1 ? " are" : " is") + " typing…"
}