323 lines
8.7 KiB
Lua
323 lines
8.7 KiB
Lua
local Players = game:GetService("Players")
|
|
local GuiService = game:GetService("GuiService")
|
|
|
|
local LuaChat = script.Parent.Parent
|
|
local UserThumbnail = require(script.Parent.UserThumbnail)
|
|
local TypingIndicator = require(script.Parent.TypingIndicator)
|
|
|
|
local UserChatBubble = require(script.Parent.UserChatBubble)
|
|
local AssetCard = require(script.Parent.AssetCard)
|
|
|
|
local Create = require(LuaChat.Create)
|
|
local Constants = require(LuaChat.Constants)
|
|
local WebApi = require(LuaChat.WebApi)
|
|
|
|
local Modules = game:GetService("CoreGui").RobloxGui.Modules
|
|
local LuaApp = Modules.LuaApp
|
|
local NotificationType = require(LuaApp.Enum.NotificationType)
|
|
|
|
local FFlagLuaChatToSplitRbxConnections = settings():GetFFlag("LuaChatToSplitRbxConnections")
|
|
|
|
local RECEIVED_BUBBLE = "rbxasset://textures/ui/LuaChat/9-slice/chat-bubble2.png"
|
|
local RECEIVED_BUBBLE_WITH_TAIL = "rbxasset://textures/ui/LuaChat/9-slice/chat-bubble.png"
|
|
local RECEIVED_TAIL = "rbxasset://textures/ui/LuaChat/9-slice/chat-bubble-tip.png"
|
|
|
|
local SENT_BUBBLE = "rbxasset://textures/ui/LuaChat/9-slice/chat-bubble-self2.png"
|
|
local SENT_BUBBLE_WITH_TAIL = "rbxasset://textures/ui/LuaChat/9-slice/chat-bubble-self.png"
|
|
local SENT_TAIL = "rbxasset://textures/ui/LuaChat/9-slice/chat-bubble-self-tip.png"
|
|
|
|
local SENT_BUBBLE_OUTLINE = "rbxasset://textures/ui/LuaChat/9-slice/chat-bubble2.png"
|
|
local SENT_BUBBLE_OUTLINE_WITH_TAIL = "rbxasset://textures/ui/LuaChat/9-slice/chat-bubble-right.png"
|
|
local SENT_OUTLINE_TAIL = "rbxasset://textures/ui/LuaChat/9-slice/chat-bubble-tip-right.png"
|
|
|
|
local FFlagLuaChatInfiniteRelayoutRecursionFix = settings():GetFFlag("LuaChatInfiniteRelayoutRecursionFix")
|
|
|
|
local function isOutgoingMessage(message)
|
|
local localUserId = tostring(Players.LocalPlayer.UserId)
|
|
return message.senderTargetId == localUserId
|
|
end
|
|
|
|
local function isMessageSending(conversation, message)
|
|
if conversation and conversation.sendingMessages then
|
|
return conversation.sendingMessages:Get(message.id) ~= nil
|
|
end
|
|
return false
|
|
end
|
|
|
|
local PROTOCOL_IDENTIFIERS = {
|
|
"https?://", ""
|
|
}
|
|
|
|
local RESOURCE_NAMES = {
|
|
"www%.", "web%.", ""
|
|
}
|
|
|
|
local WHITELISTED_DOMAINS = {
|
|
"roblox", "sitetest%d%.robloxlabs", "gametest%d%.robloxlabs"
|
|
}
|
|
|
|
local MESSAGE_CONTENT_PATTERNS = {
|
|
GAME_LINK = "%.com/games[^%d]*(%d+)/?",
|
|
}
|
|
|
|
local ChatBubble = {}
|
|
|
|
ChatBubble.__index = ChatBubble
|
|
|
|
ChatBubble.BubbleType = {
|
|
AssetCard = "AssetCard",
|
|
ChatBubble = "UserChatBubble",
|
|
}
|
|
|
|
local function getBubbleImages(message, bubbleType)
|
|
if isOutgoingMessage(message) and bubbleType ~= ChatBubble.BubbleType.AssetCard then
|
|
return SENT_BUBBLE, SENT_BUBBLE_WITH_TAIL, SENT_TAIL
|
|
elseif isOutgoingMessage(message) then
|
|
return SENT_BUBBLE_OUTLINE, SENT_BUBBLE_OUTLINE_WITH_TAIL, SENT_OUTLINE_TAIL
|
|
else
|
|
return RECEIVED_BUBBLE, RECEIVED_BUBBLE_WITH_TAIL, RECEIVED_TAIL
|
|
end
|
|
end
|
|
|
|
function ChatBubble.new(appState, message, width)
|
|
width = width or 0
|
|
|
|
local self = {}
|
|
setmetatable(self, ChatBubble)
|
|
|
|
local conversationId = message.conversationId
|
|
local isSending = isMessageSending(appState.store:getState().ChatAppReducer.Conversations[conversationId], message)
|
|
|
|
if FFlagLuaChatInfiniteRelayoutRecursionFix then
|
|
self.width = width
|
|
end
|
|
self.appState = appState
|
|
self.message = message
|
|
self.bubbles = {}
|
|
self.connections = {}
|
|
if FFlagLuaChatToSplitRbxConnections then
|
|
self.rbx_connections = {}
|
|
end
|
|
self.tailVisible = false
|
|
|
|
self.rbx = Create.new "Frame" {
|
|
Name = "ChatContainer",
|
|
BackgroundTransparency = 1,
|
|
|
|
Size = UDim2.new(1, 0, 0, 0),
|
|
|
|
Create.new "UIListLayout" {
|
|
SortOrder = Enum.SortOrder.LayoutOrder,
|
|
},
|
|
}
|
|
|
|
if message.moderated or isSending then
|
|
self:AddBubble(UserChatBubble.new(appState, message, nil, self.width), 1)
|
|
|
|
-- Specifically whitelist strings with .com/games in the url
|
|
elseif message.content:lower():match(MESSAGE_CONTENT_PATTERNS.GAME_LINK) then
|
|
local text = self:FilterForLinks()
|
|
|
|
-- Flush remaining text if it is not empty
|
|
if text:gsub("%s+","") ~= "" then
|
|
self:AddBubble(UserChatBubble.new(appState, message, text, self.width))
|
|
end
|
|
else
|
|
self:AddBubble(UserChatBubble.new(appState, message, nil, self.width), 1)
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
function ChatBubble:FilterForLinks()
|
|
local text = self.message.content
|
|
for _, protocol in pairs(PROTOCOL_IDENTIFIERS) do
|
|
for _, resource in pairs(RESOURCE_NAMES) do
|
|
for _, domain in pairs(WHITELISTED_DOMAINS) do
|
|
|
|
local constructedUrlPattern = protocol .. resource .. domain .. MESSAGE_CONTENT_PATTERNS.GAME_LINK
|
|
for assetId in text:lower():gmatch(constructedUrlPattern) do
|
|
local linkStart, endLink = text:lower():find("[^%s*]*" .. constructedUrlPattern .. "[^%s*]*")
|
|
if linkStart then
|
|
local textBefore = text:sub(1, linkStart - 1)
|
|
|
|
if textBefore:gsub("%s+","") ~= "" then
|
|
self:AddBubble(UserChatBubble.new(self.appState, self.message, textBefore, self.width))
|
|
end
|
|
|
|
self:AddBubble(AssetCard.new(self.appState, self.message, assetId))
|
|
|
|
text = text:sub(endLink + 1)
|
|
else
|
|
return text
|
|
end
|
|
end
|
|
|
|
end
|
|
end
|
|
end
|
|
|
|
return text
|
|
end
|
|
|
|
function ChatBubble:AddBubble(bubble, placement)
|
|
table.insert(self.bubbles, placement or #self.bubbles+1 ,bubble)
|
|
bubble.rbx.Parent = self.rbx
|
|
bubble.LayoutOrder = placement or #self.bubbles
|
|
|
|
for i=1,#self.bubbles do
|
|
self.bubbles[i].LayoutOrder = i
|
|
end
|
|
|
|
local connection = bubble.rbx:GetPropertyChangedSignal("AbsoluteSize"):Connect(function()
|
|
self:Resize()
|
|
end)
|
|
if FFlagLuaChatToSplitRbxConnections then
|
|
table.insert(self.rbx_connections, connection)
|
|
else
|
|
table.insert(self.connections, connection)
|
|
end
|
|
|
|
self:Update()
|
|
end
|
|
|
|
function ChatBubble:SetUsernameVisible(value)
|
|
local bubblePos = self.bubbles[1].bubble.Position
|
|
|
|
if value then
|
|
self.bubbles[1].usernameLabel.Visible = true
|
|
|
|
self.bubbles[1].bubble.Position = UDim2.new(
|
|
bubblePos.X.Scale,
|
|
bubblePos.X.Offset,
|
|
0,
|
|
16
|
|
)
|
|
else
|
|
self.bubbles[1].usernameLabel.Visible = false
|
|
|
|
self.bubbles[1].bubble.Position = UDim2.new(
|
|
bubblePos.X.Scale,
|
|
bubblePos.X.Offset,
|
|
0,
|
|
0
|
|
)
|
|
end
|
|
|
|
self:Resize()
|
|
end
|
|
|
|
function ChatBubble:SetTypingIndicatorVisible(value)
|
|
if value and not self.indicator then
|
|
local indicator = TypingIndicator.new(self.appState, .4)
|
|
indicator.rbx.AnchorPoint = Vector2.new(0,0.5)
|
|
indicator.rbx.Position = UDim2.new(0, self.bubbles[1].usernameLabel.TextBounds.X + 3, 0.5, 0)
|
|
indicator.rbx.Parent = self.bubbles[1].usernameLabel
|
|
|
|
self.indicator = indicator
|
|
elseif self.indicator and not value then
|
|
self.indicator:Destroy()
|
|
self.indicator = nil
|
|
end
|
|
end
|
|
|
|
function ChatBubble:SetThumbnailVisible(value)
|
|
if value then
|
|
self.thumbnail = UserThumbnail.new(self.appState, self.message.senderTargetId, true)
|
|
self.thumbnail.rbx.Position = UDim2.new(0, 10, 0, 0)
|
|
self.thumbnail.rbx.Overlay.ImageColor3 = Constants.Color.GRAY6
|
|
self.thumbnail.rbx.Parent = self.bubbles[1].bubbleContainer
|
|
|
|
self.thumbnail.clicked:connect(function()
|
|
local user = self.appState.store:getState().Users[self.message.senderTargetId]
|
|
local userId = user and user.id
|
|
if userId then
|
|
GuiService:BroadcastNotification(WebApi.MakeUserProfileUrl(userId),
|
|
NotificationType.VIEW_PROFILE)
|
|
end
|
|
end)
|
|
else
|
|
if self.thumbnail then
|
|
self.thumbnail:Destruct()
|
|
end
|
|
end
|
|
end
|
|
|
|
function ChatBubble:SetTailVisible(value)
|
|
self.tailVisible = value
|
|
if not self.bubbles[1] then return end
|
|
|
|
for i, bubble in pairs(self.bubbles) do
|
|
local bubbleImage, bubbleWithTail, tailImage = getBubbleImages(self.message, bubble.bubbleType)
|
|
if value and i == 1 then
|
|
bubble.bubble.Image = bubbleWithTail
|
|
bubble.tail.Image = tailImage
|
|
bubble.tail.Visible = true
|
|
else
|
|
bubble.bubble.Image = bubbleImage
|
|
bubble.tail.Visible = false
|
|
end
|
|
end
|
|
end
|
|
|
|
function ChatBubble:SetPaddingObject(object)
|
|
if not self.bubbles[1] then return end
|
|
|
|
if self.bubbles[1].paddingObject then
|
|
self.bubbles[1].paddingObject:Destroy()
|
|
end
|
|
|
|
object.LayoutOrder = 1
|
|
object.Parent = self.bubbles[1].rbx
|
|
self.bubbles[1].paddingObject = object
|
|
self.bubbles[1]:Resize()
|
|
end
|
|
|
|
function ChatBubble:Resize()
|
|
if not FFlagLuaChatInfiniteRelayoutRecursionFix then
|
|
for _,bubble in pairs(self.bubbles) do
|
|
bubble:Resize()
|
|
end
|
|
end
|
|
|
|
local height = 0
|
|
for _, child in ipairs(self.rbx:GetChildren()) do
|
|
if child:IsA("GuiObject") then
|
|
height = height + child.AbsoluteSize.Y
|
|
end
|
|
end
|
|
|
|
self.rbx.Size = UDim2.new(1, 0, 0, height)
|
|
end
|
|
|
|
|
|
function ChatBubble:Update()
|
|
self:SetTailVisible(self.tailVisible)
|
|
self:Resize()
|
|
end
|
|
|
|
function ChatBubble:Destruct()
|
|
for _, connection in ipairs(self.connections) do
|
|
connection:disconnect()
|
|
end
|
|
self.connections = {}
|
|
if FFlagLuaChatToSplitRbxConnections then
|
|
for _, connection in ipairs(self.rbx_connections) do
|
|
connection:Disconnect()
|
|
end
|
|
self.rbx_connections = {}
|
|
end
|
|
|
|
for _, bubble in ipairs(self.bubbles) do
|
|
bubble:Destruct()
|
|
end
|
|
|
|
if self.thumbnail then
|
|
self.thumbnail:Destruct()
|
|
end
|
|
self.thumbnail = nil
|
|
|
|
self.rbx:Destroy()
|
|
end
|
|
|
|
return ChatBubble |