Initial Commit.

Floof Chat moved to script archive.
Fixed local message distance check.

Signed-off-by: Armored Dragon <publicmail@armoreddragon.com>
This commit is contained in:
Armored Dragon 2024-05-17 15:59:23 -05:00
parent 8661e8a858
commit 002fdf0b42
No known key found for this signature in database
GPG key ID: C7207ACC3382AD8B
22 changed files with 647 additions and 2 deletions

View file

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

View file

@ -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);
}
})();

View file

@ -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)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 778 B

View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="32"
viewBox="0 -960 760 640"
width="38"
version="1.1"
id="svg4"
sodipodi:docname="send.svg"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
inkscape:export-filename="send_black.png"
inkscape:export-xdpi="300"
inkscape:export-ydpi="300"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
showgrid="false"
inkscape:zoom="21.395833"
inkscape:cx="17.363194"
inkscape:cy="16.031159"
inkscape:window-width="2560"
inkscape:window-height="1366"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<path
d="m 0,-320 v -640 l 760,320 z M 60,-413 604,-640 60,-870 v 168 l 242,62 -242,60 z m 0,0 v -457 z"
id="path2"
style="fill:#000000" />
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View file

@ -7,7 +7,7 @@
//
// Copyright 2014 High Fidelity, Inc.
// Copyright 2020 Vircadia contributors.
// Copyright 2022 Overte e.V.
// 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
@ -47,7 +47,7 @@ var DEFAULT_SCRIPTS_SEPARATE = [
"communityScripts/notificationCore/notificationCore.js",
"simplifiedUI/ui/simplifiedNametag/simplifiedNametag.js",
{"stable": "system/more/app-more.js", "beta": "https://more.overte.org/more/app-more.js"},
{"stable": "communityScripts/chat/FloofChat.js", "beta": "https://content.fluffy.ws/scripts/chat/FloofChat.js"}
"communityScripts/armored-chat/armored_chat.js",
//"system/chat.js"
];