add gs
This commit is contained in:
@@ -0,0 +1,475 @@
|
||||
--
|
||||
-- ChatGameCard
|
||||
--
|
||||
-- This is a game that is shown (and possibly pinned) at the top of a chat
|
||||
-- conversation.
|
||||
--
|
||||
|
||||
local CoreGui = game:GetService("CoreGui")
|
||||
|
||||
local Modules = CoreGui.RobloxGui.Modules
|
||||
local LuaApp = Modules.LuaApp
|
||||
local LuaChat = Modules.LuaChat
|
||||
|
||||
local Constants = require(LuaApp.Constants)
|
||||
local ContextualMenu = require(LuaApp.Components.ContextualMenu)
|
||||
local FriendCarousel = require(LuaChat.Components.FriendCarousel)
|
||||
local GetMultiplePlaceInfos = require(LuaChat.Actions.GetMultiplePlaceInfos)
|
||||
local GetPlaceThumbnail = require(LuaChat.Actions.GetPlaceThumbnail)
|
||||
local Roact = require(Modules.Common.Roact)
|
||||
local RoactRodux = require(Modules.Common.RoactRodux)
|
||||
local RoactAnalyticsGameCardLoaded = require(LuaChat.Services.RoactAnalyticsGameCardLoaded)
|
||||
local RoactServices = require(LuaApp.RoactServices)
|
||||
|
||||
local ChatGameCard = Roact.PureComponent:extend("ChatGameCard")
|
||||
|
||||
local FFlagLuaChatLoadGameLinkCardInChatAnalytics = settings():GetFFlag("LuaChatLoadGameLinkCardInChatAnalytics")
|
||||
|
||||
local ICON_SIZE_SMALL = 36
|
||||
local ICON_SIZE_LARGE = 60
|
||||
local ICON_SIZE_SMALL_AMENDED = 48
|
||||
|
||||
local CARD_MARGINS = 12
|
||||
local CARD_HEIGHT_SMALL = ICON_SIZE_SMALL + (CARD_MARGINS * 2)
|
||||
local CARD_HEIGHT_LARGE = ICON_SIZE_LARGE + (CARD_MARGINS * 2)
|
||||
|
||||
local GAME_TEXT_COLOR = Constants.Color.GRAY1
|
||||
local GAME_TEXT_FONT = Enum.Font.SourceSans
|
||||
local GAME_TEXT_HEIGHT = 25
|
||||
local GAME_TEXT_HEIGHT_WITH_SUBTITLE = 20
|
||||
local GAME_TEXT_SIZE = 23
|
||||
local GAME_TEXT_SIZE_WITH_SUBTITLE = 20
|
||||
|
||||
local SUBTITLE_TEXT_SIZE = 18
|
||||
local SUBTITLE_TEXT_COLOR = Constants.Color.GRAY2
|
||||
|
||||
local CAROUSEL_SMALL_ICON_SIZE = 24
|
||||
local CAROUSEL_LARGE_ICON_SIZE = 32
|
||||
local CAROUSEL_SMALL_GAP = 3
|
||||
local CAROUSEL_LARGE_GAP = 9
|
||||
local CAROUSEL_SMALL_DOT_SIZE = 8
|
||||
local CAROUSEL_LARGE_DOT_SIZE = 10
|
||||
|
||||
local ACTION_BUTTON_WIDTH = 60
|
||||
local ACTION_BUTTON_HEIGHT = 32
|
||||
local ACTION_COLOR_PLAY = Constants.Color.GREEN_PRIMARY
|
||||
local ACTION_COLOR_TEXT = Constants.Color.WHITE
|
||||
|
||||
local DEBUG_OUTLINE = 0
|
||||
local DEBUG_TRANSPARENCY = 1
|
||||
|
||||
local DEFAULT_GAME_ICON = "rbxasset://textures/ui/LuaApp/icons/ic-game.png"
|
||||
local FADEOUT_MASK_IMAGE = "rbxasset://textures/ui/LuaChat/graphic/friendmask.png"
|
||||
local FADEOUT_MASK_WIDTH = 10
|
||||
local GAME_MASK_IMAGE = "rbxasset://textures/ui/LuaChat/9-slice/gr-mask-game-icon.png"
|
||||
local ROUNDED_BUTTON = "rbxasset://textures/ui/LuaChat/9-slice/input-default.png"
|
||||
|
||||
-- Set up some default state for this control:
|
||||
function ChatGameCard:init()
|
||||
self.state = {
|
||||
isMenuOpen = false,
|
||||
}
|
||||
|
||||
-- Localize strings. Needs to be done in context because of the way the localization object is being passed to us:
|
||||
local localization = self.props.Localization
|
||||
self.MenuInfoPlayGame = {
|
||||
displayIcon = "rbxasset://textures/ui/LuaApp/icons/ic-games.png",
|
||||
name = "PlayGameButton",
|
||||
displayName = localization:Format("Feature.Chat.Drawer.PlayGame"),
|
||||
}
|
||||
self.MenuInfoPinGame = {
|
||||
displayIcon = "rbxasset://textures/ui/LuaChat/icons/ic-pin.png",
|
||||
name = "PinGameButton",
|
||||
displayName = localization:Format("Feature.Chat.Drawer.PinGame")
|
||||
}
|
||||
self.MenuInfoUnpinGame = {
|
||||
displayIcon = "rbxasset://textures/ui/LuaChat/icons/ic-unpin-20x20.png",
|
||||
name = "UnpinGameButton",
|
||||
displayName = localization:Format("Feature.Chat.Drawer.UnpinGame")
|
||||
}
|
||||
self.MenuItemInfoViewGameDetail = {
|
||||
displayIcon = "rbxasset://textures/ui/LuaChat/icons/ic-viewdetails-20x20.png",
|
||||
name = "ViewDetailsButton",
|
||||
displayName = localization:Format("Feature.Chat.Drawer.ViewDetails"),
|
||||
}
|
||||
end
|
||||
|
||||
function ChatGameCard:render()
|
||||
-- Information about the game is passed in as properties. Action to take
|
||||
-- *on* the game should also be passed in, so we have containment and this
|
||||
-- module only knows the miniumum necessary.
|
||||
|
||||
local parentLayoutOrder = self.props.LayoutOrder
|
||||
|
||||
-- Visual properties of this game card:
|
||||
local isPinnedGame = self.props.isPinnedGame or false
|
||||
local isRecommendedGame = self.props.isRecommended or false
|
||||
|
||||
local game = self.props.game
|
||||
local getPlaceInfo = self.props.getPlaceInfo
|
||||
local getPlaceThumbnail = self.props.getPlaceThumbnail
|
||||
local localization = self.props.Localization
|
||||
local onGamePin = self.props.onGamePin
|
||||
local onGameStart = self.props.onGameStart
|
||||
local onGameUnpin = self.props.onGameUnpin
|
||||
local onViewDetails = self.props.onViewDetails
|
||||
local placeInfos = self.props.placeInfos
|
||||
local placeThumbnails = self.props.placeThumbnails or {}
|
||||
local renderWidth = self.props.renderWidth or 0
|
||||
|
||||
-- Unpack from our properties:
|
||||
local gameFriends = game.friends or {}
|
||||
local placeId = game.placeId
|
||||
|
||||
-- Read or retrieve information about our place:
|
||||
local placeInfo = placeInfos[placeId]
|
||||
local gameName
|
||||
local imageToken = nil
|
||||
local universeId = nil
|
||||
local isPlayable = false
|
||||
if (placeInfo == nil) then
|
||||
getPlaceInfo(placeId)
|
||||
gameName = "(" .. localization:Format("Feature.Chat.Drawer.Loading") .. ")"
|
||||
else
|
||||
gameName = placeInfo.name
|
||||
imageToken = placeInfo.imageToken
|
||||
universeId = placeInfo.universeId
|
||||
isPlayable = placeInfo.isPlayable
|
||||
end
|
||||
|
||||
-- Configure some dimensions based on properties, the GameInfo section in
|
||||
-- particular changes for a large (pinned) vs regular size card:
|
||||
local cardHeight = CARD_HEIGHT_SMALL
|
||||
local carouselItemDotSize = CAROUSEL_SMALL_DOT_SIZE
|
||||
local carouselItemGap = CAROUSEL_SMALL_GAP
|
||||
local carouselItemHeight = CAROUSEL_SMALL_ICON_SIZE
|
||||
local friendAlignment = Enum.HorizontalAlignment.Right
|
||||
local gameIconHeight = ICON_SIZE_SMALL
|
||||
local gameIconWidth = gameIconHeight
|
||||
local gameTextHeight = GAME_TEXT_HEIGHT
|
||||
local gameTextSize = GAME_TEXT_SIZE
|
||||
local infoFillDirection = Enum.FillDirection.Horizontal
|
||||
local subtitle = ""
|
||||
local subtitleVisibility = false
|
||||
|
||||
-- If we don't have an action button, zero the reserved width:
|
||||
local buttonWidth = ACTION_BUTTON_WIDTH
|
||||
if not isPlayable then
|
||||
buttonWidth = 0
|
||||
end
|
||||
|
||||
-- Scaling of elements:
|
||||
local friendsWidthOffset = 0
|
||||
local friendsWidthScale = 1
|
||||
local textWidthOffset = 0
|
||||
local textWidthScale = 1
|
||||
|
||||
if isPinnedGame then
|
||||
cardHeight = CARD_HEIGHT_LARGE
|
||||
carouselItemDotSize = CAROUSEL_LARGE_DOT_SIZE
|
||||
carouselItemGap = CAROUSEL_LARGE_GAP
|
||||
carouselItemHeight = CAROUSEL_LARGE_ICON_SIZE
|
||||
friendAlignment = Enum.HorizontalAlignment.Left
|
||||
gameIconHeight = ICON_SIZE_LARGE
|
||||
gameIconWidth = gameIconHeight
|
||||
infoFillDirection = Enum.FillDirection.Vertical
|
||||
elseif isRecommendedGame then
|
||||
carouselItemHeight = 0
|
||||
gameTextHeight = GAME_TEXT_HEIGHT_WITH_SUBTITLE
|
||||
gameTextSize = GAME_TEXT_SIZE_WITH_SUBTITLE
|
||||
infoFillDirection = Enum.FillDirection.Vertical
|
||||
subtitle = localization:Format("Feature.Chat.Drawer.Recommended")
|
||||
subtitleVisibility = true
|
||||
else
|
||||
friendsWidthScale = 0.5
|
||||
friendsWidthOffset = 0
|
||||
textWidthScale = 0.5
|
||||
textWidthOffset = 0
|
||||
end
|
||||
|
||||
-- This is how much space we have in the center of the card:
|
||||
local centerNegativeSpace = CARD_MARGINS + gameIconWidth + CARD_MARGINS + CARD_MARGINS
|
||||
local countFriends = #gameFriends
|
||||
|
||||
-- Default is Play if nobody else is in the game, Join if we have friends:
|
||||
local actionText
|
||||
if isPlayable then
|
||||
centerNegativeSpace = centerNegativeSpace + buttonWidth + CARD_MARGINS
|
||||
local actionTextKey = "Feature.Chat.Drawer.Play"
|
||||
if countFriends > 0 then
|
||||
actionTextKey = "Feature.Chat.Drawer.Join"
|
||||
end
|
||||
actionText = localization:Format(actionTextKey)
|
||||
end
|
||||
local actionColor = ACTION_COLOR_PLAY
|
||||
local actionTextColor = ACTION_COLOR_TEXT
|
||||
|
||||
-- ...and as a last metrics step, adjust the ratio of the game name and friend carousel:
|
||||
if not (isPinnedGame or isRecommendedGame) then
|
||||
local actualSpace = renderWidth - centerNegativeSpace
|
||||
if actualSpace > 0 then
|
||||
local friendSpace = carouselItemHeight * countFriends
|
||||
local textAdjust = (actualSpace * 0.5) - (friendSpace + CARD_MARGINS)
|
||||
if textAdjust > 0 then
|
||||
textWidthOffset = textWidthOffset + textAdjust
|
||||
friendsWidthOffset = friendsWidthOffset - textAdjust
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Obtain the thumbnail for this game:
|
||||
local gameIcon = DEFAULT_GAME_ICON
|
||||
if placeInfo then
|
||||
local thumbnail = placeThumbnails[imageToken]
|
||||
if thumbnail == nil then
|
||||
if imageToken and imageToken ~= "" then
|
||||
if gameIconWidth == ICON_SIZE_SMALL then
|
||||
getPlaceThumbnail(imageToken, ICON_SIZE_SMALL_AMENDED, ICON_SIZE_SMALL_AMENDED)
|
||||
else
|
||||
getPlaceThumbnail(imageToken, gameIconWidth, gameIconHeight)
|
||||
end
|
||||
end
|
||||
elseif thumbnail.image ~= "" then
|
||||
gameIcon = thumbnail.image
|
||||
end
|
||||
end
|
||||
|
||||
-- Build up a horizontal list of items for our card:
|
||||
local cardItems = {}
|
||||
cardItems["Layout"] = Roact.createElement("UIListLayout", {
|
||||
FillDirection = Enum.FillDirection.Horizontal,
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Center,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||
Padding = UDim.new(0, CARD_MARGINS)
|
||||
})
|
||||
|
||||
-- Game icon:
|
||||
local layoutOrder = 1
|
||||
cardItems["GameIcon"] = Roact.createElement("ImageLabel", {
|
||||
BackgroundTransparency = DEBUG_TRANSPARENCY,
|
||||
BorderSizePixel = DEBUG_OUTLINE,
|
||||
LayoutOrder = layoutOrder,
|
||||
Size = UDim2.new(0, gameIconHeight, 0, gameIconHeight),
|
||||
Image = gameIcon,
|
||||
}, {
|
||||
Mask = Roact.createElement("ImageLabel", {
|
||||
BackgroundTransparency = 1,
|
||||
BorderSizePixel = 0,
|
||||
Image = GAME_MASK_IMAGE,
|
||||
ImageColor3 = Constants.Color.WHITE,
|
||||
ScaleType = Enum.ScaleType.Slice,
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
SliceCenter = Rect.new(3,3,4,4),
|
||||
}),
|
||||
})
|
||||
layoutOrder = layoutOrder + 1
|
||||
|
||||
cardItems["GameInfo"] = Roact.createElement("Frame", {
|
||||
BackgroundTransparency = DEBUG_TRANSPARENCY,
|
||||
BorderSizePixel = DEBUG_OUTLINE,
|
||||
ClipsDescendants = true,
|
||||
LayoutOrder = layoutOrder,
|
||||
Size = UDim2.new(1, -centerNegativeSpace, 1, 0),
|
||||
}, {
|
||||
Layout = Roact.createElement("UIListLayout", {
|
||||
FillDirection = infoFillDirection,
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Left,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||
}),
|
||||
|
||||
GameName = Roact.createElement("TextLabel", {
|
||||
BackgroundTransparency = DEBUG_TRANSPARENCY,
|
||||
BorderSizePixel = DEBUG_OUTLINE,
|
||||
ClipsDescendants = true,
|
||||
Font = GAME_TEXT_FONT,
|
||||
LayoutOrder = 1,
|
||||
Size = UDim2.new(textWidthScale, textWidthOffset, 0, gameTextHeight),
|
||||
Text = gameName,
|
||||
TextColor3 = GAME_TEXT_COLOR,
|
||||
TextSize = gameTextSize,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextYAlignment = Enum.TextYAlignment.Top
|
||||
},{
|
||||
MaskRight = Roact.createElement("ImageLabel", {
|
||||
BackgroundTransparency = 1,
|
||||
BorderSizePixel = 0,
|
||||
Image = FADEOUT_MASK_IMAGE,
|
||||
Position = UDim2.new(1, -FADEOUT_MASK_WIDTH, 0, 0),
|
||||
Size = UDim2.new(0, FADEOUT_MASK_WIDTH, 1, 0),
|
||||
ZIndex = 2,
|
||||
}),
|
||||
}),
|
||||
|
||||
Subtitle = Roact.createElement("TextLabel", {
|
||||
BackgroundTransparency = DEBUG_TRANSPARENCY,
|
||||
BorderSizePixel = DEBUG_OUTLINE,
|
||||
ClipsDescendants = true,
|
||||
Font = GAME_TEXT_FONT,
|
||||
LayoutOrder = 1,
|
||||
Size = UDim2.new(textWidthScale, textWidthOffset, 0, ICON_SIZE_SMALL - gameTextHeight),
|
||||
Text = subtitle,
|
||||
TextColor3 = SUBTITLE_TEXT_COLOR,
|
||||
TextSize = SUBTITLE_TEXT_SIZE,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
Visible = subtitleVisibility,
|
||||
}),
|
||||
|
||||
GameFriends = Roact.createElement(FriendCarousel, {
|
||||
dotSize = carouselItemDotSize,
|
||||
friends = gameFriends,
|
||||
HorizontalAlignment = friendAlignment,
|
||||
itemGap = carouselItemGap,
|
||||
itemSize = carouselItemHeight,
|
||||
LayoutOrder = 2,
|
||||
Size = UDim2.new(friendsWidthScale, friendsWidthOffset, 0, carouselItemHeight),
|
||||
}),
|
||||
})
|
||||
layoutOrder = layoutOrder + 1
|
||||
|
||||
if isPlayable then
|
||||
cardItems["ActionButton"] = Roact.createElement("ImageButton", {
|
||||
AutoButtonColor = false,
|
||||
BackgroundTransparency = 1,
|
||||
BorderSizePixel = DEBUG_OUTLINE,
|
||||
Image = ROUNDED_BUTTON,
|
||||
ImageColor3 = actionColor,
|
||||
LayoutOrder = layoutOrder,
|
||||
ScaleType = Enum.ScaleType.Slice,
|
||||
Size = UDim2.new(0, ACTION_BUTTON_WIDTH, 0, ACTION_BUTTON_HEIGHT),
|
||||
SliceCenter = Rect.new(3,3,4,4),
|
||||
|
||||
[Roact.Event.Activated] = function(rbx)
|
||||
onGameStart()
|
||||
end
|
||||
},{
|
||||
ActionLabel = Roact.createElement("TextLabel", {
|
||||
BackgroundTransparency = 1,
|
||||
BorderSizePixel = 0,
|
||||
Font = GAME_TEXT_FONT,
|
||||
LayoutOrder = layoutOrder,
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
Text = actionText,
|
||||
TextColor3 = actionTextColor,
|
||||
TextSize = GAME_TEXT_SIZE,
|
||||
}),
|
||||
})
|
||||
end
|
||||
|
||||
if self.state.isMenuOpen then
|
||||
local menuItems
|
||||
if isPinnedGame then
|
||||
menuItems = { self.MenuInfoUnpinGame }
|
||||
else
|
||||
menuItems = { self.MenuInfoPinGame }
|
||||
end
|
||||
|
||||
if isPlayable then
|
||||
table.insert(menuItems, 1, self.MenuInfoPlayGame)
|
||||
end
|
||||
table.insert(menuItems, self.MenuItemInfoViewGameDetail)
|
||||
|
||||
local callbackCancel = function()
|
||||
self:setState({ isMenuOpen = false })
|
||||
end
|
||||
|
||||
local callbackSelect = function(item)
|
||||
if item.name == self.MenuInfoPlayGame.name then
|
||||
onGameStart()
|
||||
elseif item.name == self.MenuInfoPinGame.name then
|
||||
onGamePin(universeId)
|
||||
elseif item.name == self.MenuInfoUnpinGame.name then
|
||||
onGameUnpin()
|
||||
elseif item.name == self.MenuItemInfoViewGameDetail.name then
|
||||
onViewDetails()
|
||||
end
|
||||
callbackCancel()
|
||||
end
|
||||
|
||||
cardItems["ContextMenu"] = Roact.createElement(ContextualMenu, {
|
||||
callbackCancel = callbackCancel,
|
||||
callbackSelect = callbackSelect,
|
||||
menuItems = menuItems,
|
||||
screenShape = self.state.screenShape,
|
||||
})
|
||||
end
|
||||
|
||||
-- Put a clickable wrapper around the entire card:
|
||||
return Roact.createElement("TextButton", {
|
||||
AutoButtonColor = false,
|
||||
BackgroundTransparency = DEBUG_TRANSPARENCY,
|
||||
BorderSizePixel = DEBUG_OUTLINE,
|
||||
LayoutOrder = parentLayoutOrder,
|
||||
Size = UDim2.new(1, 0, 0, cardHeight),
|
||||
Text = "",
|
||||
[Roact.Event.Activated] = function(rbx)
|
||||
-- TODO: Move this screen size functionality into a helper component
|
||||
-- so that it doesn't get repeated everywhere (see: MOBLUAPP-241).
|
||||
|
||||
-- We need to know the size of the screen, so we can position the
|
||||
-- popout component appropriately. So we climb up the object
|
||||
-- heirachy until we find the current ScreenGui:
|
||||
local screenWidth = 0
|
||||
local screenHeight = 0
|
||||
local screenGui = rbx:FindFirstAncestorOfClass("ScreenGui")
|
||||
if screenGui ~= nil then
|
||||
screenWidth = screenGui.AbsoluteSize.X
|
||||
screenHeight = screenGui.AbsoluteSize.Y
|
||||
end
|
||||
|
||||
self:setState({
|
||||
isMenuOpen = true,
|
||||
screenShape = {
|
||||
x = rbx.AbsolutePosition.X,
|
||||
y = rbx.AbsolutePosition.Y,
|
||||
width = rbx.AbsoluteSize.X,
|
||||
height = rbx.AbsoluteSize.Y,
|
||||
parentWidth = screenWidth,
|
||||
parentHeight = screenHeight,
|
||||
},
|
||||
})
|
||||
end,
|
||||
|
||||
}, cardItems)
|
||||
end
|
||||
|
||||
function ChatGameCard:didUpdate(previousProps, previousState)
|
||||
local conversationId = self.props.conversationId
|
||||
local isGameDrawerOpen = self.props.isGameDrawerOpen
|
||||
local placeId = self.props.game.placeId
|
||||
if FFlagLuaChatLoadGameLinkCardInChatAnalytics then
|
||||
local sendAnalytics = false
|
||||
if isGameDrawerOpen ~= previousProps.isGameDrawerOpen and self.state == previousState then
|
||||
sendAnalytics = true
|
||||
end
|
||||
if isGameDrawerOpen and sendAnalytics then
|
||||
self.props.analytics.reportGameCardLoadedInLuaChat(tostring(conversationId), tostring(placeId))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ChatGameCard = RoactRodux.UNSTABLE_connect2(
|
||||
function(state, props)
|
||||
return {
|
||||
placeInfos = state.ChatAppReducer.PlaceInfos,
|
||||
placeThumbnails = state.ChatAppReducer.PlaceThumbnails,
|
||||
}
|
||||
end,
|
||||
function(dispatch)
|
||||
return {
|
||||
getPlaceInfo = function(placeId)
|
||||
dispatch(GetMultiplePlaceInfos({placeId}))
|
||||
end,
|
||||
getPlaceThumbnail = function(imageToken, iconWidth, iconHeight)
|
||||
dispatch(GetPlaceThumbnail(imageToken, iconWidth, iconHeight))
|
||||
end,
|
||||
}
|
||||
end
|
||||
)(ChatGameCard)
|
||||
|
||||
ChatGameCard = RoactServices.connect({
|
||||
analytics = RoactAnalyticsGameCardLoaded,
|
||||
})(ChatGameCard)
|
||||
|
||||
return ChatGameCard
|
||||
Reference in New Issue
Block a user