From 002fdf0b42ae7a93477d2848a70c5981aecb2ea3 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Fri, 17 May 2024 15:59:23 -0500 Subject: [PATCH 01/31] Initial Commit. Floof Chat moved to script archive. Fixed local message distance check. Signed-off-by: Armored Dragon --- .../chat/FloofChat.html | 0 .../chat/FloofChat.js | 0 .../chat/FloofChat.qml | 0 .../chat/chat.png | Bin .../chat/css/FloofChat.css | 0 .../chat/css/materialize.css | 0 .../chat/js/materialize.min.js | 0 .../chat/resources/bubblepop.wav | Bin .../armored-chat/armored_chat.js | 200 +++++++++ .../armored-chat/armored_chat.qml | 403 ++++++++++++++++++ .../armored-chat/img/icon_black.png | Bin 0 -> 400 bytes .../armored-chat/img/icon_white.png | Bin 0 -> 778 bytes .../armored-chat/img/ui/send.svg | 42 ++ .../armored-chat/img/ui/send_black.png | Bin 0 -> 1950 bytes .../armored-chat/img/ui/send_white.png | Bin 0 -> 1956 bytes .../armored-chat/img/ui/settings_black.png | Bin 0 -> 1561 bytes .../armored-chat/img/ui/settings_white.png | Bin 0 -> 1568 bytes .../armored-chat/img/ui/social_black.png | Bin 0 -> 3485 bytes .../armored-chat/img/ui/social_white.png | Bin 0 -> 3491 bytes .../armored-chat/img/ui/world_black.png | Bin 0 -> 4066 bytes .../armored-chat/img/ui/world_white.png | Bin 0 -> 3960 bytes scripts/defaultScripts.js | 4 +- 22 files changed, 647 insertions(+), 2 deletions(-) rename {scripts/communityScripts => script-archive}/chat/FloofChat.html (100%) rename {scripts/communityScripts => script-archive}/chat/FloofChat.js (100%) rename {scripts/communityScripts => script-archive}/chat/FloofChat.qml (100%) rename {scripts/communityScripts => script-archive}/chat/chat.png (100%) rename {scripts/communityScripts => script-archive}/chat/css/FloofChat.css (100%) rename {scripts/communityScripts => script-archive}/chat/css/materialize.css (100%) rename {scripts/communityScripts => script-archive}/chat/js/materialize.min.js (100%) rename {scripts/communityScripts => script-archive}/chat/resources/bubblepop.wav (100%) create mode 100644 scripts/communityScripts/armored-chat/armored_chat.js create mode 100644 scripts/communityScripts/armored-chat/armored_chat.qml create mode 100644 scripts/communityScripts/armored-chat/img/icon_black.png create mode 100644 scripts/communityScripts/armored-chat/img/icon_white.png create mode 100644 scripts/communityScripts/armored-chat/img/ui/send.svg create mode 100644 scripts/communityScripts/armored-chat/img/ui/send_black.png create mode 100644 scripts/communityScripts/armored-chat/img/ui/send_white.png create mode 100644 scripts/communityScripts/armored-chat/img/ui/settings_black.png create mode 100644 scripts/communityScripts/armored-chat/img/ui/settings_white.png create mode 100644 scripts/communityScripts/armored-chat/img/ui/social_black.png create mode 100644 scripts/communityScripts/armored-chat/img/ui/social_white.png create mode 100644 scripts/communityScripts/armored-chat/img/ui/world_black.png create mode 100644 scripts/communityScripts/armored-chat/img/ui/world_white.png diff --git a/scripts/communityScripts/chat/FloofChat.html b/script-archive/chat/FloofChat.html similarity index 100% rename from scripts/communityScripts/chat/FloofChat.html rename to script-archive/chat/FloofChat.html diff --git a/scripts/communityScripts/chat/FloofChat.js b/script-archive/chat/FloofChat.js similarity index 100% rename from scripts/communityScripts/chat/FloofChat.js rename to script-archive/chat/FloofChat.js diff --git a/scripts/communityScripts/chat/FloofChat.qml b/script-archive/chat/FloofChat.qml similarity index 100% rename from scripts/communityScripts/chat/FloofChat.qml rename to script-archive/chat/FloofChat.qml diff --git a/scripts/communityScripts/chat/chat.png b/script-archive/chat/chat.png similarity index 100% rename from scripts/communityScripts/chat/chat.png rename to script-archive/chat/chat.png diff --git a/scripts/communityScripts/chat/css/FloofChat.css b/script-archive/chat/css/FloofChat.css similarity index 100% rename from scripts/communityScripts/chat/css/FloofChat.css rename to script-archive/chat/css/FloofChat.css diff --git a/scripts/communityScripts/chat/css/materialize.css b/script-archive/chat/css/materialize.css similarity index 100% rename from scripts/communityScripts/chat/css/materialize.css rename to script-archive/chat/css/materialize.css diff --git a/scripts/communityScripts/chat/js/materialize.min.js b/script-archive/chat/js/materialize.min.js similarity index 100% rename from scripts/communityScripts/chat/js/materialize.min.js rename to script-archive/chat/js/materialize.min.js diff --git a/scripts/communityScripts/chat/resources/bubblepop.wav b/script-archive/chat/resources/bubblepop.wav similarity index 100% rename from scripts/communityScripts/chat/resources/bubblepop.wav rename to script-archive/chat/resources/bubblepop.wav diff --git a/scripts/communityScripts/armored-chat/armored_chat.js b/scripts/communityScripts/armored-chat/armored_chat.js new file mode 100644 index 00000000000..40c45104099 --- /dev/null +++ b/scripts/communityScripts/armored-chat/armored_chat.js @@ -0,0 +1,200 @@ +// +// armored_chat.js +// +// Created by Armored Dragon, 2024. +// Copyright 2024 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +(() => { + "use strict"; + + var app_is_visible = false; + var settings = { + compact_chat: false, + external_window: false, + }; + + // Global vars + var tablet; + var chat_overlay_window; + var app_button; + const channels = ["domain", "local"]; + var message_history = Settings.getValue("ArmoredChat-Messages", []); + var max_local_distance = 20; // Maximum range for the local chat + var pal_data = AvatarManager.getPalData().data; + + Messages.subscribe("chat"); + Messages.messageReceived.connect(receivedMessage); + AvatarManager.avatarAddedEvent.connect((session_id) => { + _avatarAction("connected", session_id); + }); + AvatarManager.avatarRemovedEvent.connect((session_id) => { + _avatarAction("left", session_id); + }); + + startup(); + + function startup() { + tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + + app_button = tablet.addButton({ + icon: Script.resolvePath("./img/icon_white.png"), + activeIcon: Script.resolvePath("./img/icon_black.png"), + text: "CHAT", + isActive: app_is_visible, + }); + + // When script ends, remove itself from tablet + Script.scriptEnding.connect(function () { + console.log("Shutting Down"); + tablet.removeButton(app_button); + chat_overlay_window.close(); + }); + + // Overlay button toggle + app_button.clicked.connect(toggleMainChatWindow); + + _openWindow(); + } + function toggleMainChatWindow() { + app_is_visible = !app_is_visible; + console.log(`App is now ${app_is_visible ? "visible" : "hidden"}`); + app_button.editProperties({ isActive: app_is_visible }); + chat_overlay_window.visible = app_is_visible; + + // External window was closed; the window does not exist anymore + if (chat_overlay_window.title == "" && app_is_visible) { + _openWindow(); + } + } + function _openWindow() { + chat_overlay_window = new Desktop.createWindow( + Script.resolvePath("./armored_chat.qml"), + { + title: "Chat", + size: { x: 550, y: 400 }, + additionalFlags: Desktop.ALWAYS_ON_TOP, + visible: app_is_visible, + presentationMode: Desktop.PresentationMode.VIRTUAL, + } + ); + + chat_overlay_window.closed.connect(toggleMainChatWindow); + chat_overlay_window.fromQml.connect(fromQML); + } + function receivedMessage(channel, message) { + // Is the message a chat message? + channel = channel.toLowerCase(); + if (channel !== "chat") return; + console.log(`Received message:\n${message}`); + var message = JSON.parse(message); + + message.channel = message.channel.toLowerCase(); // Make sure the "local", "domain", etc. is formatted consistently + + if (!channels.includes(message.channel)) return; // Check the channel + if ( + message.channel == "local" && + Vec3.distance(MyAvatar.position, message.position) > + max_local_distance + ) + return; // If message is local, and if player is too far away from location, don't do anything + + // Update qml view of to new message + _emitEvent({ type: "show_message", ...message }); + + Messages.sendLocalMessage( + "Floof-Notif", + JSON.stringify({ + sender: message.displayName, + text: message.message, + }) + ); + + // Save message to history + let saved_message = message; + delete saved_message.position; + saved_message.timeString = new Date().toLocaleTimeString(undefined, { + hour12: false, + }); + saved_message.dateString = new Date().toLocaleDateString(undefined, { + month: "long", + day: "numeric", + }); + message_history.push(saved_message); + if (message_history.length > settings.max_history) + message_history.shift(); + Settings.setValue("ArmoredChat-Messages", message_history); + } + + function fromQML(event) { + console.log(`New web event:\n${JSON.stringify(event)}`); + + switch (event.type) { + case "send_message": + _sendMessage(event.message, event.channel); + break; + case "initialized": + // https://github.com/overte-org/overte/issues/824 + chat_overlay_window.visible = app_is_visible; // The "visible" field in the Desktop.createWindow does not seem to work. Force set it to the initial state (false) + _loadSettings(); + break; + } + } + function _sendMessage(message, channel) { + Messages.sendMessage( + "chat", + JSON.stringify({ + position: MyAvatar.position, + message: message, + displayName: MyAvatar.sessionDisplayName, + channel: channel, + action: "send_chat_message", + }) + ); + } + function _avatarAction(type, session_id) { + Script.setTimeout(() => { + if (type == "connected") { + pal_data = AvatarManager.getPalData().data; + } + + // Get the display name of the user + let display_name = ""; + display_name = + AvatarManager.getPalData([session_id])?.data[0] + ?.sessionDisplayName || null; + if (display_name == null) { + for (let i = 0; i < pal_data.length; i++) { + if (pal_data[i].sessionUUID == session_id) { + display_name = pal_data[i].sessionDisplayName; + } + } + } + + // Format the packet + let message = {}; + message.message = `${display_name} ${type}`; + + _emitEvent({ type: "avatar_connected", ...message }); + }, 1500); + } + + function _loadSettings() { + message_history.forEach((message) => { + delete message.action; + _emitEvent({ type: "show_message", ...message }); + }); + } + function _saveSettings() {} + + /** + * Emit a packet to the HTML front end. Easy communication! + * @param {Object} packet - The Object packet to emit to the HTML + * @param {("setting_update"|"show_message")} packet.type - The type of packet it is + */ + function _emitEvent(packet = { type: "" }) { + chat_overlay_window.sendToQml(packet); + } +})(); diff --git a/scripts/communityScripts/armored-chat/armored_chat.qml b/scripts/communityScripts/armored-chat/armored_chat.qml new file mode 100644 index 00000000000..29f43a4616d --- /dev/null +++ b/scripts/communityScripts/armored-chat/armored_chat.qml @@ -0,0 +1,403 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.0 +import QtQuick.Layouts 1.3 + +Rectangle { + color: Qt.rgba(0.1,0.1,0.1,1) + signal sendToScript(var message); + + property string pageVal: "local" + property string last_message_user: "" + property date last_message_time: new Date() + + // TODO: Find a better way to do this + // When the window is created on the script side, the window starts open. + // Once the QML window is created wait, then send the initialized signal. + // This signal is mostly used to close the "Desktop overlay window" script side + // https://github.com/overte-org/overte/issues/824 + Timer { + interval: 100 + running: true + onTriggered: { + toScript({type: "initialized"}) + } + } + // Component.onCompleted: { + // toScript({type: "initialized"}) + // } + + Column { + anchors.fill: parent + spacing: 0 + + // Navigation Bar + Rectangle { + id: navigation_bar + width: parent.width + height: 40 + color:Qt.rgba(0,0,0,1) + + Item { + height: parent.height + width: parent.width + anchors.fill: parent + + Rectangle { + width: pageVal === "local" ? 100 : 60 + height: parent.height + color: pageVal === "local" ? "#505186" : "white" + id: local_page + + Image { + source: "./img/ui/" + (pageVal === "local" ? "social_white.png" : "social_black.png") + sourceSize.width: 40 + sourceSize.height: 40 + anchors.centerIn: parent + } + + Behavior on width { + NumberAnimation { + duration: 50 + // easing.type: Easeing.InOutQuad + } + } + + MouseArea { + anchors.fill: parent + onClicked: { + pageVal = "local"; + } + } + } + Rectangle { + width: pageVal === "domain" ? 100 : 60 + height: parent.height + color: pageVal === "domain" ? "#505186" : "white" + anchors.left: local_page.right + anchors.leftMargin: 5 + id: domain_page + + Image { + source: "./img/ui/" + (pageVal === "domain" ? "world_white.png" : "world_black.png") + sourceSize.width: 30 + sourceSize.height: 30 + anchors.centerIn: parent + } + + Behavior on width { + NumberAnimation { + duration: 50 + } + } + + MouseArea { + anchors.fill: parent + onClicked: { + // addMessage("usertest", "Clicked", "Now", "domain", "notification"); + pageVal = "domain" + } + } + } + + Rectangle { + width: pageVal === "settings" ? 100 : 60 + height: parent.height + color: pageVal === "settings" ? "#505186" : "white" + anchors.right: parent.right + id: settings_page + + Image { + source: "./img/ui/" + (pageVal === "settings" ? "settings_white.png" : "settings_black.png") + sourceSize.width: 30 + sourceSize.height: 30 + anchors.centerIn: parent + } + + Behavior on width { + NumberAnimation { + duration: 50 + } + } + + MouseArea { + anchors.fill: parent + onClicked: { + pageVal = "settings" + } + } + } + } + + } + + // Pages + Item { + width: parent.width + height: parent.height - 40 + anchors.top: navigation_bar.bottom + + + // Chat Message History + ListView { + width: parent.width + height: parent.height - 40 + clip: true + interactive: true + spacing: 5 + id: listview + + delegate: Loader { + width: parent.width + property int delegateIndex: index + property string delegateText: model.text + property string delegateUsername: model.username + property string delegateDate: model.date + + sourceComponent: { + if (model.type === "chat") { + return template_chat_message; + } else if (model.type === "notification") { + return template_notification; + } + } + } + + model: getChannel(pageVal) + + } + + ListModel { + id: local + } + + ListModel { + id: domain + } + + // Chat Entry + Rectangle { + width: parent.width + height: 40 + color: Qt.rgba(0.9,0.9,0.9,1) + anchors.bottom: parent.bottom + visible: ["local", "domain"].includes(pageVal) ? true : false + + Row { + width: parent.width + height: parent.height + + + TextField { + width: parent.width - 60 + height: parent.height + placeholderText: pageVal.charAt(0).toUpperCase() + pageVal.slice(1) + " chat message..." + + onAccepted: { + toScript({type: "send_message", message: text, channel: pageVal}); + text = "" + } + } + + Button { + width: 60 + height:parent.height + + Image { + source: "./img/ui/send_black.png" + sourceSize.width: 30 + sourceSize.height: 30 + anchors.centerIn: parent + } + + onClicked: { + toScript({type: "send_message", message: parent.children[0].text, channel: pageVal}); + parent.children[0].text = "" + } + Keys.onPressed: { + if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { + toScript({type: "send_message", message: parent.children[0].text, channel: pageVal}); + parent.children[0].text = "" + } + } + } + } + } + } + + } + + Component { + id: template_chat_message + + Rectangle{ + property int index: delegateIndex + property string texttest: delegateText + property string username: delegateUsername + property string date: delegateDate + + width: parent.width + height: Math.max(65, children[1].height + 30) + color: index % 2 === 0 ? "transparent" : Qt.rgba(0.15,0.15,0.15,1) + + Item { + width: parent.width + height: 22 + + Text{ + text: username + color: "lightgray" + } + + Text{ + anchors.right: parent.right + text: date + color: "lightgray" + } + } + + TextEdit{ + anchors.top: parent.children[0].bottom + text: texttest + color:"white" + font.pointSize: 12 + readOnly: true + selectByMouse: true + selectByKeyboard: true + width: parent.width * 0.8 + height: contentHeight // Adjust height to fit content + wrapMode: Text.Wrap + } + } + } + + Component { + id: template_notification + + // width: (Math.min(parent.width * 0.8, Math.max(contentWidth, parent.width))) - parent.children[0].width + + Rectangle{ + property int index: delegateIndex + property string texttest: delegateText + property string username: delegateUsername + property string date: delegateDate + color: "#171717" + width: parent.width + height: 40 + + Item { + width: 10 + height: parent.height + + Rectangle { + height: parent.height + width: 5 + color: "#505186" + } + } + + + Item { + width: parent.width - parent.children[0].width - 5 + height: parent.height + anchors.left: parent.children[0].right + + TextEdit{ + text: texttest + color:"white" + font.pointSize: 12 + readOnly: true + width: parent.width + selectByMouse: true + selectByKeyboard: true + height: parent.height + wrapMode: Text.Wrap + verticalAlignment: Text.AlignVCenter + font.italic: true + } + + Text { + text: date + color:"white" + font.pointSize: 12 + anchors.right: parent.children[0].right + height: parent.height + wrapMode: Text.Wrap + horizontalAlignment: Text.AlignRight + verticalAlignment: Text.AlignVCenter + font.italic: true + } + } + + } + + } + + + + property var channels: { + "local": local, + "domain": domain, + } + + function scrollToBottom() { + listview.positionViewAtIndex(listview.count - 1, ListView.End); + listview.positionViewAtEnd(); + listview.contentY = listview.contentY + 50; + } + + + function addMessage(username, message, date, channel, type){ + channel = getChannel(channel) + + if (type === "notification"){ + channel.append({ text: message, date: date, type: "notification" }); + last_message_user = ""; + scrollToBottom(); + last_message_time = new Date(); + return; + } + + var current_time = new Date(); + var elapsed_time = current_time - last_message_time; + var elapsed_minutes = elapsed_time / (1000 * 60); + + var last_item_index = channel.count - 1; + var last_item = channel.get(last_item_index); + + if (last_message_user === username && elapsed_minutes < 1 && last_item){ + last_item.text = last_item.text += "\n" + message; + scrollToBottom() + last_message_time = new Date(); + return; + } + + last_message_user = username; + last_message_time = new Date(); + channel.append({ text: message, username: username, date: date, type: type }); + scrollToBottom(); + } + + function getChannel(id) { + return channels[id]; + } + + // Messages from script + function fromScript(message) { + let time = new Date().toLocaleTimeString(undefined, { hour12: false }); + let date = new Date().toLocaleDateString(undefined, { month: "long", day: "numeric", }); + + switch (message.type){ + case "show_message": + addMessage(message.displayName, message.message, `[ ${time} - ${date} ]`, message.channel, "chat"); + break; + case "avatar_connected": + addMessage("SYSTEM", message.message, `[ ${time} - ${date} ]`, "domain", "notification"); + break; + } + } + + // Send message to script + function toScript(packet){ + sendToScript(packet) + } +} diff --git a/scripts/communityScripts/armored-chat/img/icon_black.png b/scripts/communityScripts/armored-chat/img/icon_black.png new file mode 100644 index 0000000000000000000000000000000000000000..410dc40b5933b126b7a996926e9c2a98a6471316 GIT binary patch literal 400 zcmV;B0dM|^P)EX>4Tx04R}tkv&MmKpe$iTcs)$5j#k6$WWc^qD35Q6^c+H)C#RSm|Xe=O&XFE z7e~Rh;NZt%)xpJCR|i)?5c~jfc5qU3krMxx6k5c1aNLh~_a1le0DrT}RI?`msG4PD zQb{3~UloF{2w)ifh#)R8Q=b#XG(5-GJ$!t6^ClDu?Zdk+{#50?g z&Uv3W%*v8Nd`>)R&;^Mfxh}i>#<}FMpJ#@RY-XM~Oe~bTSngt0HdNv`aa2(?%J=77 zRyc2QR;zW^z9)ZSsGzMZbDicGQdqkM+H?_h|#K%Vj@HPNe_R-@r&e=$yEU( z#{z0lAvu2VKlt6PS)877lR`K2d-P^xs+Wq|isK9c(_I@@V00006VoOIv00000008+zyMF)x010qNS#tmY z4c7nw4c7reD4Tcy000McNliru=mQZ69uX+!UB>_b0S8G$K~!ko?U*|b!Y~j-XCb1Y zf(lN=O}GLj2xxK#L`g-(o%mE7fM#eA3K5?Gvb-zJbe5ZW$;{d^AW71LNERfQBoFkX z1IY!+q6xr)el@TAwI={a00XB1Ihon;69Dgv^%Uo9x z%6yr-XpCx%3Lry?Np=A20E{yG-GBWDNVWlN9GC}yb)&{tWF%J(UZwXW=S`p2t14^j znfp?_`3lnztqyJe1wb70PXJ<>uK~m}Ujc||{su6EBuQa(1+-JEwmsQ(G5`Po07*qo IM6N<$f=@n5P5=M^ literal 0 HcmV?d00001 diff --git a/scripts/communityScripts/armored-chat/img/ui/send.svg b/scripts/communityScripts/armored-chat/img/ui/send.svg new file mode 100644 index 00000000000..82c70a6dafe --- /dev/null +++ b/scripts/communityScripts/armored-chat/img/ui/send.svg @@ -0,0 +1,42 @@ + + + + + + diff --git a/scripts/communityScripts/armored-chat/img/ui/send_black.png b/scripts/communityScripts/armored-chat/img/ui/send_black.png new file mode 100644 index 0000000000000000000000000000000000000000..bc9ece7a1186440aefa69560047589a94f5a2efe GIT binary patch literal 1950 zcmV;P2VwY$P)F0KBn)fs&lHl0AB;+ zfT<$#FBQ?y1vt?9>n*@!V3>&foY{;jQX?tXvmclPi~v3qk@nxOC)8#w4~!F$=EkQ~krrvlO|let9hfE}J5n6Cq7KrQ>)8s-1YQ%7RR-f%)PR*- zk6$y71Kt&peTK0r+F>m>$y#7MFhNBA3>34}gp}*q1-t_c6_I6uW0#67zle-i)nkFXfhOP*v__-oi zay_cr4Y&iBW#4WTW!5MnFvCT<7p zCxh`#DYRUVs`do#2VMfMHV9G3Lcb{FCTU$WH{tfQdl-cH5qD^j`{ualrvH{fyZ%c<_io$>;aUF*UY7q>q)Gcn{XvUX_p28e2;66n-ID=uGDgq zsOqJ-EzbVHDFz{Yq2F*3S!pnS2P(PkP}LKGhkzG=s|-Q}TQk=xcOEbm_)J7rTTLcU zBuaw@0(W7v&AAR&n?1>5&0J5f&?aCm& zHE0@*U@f?x>}KHOnCq4jr9p#$n~lPxR_JHZ<+lzedOk2G*2_==w9_iFPeo))!1!haXs3a;Lcg)K{JUk{e7z4?M%!cj za}n8=;e1jdKs${u^cyK6-yZG{Wi7Wodx7P&J;vu1x#K?3PSXTjVHD=Dg?@RH+n$}c zk{ACNpDQA}vz=1sY1d9;RT{JbcRRso5!np+m)o8lzyjJH;|ue2#8wLd+G+fnc|=!( z!7bUJ^#oGDBGEb|7TtDfvJLo(w$I?DB2qZPfhsmTJeKn&cQde%w$Io?%Jd}8jhlcA zjlvu@p~_nBCSVb5GMB{AjTd0|s-~q-Xt^7KFL3|c<`IcZIB4H_FmRJmm`-hCSj$Zw zD$EB~#v;?Bs+Zs{U+V{)W)Pz8?Z>p`9wal?tAzq3NDUtxG%7bKGy1dL0EKms;*nox z<*uXc^YW9jyj&_kf@E@7U#r|vz$<0CbW>G(0T1I20Io9%V_IfYtK1c35gRB$3Q{1K z^*T2f1gc*SUfNn}xrr)@XR%9=GAl|irQ84sQZCv_N+UPXpgkD4)+kJ_8o{EM8z4b~ zoVIXADK}7pl>6rEsN@DpkP35cLKJcXBuIt4@T9PE10_g>zN)Bjaua>DR(DT9B$td8 zLT;c0DZ1+cb1gSerX6PcP;MGJ0Z6FAM=W} z+(H(X#e$XGt-ws&w%{tG7!^k$ZMjQvO;6K8H$7E!Bn`RI6_&+9BXWIV*{E1=LoYh@ z$jxQbQ$G3O82|tP literal 0 HcmV?d00001 diff --git a/scripts/communityScripts/armored-chat/img/ui/send_white.png b/scripts/communityScripts/armored-chat/img/ui/send_white.png new file mode 100644 index 0000000000000000000000000000000000000000..2730d2f84c769991885250798ac5e453a5c47d19 GIT binary patch literal 1956 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 LNkvXXu0mjfD_g-8 literal 0 HcmV?d00001 diff --git a/scripts/communityScripts/armored-chat/img/ui/settings_white.png b/scripts/communityScripts/armored-chat/img/ui/settings_white.png new file mode 100644 index 0000000000000000000000000000000000000000..12a35ad58c9149f9c3d07155798130e30fd971fa GIT binary patch literal 1568 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 Uao=#1VE_OC07*qoM6N<$g5Qv^mjD0& literal 0 HcmV?d00001 diff --git a/scripts/communityScripts/armored-chat/img/ui/world_white.png b/scripts/communityScripts/armored-chat/img/ui/world_white.png new file mode 100644 index 0000000000000000000000000000000000000000..1f152b47b2ceb6144197886a1ed20a14fafff56b GIT binary patch literal 3960 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 SF%fA10000 Date: Sat, 18 May 2024 13:46:06 -0500 Subject: [PATCH 02/31] Added Settings. Erase messages. Toggle external window. Signed-off-by: Armored Dragon --- .../armored-chat/armored_chat.js | 23 ++++++- .../armored-chat/armored_chat.qml | 65 ++++++++++++++++++- 2 files changed, 84 insertions(+), 4 deletions(-) diff --git a/scripts/communityScripts/armored-chat/armored_chat.js b/scripts/communityScripts/armored-chat/armored_chat.js index 40c45104099..27f40b3a159 100644 --- a/scripts/communityScripts/armored-chat/armored_chat.js +++ b/scripts/communityScripts/armored-chat/armored_chat.js @@ -129,12 +129,33 @@ } function fromQML(event) { - console.log(`New web event:\n${JSON.stringify(event)}`); + console.log(`New QML event:\n${JSON.stringify(event)}`); switch (event.type) { case "send_message": _sendMessage(event.message, event.channel); break; + case "setting_change": + settings[event.setting] = event.value; // Update local settings + _saveSettings(); // Save local settings + + switch (event.setting) { + case "external_window": + chat_overlay_window.presentationMode = event.value + ? Desktop.PresentationMode.NATIVE + : Desktop.PresentationMode.VIRTUAL; + break; + } + + break; + case "action": + switch (event.action) { + case "erase_history": + Settings.setValue("ArmoredChat-Messages", []); + break; + } + break; + case "initialized": // https://github.com/overte-org/overte/issues/824 chat_overlay_window.visible = app_is_visible; // The "visible" field in the Desktop.createWindow does not seem to work. Force set it to the initial state (false) diff --git a/scripts/communityScripts/armored-chat/armored_chat.qml b/scripts/communityScripts/armored-chat/armored_chat.qml index 29f43a4616d..af3314cc99a 100644 --- a/scripts/communityScripts/armored-chat/armored_chat.qml +++ b/scripts/communityScripts/armored-chat/armored_chat.qml @@ -26,9 +26,8 @@ Rectangle { // toScript({type: "initialized"}) // } - Column { + Item { anchors.fill: parent - spacing: 0 // Navigation Bar Rectangle { @@ -135,6 +134,7 @@ Rectangle { width: parent.width height: parent.height - 40 anchors.top: navigation_bar.bottom + visible: ["local", "domain"].includes(pageVal) ? true : false // Chat Message History @@ -180,7 +180,6 @@ Rectangle { height: 40 color: Qt.rgba(0.9,0.9,0.9,1) anchors.bottom: parent.bottom - visible: ["local", "domain"].includes(pageVal) ? true : false Row { width: parent.width @@ -224,6 +223,66 @@ Rectangle { } } + Item { + width: parent.width + height: parent.height - 40 + anchors.top: navigation_bar.bottom + visible: ["local", "domain"].includes(pageVal) ? false : true + + Column { + width: parent.width - 10 + height: parent.height - 10 + anchors.centerIn: parent + spacing: 0 + + Rectangle { + width: parent.width + height: 40 + color: "transparent" + + Text{ + text: "External window" + color: "white" + font.pointSize: 12 + anchors.verticalCenter: parent.verticalCenter + } + + CheckBox{ + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + + onCheckedChanged: { + toScript({type: 'setting_change', setting: 'external_window', value: checked}) + } + } + } + + Rectangle { + width: parent.width + height: 40 + color: Qt.rgba(0.15,0.15,0.15,1); + + Text{ + text: "Erase chat history" + color: "white" + font.pointSize: 12 + anchors.verticalCenter: parent.verticalCenter + } + + Button { + anchors.right: parent.right + text: "Erase" + height: parent.height + anchors.verticalCenter: parent.verticalCenter + + onClicked: { + toScript({type: "action", action: "erase_history"}) + } + } + } + } + } + } Component { From 7b50f9184fb46469cde2bb297035dadc99400434 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Sat, 18 May 2024 14:12:00 -0500 Subject: [PATCH 03/31] Clear already logged messages. Signed-off-by: Armored Dragon --- scripts/communityScripts/armored-chat/armored_chat.js | 5 +++-- scripts/communityScripts/armored-chat/armored_chat.qml | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/scripts/communityScripts/armored-chat/armored_chat.js b/scripts/communityScripts/armored-chat/armored_chat.js index 27f40b3a159..4bc75f93450 100644 --- a/scripts/communityScripts/armored-chat/armored_chat.js +++ b/scripts/communityScripts/armored-chat/armored_chat.js @@ -152,10 +152,12 @@ switch (event.action) { case "erase_history": Settings.setValue("ArmoredChat-Messages", []); + _emitEvent({ + type: "clear_messages", + }); break; } break; - case "initialized": // https://github.com/overte-org/overte/issues/824 chat_overlay_window.visible = app_is_visible; // The "visible" field in the Desktop.createWindow does not seem to work. Force set it to the initial state (false) @@ -201,7 +203,6 @@ _emitEvent({ type: "avatar_connected", ...message }); }, 1500); } - function _loadSettings() { message_history.forEach((message) => { delete message.action; diff --git a/scripts/communityScripts/armored-chat/armored_chat.qml b/scripts/communityScripts/armored-chat/armored_chat.qml index af3314cc99a..9b313162fec 100644 --- a/scripts/communityScripts/armored-chat/armored_chat.qml +++ b/scripts/communityScripts/armored-chat/armored_chat.qml @@ -452,6 +452,9 @@ Rectangle { case "avatar_connected": addMessage("SYSTEM", message.message, `[ ${time} - ${date} ]`, "domain", "notification"); break; + case "clear_messages": + local.clear() + domain.clear() } } From 66fd21777cf62318e328cf6147a901a116f33f4e Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Tue, 21 May 2024 12:19:03 -0500 Subject: [PATCH 04/31] Maximum messages setting. Settings now initialize properly. Settings now save properly. Removed compact_chat setting as deprecated. Added spacing between settings. Message history now prunes itself if the number of messages exceeds maximum. Signed-off-by: Armored Dragon --- .../armored-chat/armored_chat.js | 21 +++++++-- .../armored-chat/armored_chat.qml | 43 ++++++++++++++++++- 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/scripts/communityScripts/armored-chat/armored_chat.js b/scripts/communityScripts/armored-chat/armored_chat.js index 4bc75f93450..4130c05d2f6 100644 --- a/scripts/communityScripts/armored-chat/armored_chat.js +++ b/scripts/communityScripts/armored-chat/armored_chat.js @@ -12,8 +12,8 @@ var app_is_visible = false; var settings = { - compact_chat: false, external_window: false, + maximum_messages: 200, }; // Global vars @@ -123,8 +123,9 @@ day: "numeric", }); message_history.push(saved_message); - if (message_history.length > settings.max_history) + while (message_history.length > settings.maximum_messages) { message_history.shift(); + } Settings.setValue("ArmoredChat-Messages", message_history); } @@ -145,6 +146,9 @@ ? Desktop.PresentationMode.NATIVE : Desktop.PresentationMode.VIRTUAL; break; + case "maximum_messages": + // Do nothing + break; } break; @@ -204,12 +208,23 @@ }, 1500); } function _loadSettings() { + settings = Settings.getValue("ArmoredChat-Config", settings); + + // Load message history message_history.forEach((message) => { delete message.action; _emitEvent({ type: "show_message", ...message }); }); + + // Send current settings to the app + _emitEvent({ type: "initial_settings", settings: settings }); + + console.log(`\n\n\n` + JSON.stringify(settings)); + } + function _saveSettings() { + console.log("Saving config"); + Settings.setValue("ArmoredChat-Config", settings); } - function _saveSettings() {} /** * Emit a packet to the HTML front end. Easy communication! diff --git a/scripts/communityScripts/armored-chat/armored_chat.qml b/scripts/communityScripts/armored-chat/armored_chat.qml index 9b313162fec..84b8ed5d01c 100644 --- a/scripts/communityScripts/armored-chat/armored_chat.qml +++ b/scripts/communityScripts/armored-chat/armored_chat.qml @@ -1,6 +1,7 @@ import QtQuick 2.7 import QtQuick.Controls 2.0 import QtQuick.Layouts 1.3 +import controlsUit 1.0 as HifiControlsUit Rectangle { color: Qt.rgba(0.1,0.1,0.1,1) @@ -18,6 +19,7 @@ Rectangle { Timer { interval: 100 running: true + repeat: false onTriggered: { toScript({type: "initialized"}) } @@ -26,6 +28,7 @@ Rectangle { // toScript({type: "initialized"}) // } + // User view Item { anchors.fill: parent @@ -233,8 +236,9 @@ Rectangle { width: parent.width - 10 height: parent.height - 10 anchors.centerIn: parent - spacing: 0 + spacing: 10 + // External Window Rectangle { width: parent.width height: 40 @@ -248,6 +252,7 @@ Rectangle { } CheckBox{ + id: s_external_window anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter @@ -257,6 +262,38 @@ Rectangle { } } + // Maximum saved messages + Rectangle { + width: parent.width + height: 40 + color: "transparent" + + Text{ + text: "Maximum saved messages" + color: "white" + font.pointSize: 12 + anchors.verticalCenter: parent.verticalCenter + } + + + HifiControlsUit.SpinBox { + id: s_maximum_messages + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + decimals: 0 + width: 100 + height: parent.height + realFrom: 1 + realTo: 1000 + backgroundColor: "#cccccc" + + onValueChanged: { + toScript({type: 'setting_change', setting: 'maximum_messages', value: value}) + } + } + } + + // Erase History Rectangle { width: parent.width height: 40 @@ -285,6 +322,7 @@ Rectangle { } + // Templates Component { id: template_chat_message @@ -455,6 +493,9 @@ Rectangle { case "clear_messages": local.clear() domain.clear() + case "initial_settings": + if (message.settings.external_window) s_external_window.checked = true + if (message.settings.maximum_messages) s_maximum_messages.value = message.settings.maximum_messages } } From 391a33d14921fcef47fa0c6e6942dfe0b6df7d68 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Tue, 21 May 2024 16:46:41 -0500 Subject: [PATCH 05/31] Fixed saved date on messages. Hotfix for empty message_history setting. Signed-off-by: Armored Dragon --- .../communityScripts/armored-chat/armored_chat.js | 15 +++++++++------ .../armored-chat/armored_chat.qml | 13 +++++++++++-- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/scripts/communityScripts/armored-chat/armored_chat.js b/scripts/communityScripts/armored-chat/armored_chat.js index 4130c05d2f6..c479899c499 100644 --- a/scripts/communityScripts/armored-chat/armored_chat.js +++ b/scripts/communityScripts/armored-chat/armored_chat.js @@ -21,7 +21,7 @@ var chat_overlay_window; var app_button; const channels = ["domain", "local"]; - var message_history = Settings.getValue("ArmoredChat-Messages", []); + var message_history = Settings.getValue("ArmoredChat-Messages", []) || []; var max_local_distance = 20; // Maximum range for the local chat var pal_data = AvatarManager.getPalData().data; @@ -119,6 +119,7 @@ hour12: false, }); saved_message.dateString = new Date().toLocaleDateString(undefined, { + year: "numeric", month: "long", day: "numeric", }); @@ -210,11 +211,13 @@ function _loadSettings() { settings = Settings.getValue("ArmoredChat-Config", settings); - // Load message history - message_history.forEach((message) => { - delete message.action; - _emitEvent({ type: "show_message", ...message }); - }); + if (message_history) { + // Load message history + message_history.forEach((message) => { + delete message.action; + _emitEvent({ type: "show_message", ...message }); + }); + } // Send current settings to the app _emitEvent({ type: "initial_settings", settings: settings }); diff --git a/scripts/communityScripts/armored-chat/armored_chat.qml b/scripts/communityScripts/armored-chat/armored_chat.qml index 84b8ed5d01c..462966da11f 100644 --- a/scripts/communityScripts/armored-chat/armored_chat.qml +++ b/scripts/communityScripts/armored-chat/armored_chat.qml @@ -150,11 +150,11 @@ Rectangle { id: listview delegate: Loader { - width: parent.width property int delegateIndex: index property string delegateText: model.text property string delegateUsername: model.username property string delegateDate: model.date + width: parent.width // FIXME: Causes warning, but required for style? sourceComponent: { if (model.type === "chat") { @@ -165,6 +165,12 @@ Rectangle { } } + ScrollBar.vertical: ScrollBar { + id: chat_scrollbar + height: 100 + size: 0.05 + } + model: getChannel(pageVal) } @@ -437,9 +443,12 @@ Rectangle { } function scrollToBottom() { + if (listview.count == 0) return; + // if (chat_scrollbar.visualPosition > 0.85) { listview.positionViewAtIndex(listview.count - 1, ListView.End); listview.positionViewAtEnd(); listview.contentY = listview.contentY + 50; + // } } @@ -485,7 +494,7 @@ Rectangle { switch (message.type){ case "show_message": - addMessage(message.displayName, message.message, `[ ${time} - ${date} ]`, message.channel, "chat"); + addMessage(message.displayName, message.message, `[ ${message.timeString || time} - ${message.dateString || date} ]`, message.channel, "chat"); break; case "avatar_connected": addMessage("SYSTEM", message.message, `[ ${time} - ${date} ]`, "domain", "notification"); From c48e9d3a69e9e9d3a66ffa52fe1f3106cbfcbad2 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Wed, 22 May 2024 02:50:29 -0500 Subject: [PATCH 06/31] Clickable links. Messages are now rich text. Fix clear_message command flow over. Signed-off-by: Armored Dragon --- .../armored-chat/armored_chat.qml | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/scripts/communityScripts/armored-chat/armored_chat.qml b/scripts/communityScripts/armored-chat/armored_chat.qml index 462966da11f..706d431a955 100644 --- a/scripts/communityScripts/armored-chat/armored_chat.qml +++ b/scripts/communityScripts/armored-chat/armored_chat.qml @@ -369,6 +369,11 @@ Rectangle { width: parent.width * 0.8 height: contentHeight // Adjust height to fit content wrapMode: Text.Wrap + textFormat: TextEdit.RichText + + onLinkActivated: { + Window.openWebBrowser(link) + } } } } @@ -455,6 +460,14 @@ Rectangle { function addMessage(username, message, date, channel, type){ channel = getChannel(channel) + // Linkify + var regex = /(https?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+(?:\/[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]*)?(?=\s|$)/g; + message = message.replace(regex, (match) => { + return "" + match + ""; + }); + + message = sanatizeContent(message); // Make sure we don't have any nasties + if (type === "notification"){ channel.append({ text: message, date: date, type: "notification" }); last_message_user = ""; @@ -471,6 +484,7 @@ Rectangle { var last_item = channel.get(last_item_index); if (last_message_user === username && elapsed_minutes < 1 && last_item){ + message = "
" + message last_item.text = last_item.text += "\n" + message; scrollToBottom() last_message_time = new Date(); @@ -487,10 +501,15 @@ Rectangle { return channels[id]; } + function sanatizeContent(mess) { + var script_tag = /]*>/gi; + return mess.replace(script_tag, "script"); + } + // Messages from script function fromScript(message) { let time = new Date().toLocaleTimeString(undefined, { hour12: false }); - let date = new Date().toLocaleDateString(undefined, { month: "long", day: "numeric", }); + let date = new Date().toLocaleDateString(undefined, { year: "numeric", month: "long", day: "numeric", }); switch (message.type){ case "show_message": @@ -502,6 +521,7 @@ Rectangle { case "clear_messages": local.clear() domain.clear() + break; case "initial_settings": if (message.settings.external_window) s_external_window.checked = true if (message.settings.maximum_messages) s_maximum_messages.value = message.settings.maximum_messages From 2a42bceaec76633973c947d1457aa2267daf91e4 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Wed, 22 May 2024 03:03:02 -0500 Subject: [PATCH 07/31] Minor formatting adjustments. Hopefully makes code pasting easier. Added missing break to prevent unintended executions. Signed-off-by: Armored Dragon --- .../armored-chat/armored_chat.qml | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/scripts/communityScripts/armored-chat/armored_chat.qml b/scripts/communityScripts/armored-chat/armored_chat.qml index 706d431a955..87c255663f9 100644 --- a/scripts/communityScripts/armored-chat/armored_chat.qml +++ b/scripts/communityScripts/armored-chat/armored_chat.qml @@ -460,13 +460,8 @@ Rectangle { function addMessage(username, message, date, channel, type){ channel = getChannel(channel) - // Linkify - var regex = /(https?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+(?:\/[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]*)?(?=\s|$)/g; - message = message.replace(regex, (match) => { - return "" + match + ""; - }); - - message = sanatizeContent(message); // Make sure we don't have any nasties + // Format content + message = formatContent(message); if (type === "notification"){ channel.append({ text: message, date: date, type: "notification" }); @@ -501,9 +496,16 @@ Rectangle { return channels[id]; } - function sanatizeContent(mess) { + function formatContent(mess) { + var link = /(https?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+(?:\/[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]*)?(?=\s|$)/g; + mess = mess.replace(link, () => {return "" + match + ""}); + var script_tag = /]*>/gi; - return mess.replace(script_tag, "script"); + mess = mess.replace(script_tag, "script"); + + var newline = /\n/gi; + mess = mess.replace(newline, "
"); + return mess } // Messages from script @@ -519,12 +521,13 @@ Rectangle { addMessage("SYSTEM", message.message, `[ ${time} - ${date} ]`, "domain", "notification"); break; case "clear_messages": - local.clear() - domain.clear() + local.clear(); + domain.clear(); break; case "initial_settings": - if (message.settings.external_window) s_external_window.checked = true - if (message.settings.maximum_messages) s_maximum_messages.value = message.settings.maximum_messages + if (message.settings.external_window) s_external_window.checked = true; + if (message.settings.maximum_messages) s_maximum_messages.value = message.settings.maximum_messages; + break; } } From 9d9b57dae432a71f9b0399fe3e38f03caf42b589 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Wed, 22 May 2024 03:14:17 -0500 Subject: [PATCH 08/31] Fix notification width. Allows for kinetic mouse scrolling. Signed-off-by: Armored Dragon --- scripts/communityScripts/armored-chat/armored_chat.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/communityScripts/armored-chat/armored_chat.qml b/scripts/communityScripts/armored-chat/armored_chat.qml index 87c255663f9..e8540ed3ae2 100644 --- a/scripts/communityScripts/armored-chat/armored_chat.qml +++ b/scripts/communityScripts/armored-chat/armored_chat.qml @@ -414,7 +414,7 @@ Rectangle { color:"white" font.pointSize: 12 readOnly: true - width: parent.width + width: parent.width * 0.8 selectByMouse: true selectByKeyboard: true height: parent.height @@ -427,7 +427,7 @@ Rectangle { text: date color:"white" font.pointSize: 12 - anchors.right: parent.children[0].right + anchors.right: parent.right height: parent.height wrapMode: Text.Wrap horizontalAlignment: Text.AlignRight From 7920610d4dac392a49ff91df6ed41a74ff7d2baf Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Wed, 22 May 2024 03:50:34 -0500 Subject: [PATCH 09/31] README Signed-off-by: Armored Dragon --- .../communityScripts/armored-chat/README.md | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 scripts/communityScripts/armored-chat/README.md diff --git a/scripts/communityScripts/armored-chat/README.md b/scripts/communityScripts/armored-chat/README.md new file mode 100644 index 00000000000..16cd5389153 --- /dev/null +++ b/scripts/communityScripts/armored-chat/README.md @@ -0,0 +1,57 @@ +# Armored Chat + +1. What is Armored Chat (AC) and what AC is not +2. User manual + - Installation + - Settings + - Usability tips + +## What is Armored Chat + +Armored Chat is a chat application strictly made to communicate between players in the same domain. It is made using QML and to be as light weight as reasonably possible. +AC uses the Overte [Messages](https://apidocs.overte.org/Messages.html) API to communicate. + +## User manual + +### Installation + +Armored Chat is preinstalled courtesy of [defaultScripts.js](https://github.com/overte-org/overte/blob/8661e8a858663b48e8485c2cd7120dc3e2d7b87e/scripts/defaultScripts.js). + +If AC is not preinstalled, or for some other reason it can not be automatically installed, you can install it manually by following [these instructions](https://github.com/overte-org/overte/blob/8661e8a858663b48e8485c2cd7120dc3e2d7b87e/scripts/defaultScripts.js) to open your script management application, and loading the script url: + +``` +https://raw.githubusercontent.com/overte-org/overte/master/scripts/communityScripts/armored-chat/armored_chat.js +``` + +--- + +### Settings + +Armored Chat comes with basic settings for managing itself. + +#### External window + +This boolean setting toggles whether AC will be a in-game overlay window, or whether AC will be a external floating window. + +Default is `false`. + +#### Maximum saved messages + +This integer represents the amount of messages to save in the AC history. More messages may be present if AC is left on long enough. This setting only sets the number of saved messages and not the maximum amount of messages that can be viewed at any time. + +This means if you set the value to `5`, your history will save a maximum of 5 messages, however you will still be able to see a longer history in the session should you receive more. Once AC completely closes and fetches your message history as it initializes, you will only see the last 5 messages. + +Default value is `200` + +#### Erase chat history + +This action immediately clears the AC history and the session. Functionally this will set the message list to a empty Array. + +--- + +### Usability tips + +You can scroll quickly using kinetic scrolling! Try "grabbing" the right side of messages, where the timestamp is, and flinging yourself in a direction. + +You can format messages using basic HTML elements. Try `
Red text!
` to color your text red. +Find the full list of Qt rich text tags [here](https://doc.qt.io/qt-6/richtext-html-subset.html). Please note that some of these tags may be intentionally restricted. From 9bf5f3d0d372ac7a5290553a9fda7956a94c0e27 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Wed, 22 May 2024 04:47:37 -0500 Subject: [PATCH 10/31] Linking fix. Disabled sending on Shift + Enter to prevent confusion. Signed-off-by: Armored Dragon --- .../communityScripts/armored-chat/armored_chat.qml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/communityScripts/armored-chat/armored_chat.qml b/scripts/communityScripts/armored-chat/armored_chat.qml index e8540ed3ae2..0cdf154b2fe 100644 --- a/scripts/communityScripts/armored-chat/armored_chat.qml +++ b/scripts/communityScripts/armored-chat/armored_chat.qml @@ -194,18 +194,18 @@ Rectangle { width: parent.width height: parent.height - TextField { width: parent.width - 60 height: parent.height placeholderText: pageVal.charAt(0).toUpperCase() + pageVal.slice(1) + " chat message..." - - onAccepted: { - toScript({type: "send_message", message: text, channel: pageVal}); - text = "" + Keys.onPressed: { + if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && !(event.modifiers & Qt.ShiftModifier)) { + event.accepted = true; + toScript({type: "send_message", message: text, channel: pageVal}); + text = "" + } } } - Button { width: 60 height:parent.height @@ -498,7 +498,7 @@ Rectangle { function formatContent(mess) { var link = /(https?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+(?:\/[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]*)?(?=\s|$)/g; - mess = mess.replace(link, () => {return "" + match + ""}); + mess = mess.replace(link, (match) => {return "" + match + " "}); var script_tag = /]*>/gi; mess = mess.replace(script_tag, "script"); From 86f8e290965d554610d7f50569d2dd14c04b89c8 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Wed, 22 May 2024 20:37:10 -0500 Subject: [PATCH 11/31] Quick Message hotbar. Signed-off-by: Armored Dragon --- .../armored-chat/armored_chat.js | 17 +++- .../armored-chat/armored_chat.qml | 1 + .../armored_chat_quick_message.qml | 99 +++++++++++++++++++ 3 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 scripts/communityScripts/armored-chat/armored_chat_quick_message.qml diff --git a/scripts/communityScripts/armored-chat/armored_chat.js b/scripts/communityScripts/armored-chat/armored_chat.js index c479899c499..6674a74538d 100644 --- a/scripts/communityScripts/armored-chat/armored_chat.js +++ b/scripts/communityScripts/armored-chat/armored_chat.js @@ -20,11 +20,13 @@ var tablet; var chat_overlay_window; var app_button; + var quick_message; const channels = ["domain", "local"]; var message_history = Settings.getValue("ArmoredChat-Messages", []) || []; var max_local_distance = 20; // Maximum range for the local chat var pal_data = AvatarManager.getPalData().data; + Controller.keyPressEvent.connect(keyPressEvent); Messages.subscribe("chat"); Messages.messageReceived.connect(receivedMessage); AvatarManager.avatarAddedEvent.connect((session_id) => { @@ -56,6 +58,10 @@ // Overlay button toggle app_button.clicked.connect(toggleMainChatWindow); + quick_message = new OverlayWindow({ + source: Script.resolvePath("./armored_chat_quick_message.qml"), + }); + _openWindow(); } function toggleMainChatWindow() { @@ -83,6 +89,7 @@ chat_overlay_window.closed.connect(toggleMainChatWindow); chat_overlay_window.fromQml.connect(fromQML); + quick_message.fromQml.connect(fromQML); } function receivedMessage(channel, message) { // Is the message a chat message? @@ -129,7 +136,6 @@ } Settings.setValue("ArmoredChat-Messages", message_history); } - function fromQML(event) { console.log(`New QML event:\n${JSON.stringify(event)}`); @@ -170,6 +176,15 @@ break; } } + function keyPressEvent(event) { + switch (JSON.stringify(event.key)) { + case "16777220": // Enter key + quick_message.sendToQml({ + type: "change_visibility", + value: true, + }); + } + } function _sendMessage(message, channel) { Messages.sendMessage( "chat", diff --git a/scripts/communityScripts/armored-chat/armored_chat.qml b/scripts/communityScripts/armored-chat/armored_chat.qml index 0cdf154b2fe..826e8483703 100644 --- a/scripts/communityScripts/armored-chat/armored_chat.qml +++ b/scripts/communityScripts/armored-chat/armored_chat.qml @@ -198,6 +198,7 @@ Rectangle { width: parent.width - 60 height: parent.height placeholderText: pageVal.charAt(0).toUpperCase() + pageVal.slice(1) + " chat message..." + clip: false Keys.onPressed: { if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && !(event.modifiers & Qt.ShiftModifier)) { event.accepted = true; diff --git a/scripts/communityScripts/armored-chat/armored_chat_quick_message.qml b/scripts/communityScripts/armored-chat/armored_chat_quick_message.qml new file mode 100644 index 00000000000..a3b897083d7 --- /dev/null +++ b/scripts/communityScripts/armored-chat/armored_chat_quick_message.qml @@ -0,0 +1,99 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 + +Rectangle { + id: root + property var window + + Binding { target: root; property:'window'; value: parent.parent; when: Boolean(parent.parent) } + Binding { target: window; property: 'shown'; value: false; when: Boolean(window) } + Component.onDestruction: chat_bar && chat_bar.destroy() + + property alias chat_bar: chat_bar + + Rectangle { + id: chat_bar + parent: desktop + x: 0 + y: parent.height - height + width: parent.width + height: 50 + z: 99 + visible: false + + TextArea { + id: textArea + x: 0 + width: parent.width + height: parent.height + text:"" + textColor: "#ffffff" + clip: false + font.pointSize: 18 + + Keys.onReturnPressed: { _onEnterPressed(); } + Keys.onEnterPressed: { _onEnterPressed(); } + } + + Text { + text: "Local message..." + font.pointSize: 16 + color: "gray" + x: 0 + width: parent.width + anchors.verticalCenter: parent.verticalCenter + visible: textArea.text == "" + } + + Button { + id: button + x: parent.width - width + y: 0 + width: 64 + height: parent.height + clip: false + visible: true + + Image { + id: image + width: 30 + height: 30 + fillMode: Image.PreserveAspectFit + visible: true + anchors.centerIn: parent + source: "./img/ui/send_white.png" + } + + onClicked: { + _onEnterPressed(); + } + } + + } + + function _onEnterPressed() { + changeVisibility(false) + toScript({type: "send_message", message: textArea.text, channel: "local"}) + textArea.text = ""; + } + + function changeVisibility(state){ + chat_bar.visible = state + if (state) textArea.forceActiveFocus(); + else root.parent.forceActiveFocus(); + } + + // Messages from script + function fromScript(message) { + switch (message.type){ + case "change_visibility": + changeVisibility(message.value) + break; + } + } + + // Send message to script + function toScript(packet){ + sendToScript(packet) + } +} \ No newline at end of file From 0bc11e8da44428206ffbc57259145f6899f4f34a Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Wed, 22 May 2024 20:55:31 -0500 Subject: [PATCH 12/31] Fix for #975 sticky keys. Signed-off-by: Armored Dragon --- .../armored-chat/armored_chat_quick_message.qml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/scripts/communityScripts/armored-chat/armored_chat_quick_message.qml b/scripts/communityScripts/armored-chat/armored_chat_quick_message.qml index a3b897083d7..ae110db379e 100644 --- a/scripts/communityScripts/armored-chat/armored_chat_quick_message.qml +++ b/scripts/communityScripts/armored-chat/armored_chat_quick_message.qml @@ -33,6 +33,19 @@ Rectangle { Keys.onReturnPressed: { _onEnterPressed(); } Keys.onEnterPressed: { _onEnterPressed(); } + Keys.onLeftPressed: { moveLeft(); } + Keys.onRightPressed: { moveRight(); } + + function moveLeft(){ + if (cursorPosition > 0){ + cursorPosition-- + } + } + function moveRight(){ + if (cursorPosition < text.length){ + cursorPosition++ + } + } } Text { From 1cddc5a3980d00dba1cf2b115edd3dea861ee8c1 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Wed, 22 May 2024 21:18:08 -0500 Subject: [PATCH 13/31] Added message padding. Resolved "listview" rendering warnings. Prevent sending empty messages. Signed-off-by: Armored Dragon --- .../communityScripts/armored-chat/armored_chat.js | 2 ++ .../communityScripts/armored-chat/armored_chat.qml | 12 +++++------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/communityScripts/armored-chat/armored_chat.js b/scripts/communityScripts/armored-chat/armored_chat.js index 6674a74538d..49c4ba07900 100644 --- a/scripts/communityScripts/armored-chat/armored_chat.js +++ b/scripts/communityScripts/armored-chat/armored_chat.js @@ -186,6 +186,8 @@ } } function _sendMessage(message, channel) { + if (message.length == 0) return; + Messages.sendMessage( "chat", JSON.stringify({ diff --git a/scripts/communityScripts/armored-chat/armored_chat.qml b/scripts/communityScripts/armored-chat/armored_chat.qml index 826e8483703..b34d941fc51 100644 --- a/scripts/communityScripts/armored-chat/armored_chat.qml +++ b/scripts/communityScripts/armored-chat/armored_chat.qml @@ -11,7 +11,6 @@ Rectangle { property string last_message_user: "" property date last_message_time: new Date() - // TODO: Find a better way to do this // When the window is created on the script side, the window starts open. // Once the QML window is created wait, then send the initialized signal. // This signal is mostly used to close the "Desktop overlay window" script side @@ -154,7 +153,7 @@ Rectangle { property string delegateText: model.text property string delegateUsername: model.username property string delegateDate: model.date - width: parent.width // FIXME: Causes warning, but required for style? + width: listview.width sourceComponent: { if (model.type === "chat") { @@ -339,12 +338,12 @@ Rectangle { property string username: delegateUsername property string date: delegateDate - width: parent.width height: Math.max(65, children[1].height + 30) color: index % 2 === 0 ? "transparent" : Qt.rgba(0.15,0.15,0.15,1) Item { - width: parent.width + width: parent.width - 10 + anchors.horizontalCenter: parent.horizontalCenter height: 22 Text{ @@ -361,6 +360,7 @@ Rectangle { TextEdit{ anchors.top: parent.children[0].bottom + x: 5 text: texttest color:"white" font.pointSize: 12 @@ -368,7 +368,7 @@ Rectangle { selectByMouse: true selectByKeyboard: true width: parent.width * 0.8 - height: contentHeight // Adjust height to fit content + height: contentHeight wrapMode: Text.Wrap textFormat: TextEdit.RichText @@ -382,8 +382,6 @@ Rectangle { Component { id: template_notification - // width: (Math.min(parent.width * 0.8, Math.max(contentWidth, parent.width))) - parent.children[0].width - Rectangle{ property int index: delegateIndex property string texttest: delegateText From ed3e629f19d61a8e0da3f165fb349f472bb455f1 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Wed, 22 May 2024 22:31:28 -0500 Subject: [PATCH 14/31] Image embedding. Signed-off-by: Armored Dragon --- .../armored-chat/armored_chat.qml | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/scripts/communityScripts/armored-chat/armored_chat.qml b/scripts/communityScripts/armored-chat/armored_chat.qml index b34d941fc51..3bfb7aa95d4 100644 --- a/scripts/communityScripts/armored-chat/armored_chat.qml +++ b/scripts/communityScripts/armored-chat/armored_chat.qml @@ -462,6 +462,8 @@ Rectangle { // Format content message = formatContent(message); + message = embedImages(message); + if (type === "notification"){ channel.append({ text: message, date: date, type: "notification" }); last_message_user = ""; @@ -507,6 +509,25 @@ Rectangle { return mess } + function embedImages(mess){ + var image_link = /(https?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+(?:\/[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]*)(?:png|jpe?g|gif|bmp|svg|webp)/g; + var matches = mess.match(image_link); + var new_message = "" + var listed = [] + var total_emeds = 0 + + new_message += mess + + for (var i = 0; matches && matches.length > i && total_emeds < 3; i++){ + if (!listed.includes(matches[i])) { + new_message += "
" + listed.push(matches[i]); + total_emeds++ + } + } + return new_message; + } + // Messages from script function fromScript(message) { let time = new Date().toLocaleTimeString(undefined, { hour12: false }); From 0a7b64d7be20bf547d2f74c73e339e8c01faf4c3 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Thu, 23 May 2024 01:50:38 -0500 Subject: [PATCH 15/31] Prevent VR quick_message bar. Signed-off-by: Armored Dragon --- scripts/communityScripts/armored-chat/armored_chat.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/communityScripts/armored-chat/armored_chat.js b/scripts/communityScripts/armored-chat/armored_chat.js index 49c4ba07900..2e45610aeba 100644 --- a/scripts/communityScripts/armored-chat/armored_chat.js +++ b/scripts/communityScripts/armored-chat/armored_chat.js @@ -179,6 +179,8 @@ function keyPressEvent(event) { switch (JSON.stringify(event.key)) { case "16777220": // Enter key + if (HMD.active) return; // Don't allow in VR + quick_message.sendToQml({ type: "change_visibility", value: true, From c1e511572d3e78880636315d7077888f233d47fb Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Thu, 23 May 2024 03:32:38 -0500 Subject: [PATCH 16/31] Updated README. Document image embedding. Declared notificationCore as dependency. Signed-off-by: Armored Dragon --- .../communityScripts/armored-chat/README.md | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/scripts/communityScripts/armored-chat/README.md b/scripts/communityScripts/armored-chat/README.md index 16cd5389153..35f190fd593 100644 --- a/scripts/communityScripts/armored-chat/README.md +++ b/scripts/communityScripts/armored-chat/README.md @@ -1,6 +1,6 @@ # Armored Chat -1. What is Armored Chat (AC) and what AC is not +1. What is Armored Chat 2. User manual - Installation - Settings @@ -9,8 +9,13 @@ ## What is Armored Chat Armored Chat is a chat application strictly made to communicate between players in the same domain. It is made using QML and to be as light weight as reasonably possible. + +### Dependencies + AC uses the Overte [Messages](https://apidocs.overte.org/Messages.html) API to communicate. +For notifications, AC uses [notificationCore.js](https://github.com/overte-org/overte/blob/bb8bac43eadd3b20956a2ff7b0b21c28844b0f77/scripts/communityScripts/notificationCore/notificationCore.js). + ## User manual ### Installation @@ -51,7 +56,28 @@ This action immediately clears the AC history and the session. Functionally this ### Usability tips +#### Navigation + You can scroll quickly using kinetic scrolling! Try "grabbing" the right side of messages, where the timestamp is, and flinging yourself in a direction. +#### Formatting + You can format messages using basic HTML elements. Try `
Red text!
` to color your text red. Find the full list of Qt rich text tags [here](https://doc.qt.io/qt-6/richtext-html-subset.html). Please note that some of these tags may be intentionally restricted. + +#### Media embedding + +Images can be embedded when linked directly. + +Try it out by linking to the Overte logo! `https://github.com/overte-org/overte/raw/master/interface/resources/images/brand-banner.svg` + +In order for images to be embedded, URLs must end in a image filetype. +Supported filetypes are: + +- `.png` +- `.jpg` +- `.jpeg` +- `.gif` +- `.bmp` +- `.svg` +- `.webp` From 7ef33224e3d069a0fa17806e7ac175ebe3debe0e Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Thu, 23 May 2024 17:26:59 -0500 Subject: [PATCH 17/31] Moved system chat to script-archive. Updated systemchat html script path. Renamed "chat" to "floofChat" to avoid confusion. Signed-off-by: Armored Dragon --- script-archive/{chat => floofChat}/FloofChat.html | 0 script-archive/{chat => floofChat}/FloofChat.js | 0 script-archive/{chat => floofChat}/FloofChat.qml | 0 script-archive/{chat => floofChat}/chat.png | Bin .../{chat => floofChat}/css/FloofChat.css | 0 .../{chat => floofChat}/css/materialize.css | 0 .../{chat => floofChat}/js/materialize.min.js | 0 .../{chat => floofChat}/resources/bubblepop.wav | Bin .../systemChat}/ChatPage.html | 0 .../system => script-archive/systemChat}/chat.js | 4 ++-- 10 files changed, 2 insertions(+), 2 deletions(-) rename script-archive/{chat => floofChat}/FloofChat.html (100%) rename script-archive/{chat => floofChat}/FloofChat.js (100%) rename script-archive/{chat => floofChat}/FloofChat.qml (100%) rename script-archive/{chat => floofChat}/chat.png (100%) rename script-archive/{chat => floofChat}/css/FloofChat.css (100%) rename script-archive/{chat => floofChat}/css/materialize.css (100%) rename script-archive/{chat => floofChat}/js/materialize.min.js (100%) rename script-archive/{chat => floofChat}/resources/bubblepop.wav (100%) rename {scripts/system/html => script-archive/systemChat}/ChatPage.html (100%) rename {scripts/system => script-archive/systemChat}/chat.js (99%) diff --git a/script-archive/chat/FloofChat.html b/script-archive/floofChat/FloofChat.html similarity index 100% rename from script-archive/chat/FloofChat.html rename to script-archive/floofChat/FloofChat.html diff --git a/script-archive/chat/FloofChat.js b/script-archive/floofChat/FloofChat.js similarity index 100% rename from script-archive/chat/FloofChat.js rename to script-archive/floofChat/FloofChat.js diff --git a/script-archive/chat/FloofChat.qml b/script-archive/floofChat/FloofChat.qml similarity index 100% rename from script-archive/chat/FloofChat.qml rename to script-archive/floofChat/FloofChat.qml diff --git a/script-archive/chat/chat.png b/script-archive/floofChat/chat.png similarity index 100% rename from script-archive/chat/chat.png rename to script-archive/floofChat/chat.png diff --git a/script-archive/chat/css/FloofChat.css b/script-archive/floofChat/css/FloofChat.css similarity index 100% rename from script-archive/chat/css/FloofChat.css rename to script-archive/floofChat/css/FloofChat.css diff --git a/script-archive/chat/css/materialize.css b/script-archive/floofChat/css/materialize.css similarity index 100% rename from script-archive/chat/css/materialize.css rename to script-archive/floofChat/css/materialize.css diff --git a/script-archive/chat/js/materialize.min.js b/script-archive/floofChat/js/materialize.min.js similarity index 100% rename from script-archive/chat/js/materialize.min.js rename to script-archive/floofChat/js/materialize.min.js diff --git a/script-archive/chat/resources/bubblepop.wav b/script-archive/floofChat/resources/bubblepop.wav similarity index 100% rename from script-archive/chat/resources/bubblepop.wav rename to script-archive/floofChat/resources/bubblepop.wav diff --git a/scripts/system/html/ChatPage.html b/script-archive/systemChat/ChatPage.html similarity index 100% rename from scripts/system/html/ChatPage.html rename to script-archive/systemChat/ChatPage.html diff --git a/scripts/system/chat.js b/script-archive/systemChat/chat.js similarity index 99% rename from scripts/system/chat.js rename to script-archive/systemChat/chat.js index 94a26dc6320..14696946b38 100644 --- a/scripts/system/chat.js +++ b/script-archive/systemChat/chat.js @@ -4,7 +4,7 @@ // // By Don Hopkins (dhopkins@donhopkins.com) on May 5th, 2017 // Copyright 2017 High Fidelity, Inc. -// Copyright 2023 Overte e.V. +// Copyright 2024 Overte e.V. // // // Distributed under the Apache License, Version 2.0. @@ -13,7 +13,7 @@ (function() { - var webPageURL = Script.resolvePath("html/ChatPage.html"); // URL of tablet web page. + var webPageURL = Script.resolvePath("ChatPage.html"); // URL of tablet web page. var randomizeWebPageURL = true; // Set to true for debugging. var lastWebPageURL = ""; // Last random URL of tablet web page. var onChatPage = false; // True when chat web page is opened. From c734e0de80acdee7c630edddd2691b0e3fcba043 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Sat, 25 May 2024 05:30:37 -0500 Subject: [PATCH 18/31] Updated Readme. More documentation on link related features. Signed-off-by: Armored Dragon --- scripts/communityScripts/armored-chat/README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/scripts/communityScripts/armored-chat/README.md b/scripts/communityScripts/armored-chat/README.md index 35f190fd593..6ecf36316ff 100644 --- a/scripts/communityScripts/armored-chat/README.md +++ b/scripts/communityScripts/armored-chat/README.md @@ -54,6 +54,14 @@ This action immediately clears the AC history and the session. Functionally this --- +### Usage + +AC has two chat modes: Local, and Domain. Local chat displays all other local chat messages that are within 20 units of you. Domain chat will display all other Domain messages sent though that channel regardless of distance. + +AC also handles link embedding. When you send an HTTP(S) link, it will automatically parse it using Qt RichText and allow everyone to click on the message. Next to the link you will also see a "⮺" symbol. Clicking on this symbol will open the link in an external window. + +--- + ### Usability tips #### Navigation From 60a46626d946b3f59ef5829e3665d7a6c7b8654e Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Sat, 25 May 2024 05:43:53 -0500 Subject: [PATCH 19/31] Updated URL regex. Seems to preform better at picking out what is and what is not a URL. Signed-off-by: Armored Dragon --- scripts/communityScripts/armored-chat/armored_chat.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/communityScripts/armored-chat/armored_chat.qml b/scripts/communityScripts/armored-chat/armored_chat.qml index 3bfb7aa95d4..f7f5e3b6421 100644 --- a/scripts/communityScripts/armored-chat/armored_chat.qml +++ b/scripts/communityScripts/armored-chat/armored_chat.qml @@ -498,7 +498,7 @@ Rectangle { } function formatContent(mess) { - var link = /(https?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+(?:\/[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]*)?(?=\s|$)/g; + var link = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g; mess = mess.replace(link, (match) => {return "" + match + " "}); var script_tag = /]*>/gi; @@ -510,7 +510,7 @@ Rectangle { } function embedImages(mess){ - var image_link = /(https?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+(?:\/[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]*)(?:png|jpe?g|gif|bmp|svg|webp)/g; + var image_link = /(https?:(\/){2})[\w.-]+(?:\.[\w\.-]+)+(?:\/[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]*)(?:png|jpe?g|gif|bmp|svg|webp)/g; var matches = mess.match(image_link); var new_message = "" var listed = [] From 8301bfd626cf09e643153225278d3ef2049e5ac3 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Sat, 25 May 2024 22:41:17 -0500 Subject: [PATCH 20/31] Added development manual. Changed "avatar_connected" type to generic "notification" type. Removed unused commented code. Signed-off-by: Armored Dragon --- .../communityScripts/armored-chat/README.md | 122 +++++++++++++++++- .../armored-chat/armored_chat.js | 4 +- .../armored-chat/armored_chat.qml | 4 +- 3 files changed, 121 insertions(+), 9 deletions(-) diff --git a/scripts/communityScripts/armored-chat/README.md b/scripts/communityScripts/armored-chat/README.md index 6ecf36316ff..23854946761 100644 --- a/scripts/communityScripts/armored-chat/README.md +++ b/scripts/communityScripts/armored-chat/README.md @@ -5,6 +5,7 @@ - Installation - Settings - Usability tips +3. Development ## What is Armored Chat @@ -52,16 +53,12 @@ Default value is `200` This action immediately clears the AC history and the session. Functionally this will set the message list to a empty Array. ---- - ### Usage AC has two chat modes: Local, and Domain. Local chat displays all other local chat messages that are within 20 units of you. Domain chat will display all other Domain messages sent though that channel regardless of distance. AC also handles link embedding. When you send an HTTP(S) link, it will automatically parse it using Qt RichText and allow everyone to click on the message. Next to the link you will also see a "⮺" symbol. Clicking on this symbol will open the link in an external window. ---- - ### Usability tips #### Navigation @@ -89,3 +86,120 @@ Supported filetypes are: - `.bmp` - `.svg` - `.webp` + +## Development + +### To QML communication + +Here are the signals needed to communicate from the JavaScript core to the QML interface. + +AC calls a `_emitEvent()` function that also includes a `type` key in the object. This `type` tells the QML and/or the JS core what the packet is for. +When you call the `_emitEvent()` function be sure to include the following signals as a `type`. In the examples below, the `type` is being excluded for brevity. + +Example: + +```json +{ type: "show_message", displayName: "username", ...} +``` + +#### "show_message" + +This signal tells the QML to add a new message to the ListView element list. + +Supply a `JSON` object. + +```json +{ + "displayName": "username", + "message": "chat message", + "channel": "domain", // Channel to send message on. By default it should only be "domain" or "local". + "date": "[ time and date string ]" // Optional, defaults to current time and date. +} +``` + +#### "clear_messages" + +Clear all messages displayed in the ListView elements. Note this does not clear the history and this is only a visual erasure. + +No payload required. + +#### "notification" + +Renders a notification to the domain channel. +The intended use is to provide updates about the domain and make the notifications accessible. + +Supply a `JSON` object. + +```json +{ + "message": "notification message" // Notification to render +} +``` + +#### "initial_settings" + +Visually set the settings in the QML interface based on the supplied object. + +Supply a `JSON` object. + +```json +{ + "settings": { + // JSON object of current AC settings + "external_window": false, + "maximum_messages": 200 + } +} +``` + +### To JS communication + +Here are the signals needed to communicate from the QML interface to the JavaScript core. AC is developed in a way that all actions that are not style related are preformed though the JavaScript core. +This means that what ever action you want to preform must go though the JavaScript core for processing. + +This is formatted the same was as the communication packets to the QML interface. Supply the following entries as "type"s in your packet. + +#### "send_message" + +Tell AC to broadcast a message to the domain. + +Supply a `JSON` object. + +```json +{ + "message": "message content", // The contents of the message to send. + "channel": "domain" // Channel to emit the message to. +} +``` + +#### "setting_change" + +Tell AC to change a setting. Exercise caution when using this as you can add new settings unintentionally if you are not careful. + +Supply a `JSON` object + +```json +{ + "setting": "external_window", // The name of the setting to change + "value": true // The value to change the setting to +} +``` + +#### "action" + +Tell AC to preform a generic action. This is normally reserved for functions that would get called on a button onClicked event in the QML. + +Supply a `JSON` object + +```json +{ + "action": "erase_history" // The action to preform +} +``` + +#### "initialized" + +Tell AC the QML overlay has loaded successfully. +This is called to hide the overlay on creation. + +No payload required. diff --git a/scripts/communityScripts/armored-chat/armored_chat.js b/scripts/communityScripts/armored-chat/armored_chat.js index 2e45610aeba..220f6bcc89f 100644 --- a/scripts/communityScripts/armored-chat/armored_chat.js +++ b/scripts/communityScripts/armored-chat/armored_chat.js @@ -224,7 +224,7 @@ let message = {}; message.message = `${display_name} ${type}`; - _emitEvent({ type: "avatar_connected", ...message }); + _emitEvent({ type: "notification", ...message }); }, 1500); } function _loadSettings() { @@ -251,7 +251,7 @@ /** * Emit a packet to the HTML front end. Easy communication! * @param {Object} packet - The Object packet to emit to the HTML - * @param {("setting_update"|"show_message")} packet.type - The type of packet it is + * @param {("show_message"|"clear_messages"|"notification"|"initial_settings")} packet.type - The type of packet it is */ function _emitEvent(packet = { type: "" }) { chat_overlay_window.sendToQml(packet); diff --git a/scripts/communityScripts/armored-chat/armored_chat.qml b/scripts/communityScripts/armored-chat/armored_chat.qml index f7f5e3b6421..51ee44297eb 100644 --- a/scripts/communityScripts/armored-chat/armored_chat.qml +++ b/scripts/communityScripts/armored-chat/armored_chat.qml @@ -59,7 +59,6 @@ Rectangle { Behavior on width { NumberAnimation { duration: 50 - // easing.type: Easeing.InOutQuad } } @@ -94,7 +93,6 @@ Rectangle { MouseArea { anchors.fill: parent onClicked: { - // addMessage("usertest", "Clicked", "Now", "domain", "notification"); pageVal = "domain" } } @@ -537,7 +535,7 @@ Rectangle { case "show_message": addMessage(message.displayName, message.message, `[ ${message.timeString || time} - ${message.dateString || date} ]`, message.channel, "chat"); break; - case "avatar_connected": + case "notification": addMessage("SYSTEM", message.message, `[ ${time} - ${date} ]`, "domain", "notification"); break; case "clear_messages": From 82605c50c421794d12328f3999ca2d993189ccf2 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Sun, 26 May 2024 20:30:33 -0500 Subject: [PATCH 21/31] Scroll to bottom on launch. Signed-off-by: Armored Dragon --- .../communityScripts/armored-chat/armored_chat.qml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/scripts/communityScripts/armored-chat/armored_chat.qml b/scripts/communityScripts/armored-chat/armored_chat.qml index 51ee44297eb..37dd760360c 100644 --- a/scripts/communityScripts/armored-chat/armored_chat.qml +++ b/scripts/communityScripts/armored-chat/armored_chat.qml @@ -20,11 +20,20 @@ Rectangle { running: true repeat: false onTriggered: { - toScript({type: "initialized"}) + toScript({type: "initialized"}); + } + } + Timer { + id: load_scroll_timer + interval: 1000 + running: true + repeat: false + onTriggered: { + scrollToBottom(); } } // Component.onCompleted: { - // toScript({type: "initialized"}) + // toScript({type: "initialized"}); // } // User view From 01306b39711070621b2d3c6015dde7bedbfc787f Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Sun, 26 May 2024 20:34:53 -0500 Subject: [PATCH 22/31] Scroll to bottom when changing page. Signed-off-by: Armored Dragon --- scripts/communityScripts/armored-chat/armored_chat.qml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/scripts/communityScripts/armored-chat/armored_chat.qml b/scripts/communityScripts/armored-chat/armored_chat.qml index 37dd760360c..a928391da77 100644 --- a/scripts/communityScripts/armored-chat/armored_chat.qml +++ b/scripts/communityScripts/armored-chat/armored_chat.qml @@ -16,17 +16,18 @@ Rectangle { // This signal is mostly used to close the "Desktop overlay window" script side // https://github.com/overte-org/overte/issues/824 Timer { - interval: 100 + interval: 10 running: true repeat: false onTriggered: { toScript({type: "initialized"}); + load_scroll_timer.running = true } } Timer { id: load_scroll_timer - interval: 1000 - running: true + interval: 100 + running: false repeat: false onTriggered: { scrollToBottom(); @@ -75,6 +76,7 @@ Rectangle { anchors.fill: parent onClicked: { pageVal = "local"; + load_scroll_timer.running = true; } } } @@ -103,6 +105,7 @@ Rectangle { anchors.fill: parent onClicked: { pageVal = "domain" + load_scroll_timer.running = true; } } } From 7fc1a3d17541b2e14afb17b0b5303849caaeca72 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Sat, 8 Jun 2024 08:34:04 -0500 Subject: [PATCH 23/31] Fixed scrolling too far. Signed-off-by: Armored Dragon --- scripts/communityScripts/armored-chat/armored_chat.qml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/scripts/communityScripts/armored-chat/armored_chat.qml b/scripts/communityScripts/armored-chat/armored_chat.qml index a928391da77..e153c2fec99 100644 --- a/scripts/communityScripts/armored-chat/armored_chat.qml +++ b/scripts/communityScripts/armored-chat/armored_chat.qml @@ -458,11 +458,7 @@ Rectangle { function scrollToBottom() { if (listview.count == 0) return; - // if (chat_scrollbar.visualPosition > 0.85) { - listview.positionViewAtIndex(listview.count - 1, ListView.End); listview.positionViewAtEnd(); - listview.contentY = listview.contentY + 50; - // } } From eb84de84b404e4148dd0158e30c2d4f980abace2 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Sat, 8 Jun 2024 08:41:53 -0500 Subject: [PATCH 24/31] Corrected case on variables. Signed-off-by: Armored Dragon --- .../armored-chat/armored_chat.js | 111 +++++++++--------- 1 file changed, 53 insertions(+), 58 deletions(-) diff --git a/scripts/communityScripts/armored-chat/armored_chat.js b/scripts/communityScripts/armored-chat/armored_chat.js index 220f6bcc89f..6525b836451 100644 --- a/scripts/communityScripts/armored-chat/armored_chat.js +++ b/scripts/communityScripts/armored-chat/armored_chat.js @@ -10,7 +10,7 @@ (() => { "use strict"; - var app_is_visible = false; + var appIsVisible = false; var settings = { external_window: false, maximum_messages: 200, @@ -18,22 +18,22 @@ // Global vars var tablet; - var chat_overlay_window; - var app_button; - var quick_message; + var chatOverlayWindow; + var appButton; + var quickMessage; const channels = ["domain", "local"]; - var message_history = Settings.getValue("ArmoredChat-Messages", []) || []; - var max_local_distance = 20; // Maximum range for the local chat - var pal_data = AvatarManager.getPalData().data; + var messageHistory = Settings.getValue("ArmoredChat-Messages", []) || []; + var maxLocalDistance = 20; // Maximum range for the local chat + var palData = AvatarManager.getPalData().data; Controller.keyPressEvent.connect(keyPressEvent); Messages.subscribe("chat"); Messages.messageReceived.connect(receivedMessage); - AvatarManager.avatarAddedEvent.connect((session_id) => { - _avatarAction("connected", session_id); + AvatarManager.avatarAddedEvent.connect((sessionId) => { + _avatarAction("connected", sessionId); }); - AvatarManager.avatarRemovedEvent.connect((session_id) => { - _avatarAction("left", session_id); + AvatarManager.avatarRemovedEvent.connect((sessionId) => { + _avatarAction("left", sessionId); }); startup(); @@ -41,62 +41,61 @@ function startup() { tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - app_button = tablet.addButton({ + appButton = tablet.addButton({ icon: Script.resolvePath("./img/icon_white.png"), activeIcon: Script.resolvePath("./img/icon_black.png"), text: "CHAT", - isActive: app_is_visible, + isActive: appIsVisible, }); // When script ends, remove itself from tablet Script.scriptEnding.connect(function () { console.log("Shutting Down"); - tablet.removeButton(app_button); - chat_overlay_window.close(); + tablet.removeButton(appButton); + chatOverlayWindow.close(); }); // Overlay button toggle - app_button.clicked.connect(toggleMainChatWindow); + appButton.clicked.connect(toggleMainChatWindow); - quick_message = new OverlayWindow({ + quickMessage = new OverlayWindow({ source: Script.resolvePath("./armored_chat_quick_message.qml"), }); _openWindow(); } function toggleMainChatWindow() { - app_is_visible = !app_is_visible; - console.log(`App is now ${app_is_visible ? "visible" : "hidden"}`); - app_button.editProperties({ isActive: app_is_visible }); - chat_overlay_window.visible = app_is_visible; + appIsVisible = !appIsVisible; + console.log(`App is now ${appIsVisible ? "visible" : "hidden"}`); + appButton.editProperties({ isActive: appIsVisible }); + chatOverlayWindow.visible = appIsVisible; // External window was closed; the window does not exist anymore - if (chat_overlay_window.title == "" && app_is_visible) { + if (chatOverlayWindow.title == "" && appIsVisible) { _openWindow(); } } function _openWindow() { - chat_overlay_window = new Desktop.createWindow( + chatOverlayWindow = new Desktop.createWindow( Script.resolvePath("./armored_chat.qml"), { title: "Chat", size: { x: 550, y: 400 }, additionalFlags: Desktop.ALWAYS_ON_TOP, - visible: app_is_visible, + visible: appIsVisible, presentationMode: Desktop.PresentationMode.VIRTUAL, } ); - chat_overlay_window.closed.connect(toggleMainChatWindow); - chat_overlay_window.fromQml.connect(fromQML); - quick_message.fromQml.connect(fromQML); + chatOverlayWindow.closed.connect(toggleMainChatWindow); + chatOverlayWindow.fromQml.connect(fromQML); + quickMessage.fromQml.connect(fromQML); } function receivedMessage(channel, message) { // Is the message a chat message? channel = channel.toLowerCase(); if (channel !== "chat") return; - console.log(`Received message:\n${message}`); - var message = JSON.parse(message); + message = JSON.parse(message); message.channel = message.channel.toLowerCase(); // Make sure the "local", "domain", etc. is formatted consistently @@ -104,7 +103,7 @@ if ( message.channel == "local" && Vec3.distance(MyAvatar.position, message.position) > - max_local_distance + maxLocalDistance ) return; // If message is local, and if player is too far away from location, don't do anything @@ -120,25 +119,23 @@ ); // Save message to history - let saved_message = message; - delete saved_message.position; - saved_message.timeString = new Date().toLocaleTimeString(undefined, { + let savedMessage = message; + delete savedMessage.position; + savedMessage.timeString = new Date().toLocaleTimeString(undefined, { hour12: false, }); - saved_message.dateString = new Date().toLocaleDateString(undefined, { + savedMessage.dateString = new Date().toLocaleDateString(undefined, { year: "numeric", month: "long", day: "numeric", }); - message_history.push(saved_message); - while (message_history.length > settings.maximum_messages) { - message_history.shift(); + messageHistory.push(savedMessage); + while (messageHistory.length > settings.maximum_messages) { + messageHistory.shift(); } - Settings.setValue("ArmoredChat-Messages", message_history); + Settings.setValue("ArmoredChat-Messages", messageHistory); } function fromQML(event) { - console.log(`New QML event:\n${JSON.stringify(event)}`); - switch (event.type) { case "send_message": _sendMessage(event.message, event.channel); @@ -149,7 +146,7 @@ switch (event.setting) { case "external_window": - chat_overlay_window.presentationMode = event.value + chatOverlayWindow.presentationMode = event.value ? Desktop.PresentationMode.NATIVE : Desktop.PresentationMode.VIRTUAL; break; @@ -171,7 +168,7 @@ break; case "initialized": // https://github.com/overte-org/overte/issues/824 - chat_overlay_window.visible = app_is_visible; // The "visible" field in the Desktop.createWindow does not seem to work. Force set it to the initial state (false) + chatOverlayWindow.visible = appIsVisible; // The "visible" field in the Desktop.createWindow does not seem to work. Force set it to the initial state (false) _loadSettings(); break; } @@ -181,7 +178,7 @@ case "16777220": // Enter key if (HMD.active) return; // Don't allow in VR - quick_message.sendToQml({ + quickMessage.sendToQml({ type: "change_visibility", value: true, }); @@ -201,28 +198,28 @@ }) ); } - function _avatarAction(type, session_id) { + function _avatarAction(type, sessionId) { Script.setTimeout(() => { if (type == "connected") { - pal_data = AvatarManager.getPalData().data; + palData = AvatarManager.getPalData().data; } // Get the display name of the user - let display_name = ""; - display_name = - AvatarManager.getPalData([session_id])?.data[0] + let displayName = ""; + displayName = + AvatarManager.getPalData([sessionId])?.data[0] ?.sessionDisplayName || null; - if (display_name == null) { - for (let i = 0; i < pal_data.length; i++) { - if (pal_data[i].sessionUUID == session_id) { - display_name = pal_data[i].sessionDisplayName; + if (displayName == null) { + for (let i = 0; i < palData.length; i++) { + if (palData[i].sessionUUID == sessionId) { + displayName = palData[i].sessionDisplayName; } } } // Format the packet let message = {}; - message.message = `${display_name} ${type}`; + message.message = `${displayName} ${type}`; _emitEvent({ type: "notification", ...message }); }, 1500); @@ -230,9 +227,9 @@ function _loadSettings() { settings = Settings.getValue("ArmoredChat-Config", settings); - if (message_history) { + if (messageHistory) { // Load message history - message_history.forEach((message) => { + messageHistory.forEach((message) => { delete message.action; _emitEvent({ type: "show_message", ...message }); }); @@ -240,8 +237,6 @@ // Send current settings to the app _emitEvent({ type: "initial_settings", settings: settings }); - - console.log(`\n\n\n` + JSON.stringify(settings)); } function _saveSettings() { console.log("Saving config"); @@ -254,6 +249,6 @@ * @param {("show_message"|"clear_messages"|"notification"|"initial_settings")} packet.type - The type of packet it is */ function _emitEvent(packet = { type: "" }) { - chat_overlay_window.sendToQml(packet); + chatOverlayWindow.sendToQml(packet); } })(); From 192d80aece5bc628a87afea2e0be456e9c1f7ba9 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Sat, 8 Jun 2024 11:51:25 -0500 Subject: [PATCH 25/31] Floofchat compatibility. Made conversion functions to allow communication between apps. Removed developer console.log function. Signed-off-by: Armored Dragon --- .../armored-chat/armored_chat.js | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/scripts/communityScripts/armored-chat/armored_chat.js b/scripts/communityScripts/armored-chat/armored_chat.js index 6525b836451..df8c3fb03f1 100644 --- a/scripts/communityScripts/armored-chat/armored_chat.js +++ b/scripts/communityScripts/armored-chat/armored_chat.js @@ -66,7 +66,6 @@ } function toggleMainChatWindow() { appIsVisible = !appIsVisible; - console.log(`App is now ${appIsVisible ? "visible" : "hidden"}`); appButton.editProperties({ isActive: appIsVisible }); chatOverlayWindow.visible = appIsVisible; @@ -97,6 +96,9 @@ if (channel !== "chat") return; message = JSON.parse(message); + // Floofchat compatibility hook + message = floofChatCompatibilityConversion(message); + message.channel = message.channel.toLowerCase(); // Make sure the "local", "domain", etc. is formatted consistently if (!channels.includes(message.channel)) return; // Check the channel @@ -197,6 +199,8 @@ action: "send_chat_message", }) ); + + floofChatCompatibilitySendMessage(message, channel); } function _avatarAction(type, sessionId) { Script.setTimeout(() => { @@ -251,4 +255,34 @@ function _emitEvent(packet = { type: "" }) { chatOverlayWindow.sendToQml(packet); } + + // + // Floofchat compatibility functions + // Added to ease the transition between Floofchat to ArmoredChat + // These functions can be safely removed at a much later date. + function floofChatCompatibilityConversion(message) { + if (message.type === "TransmitChatMessage" && !message.forApp) { + return { + position: message.position, + message: message.message, + displayName: message.displayName, + channel: message.channel.toLowerCase(), + }; + } + return message; + } + + function floofChatCompatibilitySendMessage(message, channel) { + Messages.sendMessage( + "Chat", + JSON.stringify({ + position: MyAvatar.position, + message: message, + displayName: MyAvatar.sessionDisplayName, + channel: channel.charAt(0).toUpperCase() + channel.slice(1), + type: "TransmitChatMessage", + forApp: "Floof", + }) + ); + } })(); From 7e100a18702192b3e6d958e0ee8c27fc719834e4 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Fri, 28 Jun 2024 10:42:39 -0500 Subject: [PATCH 26/31] Raise VR Keyboard when textfield is selected Signed-off-by: Armored Dragon --- scripts/communityScripts/armored-chat/armored_chat.qml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/communityScripts/armored-chat/armored_chat.qml b/scripts/communityScripts/armored-chat/armored_chat.qml index e153c2fec99..2f8670fc6d5 100644 --- a/scripts/communityScripts/armored-chat/armored_chat.qml +++ b/scripts/communityScripts/armored-chat/armored_chat.qml @@ -215,6 +215,10 @@ Rectangle { text = "" } } + onFocusChanged: { + if (focus) return ApplicationInterface.showVRKeyboardForHudUI(true); + ApplicationInterface.showVRKeyboardForHudUI(false); + } } Button { width: 60 From c6d2e567f6596315d9955f1d14f3a5b1587aa938 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Sat, 6 Jul 2024 10:26:42 -0500 Subject: [PATCH 27/31] Fix keyboard being invoked when not in VR. Signed-off-by: Armored Dragon --- scripts/communityScripts/armored-chat/armored_chat.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/communityScripts/armored-chat/armored_chat.qml b/scripts/communityScripts/armored-chat/armored_chat.qml index 2f8670fc6d5..8d43d92395f 100644 --- a/scripts/communityScripts/armored-chat/armored_chat.qml +++ b/scripts/communityScripts/armored-chat/armored_chat.qml @@ -216,6 +216,7 @@ Rectangle { } } onFocusChanged: { + if (!HMD.active) return; if (focus) return ApplicationInterface.showVRKeyboardForHudUI(true); ApplicationInterface.showVRKeyboardForHudUI(false); } From 6cf6e6c9ec945037690fd25667e6229b71d4845c Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Mon, 8 Jul 2024 11:21:04 -0500 Subject: [PATCH 28/31] Disallow all text formatting by default. Removed now redundant