Spectral/imports/Spectral/Panel/RoomPanelInput.qml

470 lines
15 KiB
QML
Raw Normal View History

2018-12-07 01:18:42 +00:00
import QtQuick 2.12
2018-12-22 14:25:03 +00:00
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import QtQuick.Controls.Material 2.12
2018-10-16 03:09:30 +00:00
import Spectral.Component 2.0
import Spectral.Component.Emoji 2.0
import Spectral.Dialog 2.0
2018-10-16 03:09:30 +00:00
import Spectral.Effect 2.0
import Spectral.Setting 0.1
import Spectral 0.1
2018-11-17 13:12:56 +00:00
Control {
2018-11-26 04:32:00 +00:00
property alias isReply: replyItem.visible
property var replyUser
2018-10-21 04:50:37 +00:00
property string replyEventID
property string replyContent
2018-11-26 04:32:00 +00:00
property alias isAutoCompleting: autoCompleteListView.visible
property var autoCompleteModel
2018-10-23 02:44:54 +00:00
property int autoCompleteBeginPosition
property int autoCompleteEndPosition
property bool hasAttachment: false
property url attachmentPath
2018-11-18 02:47:12 +00:00
id: root
2018-11-17 13:12:56 +00:00
padding: 0
2018-10-16 03:09:30 +00:00
2018-11-17 13:12:56 +00:00
background: Rectangle {
color: MSettings.darkTheme ? "#303030" : "#fafafa"
radius: 24
2018-11-16 12:30:42 +00:00
2018-11-17 13:12:56 +00:00
layer.enabled: true
layer.effect: ElevationEffect {
2019-05-10 11:26:17 +00:00
elevation: 1
2018-11-17 13:12:56 +00:00
}
2018-10-16 03:09:30 +00:00
}
contentItem: ColumnLayout {
2018-10-16 03:09:30 +00:00
spacing: 0
RowLayout {
Layout.fillWidth: true
Layout.margins: 8
id: replyItem
2018-11-26 04:32:00 +00:00
visible: false
spacing: 8
Avatar {
Layout.preferredWidth: 32
Layout.preferredHeight: 32
Layout.alignment: Qt.AlignTop
2019-01-04 12:17:26 +00:00
source: replyUser ? replyUser.avatarMediaId : ""
hint: replyUser ? replyUser.displayName : "No name"
}
Label {
Layout.fillWidth: true
text: replyContent
2019-05-06 13:34:35 +00:00
font.pixelSize: 14
wrapMode: Label.Wrap
}
2018-11-18 02:47:12 +00:00
}
2018-11-17 13:12:56 +00:00
2018-11-18 02:47:12 +00:00
EmojiPicker {
Layout.fillWidth: true
2018-10-16 03:09:30 +00:00
2018-11-18 02:47:12 +00:00
id: emojiPicker
2018-10-16 03:09:30 +00:00
2018-11-18 02:47:12 +00:00
visible: false
2018-10-19 14:02:12 +00:00
2018-11-18 02:47:12 +00:00
textArea: inputField
emojiModel: EmojiModel { id: emojiModel }
}
2018-10-19 14:02:12 +00:00
ListView {
Layout.fillWidth: true
Layout.preferredHeight: 36
Layout.margins: 8
id: autoCompleteListView
2018-11-26 04:32:00 +00:00
visible: false
model: autoCompleteModel
clip: true
spacing: 4
orientation: ListView.Horizontal
highlightFollowsCurrentItem: true
keyNavigationWraps: true
delegate: Control {
property string autoCompleteText: modelData.displayName || modelData.unicode
property bool isEmoji: modelData.unicode != null
readonly property bool highlighted: autoCompleteListView.currentIndex === index
height: 36
padding: 8
background: Rectangle {
visible: !isEmoji
color: highlighted ? Material.accent : "transparent"
border.color: Material.accent
border.width: 2
radius: height / 2
}
contentItem: Row {
spacing: 4
Text {
width: 20
height: 20
visible: isEmoji
text: autoCompleteText
font.pixelSize: 24
font.family: "Emoji"
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
Avatar {
width: 20
height: 20
visible: !isEmoji
2019-01-04 12:17:26 +00:00
source: modelData.avatarMediaId || null
}
Label {
height: parent.height
visible: !isEmoji
text: autoCompleteText
color: highlighted ? "white" : Material.accent
verticalAlignment: Text.AlignVCenter
}
}
MouseArea {
anchors.fill: parent
onClicked: {
autoCompleteListView.currentIndex = index
inputField.replaceAutoComplete(autoCompleteText)
}
}
}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
Layout.leftMargin: 12
Layout.rightMargin: 12
2018-11-18 02:47:12 +00:00
visible: emojiPicker.visible || replyItem.visible || autoCompleteListView.visible
color: MSettings.darkTheme ? "#424242" : "#e7ebeb"
2018-10-16 03:09:30 +00:00
}
2018-11-18 02:47:12 +00:00
RowLayout {
Layout.fillWidth: true
2018-10-21 04:50:37 +00:00
2018-11-18 02:47:12 +00:00
spacing: 0
2018-10-21 04:50:37 +00:00
2018-11-18 02:47:12 +00:00
ToolButton {
2018-12-07 01:18:42 +00:00
Layout.preferredWidth: 48
Layout.preferredHeight: 48
2018-11-18 02:47:12 +00:00
Layout.alignment: Qt.AlignBottom
2018-10-21 04:50:37 +00:00
2018-11-18 02:47:12 +00:00
id: uploadButton
visible: !isReply && !hasAttachment
2018-10-16 03:09:30 +00:00
2018-11-18 02:47:12 +00:00
contentItem: MaterialIcon {
icon: "\ue226"
}
2018-10-16 03:09:30 +00:00
onClicked: attachDialog.open()
2018-10-16 03:09:30 +00:00
2018-11-18 02:47:12 +00:00
BusyIndicator {
anchors.fill: parent
2018-10-16 03:09:30 +00:00
2018-11-18 02:47:12 +00:00
running: currentRoom && currentRoom.hasFileUploading
}
2018-11-17 13:12:56 +00:00
}
2018-10-16 03:09:30 +00:00
2018-11-18 02:47:12 +00:00
ToolButton {
2018-12-07 01:18:42 +00:00
Layout.preferredWidth: 48
Layout.preferredHeight: 48
2018-11-18 02:47:12 +00:00
Layout.alignment: Qt.AlignBottom
2018-10-16 03:09:30 +00:00
2018-11-18 02:47:12 +00:00
id: cancelReplyButton
2018-10-16 03:09:30 +00:00
2018-11-18 02:47:12 +00:00
visible: isReply
2018-11-18 02:47:12 +00:00
contentItem: MaterialIcon {
icon: "\ue5cd"
}
2018-11-18 02:47:12 +00:00
onClicked: clearReply()
2018-11-17 13:12:56 +00:00
}
Control {
Layout.margins: 6
Layout.preferredHeight: 36
Layout.alignment: Qt.AlignVCenter
visible: hasAttachment
rightPadding: 8
background: Rectangle {
color: MPalette.accent
radius: height / 2
antialiasing: true
}
contentItem: RowLayout {
spacing: 0
ToolButton {
Layout.preferredWidth: height
Layout.fillHeight: true
id: cancelAttachmentButton
contentItem: MaterialIcon {
icon: "\ue5cd"
color: "white"
font.pixelSize: 18
}
onClicked: {
hasAttachment = false
attachmentPath = ""
}
}
Label {
Layout.alignment: Qt.AlignVCenter
text: attachmentPath != "" ? attachmentPath.toString().substring(attachmentPath.toString().lastIndexOf('/') + 1, attachmentPath.length) : ""
color: "white"
}
}
}
2018-11-18 02:47:12 +00:00
TextArea {
property real progress: 0
2018-10-16 03:09:30 +00:00
2018-11-18 02:47:12 +00:00
Layout.fillWidth: true
Layout.minimumHeight: 48
2018-10-16 03:09:30 +00:00
2018-11-18 02:47:12 +00:00
id: inputField
2018-11-17 14:39:34 +00:00
2018-11-18 02:47:12 +00:00
wrapMode: Text.Wrap
placeholderText: "Send a Message"
2018-11-18 02:47:12 +00:00
topPadding: 0
bottomPadding: 0
selectByMouse: true
verticalAlignment: TextEdit.AlignVCenter
2018-11-26 04:17:40 +00:00
text: currentRoom != null ? currentRoom.cachedInput : ""
2018-10-16 03:09:30 +00:00
2018-12-23 03:24:01 +00:00
background: Item {}
2018-10-16 03:09:30 +00:00
2018-11-18 02:47:12 +00:00
Rectangle {
width: currentRoom && currentRoom.hasFileUploading ? parent.width * currentRoom.fileUploadingProgress / 100 : 0
height: parent.height
2018-10-16 03:09:30 +00:00
2018-11-18 02:47:12 +00:00
opacity: 0.2
color: Material.accent
2018-10-16 03:09:30 +00:00
}
2018-11-18 02:47:12 +00:00
Timer {
id: timeoutTimer
2018-10-23 23:00:01 +00:00
2018-11-18 02:47:12 +00:00
repeat: false
interval: 2000
onTriggered: {
repeatTimer.stop()
currentRoom.sendTypingNotification(false)
}
}
2018-11-18 02:47:12 +00:00
Timer {
id: repeatTimer
2018-10-16 03:09:30 +00:00
2018-11-18 02:47:12 +00:00
repeat: true
interval: 5000
triggeredOnStart: true
onTriggered: currentRoom.sendTypingNotification(true)
}
2018-10-23 10:04:07 +00:00
2018-11-18 02:47:12 +00:00
Keys.onReturnPressed: {
if (event.modifiers & Qt.ShiftModifier) {
insert(cursorPosition, "\n")
2019-05-19 14:35:08 +00:00
} else {
2018-11-18 02:47:12 +00:00
postMessage(text)
text = ""
2018-11-26 04:32:00 +00:00
closeAll()
2018-11-18 02:47:12 +00:00
}
2018-11-17 13:12:56 +00:00
}
2018-10-16 03:09:30 +00:00
2018-11-26 04:32:00 +00:00
Keys.onEscapePressed: closeAll()
2018-11-18 02:47:12 +00:00
Keys.onBacktabPressed: if (isAutoCompleting) autoCompleteListView.decrementCurrentIndex()
Keys.onTabPressed: {
if (isAutoCompleting) {
autoCompleteListView.incrementCurrentIndex()
} else {
autoCompleteBeginPosition = text.substring(0, cursorPosition).lastIndexOf(" ") + 1
var autoCompletePrefix = text.substring(0, cursorPosition).split(" ").pop()
if (!autoCompletePrefix) return
if (autoCompletePrefix.startsWith(":")) {
autoCompleteBeginPosition = text.substring(0, cursorPosition).lastIndexOf(" ") + 1
autoCompleteModel = emojiModel.filterModel(autoCompletePrefix)
if (autoCompleteModel.length === 0) return
isAutoCompleting = true
autoCompleteEndPosition = cursorPosition
} else {
autoCompleteModel = currentRoom.getUsers(autoCompletePrefix)
if (autoCompleteModel.length === 0) return
isAutoCompleting = true
autoCompleteEndPosition = cursorPosition
}
2018-10-16 03:09:30 +00:00
}
2018-11-18 02:47:12 +00:00
replaceAutoComplete(autoCompleteListView.currentItem.autoCompleteText)
2018-11-17 13:12:56 +00:00
}
2018-11-18 02:47:12 +00:00
onTextChanged: {
timeoutTimer.restart()
repeatTimer.start()
currentRoom.cachedInput = text
if (cursorPosition !== autoCompleteBeginPosition && cursorPosition !== autoCompleteEndPosition) {
isAutoCompleting = false
autoCompleteListView.currentIndex = 0
}
2018-10-16 03:09:30 +00:00
}
2018-11-17 13:12:56 +00:00
2018-11-18 02:47:12 +00:00
function replaceAutoComplete(word) {
remove(autoCompleteBeginPosition, autoCompleteEndPosition)
autoCompleteEndPosition = autoCompleteBeginPosition + word.length
insert(cursorPosition, word)
}
2018-10-16 03:09:30 +00:00
2018-11-18 02:47:12 +00:00
function postMessage(text) {
if(!currentRoom) { return }
2018-11-17 13:12:56 +00:00
if (hasAttachment) {
currentRoom.uploadFile(attachmentPath, text)
clearAttachment()
return
}
2019-05-19 14:35:08 +00:00
if (text.trim().length === 0) { return }
2018-11-18 02:47:12 +00:00
var PREFIX_ME = '/me '
var PREFIX_NOTICE = '/notice '
var PREFIX_RAINBOW = '/rainbow '
var PREFIX_HTML = '/html '
var PREFIX_MARKDOWN = '/md '
2018-10-16 03:09:30 +00:00
2018-11-18 02:47:12 +00:00
if (isReply) {
currentRoom.sendReply(replyUser.id, replyEventID, replyContent, text)
2018-11-18 02:47:12 +00:00
return
}
2018-10-16 03:09:30 +00:00
2018-11-18 02:47:12 +00:00
if (text.indexOf(PREFIX_ME) === 0) {
text = text.substr(PREFIX_ME.length)
currentRoom.postMessage(text, RoomMessageEvent.Emote)
return
}
if (text.indexOf(PREFIX_NOTICE) === 0) {
text = text.substr(PREFIX_NOTICE.length)
currentRoom.postMessage(text, RoomMessageEvent.Notice)
return
}
if (text.indexOf(PREFIX_RAINBOW) === 0) {
text = text.substr(PREFIX_RAINBOW.length)
var parsedText = ""
var rainbowColor = ["#ff2b00", "#ff5500", "#ff8000", "#ffaa00", "#ffd500", "#ffff00", "#d4ff00", "#aaff00", "#80ff00", "#55ff00", "#2bff00", "#00ff00", "#00ff2b", "#00ff55", "#00ff80", "#00ffaa", "#00ffd5", "#00ffff", "#00d4ff", "#00aaff", "#007fff", "#0055ff", "#002bff", "#0000ff", "#2a00ff", "#5500ff", "#7f00ff", "#aa00ff", "#d400ff", "#ff00ff", "#ff00d4", "#ff00aa", "#ff0080", "#ff0055", "#ff002b", "#ff0000"]
for (var i = 0; i < text.length; i++) {
parsedText = parsedText + "<font color='" + rainbowColor[i % rainbowColor.length] + "'>" + text.charAt(i) + "</font>"
}
currentRoom.postHtmlMessage(text, parsedText, RoomMessageEvent.Text)
return
}
if (text.indexOf(PREFIX_HTML) === 0) {
text = text.substr(PREFIX_HTML.length)
var re = new RegExp("<.*?>")
var plainText = text.replace(re, "")
currentRoom.postHtmlMessage(plainText, text, RoomMessageEvent.Text)
return
}
if (text.indexOf(PREFIX_MARKDOWN) === 0) {
text = text.substr(PREFIX_MARKDOWN.length)
currentRoom.postMarkdownText(text)
2018-11-18 02:47:12 +00:00
return
}
2018-10-16 03:09:30 +00:00
2018-11-18 02:47:12 +00:00
currentRoom.postPlainText(text)
}
}
2018-10-16 03:09:30 +00:00
2018-11-18 02:47:12 +00:00
ToolButton {
2018-12-07 01:18:42 +00:00
Layout.preferredWidth: 48
Layout.preferredHeight: 48
2018-11-18 02:47:12 +00:00
Layout.alignment: Qt.AlignBottom
2018-10-16 03:09:30 +00:00
2018-11-18 02:47:12 +00:00
id: emojiButton
2018-10-16 03:09:30 +00:00
2018-11-18 02:47:12 +00:00
contentItem: MaterialIcon {
icon: "\ue24e"
2018-11-03 14:22:55 +00:00
}
2018-11-18 02:47:12 +00:00
onClicked: emojiPicker.visible = !emojiPicker.visible
2018-10-16 03:09:30 +00:00
}
}
}
2018-10-17 02:52:00 +00:00
ImageClipboard {
id: imageClipboard
}
2018-10-17 02:52:00 +00:00
function insert(str) {
inputField.insert(inputField.cursorPosition, str)
}
function clear() {
inputField.clear()
}
2018-10-21 04:50:37 +00:00
function clearReply() {
isReply = false
replyUser = null
2018-10-21 04:50:37 +00:00
replyEventID = ""
replyContent = ""
}
2018-11-26 04:17:40 +00:00
function focus() {
inputField.forceActiveFocus()
}
2018-11-26 04:32:00 +00:00
function closeAll() {
replyItem.visible = false
autoCompleteListView.visible = false
emojiPicker.visible = false
}
function attach(localPath) {
hasAttachment = true
attachmentPath = localPath
}
function clearAttachment() {
hasAttachment = false
attachmentPath = ""
}
2018-10-16 03:09:30 +00:00
}