mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-08-04 01:04:47 +02:00
fix up setting, url handling, images, notif sound
This commit is contained in:
parent
aa200c4e67
commit
601fe7fd3d
6 changed files with 159 additions and 108 deletions
|
@ -14,7 +14,8 @@
|
|||
var settings = {
|
||||
external_window: false,
|
||||
maximum_messages: 200,
|
||||
join_notification: true
|
||||
join_notification: true,
|
||||
use_chat_bubbles: true,
|
||||
};
|
||||
|
||||
// Global vars
|
||||
|
@ -27,7 +28,6 @@
|
|||
var maxLocalDistance = 20; // Maximum range for the local chat
|
||||
var palData = AvatarManager.getPalData().data;
|
||||
var isTyping = false;
|
||||
var useChatBubbles = false;
|
||||
|
||||
Controller.keyPressEvent.connect(keyPressEvent);
|
||||
Messages.subscribe("Chat"); // Floofchat
|
||||
|
@ -128,7 +128,7 @@
|
|||
_emitEvent({ type: "show_message", ...message });
|
||||
|
||||
// Show new message on screen
|
||||
if (message.channel !== "local" || !useChatBubbles) {
|
||||
if (message.channel !== "local" || !settings.use_chat_bubbles) {
|
||||
Messages.sendLocalMessage(
|
||||
"Floof-Notif",
|
||||
JSON.stringify({
|
||||
|
@ -167,7 +167,7 @@
|
|||
break;
|
||||
case "setting_change":
|
||||
if (event.setting === "worldspace_chat_bubbles") {
|
||||
useChatBubbles = event.value;
|
||||
settings.use_chat_bubbles = event.value;
|
||||
Messages.sendLocalMessage(
|
||||
"ChatBubbles-Config",
|
||||
JSON.stringify({
|
||||
|
@ -301,7 +301,7 @@
|
|||
settings = Settings.getValue("ArmoredChat-Config", settings);
|
||||
|
||||
const chatBubbleSettings = Settings.getValue("ChatBubbles-Config", { enabled: true });
|
||||
if (chatBubbleSettings.enabled) { useChatBubbles = true; }
|
||||
if (chatBubbleSettings.enabled) { settings.use_chat_bubbles = true; }
|
||||
|
||||
if (messageHistory) {
|
||||
// Load message history
|
||||
|
@ -314,13 +314,7 @@
|
|||
}
|
||||
|
||||
// Send current settings to the app
|
||||
_emitEvent({
|
||||
type: "initial_settings",
|
||||
settings: {
|
||||
worldspace_chat_bubbles: useChatBubbles,
|
||||
...settings
|
||||
}
|
||||
});
|
||||
_emitEvent({ type: "initial_settings", settings: settings });
|
||||
}
|
||||
function _saveSettings() {
|
||||
console.log("Saving config");
|
||||
|
|
|
@ -641,7 +641,7 @@ Rectangle {
|
|||
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.join_notification) s_join_notification.checked = true;
|
||||
if (message.settings.worldspace_chat_bubbles) s_chat_bubbles.checked = true;
|
||||
if (message.settings.use_chat_bubbles) s_chat_bubbles.checked = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,6 +43,14 @@ Item {
|
|||
Keys.onLeftPressed: { moveLeft(); }
|
||||
Keys.onRightPressed: { moveRight(); }
|
||||
|
||||
onTextChanged: {
|
||||
if (text === "") {
|
||||
toScript({type: "action", action: "end_typing"});
|
||||
} else {
|
||||
toScript({type: "action", action: "start_typing"});
|
||||
}
|
||||
}
|
||||
|
||||
function moveLeft(){
|
||||
if (cursorPosition > 0){
|
||||
cursorPosition--
|
||||
|
@ -119,4 +127,4 @@ Item {
|
|||
function toScript(packet){
|
||||
sendToScript(packet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
BIN
scripts/communityScripts/chatBubbles/assets/notify.wav
Normal file
BIN
scripts/communityScripts/chatBubbles/assets/notify.wav
Normal file
Binary file not shown.
|
@ -24,8 +24,10 @@ const BUBBLE_WIDTH_MAX_CHARS = 24; // roughly 18 ems per meter
|
|||
const MAX_DISTANCE = 20;
|
||||
const SELF_BUBBLES = false;
|
||||
|
||||
const NOTIFY_SOUND = SoundCache.getSound(Script.resolvePath("./assets/notify.wav"));
|
||||
|
||||
let settings = {
|
||||
enabled: true,
|
||||
enabled: true,
|
||||
};
|
||||
|
||||
let currentBubbles = {};
|
||||
|
@ -101,38 +103,72 @@ function ChatBubbles_SpawnBubble(data, senderID) {
|
|||
const scale = AvatarList.getAvatar(senderID).scale;
|
||||
|
||||
let link;
|
||||
let linkIsImage = false;
|
||||
|
||||
try {
|
||||
const maybeURL = data.message.trim();
|
||||
// only handles cases where the whole message is just a URL,
|
||||
// text with a URL in the middle is ignored
|
||||
const maybeURL = data.message.trim();
|
||||
|
||||
if (maybeURL.startsWith("https://") || maybeURL.startsWith("http://")) {
|
||||
link = maybeURL;
|
||||
if (
|
||||
(maybeURL.startsWith("https://") || maybeURL.startsWith("http://")) &&
|
||||
!/\s+/g.test(maybeURL) &&
|
||||
/[A-Za-z0-9-._~:/?#\[\]@!$&'()*+,;%=]+/g.test(maybeURL)
|
||||
) {
|
||||
link = maybeURL;
|
||||
|
||||
const chunkBeforeQuery = maybeURL.split("?", 2)[0];
|
||||
|
||||
if (
|
||||
chunkBeforeQuery.endsWith(".jpg") ||
|
||||
chunkBeforeQuery.endsWith(".png") ||
|
||||
chunkBeforeQuery.endsWith(".gif") ||
|
||||
chunkBeforeQuery.endsWith(".svg") ||
|
||||
chunkBeforeQuery.endsWith(".webp")
|
||||
) {
|
||||
linkIsImage = true;
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
const [text, lineCount] = ChatBubbles_WrapText(data.message);
|
||||
const height = lineCount * BUBBLE_LINE_HEIGHT;
|
||||
let height = lineCount * BUBBLE_LINE_HEIGHT;
|
||||
|
||||
const bubbleEntity = Entities.addEntity({
|
||||
type: "Text",
|
||||
parentID: senderID,
|
||||
text: text,
|
||||
unlit: true,
|
||||
ignorePickIntersection: (link === undefined),
|
||||
lineHeight: BUBBLE_LINE_HEIGHT,
|
||||
dimensions: [BUBBLE_WIDTH, height + 0.04, 0.01],
|
||||
localPosition: [0, scale + (height / 2) + 0.1, 0],
|
||||
backgroundAlpha: 0.5,
|
||||
textColor: (link === undefined) ? [255, 255, 255] : [128, 240, 255],
|
||||
textEffect: "outline fill",
|
||||
textEffectColor: "#000",
|
||||
textEffectThickness: 0.4,
|
||||
canCastShadow: false,
|
||||
billboardMode: "yaw",
|
||||
alignment: "center",
|
||||
verticalAlignment: "center",
|
||||
grab: {grabbable: false},
|
||||
script: (link === undefined) ? undefined :
|
||||
let bubbleEntity;
|
||||
if (link !== undefined && linkIsImage) {
|
||||
height = BUBBLE_WIDTH / 3;
|
||||
bubbleEntity = Entities.addEntity({
|
||||
type: "Image",
|
||||
parentID: senderID,
|
||||
imageURL: link,
|
||||
emissive: true,
|
||||
keepAspectRatio: true,
|
||||
ignorePickIntersection: true,
|
||||
dimensions: [BUBBLE_WIDTH, height, 0.01],
|
||||
localPosition: [0, scale + (height / 2) + 0.1, 0],
|
||||
canCastShadow: false,
|
||||
billboardMode: "yaw",
|
||||
grab: {grabbable: false},
|
||||
}, "local");
|
||||
} else {
|
||||
bubbleEntity = Entities.addEntity({
|
||||
type: "Text",
|
||||
parentID: senderID,
|
||||
text: text,
|
||||
unlit: true,
|
||||
ignorePickIntersection: (link === undefined),
|
||||
lineHeight: BUBBLE_LINE_HEIGHT,
|
||||
dimensions: [BUBBLE_WIDTH, height + 0.04, 0.01],
|
||||
localPosition: [0, scale + (height / 2) + 0.1, 0],
|
||||
backgroundAlpha: 0.5,
|
||||
textColor: (link === undefined) ? [255, 255, 255] : [128, 240, 255],
|
||||
textEffect: "outline fill",
|
||||
textEffectColor: "#000",
|
||||
textEffectThickness: 0.4,
|
||||
canCastShadow: false,
|
||||
billboardMode: "yaw",
|
||||
alignment: "center",
|
||||
verticalAlignment: "center",
|
||||
grab: {grabbable: false},
|
||||
script: (link === undefined && !linkIsImage) ? undefined :
|
||||
`(function() {
|
||||
this.mousePressOnEntity = function(entity, event) {
|
||||
if (event.isPrimaryButton) {
|
||||
|
@ -141,7 +177,8 @@ function ChatBubbles_SpawnBubble(data, senderID) {
|
|||
}
|
||||
};
|
||||
})`
|
||||
}, "local");
|
||||
}, "local");
|
||||
}
|
||||
|
||||
for (const bubble of Object.values(currentBubbles[senderID])) {
|
||||
let { localPosition } = Entities.getEntityProperties(bubble.entity, "localPosition");
|
||||
|
@ -152,12 +189,16 @@ function ChatBubbles_SpawnBubble(data, senderID) {
|
|||
let bubbleIndex = Uuid.generate();
|
||||
|
||||
let bubble = {
|
||||
entity: bubbleEntity,
|
||||
timeout: Script.setTimeout(() => {
|
||||
entity: bubbleEntity,
|
||||
timeout: Script.setTimeout(() => {
|
||||
let fade = 1.0;
|
||||
|
||||
const fadeInterval = Script.setInterval(() => {
|
||||
Entities.editEntity(bubble.entity, { textAlpha: fade, backgroundAlpha: fade * 0.5 });
|
||||
if (linkIsImage) {
|
||||
Entities.editEntity(bubble.entity, { alpha: fade });
|
||||
} else {
|
||||
Entities.editEntity(bubble.entity, { textAlpha: fade, backgroundAlpha: fade * 0.5 });
|
||||
}
|
||||
fade -= (1 / BUBBLE_ANIM_FPS) / BUBBLE_FADE_TIME;
|
||||
}, 1000 / BUBBLE_ANIM_FPS);
|
||||
|
||||
|
@ -166,67 +207,73 @@ function ChatBubbles_SpawnBubble(data, senderID) {
|
|||
Entities.deleteEntity(bubble.entity);
|
||||
delete currentBubbles[senderID][bubbleIndex];
|
||||
}, BUBBLE_FADE_TIME * 1000);
|
||||
}, BUBBLE_LIFETIME_SECS * 1000),
|
||||
};
|
||||
}, BUBBLE_LIFETIME_SECS * 1000),
|
||||
};
|
||||
|
||||
currentBubbles[senderID][bubbleIndex] = bubble;
|
||||
currentBubbles[senderID][bubbleIndex] = bubble;
|
||||
|
||||
Audio.playSound(NOTIFY_SOUND, {
|
||||
position: data.position,
|
||||
volume: 0.25,
|
||||
localOnly: true,
|
||||
});
|
||||
}
|
||||
|
||||
function ChatBubbles_IndicatorTick(senderID) {
|
||||
const data = typingIndicators[senderID];
|
||||
const data = typingIndicators[senderID];
|
||||
|
||||
const lowColor = [128, 192, 192];
|
||||
const hiColor = [255, 255, 255];
|
||||
const lowColor = [128, 192, 192];
|
||||
const hiColor = [255, 255, 255];
|
||||
|
||||
let colorFade = 0.5 + (Math.cos(data.age / 5) * 0.5);
|
||||
let colorFade = 0.5 + (Math.cos(data.age / 5) * 0.5);
|
||||
|
||||
Entities.editEntity(data.entity, {textColor: Vec3.mix(lowColor, hiColor, colorFade)});
|
||||
Entities.editEntity(data.entity, {textColor: Vec3.mix(lowColor, hiColor, colorFade)});
|
||||
|
||||
data.age += 1;
|
||||
data.age += 1;
|
||||
}
|
||||
|
||||
function ChatBubbles_ShowTypingIndicator(senderID) {
|
||||
if (typingIndicators[senderID]) { return; }
|
||||
if (typingIndicators[senderID]) { return; }
|
||||
|
||||
const scale = AvatarList.getAvatar(senderID).scale;
|
||||
|
||||
const indicatorEntity = Entities.addEntity({
|
||||
type: "Text",
|
||||
parentID: senderID,
|
||||
text: "•••",
|
||||
unlit: true,
|
||||
lineHeight: 0.15,
|
||||
dimensions: [0.18, 0.08, 0.01],
|
||||
localPosition: [0, scale, 0],
|
||||
backgroundAlpha: 0.8,
|
||||
canCastShadow: false,
|
||||
billboardMode: "full",
|
||||
alignment: "center",
|
||||
verticalAlignment: "center",
|
||||
textEffect: "outline fill",
|
||||
textEffectColor: "#000",
|
||||
textEffectThickness: 0.3,
|
||||
topMargin: -0.06,
|
||||
const indicatorEntity = Entities.addEntity({
|
||||
type: "Text",
|
||||
parentID: senderID,
|
||||
text: "•••",
|
||||
unlit: true,
|
||||
lineHeight: 0.15,
|
||||
dimensions: [0.18, 0.08, 0.01],
|
||||
localPosition: [0, scale, 0],
|
||||
backgroundAlpha: 0.8,
|
||||
canCastShadow: false,
|
||||
billboardMode: "full",
|
||||
alignment: "center",
|
||||
verticalAlignment: "center",
|
||||
textEffect: "outline fill",
|
||||
textEffectColor: "#000",
|
||||
textEffectThickness: 0.3,
|
||||
topMargin: -0.06,
|
||||
grab: {grabbable: false},
|
||||
}, "local");
|
||||
}, "local");
|
||||
|
||||
const indicatorInterval = Script.setInterval(() => ChatBubbles_IndicatorTick(senderID), 1000 / BUBBLE_ANIM_FPS);
|
||||
const indicatorInterval = Script.setInterval(() => ChatBubbles_IndicatorTick(senderID), 1000 / BUBBLE_ANIM_FPS);
|
||||
|
||||
typingIndicators[senderID] = {
|
||||
entity: indicatorEntity,
|
||||
interval: indicatorInterval,
|
||||
age: 0,
|
||||
};
|
||||
typingIndicators[senderID] = {
|
||||
entity: indicatorEntity,
|
||||
interval: indicatorInterval,
|
||||
age: 0,
|
||||
};
|
||||
}
|
||||
|
||||
function ChatBubbles_HideTypingIndicator(senderID) {
|
||||
const data = typingIndicators[senderID];
|
||||
const data = typingIndicators[senderID];
|
||||
|
||||
if (!data) { return; }
|
||||
if (!data) { return; }
|
||||
|
||||
Entities.deleteEntity(data.entity);
|
||||
Script.clearInterval(data.interval);
|
||||
delete typingIndicators[senderID];
|
||||
Entities.deleteEntity(data.entity);
|
||||
Script.clearInterval(data.interval);
|
||||
delete typingIndicators[senderID];
|
||||
}
|
||||
|
||||
function ChatBubbles_RecvMsg(channel, msg, senderID, localOnly) {
|
||||
|
@ -243,6 +290,8 @@ function ChatBubbles_RecvMsg(channel, msg, senderID, localOnly) {
|
|||
for (const [key, value] of Object.entries(data)) {
|
||||
settings[key] = value;
|
||||
}
|
||||
|
||||
Settings.setValue("ChatBubbles-Config", settings);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -252,43 +301,43 @@ function ChatBubbles_RecvMsg(channel, msg, senderID, localOnly) {
|
|||
// don't spawn bubbles for MyAvatar if the setting is disabled
|
||||
if (!SELF_BUBBLES && (senderID === MyAvatar.sessionUUID || !MyAvatar.sessionUUID)) { return; }
|
||||
|
||||
let data;
|
||||
try {
|
||||
data = JSON.parse(msg);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return;
|
||||
}
|
||||
let data;
|
||||
try {
|
||||
data = JSON.parse(msg);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (channel === TYPING_NOTIFICATION_CHANNEL) {
|
||||
if (data.action === "typing_start") {
|
||||
if (channel === TYPING_NOTIFICATION_CHANNEL) {
|
||||
if (data.action === "typing_start") {
|
||||
// don't spawn a bubble if they're too far away
|
||||
if (Vec3.distance(MyAvatar.position, data.position) > MAX_DISTANCE) { return; }
|
||||
ChatBubbles_ShowTypingIndicator(senderID);
|
||||
} else if (data.action === "typing_stop") {
|
||||
ChatBubbles_HideTypingIndicator(senderID);
|
||||
}
|
||||
} else if (data.action === "send_chat_message" && settings.enabled) {
|
||||
ChatBubbles_ShowTypingIndicator(senderID);
|
||||
} else if (data.action === "typing_stop") {
|
||||
ChatBubbles_HideTypingIndicator(senderID);
|
||||
}
|
||||
} else if (data.action === "send_chat_message" && settings.enabled) {
|
||||
// don't spawn a bubble if they're too far away
|
||||
if (data.channel !== "local") { return; }
|
||||
if (Vec3.distance(MyAvatar.position, data.position) > MAX_DISTANCE) { return; }
|
||||
ChatBubbles_SpawnBubble(data, senderID);
|
||||
ChatBubbles_SpawnBubble(data, senderID);
|
||||
}
|
||||
}
|
||||
|
||||
function ChatBubbles_DeleteAll() {
|
||||
for (const [_, bubbleList] of Object.entries(currentBubbles)) {
|
||||
for (const [_, bubbleList] of Object.entries(currentBubbles)) {
|
||||
for (const [id, bubble] of Object.entries(bubbleList)) {
|
||||
Entities.deleteEntity(bubble.entity);
|
||||
Script.clearTimeout(bubble.timeout);
|
||||
delete bubbleList[id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const [_, indicator] of Object.entries(typingIndicators)) {
|
||||
Entities.deleteEntity(indicator.entity);
|
||||
Script.clearInterval(indicator.interval);
|
||||
}
|
||||
for (const [_, indicator] of Object.entries(typingIndicators)) {
|
||||
Entities.deleteEntity(indicator.entity);
|
||||
Script.clearInterval(indicator.interval);
|
||||
}
|
||||
|
||||
currentBubbles = {};
|
||||
typingIndicators = {};
|
||||
|
@ -326,8 +375,8 @@ Messages.messageReceived.connect(ChatBubbles_RecvMsg);
|
|||
Messages.subscribe(TYPING_NOTIFICATION_CHANNEL);
|
||||
|
||||
Script.scriptEnding.connect(() => {
|
||||
Settings.setValue("ChatBubbles-Config", settings);
|
||||
Messages.messageReceived.disconnect(ChatBubbles_RecvMsg);
|
||||
Settings.setValue("ChatBubbles-Config", settings);
|
||||
Messages.messageReceived.disconnect(ChatBubbles_RecvMsg);
|
||||
Messages.unsubscribe(TYPING_NOTIFICATION_CHANNEL);
|
||||
ChatBubbles_DeleteAll();
|
||||
});
|
|
@ -47,7 +47,7 @@ var DEFAULT_SCRIPTS_SEPARATE = [
|
|||
"simplifiedUI/ui/simplifiedNametag/simplifiedNametag.js",
|
||||
{"stable": "system/more/app-more.js", "beta": "https://more.overte.org/more/app-more.js"},
|
||||
"communityScripts/armored-chat/armored_chat.js",
|
||||
"communityScripts/chatBubbles.js",
|
||||
"communityScripts/chatBubbles/chatBubbles.js",
|
||||
//"system/chat.js"
|
||||
];
|
||||
|
||||
|
|
Loading…
Reference in a new issue