This commit is contained in:
lx
2026-07-06 14:01:11 -04:00
commit c4f97d729d
16468 changed files with 935321 additions and 0 deletions
@@ -0,0 +1,9 @@
{
"lint": {
"SameLineStatement": "fatal",
"LocalShadow": "fatal",
"LocalUnused": "fatal",
"FunctionUnused": "fatal",
"ImportUnused": "fatal"
}
}
@@ -0,0 +1,5 @@
{
"lint": {
"ImplicitReturn": "fatal"
}
}
@@ -0,0 +1,6 @@
local CorePackages = game:GetService("CorePackages")
local Action = require(CorePackages.AppTempCommon.Common.Action)
return Action(script.Name, function()
return {}
end)
@@ -0,0 +1,8 @@
local CorePackages = game:GetService("CorePackages")
local Action = require(CorePackages.AppTempCommon.Common.Action)
return Action(script.Name, function(assets)
return {
assets = assets,
}
end)
@@ -0,0 +1,8 @@
local CorePackages = game:GetService("CorePackages")
local Action = require(CorePackages.AppTempCommon.Common.Action)
return Action(script.Name, function(bundles)
return {
bundles = bundles,
}
end)
@@ -0,0 +1,9 @@
local CorePackages = game:GetService("CorePackages")
local Action = require(CorePackages.AppTempCommon.Common.Action)
return Action(script.Name, function(assetId, bundleIds)
return {
assetId = assetId,
bundleIds = bundleIds,
}
end)
@@ -0,0 +1,9 @@
local CorePackages = game:GetService("CorePackages")
local Action = require(CorePackages.AppTempCommon.Common.Action)
return Action(script.Name, function(viewingDetails, assetId)
return {
viewingDetails = viewingDetails,
assetId = assetId,
}
end)
@@ -0,0 +1,8 @@
local CorePackages = game:GetService("CorePackages")
local Action = require(CorePackages.AppTempCommon.Common.Action)
return Action(script.Name, function(equippedAssets)
return {
equippedAssets = equippedAssets,
}
end)
@@ -0,0 +1,9 @@
local CorePackages = game:GetService("CorePackages")
local Action = require(CorePackages.AppTempCommon.Common.Action)
return Action(script.Name, function(id, isFavorite)
return {
id = id,
isFavorite = isFavorite,
}
end)
@@ -0,0 +1,9 @@
local CorePackages = game:GetService("CorePackages")
local Action = require(CorePackages.AppTempCommon.Common.Action)
return Action(script.Name, function(id, isFavorite)
return {
id = id,
isFavorite = isFavorite,
}
end)
@@ -0,0 +1,7 @@
local CorePackages = game:GetService("CorePackages")
local Action = require(CorePackages.AppTempCommon.Common.Action)
return Action(script.Name, function(gamepadEnabled)
return {
gamepadEnabled = gamepadEnabled,
}end)
@@ -0,0 +1,9 @@
local CorePackages = game:GetService("CorePackages")
local Action = require(CorePackages.AppTempCommon.Common.Action)
return Action(script.Name, function(itemId, itemType)
return {
itemId = itemId,
itemType = itemType,
}
end)
@@ -0,0 +1,8 @@
local CorePackages = game:GetService("CorePackages")
local Action = require(CorePackages.AppTempCommon.Common.Action)
return Action(script.Name, function(locale)
return {
locale = locale,
}
end)
@@ -0,0 +1,8 @@
local CorePackages = game:GetService("CorePackages")
local Action = require(CorePackages.AppTempCommon.Common.Action)
return Action(script.Name, function(playerId)
return {
playerId = playerId,
}
end)
@@ -0,0 +1,8 @@
local CorePackages = game:GetService("CorePackages")
local Action = require(CorePackages.AppTempCommon.Common.Action)
return Action(script.Name, function(playerName)
return {
playerName = playerName,
}
end)
@@ -0,0 +1,10 @@
local CorePackages = game:GetService("CorePackages")
local Action = require(CorePackages.AppTempCommon.Common.Action)
return Action(script.Name, function(tryingOn, assetId, assetTypeId)
return {
tryingOn = tryingOn,
assetId = assetId,
assetTypeId = assetTypeId,
}
end)
@@ -0,0 +1,8 @@
local CorePackages = game:GetService("CorePackages")
local Action = require(CorePackages.AppTempCommon.Common.Action)
return Action(script.Name, function(view)
return {
view = view,
}
end)
@@ -0,0 +1,6 @@
local CorePackages = game:GetService("CorePackages")
local Action = require(CorePackages.AppTempCommon.Common.Action)
return Action(script.Name, function()
return {}
end)
@@ -0,0 +1,6 @@
local CorePackages = game:GetService("CorePackages")
local Action = require(CorePackages.AppTempCommon.Common.Action)
return Action(script.Name, function()
return {}
end)
@@ -0,0 +1,34 @@
local CorePackages = game:GetService("CorePackages")
local Color = require(CorePackages.AppTempCommon.Common.Color)
local Colors = {
Obsidian = Color.Color3FromHex(0x18191B),
Carbon = Color.Color3FromHex(0x1F2123),
Slate = Color.Color3FromHex(0x232527),
Flint = Color.Color3FromHex(0x393B3D),
Graphite = Color.Color3FromHex(0x656668),
Pumice = Color.Color3FromHex(0xBDBEBE),
Black = Color3.fromRGB(0, 0, 0),
White = Color3.fromRGB(255, 255, 255),
Gray1 = Color.Color3FromHex(0x191919),
Gray2 = Color.Color3FromHex(0x757575),
Gray4 = Color.Color3FromHex(0xE3E3E3),
GrayBackground = Color.Color3FromHex(0xD8D8D8),
Green = Color.Color3FromHex(0x00B06F),
DisabledGreen = Color.Color3FromHex(0x9CC4A4),
}
setmetatable(Colors,
{
__newindex = function(t, key, index)
end,
__index = function(t, index)
error("Colors table has no value: " .. tostring(index))
end
}
)
return Colors
@@ -0,0 +1,46 @@
local Colors = require(script.Parent.Colors)
local CompactView = {
ContainerBackgroundTransparency = 0,
ContainerBackgroundColor = Colors.Carbon,
UseContainerAspectRatio = false,
ContainerAspectRatio = nil,
MaxAssetCardsPerRow = 3,
ContainerSize = UDim2.new(1, 0, 1, -36),
ContainerAnchorPoint = Vector2.new(0.5, 0),
ContainerPosition = UDim2.new(0.5, 0, 0, 36),
AvatarHeadShotSize = 48,
TopSizeY = 90,
ClipsDescendants = false,
PlayerViewportSize = UDim2.new(1, 0, 0.41, 0),
AssetListSize = UDim2.new(1, 0, 0.59, -42),
AssetListPosition = UDim2.new(0, 0, 0.41, 42),
CloseButtonPosition = UDim2.new(0, 6, 0, -20),
DetailsThumbnailFrameSize = UDim2.new(1, 0, 0.4, 0),
DetailsThumbnailFramePosition = UDim2.new(0, 0, 0, 15),
DetailsThumbnailAnchorPoint = Vector2.new(0.5, 0.5),
DetailsThumbnailPosition = UDim2.new(0.5, 0, 0.5, 0),
DetailsThumbnailARDominantAxis = Enum.DominantAxis.Height,
DetailsFramePosition = UDim2.new(0, 15, 0.4, 15),
DetailsFrameSize = UDim2.new(1, -30, 0.6, -30),
AssetCardMaxSizeX = 108,
AssetCardMaxSizeY = 157,
DefaultCameraOffset = CFrame.new(0, 2, -5),
ToolOffset = CFrame.new(0, 2, -8),
TryOnPosition = UDim2.new(0, 0, 0, 15),
TryOnSize = UDim2.new(1, 0, 0.4, 0),
BorderPaddingSize = 20,
AssetTextMaxSize = 14,
}
setmetatable(CompactView,
{
__newindex = function(t, key, index)
end,
__index = function(t, index)
error("CompactView table has no value: " .. tostring(index))
end
}
)
return CompactView
@@ -0,0 +1,112 @@
local CorePackages = game:GetService("CorePackages")
local InspectAndBuyFolder = script.Parent.Parent
local Roact = require(CorePackages.Roact)
local RoactRodux = require(CorePackages.RoactRodux)
local Colors = require(InspectAndBuyFolder.Colors)
local SetDetailsInformation = require(InspectAndBuyFolder.Actions.SetDetailsInformation)
local Url = require(CorePackages.AppTempCommon.LuaApp.Http.Url)
local getSelectionImageObjectRegular = require(InspectAndBuyFolder.getSelectionImageObjectRegular)
local GetFFlagUseThumbnailUrl = require(game:GetService("CoreGui").RobloxGui.Modules.Common.Flags.GetFFlagUseThumbnailUrl)
local AssetCard = Roact.Component:extend("AssetCard")
function AssetCard:init()
self.selectedImage = getSelectionImageObjectRegular()
end
function AssetCard:render()
local view = self.props.view
local viewMapping = self._context[view]
local equipped = self.props.equipped
local assetInfo = self.props.assetInfo
local openDetails = self.props.openDetails
local assetCardSizeX = self.props.assetCardSizeX
return Roact.createElement("ImageButton", {
BackgroundTransparency = 0,
BorderSizePixel = 0,
BackgroundColor3 = Colors.Gray2,
Selectable = true,
SelectionImageObject = self.selectedImage,
[Roact.Event.Activated] = function()
openDetails(assetInfo.assetId)
end,
}, {
AssetThumbnail = Roact.createElement("ImageButton", {
Size = UDim2.new(1, 0, 0, assetCardSizeX),
BackgroundTransparency = 0,
BorderSizePixel = 0,
BackgroundColor3 = Colors.Graphite,
Selectable = false,
AutoButtonColor = false,
Image = GetFFlagUseThumbnailUrl() and "rbxthumb://type=Asset&id=" .. assetInfo.assetId .. "&w=150&h=150" or Url.BASE_URL_SECURE .."/asset-thumbnail/image?assetId=" ..assetInfo.assetId
.."&width=150&height=150&format=png",
[Roact.Event.Activated] = function()
openDetails(assetInfo.assetId)
end,
}),
AssetNameBackground = Roact.createElement("Frame", {
Size = UDim2.new(1, 0, 1, -assetCardSizeX),
Position = UDim2.new(0, 0, 0, assetCardSizeX),
BorderSizePixel = 0,
BackgroundColor3 = Color3.fromRGB(44, 45, 47),
}, {
AssetName = Roact.createElement("TextLabel", {
BackgroundTransparency = 1,
Size = UDim2.new(1, -10, 1, -10),
TextTruncate = Enum.TextTruncate.AtEnd,
Position = UDim2.new(0, 5, 0, 5),
TextWrapped = true,
Text = assetInfo.name or "",
TextXAlignment = Enum.TextXAlignment.Left,
TextYAlignment = Enum.TextYAlignment.Center,
TextSize = 12,
TextScaled = true,
Font = Enum.Font.Gotham,
TextColor3 = Color3.new(1, 1, 1),
}, {
UITextSizeConstraint = Roact.createElement("UITextSizeConstraint", {
MaxTextSize = viewMapping.AssetTextMaxSize,
})
}),
}),
EquippedFrame = Roact.createElement("ImageLabel", {
Size = UDim2.new(1, 0, 1, 0),
BackgroundTransparency = 1,
ZIndex = 2,
Visible = equipped,
Image = "rbxasset://textures/ui/InspectMenu/gr-item-selector.png",
ImageColor3 = Colors.Green,
ScaleType = Enum.ScaleType.Slice,
SliceCenter = Rect.new(2.5, 2.5, 2.5, 2.5),
}),
Corner = Roact.createElement("ImageLabel", {
Position = UDim2.new(1, -25, 0, 0),
Size = UDim2.new(0, 25, 0, 25),
BackgroundTransparency = 1,
ZIndex = 2,
Visible = equipped,
Image = "rbxasset://textures/ui/InspectMenu/gr-item-selector-triangle.png",
ImageColor3 = Colors.Green,
})
})
end
return RoactRodux.UNSTABLE_connect2(
function(state, props)
local assetId = props.assetInfo.assetId
return {
view = state.view,
equipped = state.equippedAssets[assetId] == true,
}
end,
function(dispatch)
return {
openDetails = function(assetId)
dispatch(SetDetailsInformation(true, assetId))
end,
}
end
)(AssetCard)
@@ -0,0 +1,26 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local InspectAndBuyFolder = script.Parent.Parent
local TestContainer = require(InspectAndBuyFolder.Test.TestContainer)
local AssetCard = require(script.Parent.AssetCard)
local assetInfoProp = {
assetId = "1",
name = "mock",
}
it("should create and destroy without errors", function()
local element = Roact.createElement(TestContainer, nil, {
Roact.createElement(AssetCard, {
assetInfo = assetInfoProp,
assetCardSizeX = 10,
})
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,142 @@
local CorePackages = game:GetService("CorePackages")
local InspectAndBuyFolder = script.Parent.Parent
local Roact = require(CorePackages.Roact)
local RoactRodux = require(CorePackages.RoactRodux)
local DetailsText = require(InspectAndBuyFolder.Components.DetailsText)
local DetailsThumbnail = require(InspectAndBuyFolder.Components.DetailsThumbnail)
local DetailsDescription = require(InspectAndBuyFolder.Components.DetailsDescription)
local DetailsButtons = require(InspectAndBuyFolder.Components.DetailsButtons)
local TryOnViewport = require(InspectAndBuyFolder.Components.TryOnViewport)
local GetAssetBundles = require(InspectAndBuyFolder.Thunks.GetAssetBundles)
local ReportOpenDetailsPage = require(InspectAndBuyFolder.Thunks.ReportOpenDetailsPage)
local GetEconomyProductInfo = require(InspectAndBuyFolder.Thunks.GetEconomyProductInfo)
local Colors = require(InspectAndBuyFolder.Colors)
local UtilityFunctions = require(InspectAndBuyFolder.UtilityFunctions)
local AssetDetails = Roact.PureComponent:extend("AssetDetails")
function AssetDetails:setScrollingEnabled(enabled)
local scrollingEnabled = self.state.scrollingEnabled
if scrollingEnabled ~= enabled then
self:setState({
scrollingEnabled = enabled
})
end
end
function AssetDetails:init()
self.state = {
scrollingEnabled = true,
}
end
function AssetDetails:willUpdate(nextProps)
if self.props.assetInfo ~= nextProps.assetInfo and nextProps.assetInfo then
local getAssetBundles = self.props.getAssetBundles
if not nextProps.assetInfo.bundlesAssetIsIn then
getAssetBundles(nextProps.assetInfo.assetId)
end
end
end
function AssetDetails:didUpdate(prevProps)
local reportOpenDetailsPage = self.props.reportOpenDetailsPage
local assetInfo = self.props.assetInfo
local prevAssetInfo = prevProps.assetInfo
if assetInfo ~= prevAssetInfo and assetInfo and assetInfo.bundlesAssetIsIn then
local getEconomyProductInfo = self.props.getEconomyProductInfo
local bundles = self.props.bundles
local isBundle = assetInfo.bundlesAssetIsIn and #assetInfo.bundlesAssetIsIn == 1
local bundleId = isBundle and UtilityFunctions.getBundleId(assetInfo)
local productId = isBundle and bundles[bundleId].productId or assetInfo.productId
if (not isBundle and assetInfo.owned == nil) or (isBundle and bundles[bundleId].owned == nil) then
getEconomyProductInfo(productId, isBundle, bundleId)
end
end
--[[
We need to report when the item details page was opened, but we need to know if it's showing a bundle
or asset. So we have to check if we've gotten that data yet or not.
]]
local startedViewingDetails = self.props.detailsInformation.viewingDetails
and not prevProps.detailsInformation.viewingDetails
local obtainedBundlesInfo = prevAssetInfo and not prevAssetInfo.bundlesAssetIsIn
and assetInfo and assetInfo.bundlesAssetIsIn
if ((assetInfo and assetInfo.bundlesAssetIsIn and startedViewingDetails) or obtainedBundlesInfo) then
reportOpenDetailsPage(assetInfo)
end
end
function AssetDetails:render()
local view = self.props.view
local viewMapping = self._context[view]
local detailsInformation = self.props.detailsInformation
local assetInfo = self.props.assetInfo or {}
local localPlayerModel = self.props.localPlayerModel
local scrollingEnabled = self.state.scrollingEnabled
return Roact.createElement("Frame", {
Position = UDim2.new(0, viewMapping.BorderPaddingSize, 0, 0),
Size = UDim2.new(1, -(2 * viewMapping.BorderPaddingSize), 1, 0),
BackgroundTransparency = 0,
BackgroundColor3 = Colors.Carbon,
BorderSizePixel = 0,
-- Do not show asset information until we know if a bundle should be shown instead.
Visible = detailsInformation.viewingDetails and assetInfo.bundlesAssetIsIn ~= nil,
}, {
DetailsButtons = Roact.createElement(DetailsButtons),
Container = Roact.createElement("ScrollingFrame", {
BackgroundTransparency = 1,
ScrollBarThickness = 0,
Size = UDim2.new(1, 0, 1, -55),
ScrollingEnabled = scrollingEnabled,
}, {
UIListLayout = Roact.createElement("UIListLayout", {
SortOrder = Enum.SortOrder.LayoutOrder,
Padding = UDim.new(0, 10),
}),
DetailsThumbnail = Roact.createElement(DetailsThumbnail),
TryOnViewport = localPlayerModel and Roact.createElement(TryOnViewport, {
localPlayerModel = localPlayerModel,
setScrollingEnabled = function(enabled)
self:setScrollingEnabled(enabled)
end,
}),
DetailsText = Roact.createElement(DetailsText),
DetailsDescription = Roact.createElement(DetailsDescription),
})
})
end
return RoactRodux.UNSTABLE_connect2(
function(state, props)
local assetId = state.detailsInformation.assetId
return {
view = state.view,
detailsInformation = state.detailsInformation,
assetInfo = state.assets[assetId],
bundles = state.bundles,
}
end,
function(dispatch)
return {
getAssetBundles = function(assetId)
dispatch(GetAssetBundles(assetId))
end,
getEconomyProductInfo = function(productId, isBundle, bundleId)
dispatch(GetEconomyProductInfo(productId, isBundle, bundleId))
end,
reportOpenDetailsPage = function(assetInfo)
dispatch(ReportOpenDetailsPage(assetInfo))
end,
}
end
)(AssetDetails)
@@ -0,0 +1,22 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local InspectAndBuyFolder = script.Parent.Parent
local TestContainer = require(InspectAndBuyFolder.Test.TestContainer)
local AssetDetails = require(script.Parent.AssetDetails)
local mockModel = require(InspectAndBuyFolder.Test.getMockModel)()
it("should create and destroy without errors", function()
local element = Roact.createElement(TestContainer, nil, {
Roact.createElement(AssetDetails, {
localPlayerModel = mockModel,
})
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,133 @@
local CorePackages = game:GetService("CorePackages")
local GuiService = game:GetService("GuiService")
local InspectAndBuyFolder = script.Parent.Parent
local Roact = require(CorePackages.Roact)
local RoactRodux = require(CorePackages.RoactRodux)
local AssetCard = require(InspectAndBuyFolder.Components.AssetCard)
local AssetList = Roact.PureComponent:extend("AssetList")
local CARD_PADDING = 10
local FRAME_PADDING = 15
local ASSET_CARD_RATIO = 0.68
function AssetList:calculateCanvasSize(numCardsPerRow, numAssetCards, cardSizeY)
local rbx = self.gridFrameRef.current
local view = self.props.view
local viewMapping = self._context[view]
if rbx then
local positionDifference = viewMapping.TopSizeY + FRAME_PADDING
local canvasSize = math.ceil(numAssetCards / numCardsPerRow) * (cardSizeY + CARD_PADDING)
+ positionDifference
rbx.Parent.CanvasSize = UDim2.new(1, 0, 0, canvasSize)
end
end
function AssetList:init()
local view = self.props.view
local viewMapping = self._context[view]
self.gridFrameRef = Roact.createRef()
self.state = {
assetCardSizeX = viewMapping.AssetCardMaxSizeX,
assetCardSizeY = viewMapping.AssetCardMaxSizeY,
}
end
function AssetList:render()
local view = self.props.view
local viewMapping = self._context[view]
local assets = self.props.assets
local numCardsPerRow = viewMapping.MaxAssetCardsPerRow
local assetCardSizeX = self.state.assetCardSizeX
local assetCardSizeY = self.state.assetCardSizeY
local assetCards = {}
local numAssets = 0
assetCards["UIGridLayout"] = Roact.createElement("UIGridLayout", {
CellPadding = UDim2.new(0, CARD_PADDING, 0, CARD_PADDING),
CellSize = UDim2.new(0, assetCardSizeX, 0, assetCardSizeY),
SortOrder = Enum.SortOrder.LayoutOrder,
HorizontalAlignment = Enum.HorizontalAlignment.Left,
VerticalAlignment = Enum.VerticalAlignment.Top,
FillDirectionMaxCells = numCardsPerRow,
})
for _, assetInfo in pairs(assets) do
numAssets = numAssets + 1
assetCards[assetInfo.assetId] = Roact.createElement(AssetCard, {
assetInfo = assetInfo,
assetCardSizeX = assetCardSizeX,
})
end
self:calculateCanvasSize(numCardsPerRow, numAssets, assetCardSizeY)
return Roact.createElement("Frame", {
Size = UDim2.new(1, -(2 * viewMapping.BorderPaddingSize), 1, 0),
BackgroundTransparency = 1,
BorderSizePixel = 0,
LayoutOrder = 3,
[Roact.Ref] = self.gridFrameRef,
[Roact.Change.AbsoluteSize] = function(rbx)
spawn(function()
self:resize()
end)
end,
}, assetCards)
end
function AssetList:didMount()
self:resize()
self.mounted = true
end
function AssetList:didUpdate(prevProps)
if self.props.view ~= prevProps.view then
self:resize()
end
if self.mounted and self.props.gamepadEnabled and not self.props.detailsInformation.viewingDetails
and self.props.visible then
GuiService.SelectedCoreObject = self.gridFrameRef.current:FindFirstChildWhichIsA("GuiObject")
end
end
function AssetList:willUnmount()
self.mounted = false
end
function AssetList:resize()
local rbx = self.gridFrameRef.current
local view = self.props.view
local viewMapping = self._context[view]
if rbx then
local numCardsPerRow = viewMapping.MaxAssetCardsPerRow
local width = math.floor((rbx.AbsoluteSize.X - CARD_PADDING *
(numCardsPerRow - 1)) / numCardsPerRow)
local assetCardSizeX = math.min(width, viewMapping.AssetCardMaxSizeX)
local assetCardSizeY = math.min(width / ASSET_CARD_RATIO, viewMapping.AssetCardMaxSizeY)
if self.state.assetCardSizeX ~= assetCardSizeX or self.state.assetCardSizeY ~= assetCardSizeY then
self:setState({
assetCardSizeX = assetCardSizeX,
assetCardSizeY = assetCardSizeY,
})
end
end
end
return RoactRodux.UNSTABLE_connect2(
function(state, props)
return {
view = state.view,
visible = state.visible,
assets = state.assets,
detailsInformation = state.detailsInformation,
gamepadEnabled = state.gamepadEnabled,
}
end
)(AssetList)
@@ -0,0 +1,20 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local InspectAndBuyFolder = script.Parent.Parent
local TestContainer = require(InspectAndBuyFolder.Test.TestContainer)
local AssetList = require(script.Parent.AssetList)
it("should create and destroy without errors", function()
local element = Roact.createElement(TestContainer, nil, {
Roact.createElement("ScrollingFrame", {}, {
Roact.createElement(AssetList, {})
})
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,55 @@
local CorePackages = game:GetService("CorePackages")
local CoreGui = game:GetService("CoreGui")
local InspectAndBuyFolder = script.Parent.Parent
local Roact = require(CorePackages.Roact)
local RoactRodux = require(CorePackages.RoactRodux)
local Colors = require(InspectAndBuyFolder.Colors)
local AvatarHeadShot = Roact.PureComponent:extend("AvatarHeadShot")
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
local CoreGuiModules = RobloxGui:WaitForChild("Modules")
local GetFFlagUseThumbnailUrl = require(CoreGuiModules.Common.Flags.GetFFlagUseThumbnailUrl)
local LegacyThumbnailUrls = require(CoreGuiModules.Common.LegacyThumbnailUrls)
local HEADSHOT_THUMBNAIL_SIZE = 48
local HEAD_SHOT_URL = LegacyThumbnailUrls.Headshot
function AvatarHeadShot:render()
local playerId = self.props.playerId
local headshotUrl = nil
if playerId and playerId ~= "" then
headshotUrl = HEAD_SHOT_URL:format(HEADSHOT_THUMBNAIL_SIZE, HEADSHOT_THUMBNAIL_SIZE, playerId)
if GetFFlagUseThumbnailUrl() then
headshotUrl = HEAD_SHOT_URL:format(playerId, HEADSHOT_THUMBNAIL_SIZE, HEADSHOT_THUMBNAIL_SIZE)
end
end
return Roact.createElement("ImageLabel", {
Size = UDim2.new(0, 48, 0, 48),
BackgroundColor3 = Colors.GrayBackground,
BorderSizePixel = 0,
Image = headshotUrl,
LayoutOrder = 1,
}, {
MaskFrame = Roact.createElement("ImageLabel", {
Size = UDim2.new(1, 0, 1, 0),
BackgroundTransparency = 1,
BorderSizePixel = 0,
Image = "rbxasset://textures/ui/LuaApp/graphic/gr-avatar mask-90x90.png",
ImageColor3 = Colors.Carbon,
}),
})
end
return RoactRodux.UNSTABLE_connect2(
function(state, props)
return {
playerId = state.playerId,
}
end
)(AvatarHeadShot)
@@ -0,0 +1,18 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local InspectAndBuyFolder = script.Parent.Parent
local TestContainer = require(InspectAndBuyFolder.Test.TestContainer)
local AvatarHeadShot = require(script.Parent.AvatarHeadShot)
it("should create and destroy without errors", function()
local element = Roact.createElement(TestContainer, nil, {
Roact.createElement(AvatarHeadShot, {})
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,240 @@
local CorePackages = game:GetService("CorePackages")
local ContextActionService = game:GetService("ContextActionService")
local UserInputService = game:GetService('UserInputService')
local RunService = game:GetService('RunService')
local InspectAndBuyFolder = script.Parent.Parent
local Roact = require(CorePackages.Roact)
local RoactRodux = require(CorePackages.RoactRodux)
local Colors = require(InspectAndBuyFolder.Colors)
local rebuildJoints = require(InspectAndBuyFolder.rebuildJoints)
local CHARACTER_ROTATION_SPEED = .0065
local STICK_ROTATION_MULTIPLIER = 3
local THUMBSTICK_DEADZONE = 0.2
local AUTO_ROTATE_WAIT_TIME = 2
local AUTO_SPIN_STEP = math.rad(0.70)
local AvatarViewport = Roact.PureComponent:extend("AvatarViewport")
function AvatarViewport:init()
self.viewportFrameRef = Roact.createRef()
self.connections = {}
self.xRotation = 0
self.yRotation = 0
self.rotationDeltaX = 0
self.rotationDeltaY = 0
self.mouseOrTouchDown = false
self.delta = 0
self.keys = {}
self.lastPosition = Vector3.new(0, 0, 0)
self.viewportCamera = Instance.new("Camera")
self.viewportCamera.CameraType = Enum.CameraType.Scriptable
self.model = self.props.model
self.lastInputTime = AUTO_ROTATE_WAIT_TIME
end
function AvatarViewport:didMount()
if self.props.humanoidDescription and self.model then
self.viewportCamera.Parent = self.viewportFrameRef.current
self.viewportFrameRef.current.CurrentCamera = self.viewportCamera
self:setRotation()
rebuildJoints(self.model)
self.model.Parent = self.viewportFrameRef.current
end
self.isMounted = true
self:handleSpin()
self:setUpGamepad()
end
function AvatarViewport:didUpdate(prevProps, prevState)
if self.props.visible and self.props.humanoidDescription ~= prevProps.humanoidDescription and self.model then
coroutine.wrap(function()
self.model.Parent = self.viewportFrameRef.current
self.model.Humanoid:ApplyDescriptionClientServer(self.props.humanoidDescription)
rebuildJoints(self.model)
end)()
end
self.gamepadRotating = false
self.viewportCamera.Parent = self.viewportFrameRef.current
self.viewportFrameRef.current.CurrentCamera = self.viewportCamera
if self.props.gamepadEnabled ~= prevProps.gamepadEnabled then
if self.props.gamepadEnabled then
self:setUpGamepad()
else
self:unbindActions()
end
end
end
-- Note: Because the camera is the object moving, the character will always be upright.
function AvatarViewport:setRotation()
local currentCharacter = self.model
local view = self.props.view
local viewMapping = self._context[view]
if currentCharacter then
local hrp = currentCharacter.HumanoidRootPart
local offset = viewMapping.DefaultCameraOffset
if self.model:FindFirstChildOfClass("Tool") then
offset = viewMapping.ToolOffset
end
local cameraPosition = (
CFrame.new(hrp.Position)
* CFrame.Angles(0, -self.yRotation, 0)
* CFrame.Angles(self.xRotation, 0, 0)
* offset
).p
self.viewportCamera.CFrame = CFrame.new(cameraPosition, hrp.Position)
end
end
function AvatarViewport:render()
local size = self.props.size
local position = self.props.position
local visible = self.props.visible
local backgroundTransparency = self.props.backgroundTransparency
local setScrollingEnabled = self.props.setScrollingEnabled
return Roact.createElement("ViewportFrame", {
AnchorPoint = Vector2.new(0.5, 0.5),
Position = position,
Size = size,
BackgroundTransparency = backgroundTransparency,
BackgroundColor3 = Colors.Graphite,
BorderSizePixel = 0,
Active = true,
Visible = visible,
[Roact.Ref] = self.viewportFrameRef,
[Roact.Event.InputBegan] = function(rbx, input)
if input.UserInputState == Enum.UserInputState.Begin then
self.mouseOrTouchDown = true
self.lastPosition = input.Position
setScrollingEnabled(false)
local changedConnection = UserInputService.InputChanged:connect(function(inputObject, gameProcessedEvent)
if (inputObject.UserInputType == Enum.UserInputType.MouseMovement and self.mouseOrTouchDown)
or inputObject.UserInputType == Enum.UserInputType.Touch then
local touchDelta = (inputObject.Position - self.lastPosition)
self.lastPosition = inputObject.Position
self.yRotation = self.yRotation + touchDelta.x * CHARACTER_ROTATION_SPEED
self.xRotation = self.xRotation + touchDelta.y * CHARACTER_ROTATION_SPEED
self.lastInputTime = tick()
end
end)
local endConnection = UserInputService.InputEnded:connect(function(inputObject, gameProcessedEvent)
if inputObject.UserInputType == Enum.UserInputType.MouseButton1
or inputObject.UserInputType == Enum.UserInputType.Touch then
if self.mouseOrTouchDown then
self.mouseOrTouchDown = false
self.lastInputTime = tick()
self:removeConnections()
end
setScrollingEnabled(true)
end
end)
table.insert(self.connections, changedConnection)
table.insert(self.connections, endConnection)
end
end,
})
end
--[[
Handle rotating the avatar.
]]
function AvatarViewport:handleSpin()
local isSpinning = false
spawn(function()
while self.isMounted do
-- Automatically rotate the avatar if the user is not interacting with it.
if tick() - self.lastInputTime > AUTO_ROTATE_WAIT_TIME and not self.mouseOrTouchDown
and not self.gamepadRotating then
isSpinning = true
else
isSpinning = false
end
local function returnToDefaultPosition(current, maxStep)
current = current % math.rad(360)
if current > math.rad(180) then
current = current + maxStep
if current > math.rad(360) then
return 0
end
else
current = current - maxStep
if current < 0 then
return 0
end
end
return current
end
if isSpinning then
self.xRotation = returnToDefaultPosition(self.xRotation, math.rad(2))
self.yRotation = self.yRotation + AUTO_SPIN_STEP
elseif self.gamepadRotating then
self.yRotation = self.yRotation + self.delta * self.rotationDeltaY
self.xRotation = self.xRotation + self.delta * self.rotationDeltaX
end
self:setRotation()
self.delta = RunService.RenderStepped:wait()
end
end)
end
function AvatarViewport:setUpGamepad()
if UserInputService.GamepadEnabled then
local gamepadInput = Vector2.new(0, 0)
ContextActionService:UnbindCoreAction("RotateModel")
ContextActionService:BindCoreAction("RotateModel", function(actionName, inputState, inputObject)
if inputState == Enum.UserInputState.Change then
gamepadInput = inputObject.Position or gamepadInput
gamepadInput = Vector2.new(gamepadInput.X, gamepadInput.Y)
if math.abs(gamepadInput.X) > THUMBSTICK_DEADZONE or math.abs(gamepadInput.Y) > THUMBSTICK_DEADZONE then
self.lastInputTime = tick()
self.gamepadRotating = true
self.rotationDeltaY = STICK_ROTATION_MULTIPLIER * gamepadInput.X
self.rotationDeltaX = STICK_ROTATION_MULTIPLIER * gamepadInput.Y
else
self.gamepadRotating = false
end
end
return Enum.ContextActionResult.Sink
end,
false, Enum.KeyCode.Thumbstick2, Enum.KeyCode.ButtonA)
end
end
function AvatarViewport:unbindActions()
ContextActionService:UnbindCoreAction("RotateModel")
end
function AvatarViewport:removeConnections()
for _, listener in pairs(self.connections) do
listener:disconnect()
end
end
function AvatarViewport:willUnmount()
self.isMounted = false
self:unbindActions()
self:removeConnections()
end
return RoactRodux.UNSTABLE_connect2(
function(state, props)
return {
view = state.view,
tryingOnInfo = state.tryingOnInfo,
gamepadEnabled = state.gamepadEnabled,
}
end
)(AvatarViewport)
@@ -0,0 +1,18 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local InspectAndBuyFolder = script.Parent.Parent
local TestContainer = require(InspectAndBuyFolder.Test.TestContainer)
local AvatarViewport = require(script.Parent.AvatarViewport)
it("should create and destroy without errors", function()
local element = Roact.createElement(TestContainer, nil, {
Roact.createElement(AvatarViewport, {})
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,113 @@
local CorePackages = game:GetService("CorePackages")
local TextService = game:GetService("TextService")
local Roact = require(CorePackages.Roact)
local RoactRodux = require(CorePackages.RoactRodux)
local InspectAndBuyFolder = script.Parent.Parent
local Colors = require(InspectAndBuyFolder.Colors)
local PromptPurchase = require(InspectAndBuyFolder.Thunks.PromptPurchase)
local getSelectionImageObjectRounded = require(InspectAndBuyFolder.getSelectionImageObjectRounded)
local TEXT_SIZE = 16
local MIN_SIZE = 32
local ROBUX_ICON_SIZE = 16
local BUTTON_PADDING = 10
local ROBLOX_CREATOR_ID = "1"
local BuyButton = Roact.PureComponent:extend("BuyButton")
function BuyButton:init()
self.selectedImage = getSelectionImageObjectRounded()
end
function BuyButton:render()
local itemType = self.props.itemType
local itemId = self.props.itemId
local showRobuxIcon = self.props.showRobuxIcon
local forSale = self.props.forSale
local promptPurchase = self.props.promptPurchase
local buyText = self.props.buyText
local buyButtonRef = self.props.buyButtonRef
local size = UDim2.new(0, self:getBuyButtonTextSize(buyText), 1, 0)
local assetInfo = self.props.assetInfo
local creatorId = assetInfo and assetInfo.creatorId or 0
local sizeXAdjustment = creatorId == ROBLOX_CREATOR_ID and -32 or -BUTTON_PADDING / 2
local transparencyOverride = 0
if not forSale then
transparencyOverride = 0.5
end
return Roact.createElement("ImageButton", {
AnchorPoint = Vector2.new(0.5, 0),
BackgroundTransparency = 1,
Size = UDim2.new(0.5, sizeXAdjustment, 0, 44),
Image = "rbxasset://textures/ui/InspectMenu/Button_white.png",
ImageColor3 = Colors.Green,
SelectionImageObject = self.selectedImage,
LayoutOrder = 3,
ImageTransparency = transparencyOverride,
[Roact.Ref] = buyButtonRef,
[Roact.Event.Activated] = function()
if forSale then
promptPurchase(itemId, itemType)
end
end,
}, {
UIListLayout = Roact.createElement("UIListLayout", {
FillDirection = Enum.FillDirection.Horizontal,
SortOrder = Enum.SortOrder.LayoutOrder,
VerticalAlignment = Enum.VerticalAlignment.Center,
HorizontalAlignment = Enum.HorizontalAlignment.Center,
}),
RobuxIcon = Roact.createElement("ImageLabel", {
BackgroundTransparency = 1,
Size = UDim2.new(0, ROBUX_ICON_SIZE, 0, ROBUX_ICON_SIZE),
Image = "rbxasset://textures/ui/common/robux_small.png",
ImageTransparency = transparencyOverride,
ImageColor3 = Colors.White,
LayoutOrder = 1,
Visible = showRobuxIcon,
}),
BuyText = Roact.createElement("TextLabel", {
BackgroundTransparency = 1,
Size = size,
Text = buyText,
Font = Enum.Font.Gotham,
TextSize = TEXT_SIZE,
TextColor3 = Colors.White,
TextTransparency = transparencyOverride,
LayoutOrder = 2,
TextXAlignment = Enum.TextXAlignment.Center,
})
})
end
function BuyButton:getBuyButtonTextSize(buyText)
if self.props.buyButtonRef.current then
local buyButtonTextSize = TextService:GetTextSize(buyText,
TEXT_SIZE, Enum.Font.Gotham, Vector2.new(self.props.buyButtonRef.current.AbsoluteSize.X, 5000))
self.props.buyButtonRef.Size = UDim2.new(0, 1, 0, buyButtonTextSize.Y)
return buyButtonTextSize.X > MIN_SIZE and buyButtonTextSize.X or MIN_SIZE
end
return 0
end
return RoactRodux.UNSTABLE_connect2(
function(state, props)
local assetId = state.detailsInformation.assetId
return {
locale = state.locale,
view = state.view,
assetInfo = state.assets[assetId],
}
end,
function(dispatch)
return {
promptPurchase = function(itemId, itemType)
dispatch(PromptPurchase(itemId, itemType))
end,
}
end
)(BuyButton)
@@ -0,0 +1,20 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local InspectAndBuyFolder = script.Parent.Parent
local TestContainer = require(InspectAndBuyFolder.Test.TestContainer)
local BuyButton = require(script.Parent.BuyButton)
it("should create and destroy without errors", function()
local element = Roact.createElement(TestContainer, nil, {
Roact.createElement(BuyButton, {
buyButtonRef = Roact.createRef(),
})
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,54 @@
local CorePackages = game:GetService("CorePackages")
local GuiService = game:GetService("GuiService")
local Roact = require(CorePackages.Roact)
local RoactRodux = require(CorePackages.RoactRodux)
local InspectAndBuyFolder = script.Parent.Parent
local SetDetailsInformation = require(InspectAndBuyFolder.Actions.SetDetailsInformation)
local SetTryingOnInfo = require(InspectAndBuyFolder.Actions.SetTryingOnInfo)
local CloseButton = Roact.PureComponent:extend("CloseButton")
function CloseButton:render()
local view = self.props.view
local viewMapping = self._context[view]
local viewingDetails = self.props.detailsInformation.viewingDetails
local closeDetails = self.props.closeDetails
local setTryingOnInfo = self.props.setTryingOnInfo
return Roact.createElement("ImageButton", {
Size = UDim2.new(0, 26, 0, 26),
Position = viewMapping.CloseButtonPosition,
BackgroundTransparency = 1,
Selectable = false,
Image = viewingDetails and "rbxasset://textures/ui/InspectMenu/caret_tail_left.png"
or "rbxasset://textures/ui/InspectMenu/x.png",
ZIndex = 2,
[Roact.Event.Activated] = function()
if viewingDetails then
closeDetails()
setTryingOnInfo(false, nil)
else
GuiService:CloseInspectMenu()
end
end,
})
end
return RoactRodux.UNSTABLE_connect2(
function(state, props)
return {
view = state.view,
detailsInformation = state.detailsInformation,
}
end,
function(dispatch)
return {
closeDetails = function()
dispatch(SetDetailsInformation(false, nil))
end,
setTryingOnInfo = function(tryingOn, assetId)
dispatch(SetTryingOnInfo(tryingOn, assetId))
end
}
end
)(CloseButton)
@@ -0,0 +1,18 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local InspectAndBuyFolder = script.Parent.Parent
local TestContainer = require(InspectAndBuyFolder.Test.TestContainer)
local CloseButton = require(script.Parent.CloseButton)
it("should create and destroy without errors", function()
local element = Roact.createElement(TestContainer, nil, {
Roact.createElement(CloseButton, {})
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,117 @@
local CorePackages = game:GetService("CorePackages")
local GuiService = game:GetService("GuiService")
local InspectAndBuyFolder = script.Parent.Parent
local Roact = require(CorePackages.Roact)
local RoactRodux = require(CorePackages.RoactRodux)
local InitialView = require(InspectAndBuyFolder.Components.InitialView)
local AssetDetails = require(InspectAndBuyFolder.Components.AssetDetails)
local CloseButton = require(InspectAndBuyFolder.Components.CloseButton)
local NoInventoryNotice = require(InspectAndBuyFolder.Components.NoInventoryNotice)
local Colors = require(InspectAndBuyFolder.Colors)
local Constants = require(InspectAndBuyFolder.Constants)
local FFlagInspectAndBuyResetSelectedCoreObject = game:DefineFastFlag("InspectAndBuyResetSelectedCoreObject", false)
local Container = Roact.PureComponent:extend("Container")
function Container:init()
self.frameRef = Roact.createRef()
self.lastSavedSelectedCoreGui = nil
end
function Container:didMount()
local gamepadEnabled = self.props.gamepadEnabled
self.selectedCoreObjectChangedSignal = GuiService:GetPropertyChangedSignal('SelectedCoreObject'):connect(function()
if self.props.visible and GuiService.SelectedCoreObject then
self.lastSavedSelectedCoreGui = GuiService.SelectedCoreObject
end
end)
if gamepadEnabled then
GuiService:AddSelectionParent("InspectMenu", self.frameRef.current)
end
end
function Container:didUpdate(prevProps)
local visible = self.props.visible
local gamepadEnabled = self.props.gamepadEnabled
if visible and gamepadEnabled and self.frameRef.current then
GuiService:RemoveSelectionGroup("InspectMenu")
GuiService:AddSelectionParent("InspectMenu", self.frameRef.current)
if self.lastSavedSelectedCoreGui then
GuiService.SelectedCoreObject = self.lastSavedSelectedCoreGui
end
elseif (not gamepadEnabled and prevProps.gamepadEnabled) or not visible then
GuiService:RemoveSelectionGroup("InspectMenu")
end
if visible and not prevProps.visible then
GuiService.SelectedCoreObject = self.lastSavedSelectedCoreGui
end
end
function Container:willUnmount()
self.selectedCoreObjectChangedSignal:disconnect()
GuiService:RemoveSelectionGroup("InspectMenu")
if FFlagInspectAndBuyResetSelectedCoreObject then
GuiService.SelectedCoreObject = nil
end
end
function Container:render()
local view = self.props.view
local viewMapping = self._context[view]
local localPlayerModel = self.props.localPlayerModel
local visible = self.props.visible
return Roact.createElement("ImageButton", {
Size = UDim2.new(1, 0, 1, 46),
Position = UDim2.new(0, 0, 0, -46),
BackgroundColor3 = viewMapping.ContainerBackgroundColor,
BackgroundTransparency = viewMapping.ContainerBackgroundTransparency,
Visible = visible,
Selectable = false,
AutoButtonColor = false,
AutoLocalize = false,
[Roact.Event.Activated] = function()
if view == Constants.View.Wide then
GuiService:CloseInspectMenu()
end
end,
}, {
MainContainer = Roact.createElement("ImageButton", {
AnchorPoint = viewMapping.ContainerAnchorPoint,
Size = viewMapping.ContainerSize,
Position = viewMapping.ContainerPosition,
BackgroundColor3 = Colors.Carbon,
BorderSizePixel = 0,
Active = true,
AutoButtonColor = false,
Selectable = false,
[Roact.Ref] = self.frameRef,
}, {
AspectRatioConstraint = viewMapping.UseContainerAspectRatio and Roact.createElement("UIAspectRatioConstraint", {
AspectRatio = viewMapping.ContainerAspectRatio,
}),
CloseButton = Roact.createElement(CloseButton),
InitialView = Roact.createElement(InitialView),
AssetDetails = Roact.createElement(AssetDetails, {
localPlayerModel = localPlayerModel,
}),
NoInventoryNotice = Roact.createElement(NoInventoryNotice),
})
})
end
return RoactRodux.UNSTABLE_connect2(
function(state, props)
return {
view = state.view,
visible = state.visible,
gamepadEnabled = state.gamepadEnabled,
}
end
)(Container)
@@ -0,0 +1,24 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local InspectAndBuyFolder = script.Parent.Parent
local TestContainer = require(InspectAndBuyFolder.Test.TestContainer)
local Container = require(script.Parent.Container)
local mockModel = require(InspectAndBuyFolder.Test.getMockModel)()
it("should create and destroy without errors", function()
local element = Roact.createElement(TestContainer, nil, {
Roact.createElement(Container, {
humanoidDescription = Instance.new("HumanoidDescription"),
inspectingModel = mockModel,
localPlayerModel = mockModel,
})
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,180 @@
local CorePackages = game:GetService("CorePackages")
local CoreGui = game:GetService("CoreGui")
local GuiService = game:GetService("GuiService")
local Players = game:GetService("Players")
local InspectAndBuyFolder = script.Parent.Parent
local Roact = require(CorePackages.Roact)
local RoactRodux = require(CorePackages.RoactRodux)
local RobloxTranslator = require(CoreGui.RobloxGui.Modules.RobloxTranslator)
local Constants = require(InspectAndBuyFolder.Constants)
local UtilityFunctions = require(InspectAndBuyFolder.UtilityFunctions)
local FavoritesButton = require(InspectAndBuyFolder.Components.FavoritesButton)
local TryOnButton = require(InspectAndBuyFolder.Components.TryOnButton)
local BuyButton = require(InspectAndBuyFolder.Components.BuyButton)
local GetFFlagLuaPremiumCatalogIGIAB
= require(CoreGui.RobloxGui.Modules.Flags.GetFFlagLuaPremiumCatalogIGIAB)
local DetailsButtons = Roact.PureComponent:extend("DetailsButtons")
local OFFSALE_KEY = "InGame.InspectMenu.Label.Offsale"
local LIMITED_KEY = "InGame.InspectMenu.Label.Limited"
local OWNED_KEY = "InGame.InspectMenu.Label.Owned"
local PREMIUM_ONLY_KEY = "InGame.InspectMenu.Label.PremiumOnly"
local ROBLOX_CREATOR_ID = "1"
--[[
If an asset is an animation (or emotes in the future) we cannot support
try on functionality.
]]
local function isAnimationAsset(assetTypeId)
return Constants.AnimationAssetTypes[assetTypeId] ~= nil
end
local function getBuyText(itemInfo, locale)
local buyText
if itemInfo.owned then
buyText = RobloxTranslator:FormatByKeyForLocale(OWNED_KEY, locale)
elseif itemInfo.isLimited then
buyText = RobloxTranslator:FormatByKeyForLocale(LIMITED_KEY, locale)
elseif not itemInfo.isForSale and not itemInfo.isLimited then
buyText = RobloxTranslator:FormatByKeyForLocale(OFFSALE_KEY, locale)
elseif itemInfo.isForSale then
if GetFFlagLuaPremiumCatalogIGIAB() then
if itemInfo.premiumPricing ~= nil then
if itemInfo.price == nil and Players.LocalPlayer.MembershipType ~= Enum.MembershipType.Premium then
buyText = RobloxTranslator:FormatByKeyForLocale(PREMIUM_ONLY_KEY, locale)
else
buyText = itemInfo.premiumPricing.premiumPriceInRobux
end
else
buyText = itemInfo.price
end
else
buyText = itemInfo.price
end
end
return buyText
end
function DetailsButtons:init()
self.tryOnButtonRef = Roact.createRef()
self.buyButtonRef = Roact.createRef()
self.favoriteButtonRef = Roact.createRef()
end
function DetailsButtons:didUpdate(prevProps)
local detailsInformation = self.props.detailsInformation
local gamepadEnabled = self.props.gamepadEnabled
if (prevProps.assetInfo.bundlesAssetIsIn == nil and self.props.assetInfo.bundlesAssetIsIn ~= nil)
and detailsInformation.viewingDetails then
local assetInfo = self.props.assetInfo
local showTryOn = not isAnimationAsset(assetInfo.assetTypeId)
local visible = self.props.visible
if gamepadEnabled and visible then
local creatorId = assetInfo and assetInfo.creatorId or 0
if creatorId == ROBLOX_CREATOR_ID then
GuiService.SelectedCoreObject = self.favoriteButtonRef.current
elseif showTryOn then
GuiService.SelectedCoreObject = self.tryOnButtonRef.current
else
GuiService.SelectedCoreObject = self.buyButtonRef.current
end
end
elseif self.props.assetInfo.bundlesAssetIsIn == nil and detailsInformation.viewingDetails and gamepadEnabled then
GuiService.SelectedCoreObject = nil
end
end
function DetailsButtons:render()
local locale = self.props.locale
local assetInfo = self.props.assetInfo
local bundleInfo = self.props.bundleInfo
local isLimited = assetInfo.isLimited or false
local showRobuxIcon = false
local showTryOn = false
local buyText, forSale, partOfBundle, bundleId, itemType, itemId
if assetInfo then
partOfBundle = assetInfo.bundlesAssetIsIn and #assetInfo.bundlesAssetIsIn == 1
if partOfBundle then
bundleId = UtilityFunctions.getBundleId(assetInfo)
itemType = Constants.ItemType.Bundle
itemId = bundleId
if bundleInfo[bundleId] then
buyText = getBuyText(bundleInfo[bundleId], locale)
forSale = bundleInfo[bundleId].isForSale and not bundleInfo[bundleId].owned
showRobuxIcon = bundleInfo[bundleId].price ~= nil and not bundleInfo[bundleId].owned and forSale
end
else
itemType = Constants.ItemType.Asset
itemId = assetInfo.assetId
forSale = assetInfo.isForSale and not assetInfo.owned and not isLimited and assetInfo.owned ~= nil
if GetFFlagLuaPremiumCatalogIGIAB() and forSale and assetInfo.price == nil and assetInfo.premiumPricing ~= nil then
forSale = Players.LocalPlayer.MembershipType == Enum.MembershipType.Premium
end
buyText = getBuyText(assetInfo, locale)
showRobuxIcon = assetInfo.price ~= nil and not assetInfo.owned and forSale
end
showTryOn = not isAnimationAsset(assetInfo.assetTypeId)
end
return Roact.createElement("Frame", {
BackgroundTransparency = 1,
Size = UDim2.new(1, 0, 0, 51),
Position = UDim2.new(0, 0, 1, -51),
}, {
UIListLayout = Roact.createElement("UIListLayout", {
Padding = UDim.new(0, 10),
FillDirection = Enum.FillDirection.Horizontal,
SortOrder = Enum.SortOrder.LayoutOrder,
}),
FavoriteButton = Roact.createElement(FavoritesButton, {
favoriteButtonRef = self.favoriteButtonRef,
}),
TryOnButton = Roact.createElement(TryOnButton, {
showTryOn = showTryOn,
assetInfo = assetInfo,
partOfBundle = partOfBundle,
bundleId = bundleId,
tryOnButtonRef = self.tryOnButtonRef,
}),
TryOnFiller = Roact.createElement("ImageLabel", {
AnchorPoint = Vector2.new(0.5, 0),
BackgroundTransparency = 1,
Size = UDim2.new(0.5, -32, 0, 44),
Visible = not showTryOn,
LayoutOrder = 2,
Image = "",
ScaleType = Enum.ScaleType.Slice,
SliceCenter = Rect.new(5, 5, 120, 20),
}),
BuyButton = Roact.createElement(BuyButton, {
itemType = itemType,
itemId = itemId,
showRobuxIcon = showRobuxIcon,
forSale = forSale,
buyText = buyText,
buyButtonRef = self.buyButtonRef,
}),
})
end
return RoactRodux.UNSTABLE_connect2(
function(state, props)
local assetId = state.detailsInformation.assetId
return {
visible = state.visible,
assetInfo = state.assets[assetId] or {},
detailsInformation = state.detailsInformation,
bundleInfo = state.bundles,
locale = state.locale,
gamepadEnabled = state.gamepadEnabled,
}
end
)(DetailsButtons)
@@ -0,0 +1,18 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local InspectAndBuyFolder = script.Parent.Parent
local TestContainer = require(InspectAndBuyFolder.Test.TestContainer)
local DetailsButtons = require(script.Parent.DetailsButtons)
it("should create and destroy without errors", function()
local element = Roact.createElement(TestContainer, nil, {
Roact.createElement(DetailsButtons, {})
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,77 @@
local CorePackages = game:GetService("CorePackages")
local TextService = game:GetService("TextService")
local Roact = require(CorePackages.Roact)
local RoactRodux = require(CorePackages.RoactRodux)
local InspectAndBuyFolder = script.Parent.Parent
local Colors = require(InspectAndBuyFolder.Colors)
local UtilityFunctions = require(InspectAndBuyFolder.UtilityFunctions)
local TEXT_SIZE_SMALL = 16
local DETAILS_SIZES = 451
local DetailsDescription = Roact.PureComponent:extend("DetailsDescription")
--[[
Sets the variables used to display text. If a bundle is
being shown, they will display the bundle's data instead.
]]
function DetailsDescription:setText()
local assetInfo = self.props.assetInfo or {}
local partOfBundle = assetInfo.bundlesAssetIsIn and #assetInfo.bundlesAssetIsIn == 1
local bundleInfo = self.props.bundleInfo or {}
if partOfBundle then
local bundleId = UtilityFunctions.getBundleId(assetInfo)
if bundleInfo[bundleId] then
self.description = bundleInfo[bundleId].description or ""
end
elseif assetInfo then
self.description = assetInfo.description or ""
end
end
function DetailsDescription:init()
self.description = ""
self.descriptionRef = Roact.createRef()
end
function DetailsDescription:render()
self:setText()
local size = self:calculateSize()
return Roact.createElement("TextLabel", {
BackgroundTransparency = 1,
LayoutOrder = 4,
Size = UDim2.new(1, -20, 0, size),
Text = self.description or "",
TextXAlignment = Enum.TextXAlignment.Left,
TextYAlignment = Enum.TextYAlignment.Top,
TextSize = TEXT_SIZE_SMALL,
TextWrapped = true,
Font = Enum.Font.Gotham,
TextColor3 = Colors.White,
[Roact.Ref] = self.descriptionRef,
})
end
function DetailsDescription:calculateSize()
if self.descriptionRef.current and self.props.assetInfo then
local size = TextService:GetTextSize(self.description,
TEXT_SIZE_SMALL, Enum.Font.Gotham, Vector2.new(self.descriptionRef.current.AbsoluteSize.X, 5000)).Y
self.descriptionRef.current.Parent.CanvasSize = UDim2.new(1, 0, 0, DETAILS_SIZES + size)
return size
end
end
return RoactRodux.UNSTABLE_connect2(
function(state, props)
local assetId = state.detailsInformation.assetId
return {
view = state.view,
assetInfo = state.assets[assetId],
detailsInformation = state.detailsInformation,
bundleInfo = state.bundles,
}
end
)(DetailsDescription)
@@ -0,0 +1,18 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local InspectAndBuyFolder = script.Parent.Parent
local TestContainer = require(InspectAndBuyFolder.Test.TestContainer)
local DetailsDescription = require(script.Parent.DetailsDescription)
it("should create and destroy without errors", function()
local element = Roact.createElement(TestContainer, nil, {
Roact.createElement(DetailsDescription, {})
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,166 @@
local CorePackages = game:GetService("CorePackages")
local CoreGui = game:GetService("CoreGui")
local InspectAndBuyFolder = script.Parent.Parent
local Roact = require(CorePackages.Roact)
local RoactRodux = require(CorePackages.RoactRodux)
local Cryo = require(CorePackages.Cryo)
local UIBlox = require(CorePackages.UIBlox)
local UIBloxImages = UIBlox.App.ImageSet.Images
local UIBloxIconSize = UIBlox.App.Constant.IconSize
local Notification = require(InspectAndBuyFolder.Components.Notification)
local Favorites = require(InspectAndBuyFolder.Components.Favorites)
local UtilityFunctions = require(InspectAndBuyFolder.UtilityFunctions)
local Colors = require(InspectAndBuyFolder.Colors)
local Constants = require(InspectAndBuyFolder.Constants)
local RobloxTranslator = require(CoreGui.RobloxGui.Modules.RobloxTranslator)
local getSelectionImageObjectRegular = require(InspectAndBuyFolder.getSelectionImageObjectRegular)
local GetFFlagLuaPremiumCatalogIGIAB
= require(CoreGui.RobloxGui.Modules.Flags.GetFFlagLuaPremiumCatalogIGIAB)
local PremiumIcon = UIBloxImages["icons/status/premium"]
local BY_KEY = "InGame.InspectMenu.Label.By"
local TEXT_SIZE_SMALL = 12
local TITLE_TEXT_SIZE = 20
local FAVORITES_SIZE = 16
local ASSET_NAME_SIZE = 50
local CREATOR_SIZE = 20
local NOTIFICATION_SIZE = 45
local PREMIUM_ICON_PADDING = 5
local DetailsText = Roact.PureComponent:extend("DetailsText")
function DetailsText:init()
self.name = ""
self.creator = ""
self.selectedImage = getSelectionImageObjectRegular()
end
--[[
Sets the variables used to display text. If a bundle is
being shown, they will display the bundle's data instead.
]]
function DetailsText:setText()
local assetInfo = self.props.assetInfo or {}
local partOfBundle = assetInfo.bundlesAssetIsIn and #assetInfo.bundlesAssetIsIn == 1
local bundleInfo = self.props.bundleInfo or {}
if partOfBundle then
local bundleId = UtilityFunctions.getBundleId(assetInfo)
if bundleInfo[bundleId] then
self.name = bundleInfo[bundleId].name or ""
self.creator = bundleInfo[bundleId].creatorName or ""
end
elseif assetInfo then
self.name = assetInfo.name or ""
self.creator = assetInfo.creatorName or ""
end
end
function DetailsText:render()
self:setText()
local view = self.props.view
local viewMapping = self._context[view]
local locale = self.props.locale
local assetInfo = self.props.assetInfo or {}
local partOfBundle = assetInfo.bundlesAssetIsIn and #assetInfo.bundlesAssetIsIn == 1
local multipleBundles = assetInfo.bundlesAssetIsIn and #assetInfo.bundlesAssetIsIn > 1
local showPremiumIcon = GetFFlagLuaPremiumCatalogIGIAB() and assetInfo.premiumPricing ~= nil
local premiumIconPadding = showPremiumIcon and (UIBloxIconSize.Regular + PREMIUM_ICON_PADDING) or 0
local noticeKey = nil
if multipleBundles then
noticeKey = Constants.NotificationKeys.MultipleBundleNoticeKey
elseif partOfBundle then
noticeKey = Constants.NotificationKeys.SingleBundleNoticeKey
elseif assetInfo.isLimited then
noticeKey = Constants.NotificationKeys.LimitedItemNoticeKey
end
return Roact.createElement("Frame", {
Position = viewMapping.DetailsFramePosition,
Size = UDim2.new(1, 0, 0, ASSET_NAME_SIZE + CREATOR_SIZE + FAVORITES_SIZE + NOTIFICATION_SIZE),
BackgroundTransparency = 1,
LayoutOrder = 1,
}, {
Layout = Roact.createElement("UIListLayout", {
SortOrder = Enum.SortOrder.LayoutOrder,
HorizontalAlignment = Enum.HorizontalAlignment.Left,
VerticalAlignment = Enum.VerticalAlignment.Top,
FillDirection = Enum.FillDirection.Vertical,
}),
Notification = noticeKey and Roact.createElement(Notification, {
noticeKey = noticeKey,
}),
AssetName = GetFFlagLuaPremiumCatalogIGIAB() and Roact.createElement("Frame", {
BackgroundTransparency = 1,
Size = UDim2.new(1, -10, 0, ASSET_NAME_SIZE),
LayoutOrder = 1,
}, {
PremiumIcon = showPremiumIcon and Roact.createElement("ImageLabel", Cryo.Dictionary.join(PremiumIcon, {
AnchorPoint = Vector2.new(0, 0.5),
Position = UDim2.new(0, 0, 0.5, 0),
BackgroundTransparency = 1,
Size = UDim2.new(0, UIBloxIconSize.Regular, 0, UIBloxIconSize.Regular),
ImageColor3 = Color3.new(1, 1, 1),
})) or nil,
Roact.createElement("TextLabel", {
BackgroundTransparency = 1,
Position = UDim2.new(0, premiumIconPadding, 0, 0),
Size = UDim2.new(1, premiumIconPadding, 0, ASSET_NAME_SIZE),
LayoutOrder = 1,
Text = self.name or "",
TextXAlignment = Enum.TextXAlignment.Left,
TextYAlignment = Enum.TextYAlignment.Center,
TextScaled = true,
TextSize = TITLE_TEXT_SIZE,
Font = Enum.Font.GothamBold,
TextColor3 = Colors.White,
}, {
UITextSizeConstraint = Roact.createElement("UITextSizeConstraint", {
MaxTextSize = 32,
})
}),
}) or Roact.createElement("TextLabel", {
BackgroundTransparency = 1,
Size = UDim2.new(1, -10, 0, ASSET_NAME_SIZE),
LayoutOrder = 1,
Text = self.name or "",
TextXAlignment = Enum.TextXAlignment.Left,
TextYAlignment = Enum.TextYAlignment.Center,
TextScaled = true,
TextSize = TITLE_TEXT_SIZE,
Font = Enum.Font.GothamBold,
TextColor3 = Colors.White,
}, {
UITextSizeConstraint = Roact.createElement("UITextSizeConstraint", {
MaxTextSize = 32,
})
}),
CreatorLabel = Roact.createElement("TextLabel", {
BackgroundTransparency = 1,
Size = UDim2.new(1, 0, 0, CREATOR_SIZE),
LayoutOrder = 2,
Text = RobloxTranslator:FormatByKeyForLocale(BY_KEY, locale, { CREATOR = self.creator }),
Font = Enum.Font.Gotham,
TextScaled = false,
TextSize = TEXT_SIZE_SMALL,
TextColor3 = Colors.White,
TextXAlignment = Enum.TextXAlignment.Left,
TextYAlignment = Enum.TextYAlignment.Center,
}),
FavoriteContainer = Roact.createElement(Favorites),
})
end
return RoactRodux.UNSTABLE_connect2(
function(state, props)
local assetId = state.detailsInformation.assetId
return {
view = state.view,
locale = state.locale,
assetInfo = state.assets[assetId],
bundleInfo = state.bundles,
}
end
)(DetailsText)
@@ -0,0 +1,18 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local InspectAndBuyFolder = script.Parent.Parent
local TestContainer = require(InspectAndBuyFolder.Test.TestContainer)
local DetailsText = require(script.Parent.DetailsText)
it("should create and destroy without errors", function()
local element = Roact.createElement(TestContainer, nil, {
Roact.createElement(DetailsText, {})
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,78 @@
local CorePackages = game:GetService("CorePackages")
local InspectAndBuyFolder = script.Parent.Parent
local Roact = require(CorePackages.Roact)
local RoactRodux = require(CorePackages.RoactRodux)
local Url = require(CorePackages.AppTempCommon.LuaApp.Http.Url)
local UtilityFunctions = require(InspectAndBuyFolder.UtilityFunctions)
local GetFFlagUseThumbnailUrl = require(game:GetService("CoreGui").RobloxGui.Modules.Common.Flags.GetFFlagUseThumbnailUrl)
local DetailsThumbnail = Roact.PureComponent:extend("DetailsThumbnail")
local function isPartOfBundle(assetInfo)
return assetInfo and assetInfo.bundlesAssetIsIn and #assetInfo.bundlesAssetIsIn == 1
end
function DetailsThumbnail:getUrl()
local assetInfo = self.props.assetInfo
local detailsInformation = self.props.detailsInformation
local bundles = self.props.bundles
local partOfBundle = isPartOfBundle(assetInfo)
local url = ""
if partOfBundle then
local bundleId = UtilityFunctions.getBundleId(assetInfo)
if bundles[bundleId] then
url = GetFFlagUseThumbnailUrl() and "rbxthumb://type=Outfit&id=" .. bundles[bundleId].costumeId .. "&w=420&h=420" or Url.BASE_URL_SECURE .."outfit-thumbnail/image?userOutfitId=" ..bundles[bundleId].costumeId
.."&width=420&height=420&format=png"
end
else
local assetId = detailsInformation.assetId
url = GetFFlagUseThumbnailUrl() and "rbxthumb://type=Asset&id=" .. assetId .. "&w=420&h=420" or Url.BASE_URL_SECURE .."asset-thumbnail/image?assetId=" ..assetId .."&width=420&height=420&format=png"
end
return url
end
function DetailsThumbnail:render()
local view = self.props.view
local viewMapping = self._context[view]
local detailsInformation = self.props.detailsInformation
local tryingOnInfo = self.props.tryingOnInfo
return Roact.createElement("Frame", {
Position = viewMapping.DetailsThumbnailFramePosition,
Size = UDim2.new(1, 0, 0, 300),
BackgroundTransparency = 1,
LayoutOrder = 2,
Visible = not tryingOnInfo.tryingOn,
}, {
Thumbnail = detailsInformation.viewingDetails and Roact.createElement("ImageLabel", {
Position = viewMapping.DetailsThumbnailPosition,
AnchorPoint = viewMapping.DetailsThumbnailAnchorPoint,
Size = UDim2.new(1, 0, 1, 0),
BackgroundTransparency = 1,
Image = self:getUrl(),
}, {
AspectRatioConstraint = Roact.createElement("UIAspectRatioConstraint", {
AspectRatio = 1,
AspectType = Enum.AspectType.FitWithinMaxSize,
DominantAxis = viewMapping.DetailsThumbnailARDominantAxis,
}),
})
})
end
return RoactRodux.UNSTABLE_connect2(
function(state, props)
local assetId = state.detailsInformation.assetId
return {
view = state.view,
tryingOnInfo = state.tryingOnInfo,
detailsInformation = state.detailsInformation,
assetInfo = state.assets[assetId],
bundles = state.bundles,
}
end
)(DetailsThumbnail)
@@ -0,0 +1,18 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local InspectAndBuyFolder = script.Parent.Parent
local TestContainer = require(InspectAndBuyFolder.Test.TestContainer)
local DetailsThumbnail = require(script.Parent.DetailsThumbnail)
it("should create and destroy without errors", function()
local element = Roact.createElement(TestContainer, nil, {
Roact.createElement(DetailsThumbnail, {})
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,120 @@
local CorePackages = game:GetService("CorePackages")
local InspectAndBuyFolder = script.Parent.Parent
local Roact = require(CorePackages.Roact)
local RoactRodux = require(CorePackages.RoactRodux)
local UtilityFunctions = require(InspectAndBuyFolder.UtilityFunctions)
local GetAssetFavoriteCount = require(InspectAndBuyFolder.Thunks.GetAssetFavoriteCount)
local GetBundleFavoriteCount = require(InspectAndBuyFolder.Thunks.GetBundleFavoriteCount)
local GotCurrentFavoriteCount = require(InspectAndBuyFolder.Selectors.GotCurrentFavoriteCount)
local getSelectionImageObjectRegular = require(InspectAndBuyFolder.getSelectionImageObjectRegular)
local FAVORITES_SIZE = 16
local FAVORITE_IMAGE_FILLED = "rbxasset://textures/ui/InspectMenu/ico_favorite.png"
local Favorites = Roact.PureComponent:extend("Favorites")
--[[
Sets the variables used to display text. If a bundle is
being shown, they will display the bundle's data instead.
]]
function Favorites:setText()
local assetInfo = self.props.assetInfo or {}
local partOfBundle = assetInfo.bundlesAssetIsIn and #assetInfo.bundlesAssetIsIn > 0
local bundleInfo = self.props.bundleInfo
if partOfBundle then
local bundleId = UtilityFunctions.getBundleId(assetInfo)
if bundleInfo[bundleId] then
self.numFavorites = bundleInfo[bundleId].numFavorites
end
elseif assetInfo then
self.numFavorites = assetInfo.numFavorites
end
end
function Favorites:init()
self.numFavorites = ""
self.selectionImageObject = getSelectionImageObjectRegular()
end
function Favorites:willUpdate(nextProps)
local getAssetFavoriteCount = self.props.getAssetFavoriteCount
local getBundleFavoriteCount = self.props.getBundleFavoriteCount
-- We need to check if this asset is in any bundles, so that web call
-- needs to be completed first.
if nextProps.assetInfo and nextProps.assetInfo.bundlesAssetIsIn then
local assetInfo = nextProps.assetInfo
local partOfBundle = #assetInfo.bundlesAssetIsIn > 0
local gotCurrentFavoriteCount = nextProps.gotCurrentFavoriteCount
if not gotCurrentFavoriteCount then
coroutine.wrap(function()
if not partOfBundle then
getAssetFavoriteCount(assetInfo.assetId)
else
local bundleId = UtilityFunctions.getBundleId(assetInfo)
getBundleFavoriteCount(bundleId)
end
end)()
end
end
end
function Favorites:render()
self:setText()
return Roact.createElement("Frame", {
Size = UDim2.new(1, 0, 0, FAVORITES_SIZE),
BackgroundTransparency = 1,
LayoutOrder = 3,
}, {
FavoriteIcon = Roact.createElement("ImageButton", {
Size = UDim2.new(0, FAVORITES_SIZE, 0, FAVORITES_SIZE),
BackgroundTransparency = 1,
Image = FAVORITE_IMAGE_FILLED,
SelectionImageObject = self.selectionImageObject,
}),
FavoriteText = Roact.createElement("TextLabel", {
Size = UDim2.new(1, -FAVORITES_SIZE, 1, 0),
Position = UDim2.new(0, FAVORITES_SIZE + 5, 0, 0),
BackgroundTransparency = 1,
Text = self.numFavorites or "",
Font = Enum.Font.Gotham,
TextXAlignment = Enum.TextXAlignment.Left,
TextSize = 12,
TextColor3 = Color3.new(1, 1, 1),
})
})
end
function Favorites:shouldUpdate(nextProps)
if not nextProps.detailsInformation.viewingDetails then
return false
end
return true
end
return RoactRodux.UNSTABLE_connect2(
function(state, props)
local assetId = state.detailsInformation.assetId
return {
detailsInformation = state.detailsInformation,
assetInfo = state.assets[assetId],
bundleInfo = state.bundles,
gotCurrentFavoriteCount = GotCurrentFavoriteCount(state),
}
end,
function(dispatch)
return {
getAssetFavoriteCount = function(assetId)
dispatch(GetAssetFavoriteCount(assetId))
end,
getBundleFavoriteCount = function(bundleId)
dispatch(GetBundleFavoriteCount(bundleId))
end,
}
end
)(Favorites)
@@ -0,0 +1,18 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local InspectAndBuyFolder = script.Parent.Parent
local TestContainer = require(InspectAndBuyFolder.Test.TestContainer)
local Favorites = require(script.Parent.Favorites)
it("should create and destroy without errors", function()
local element = Roact.createElement(TestContainer, nil, {
Roact.createElement(Favorites, {})
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,136 @@
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local RoactRodux = require(CorePackages.RoactRodux)
local InspectAndBuyFolder = script.Parent.Parent
local Colors = require(InspectAndBuyFolder.Colors)
local CreateFavoriteForAsset = require(InspectAndBuyFolder.Thunks.CreateFavoriteForAsset)
local DeleteFavoriteForAsset = require(InspectAndBuyFolder.Thunks.DeleteFavoriteForAsset)
local CreateFavoriteForBundle = require(InspectAndBuyFolder.Thunks.CreateFavoriteForBundle)
local DeleteFavoriteForBundle = require(InspectAndBuyFolder.Thunks.DeleteFavoriteForBundle)
local GetFavoriteForAsset = require(InspectAndBuyFolder.Thunks.GetFavoriteForAsset)
local GetFavoriteForBundle = require(InspectAndBuyFolder.Thunks.GetFavoriteForBundle)
local GotFavoriteForDetailsItem = require(InspectAndBuyFolder.Selectors.GotFavoriteForDetailsItem)
local IsDetailsItemPartOfBundle = require(InspectAndBuyFolder.Selectors.IsDetailsItemPartOfBundle)
local GetIsFavorite = require(InspectAndBuyFolder.Selectors.GetIsFavorite)
local UtilityFunctions = require(InspectAndBuyFolder.UtilityFunctions)
local getSelectionImageObjectRounded = require(InspectAndBuyFolder.getSelectionImageObjectRounded)
local FAVORITE_IMAGE_FILLED = "rbxasset://textures/ui/InspectMenu/ico_favorite.png"
local FAVORITE_IMAGE_NOT_FILLED = "rbxasset://textures/ui/InspectMenu/ico_favorite_off.png"
local ROBLOX_CREATOR_ID = "1"
local FavoritesButton = Roact.PureComponent:extend("FavoritesButton")
function FavoritesButton:init()
self.selectedImage = getSelectionImageObjectRounded()
end
function FavoritesButton:willUpdate(nextProps)
local gotFavoriteForDetailsItem = nextProps.gotFavoriteForDetailsItem
local getFavoriteForAsset = self.props.getFavoriteForAsset
local getFavoriteForBundle = self.props.getFavoriteForBundle
if nextProps.assetInfo and nextProps.assetInfo.bundlesAssetIsIn and not gotFavoriteForDetailsItem then
local assetInfo = nextProps.assetInfo
local partOfBundle = #assetInfo.bundlesAssetIsIn > 0
coroutine.wrap(function()
if not partOfBundle then
getFavoriteForAsset(assetInfo.assetId)
else
local bundleId = UtilityFunctions.getBundleId(assetInfo)
getFavoriteForBundle(bundleId)
end
end)()
end
end
function FavoritesButton:render()
local isFavorited = self.props.isFavorited
local createFavoriteForAsset = self.props.createFavoriteForAsset
local deleteFavoriteForAsset = self.props.deleteFavoriteForAsset
local createFavoriteForBundle = self.props.createFavoriteForBundle
local deleteFavoriteForBundle = self.props.deleteFavoriteForBundle
local isDetailsItemPartOfBundle = self.props.isDetailsItemPartOfBundle
local favoriteButtonRef = self.props.favoriteButtonRef
local assetInfo = self.props.assetInfo
local creatorId = assetInfo and assetInfo.creatorId or 0
return Roact.createElement("ImageButton", {
AnchorPoint = Vector2.new(0.5, 0),
BackgroundTransparency = 1,
Size = UDim2.new(0, 44, 0, 44),
LayoutOrder = 1,
Image = "rbxasset://textures/ui/InspectMenu/Button_outline.png",
ImageColor3 = Colors.White,
ScaleType = Enum.ScaleType.Slice,
SliceCenter = Rect.new(5, 5, 120, 20),
SelectionImageObject = self.selectedImage,
-- Users can only favorite Roblox created items, otherwise they'll be captcha'd. We do not support captchas in game.
Visible = creatorId == ROBLOX_CREATOR_ID,
[Roact.Ref] = favoriteButtonRef,
[Roact.Event.Activated] = function()
if isFavorited then
if isDetailsItemPartOfBundle then
local bundleId = UtilityFunctions.getBundleId(assetInfo)
deleteFavoriteForBundle(bundleId)
else
deleteFavoriteForAsset(assetInfo.assetId)
end
else
if isDetailsItemPartOfBundle then
local bundleId = UtilityFunctions.getBundleId(assetInfo)
createFavoriteForBundle(bundleId)
else
createFavoriteForAsset(assetInfo.assetId)
end
end
end,
}, {
FavoriteIcon = Roact.createElement("ImageLabel", {
AnchorPoint = Vector2.new(0.5, 0.5),
BackgroundTransparency = 1,
Size = UDim2.new(0, 26, 0, 26),
Position = UDim2.new(0, 22, 0, 22),
Image = isFavorited and FAVORITE_IMAGE_FILLED or FAVORITE_IMAGE_NOT_FILLED,
ImageColor3 = Colors.White,
}),
})
end
return RoactRodux.UNSTABLE_connect2(
function(state, props)
local assetId = state.detailsInformation.assetId
return {
view = state.view,
assetInfo = state.assets[assetId],
bundleInfo = state.bundles,
gotFavoriteForDetailsItem = GotFavoriteForDetailsItem(state),
isFavorited = GetIsFavorite(state),
isDetailsItemPartOfBundle = IsDetailsItemPartOfBundle(state),
}
end,
function(dispatch)
return {
createFavoriteForAsset = function(assetId)
dispatch(CreateFavoriteForAsset(assetId))
end,
deleteFavoriteForAsset = function(assetId)
dispatch(DeleteFavoriteForAsset(assetId))
end,
createFavoriteForBundle = function(bundleId)
dispatch(CreateFavoriteForBundle(bundleId))
end,
deleteFavoriteForBundle = function(bundleId)
dispatch(DeleteFavoriteForBundle(bundleId))
end,
getFavoriteForAsset = function(assetId)
dispatch(GetFavoriteForAsset(assetId))
end,
getFavoriteForBundle = function(bundleId)
dispatch(GetFavoriteForBundle(bundleId))
end,
}
end
)(FavoritesButton)
@@ -0,0 +1,20 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local InspectAndBuyFolder = script.Parent.Parent
local TestContainer = require(InspectAndBuyFolder.Test.TestContainer)
local FavoritesButton = require(script.Parent.FavoritesButton)
it("should create and destroy without errors", function()
local element = Roact.createElement(TestContainer, nil, {
Roact.createElement(FavoritesButton, {
FavoritesButtonRef = Roact.createRef(),
})
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,54 @@
local CorePackages = game:GetService("CorePackages")
local InspectAndBuyFolder = script.Parent.Parent
local Roact = require(CorePackages.Roact)
local RoactRodux = require(CorePackages.RoactRodux)
local PlayerNameLabel = require(InspectAndBuyFolder.Components.PlayerNameLabel)
local AvatarHeadShot = require(InspectAndBuyFolder.Components.AvatarHeadShot)
local AssetList = require(InspectAndBuyFolder.Components.AssetList)
local InitialView = Roact.PureComponent:extend("InitialView")
function InitialView:render()
local view = self.props.view
local viewMapping = self._context[view]
local detailsInformation = self.props.detailsInformation
return Roact.createElement("ScrollingFrame", {
BackgroundTransparency = 1,
Position = UDim2.new(0, viewMapping.BorderPaddingSize, 0, 10),
Size = UDim2.new(1, -(2 * viewMapping.BorderPaddingSize), 1, -36),
ScrollBarThickness = 0,
ScrollingDirection = Enum.ScrollingDirection.Y,
Visible = not detailsInformation.viewingDetails,
Selectable = false,
}, {
UIListLayout = Roact.createElement("UIListLayout", {
Padding = UDim.new(0, 15),
HorizontalAlignment = Enum.HorizontalAlignment.Left,
SortOrder = Enum.SortOrder.LayoutOrder,
}),
Top = Roact.createElement("Frame", {
Size = UDim2.new(1, 0, 0, viewMapping.TopSizeY),
BackgroundTransparency = 1,
LayoutOrder = 2
}, {
UIListLayout = Roact.createElement("UIListLayout", {
Padding = UDim.new(0, 10),
SortOrder = Enum.SortOrder.LayoutOrder,
FillDirection = Enum.FillDirection.Horizontal,
}),
AvatarHeadShot = Roact.createElement(AvatarHeadShot),
PlayerNameLabel = Roact.createElement(PlayerNameLabel),
}),
AssetList = Roact.createElement(AssetList),
})
end
return RoactRodux.UNSTABLE_connect2(
function(state, props)
return {
view = state.view,
detailsInformation = state.detailsInformation,
}
end
)(InitialView)
@@ -0,0 +1,18 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local InspectAndBuyFolder = script.Parent.Parent
local TestContainer = require(InspectAndBuyFolder.Test.TestContainer)
local InitialView = require(script.Parent.InitialView)
it("should create and destroy without errors", function()
local element = Roact.createElement(TestContainer, nil, {
Roact.createElement(InitialView, {})
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,268 @@
local CorePackages = game:GetService("CorePackages")
local LocalizationService = game:GetService("LocalizationService")
local UserInputService = game:GetService("UserInputService")
local ContextActionService = game:GetService("ContextActionService")
local GuiService = game:GetService("GuiService")
local MarketplaceService = game:GetService("MarketplaceService")
local Players = game:GetService("Players")
local MouseIconOverrideService = require(CorePackages.InGameServices.MouseIconOverrideService)
local Roact = require(CorePackages.Roact)
local Rodux = require(CorePackages.Rodux)
local RoactRodux = require(CorePackages.RoactRodux)
local Symbol = require(CorePackages.Symbol)
local InspectAndBuyFolder = script.Parent.Parent
local SetDetailsInformation = require(InspectAndBuyFolder.Actions.SetDetailsInformation)
local SetTryingOnInfo = require(InspectAndBuyFolder.Actions.SetTryingOnInfo)
local Thunk = require(InspectAndBuyFolder.Thunk)
local Network = require(InspectAndBuyFolder.Services.Network)
local Analytics = require(InspectAndBuyFolder.Services.Analytics)
local InspectAndBuyReducer = require(InspectAndBuyFolder.Reducers.InspectAndBuyReducer)
local Container = require(InspectAndBuyFolder.Components.Container)
local Constants = require(InspectAndBuyFolder.Constants)
local CompactView = require(InspectAndBuyFolder.CompactView)
local WideView = require(InspectAndBuyFolder.WideView)
local WideLandscapeView = require(InspectAndBuyFolder.WideLandscapeView)
local SetGamepadEnabled = require(InspectAndBuyFolder.Actions.SetGamepadEnabled)
local SetView = require(InspectAndBuyFolder.Actions.SetView)
local SetPlayerId = require(InspectAndBuyFolder.Actions.SetPlayerId)
local SetPlayerName = require(InspectAndBuyFolder.Actions.SetPlayerName)
local SetLocale = require(InspectAndBuyFolder.Actions.SetLocale)
local SetItemBeingPurchased = require(InspectAndBuyFolder.Actions.SetItemBeingPurchased)
local UpdateStoreId = require(InspectAndBuyFolder.Actions.UpdateStoreId)
local GetAssetsFromHumanoidDescription = require(InspectAndBuyFolder.Thunks.GetAssetsFromHumanoidDescription)
local UpdateOwnedStatus = require(InspectAndBuyFolder.Thunks.UpdateOwnedStatus)
local GetCharacterModelFromUserId = require(InspectAndBuyFolder.Thunks.GetCharacterModelFromUserId)
local GetPlayerName = require(InspectAndBuyFolder.Thunks.GetPlayerName)
local FFlagFixInspectAndBuyPerformFetch = require(InspectAndBuyFolder.Flags.FFlagFixInspectAndBuyPerformFetch)
local COMPACT_VIEW_MAX_WIDTH = 600
local CURSOR_OVERRIDE_KEY = Symbol.named("OverrideCursorInspectMenu")
local BACK_BUTTON_KEY = "BackButtonInspectMenu"
local InspectAndBuy = Roact.PureComponent:extend("InspectAndBuy")
function InspectAndBuy:pushMouseIconOverride()
local input = UserInputService:GetLastInputType()
local isGamepad = input.Name:find('Gamepad')
if isGamepad then
MouseIconOverrideService.push(CURSOR_OVERRIDE_KEY, Enum.OverrideMouseIconBehavior.ForceHide)
else
MouseIconOverrideService.push(CURSOR_OVERRIDE_KEY, Enum.OverrideMouseIconBehavior.ForceShow)
end
end
function InspectAndBuy:popMouseIconOverride()
MouseIconOverrideService.pop(CURSOR_OVERRIDE_KEY)
end
function InspectAndBuy:configureInputType(lastInputType)
local input = lastInputType or UserInputService:GetLastInputType()
local isGamepad = input.Name:find('Gamepad')
if isGamepad then
self.state.store:dispatch(SetGamepadEnabled(true))
else
self.state.store:dispatch(SetGamepadEnabled(false))
end
end
function InspectAndBuy:updateView()
local viewportSize = workspace.CurrentCamera.ViewportSize
local view = Constants.View.Wide
if viewportSize.X < COMPACT_VIEW_MAX_WIDTH then
view = Constants.View.Compact
elseif viewportSize.X > viewportSize.Y and viewportSize.X < 800 then
view = Constants.View.WideLandscape
end
self.state.store:dispatch(SetView(view))
end
function InspectAndBuy:init()
local playerId = self.props.playerId
local playerName = self.props.playerName
local ctx = self.props.ctx
self.connections = {}
self.network = Network.new()
self.analytics = Analytics.new(playerId, ctx)
self.humanoidDescription = self.props.humanoidDescription
self.analytics.reportOpenInspectMenu()
self.state = {
store = Rodux.Store.new(InspectAndBuyReducer, {}, {
Thunk.middleware({
[Network] = self.network,
[Analytics] = self.analytics,
}),
}),
}
if FFlagFixInspectAndBuyPerformFetch then
self.state.store:dispatch(UpdateStoreId())
end
self._context[Constants.View.Compact] = CompactView
self._context[Constants.View.Wide] = WideView
self._context[Constants.View.WideLandscape] = WideLandscapeView
self.state.store:dispatch(SetPlayerName(playerName))
self.state.store:dispatch(SetLocale(LocalizationService.RobloxLocaleId))
if playerId then
self.state.store:dispatch(SetPlayerId(playerId))
end
if self.humanoidDescription then
self.state.store:dispatch(GetAssetsFromHumanoidDescription(self.humanoidDescription, false))
end
end
function InspectAndBuy:didMount()
local playerId = self.props.playerId
self.isMounted = true
self:updateView()
self:bindButtonB()
self:configureInputType()
self:pushMouseIconOverride()
local inputTypeChangedListener = UserInputService.LastInputTypeChanged:Connect(function(lastInputType)
self:configureInputType(lastInputType)
end)
local viewportSizeListener = workspace.CurrentCamera:GetPropertyChangedSignal("ViewportSize"):connect(function()
self:updateView()
end)
local localeListener = LocalizationService:GetPropertyChangedSignal("RobloxLocaleId"):connect(function()
self.state.store:dispatch(SetLocale(LocalizationService.RobloxLocaleId))
end)
local menuOpenedConnection = GuiService.MenuOpened:Connect(function()
self:popMouseIconOverride()
GuiService.SelectedCoreObject = nil
end)
local menuClosedConnection = GuiService.MenuClosed:Connect(function()
self:pushMouseIconOverride()
end)
-- Update the owned status of an item if a user purchases it.
local marketplaceServicePurchaseFinishedListener =
MarketplaceService.PromptPurchaseFinished:Connect(function(player, itemId, isPurchased)
local purchasedInformation = self.state.store:getState().itemBeingPurchased
if isPurchased and tostring(itemId) == purchasedInformation.itemId then
self.analytics.reportPurchaseSuccess(purchasedInformation.itemType, purchasedInformation.itemId)
self.state.store:dispatch(UpdateOwnedStatus(purchasedInformation.itemId, purchasedInformation.itemType))
end
self.state.store:dispatch(SetItemBeingPurchased(nil, nil))
end)
local storeChangedConnection = self.state.store.changed:connect(function(state, oldState)
self:update(state, oldState)
end)
table.insert(self.connections, viewportSizeListener)
table.insert(self.connections, localeListener)
table.insert(self.connections, menuOpenedConnection)
table.insert(self.connections, menuClosedConnection)
table.insert(self.connections, inputTypeChangedListener)
table.insert(self.connections, marketplaceServicePurchaseFinishedListener)
table.insert(self.connections, storeChangedConnection)
local localUserId = Players.LocalPlayer.UserId
self.state.store:dispatch(GetCharacterModelFromUserId(localUserId, true, function(localPlayerModel)
if self and self.isMounted then
self.localPlayerModel = localPlayerModel
local humanoidDescriptionForLocalPlayer = localPlayerModel.Humanoid.HumanoidDescription
if humanoidDescriptionForLocalPlayer then
self.state.store:dispatch(GetAssetsFromHumanoidDescription(humanoidDescriptionForLocalPlayer, true))
end
self:setState({
obtainedLocalPlayerModel = true,
})
end
end))
if playerId then
self.state.store:dispatch(GetCharacterModelFromUserId(playerId, false, function(playerModel)
if self and self.isMounted then
self.playerModel = playerModel
local humanoidDescription = self.playerModel.Humanoid.HumanoidDescription
if humanoidDescription then
self.state.store:dispatch(GetAssetsFromHumanoidDescription(humanoidDescription, false))
end
self:setState({
obtainedPlayerModel = true,
})
end
end))
self.state.store:dispatch(GetPlayerName(playerId))
end
end
function InspectAndBuy:update(newState, oldState)
if not newState.visible and oldState.visible then
ContextActionService:UnbindCoreAction(BACK_BUTTON_KEY)
elseif newState.visible and not oldState.visible then
self:bindButtonB()
end
end
function InspectAndBuy:willUnmount()
self.isMounted = false
for _, connection in pairs(self.connections) do
connection:disconnect()
end
ContextActionService:UnbindCoreAction(BACK_BUTTON_KEY)
self:popMouseIconOverride()
end
function InspectAndBuy:render()
local localPlayerModel = self.localPlayerModel
return Roact.createElement(RoactRodux.StoreProvider, {
store = self.state.store,
}, {
Container = Roact.createElement(Container, {
localPlayerModel = localPlayerModel,
}),
})
end
function InspectAndBuy:bindButtonB()
ContextActionService:BindCoreAction(BACK_BUTTON_KEY, function(actionName, inputState, inputObject)
if inputState == Enum.UserInputState.End and inputObject.KeyCode == Enum.KeyCode.ButtonB then
local viewingDetails = self.state.store:getState().detailsInformation.viewingDetails
if viewingDetails then
self.state.store:dispatch(SetDetailsInformation(false, nil))
self.state.store:dispatch(SetTryingOnInfo(false, nil))
else
GuiService:CloseInspectMenu()
end
elseif inputState == Enum.UserInputState.Begin
and inputObject.KeyCode == Enum.KeyCode.Escape or inputObject.KeyCode == Enum.KeyCode.ButtonStart then
-- Close the Inspect Menu, allow the ESC menu to open.
GuiService:CloseInspectMenu()
return Enum.ContextActionResult.Pass
end
return Enum.ContextActionResult.Sink
end,
false, Enum.KeyCode.ButtonB, Enum.KeyCode.Escape, Enum.KeyCode.ButtonStart)
end
return InspectAndBuy
@@ -0,0 +1,23 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local InspectAndBuyFolder = script.Parent.Parent
local TestContainer = require(InspectAndBuyFolder.Test.TestContainer)
local InspectAndBuy = require(script.Parent.InspectAndBuy)
local mockModel = require(InspectAndBuyFolder.Test.getMockModel)()
it("should create and destroy without errors", function()
local element = Roact.createElement(TestContainer, nil, {
Roact.createElement(InspectAndBuy, {
localPlayerModel = mockModel,
ctx = "tests",
})
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,70 @@
local CorePackages = game:GetService("CorePackages")
local CoreGui = game:GetService("CoreGui")
local InspectAndBuyFolder = script.Parent.Parent
local Roact = require(CorePackages.Roact)
local RoactRodux = require(CorePackages.RoactRodux)
local Colors = require(InspectAndBuyFolder.Colors)
local RobloxTranslator = require(CoreGui.RobloxGui.Modules.RobloxTranslator)
local NO_INVENTORY_KEY = "InGame.InspectMenu.Description.NoInventoryNotice"
local NoInventoryNotice = Roact.PureComponent:extend("NoInventoryNotice")
function NoInventoryNotice:render()
local locale = self.props.locale
local assets = self.props.assets
local isLoaded = self.props.isLoaded
return Roact.createElement("Frame", {
Size = UDim2.new(1, 0, 1, 0),
BackgroundTransparency = 1,
Visible = next(assets) == nil and isLoaded,
}, {
UIListLayout = Roact.createElement("UIListLayout", {
Padding = UDim.new(0, 15),
FillDirection = Enum.FillDirection.Vertical,
HorizontalAlignment = Enum.HorizontalAlignment.Center,
SortOrder = Enum.SortOrder.LayoutOrder,
VerticalAlignment = Enum.VerticalAlignment.Center,
}),
NotWearingImage = Roact.createElement("ImageLabel", {
AnchorPoint = Vector2.new(0.5, 0),
BackgroundTransparency = 1,
Size = UDim2.new(0.25, 0, 0.25, 0),
LayoutOrder = 1,
Image = "rbxasset://textures/ui/InspectMenu/ico_isnt-wearing.png",
ImageColor3 = Colors.White,
}, {
UIAspectRatioConstraint = Roact.createElement("UIAspectRatioConstraint", {
AspectRatio = 1,
})
}),
NotWearingText = Roact.createElement("TextLabel", {
BackgroundTransparency = 1,
Size = UDim2.new(0.8, 0, 0.3, 0),
LayoutOrder = 2,
Font = Enum.Font.Gotham,
Text = RobloxTranslator:FormatByKeyForLocale(NO_INVENTORY_KEY, locale),
TextScaled = true,
TextColor3 = Colors.White,
TextSize = 20,
TextXAlignment = Enum.TextXAlignment.Center,
TextYAlignment = Enum.TextYAlignment.Top,
TextWrapped = true,
}, {
UITextSizeConstraint = Roact.createElement("UITextSizeConstraint", {
MaxTextSize = 20,
})
})
})
end
return RoactRodux.UNSTABLE_connect2(
function(state, props)
return {
assets = state.assets,
locale = state.locale,
isLoaded = state.isLoaded,
}
end
)(NoInventoryNotice)
@@ -0,0 +1,18 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local InspectAndBuyFolder = script.Parent.Parent
local TestContainer = require(InspectAndBuyFolder.Test.TestContainer)
local NoInventoryNotice = require(script.Parent.NoInventoryNotice)
it("should create and destroy without errors", function()
local element = Roact.createElement(TestContainer, nil, {
Roact.createElement(NoInventoryNotice, {})
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,60 @@
local CorePackages = game:GetService("CorePackages")
local CoreGui = game:GetService("CoreGui")
local InspectAndBuyFolder = script.Parent.Parent
local Roact = require(CorePackages.Roact)
local RoactRodux = require(CorePackages.RoactRodux)
local Colors = require(InspectAndBuyFolder.Colors)
local RobloxTranslator = require(CoreGui.RobloxGui.Modules.RobloxTranslator)
local Notification = Roact.PureComponent:extend("Notification")
function Notification:render()
local locale = self.props.locale
local noticeKey = self.props.noticeKey
return Roact.createElement("Frame", {
Size = UDim2.new(1, -10, 0, 45),
LayoutOrder = 0,
BackgroundTransparency = 1,
BorderSizePixel = 0,
} , {
UIListLayout = Roact.createElement("UIListLayout", {
Padding = UDim.new(0, 10),
FillDirection = Enum.FillDirection.Horizontal,
SortOrder = Enum.SortOrder.LayoutOrder,
VerticalAlignment = Enum.VerticalAlignment.Center,
}),
ExclamationPoint = Roact.createElement("ImageLabel", {
Size = UDim2.new(0, 16, 0, 16),
BackgroundTransparency = 1,
Image = "rbxasset://textures/ui/InspectMenu/ico_alert_tilt.png",
ImageColor3 = Colors.White,
LayoutOrder = 1,
}),
PartOfBundleText = Roact.createElement("TextLabel", {
Size = UDim2.new(1, -30, 1, 0),
TextColor3 = Colors.White,
BorderSizePixel = 0,
BackgroundTransparency = 1,
LayoutOrder = 2,
TextWrapped = true,
TextScaled = true,
TextSize = 12,
TextXAlignment = Enum.TextXAlignment.Left,
Font = Enum.Font.Gotham,
Text = RobloxTranslator:FormatByKeyForLocale(noticeKey, locale),
}, {
UITextSizeConstraint = Roact.createElement("UITextSizeConstraint", {
MaxTextSize = 16,
})
})
})
end
return RoactRodux.UNSTABLE_connect2(
function(state, props)
return {
locale = state.locale
}
end
)(Notification)
@@ -0,0 +1,20 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local InspectAndBuyFolder = script.Parent.Parent
local TestContainer = require(InspectAndBuyFolder.Test.TestContainer)
local Notification = require(script.Parent.Notification)
it("should create and destroy without errors", function()
local element = Roact.createElement(TestContainer, nil, {
Roact.createElement(Notification, {
noticeKey = "InGame.InspectMenu.Description.SingleBundleNotice",
})
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,52 @@
local CorePackages = game:GetService("CorePackages")
local CoreGui = game:GetService("CoreGui")
local InspectAndBuyFolder = script.Parent.Parent
local Roact = require(CorePackages.Roact)
local RoactRodux = require(CorePackages.RoactRodux)
local Colors = require(InspectAndBuyFolder.Colors)
local RobloxTranslator = require(CoreGui.RobloxGui.Modules.RobloxTranslator)
local PlayerNameLabel = Roact.PureComponent:extend("PlayerNameLabel")
local INVENTORY_KEY = "InGame.InspectMenu.Label.Avatar"
function PlayerNameLabel:render()
local locale = self.props.locale
local view = self.props.view
local viewMapping = self._context[view]
local playerName = self.props.playerName
local sizeX = viewMapping.AvatarHeadShotSize + (viewMapping.BorderPaddingSize * 2) + 10
local playerNameText
if playerName == "" then
playerNameText = ""
else
playerNameText = RobloxTranslator:FormatByKeyForLocale(INVENTORY_KEY, locale, { PLAYER_NAME = playerName })
end
return Roact.createElement("TextLabel", {
Size = UDim2.new(1, -sizeX, 0, viewMapping.TopSizeY),
BackgroundTransparency = 1,
TextColor3 = Colors.White,
TextScaled = true,
Text = playerNameText,
TextSize = 36,
Font = Enum.Font.GothamBlack,
TextXAlignment = Enum.TextXAlignment.Left,
LayoutOrder = 2,
}, {
TextSizeConstraint = Roact.createElement("UITextSizeConstraint", {
MaxTextSize = 36,
}),
})
end
return RoactRodux.UNSTABLE_connect2(
function(state, props)
return {
view = state.view,
playerName = state.playerName,
locale = state.locale
}
end
)(PlayerNameLabel)
@@ -0,0 +1,18 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local InspectAndBuyFolder = script.Parent.Parent
local TestContainer = require(InspectAndBuyFolder.Test.TestContainer)
local PlayerNameLabel = require(script.Parent.PlayerNameLabel)
it("should create and destroy without errors", function()
local element = Roact.createElement(TestContainer, nil, {
Roact.createElement(PlayerNameLabel, {})
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,112 @@
local CorePackages = game:GetService("CorePackages")
local CoreGui = game:GetService("CoreGui")
local RobloxTranslator = require(CoreGui.RobloxGui.Modules.RobloxTranslator)
local Roact = require(CorePackages.Roact)
local RoactRodux = require(CorePackages.RoactRodux)
local InspectAndBuyFolder = script.Parent.Parent
local Colors = require(InspectAndBuyFolder.Colors)
local SetTryingOnInfo = require(InspectAndBuyFolder.Actions.SetTryingOnInfo)
local TryOnItem = require(InspectAndBuyFolder.Thunks.TryOnItem)
local getSelectionImageObjectRounded = require(InspectAndBuyFolder.getSelectionImageObjectRounded)
local TRY_ON_KEY = "InGame.InspectMenu.Action.TryOn"
local TAKE_OFF_KEY = "InGame.InspectMenu.Action.TakeOff"
local TEXT_SIZE = 16
local BUTTON_PADDING = 10
local ROBLOX_CREATOR_ID = "1"
local TryOnButton = Roact.PureComponent:extend("TryOnButton")
function TryOnButton:init()
self.selectedImage = getSelectionImageObjectRounded()
end
function TryOnButton:render()
local tryOnItem = self.props.tryOnItem
local takeOffItem = self.props.takeOffItem
local tryingOn = self.props.tryingOnInfo.tryingOn
local showTryOn = self.props.showTryOn
local locale = self.props.locale
local assetInfo = self.props.assetInfo
local creatorId = assetInfo and assetInfo.creatorId or 0
local partOfBundle = self.props.partOfBundle
local bundleId = self.props.bundleId
local tryOnButtonRef = self.props.tryOnButtonRef
local sizeXAdjustment = creatorId == ROBLOX_CREATOR_ID and -32 or -BUTTON_PADDING / 2
local tryOnTextKey
if tryingOn then
tryOnTextKey = TAKE_OFF_KEY
else
tryOnTextKey = TRY_ON_KEY
end
return Roact.createElement("ImageLabel", {
AnchorPoint = Vector2.new(0.5, 0),
BackgroundTransparency = 1,
Size = UDim2.new(0.5, sizeXAdjustment, 0, 44),
Visible = showTryOn,
LayoutOrder = 2,
Image = "rbxasset://textures/ui/InspectMenu/Button_outline.png",
ScaleType = Enum.ScaleType.Slice,
SliceCenter = Rect.new(5, 5, 120, 20),
}, {
SelectionGainedImage = Roact.createElement("ImageLabel", {
BackgroundTransparency = 1,
Size = UDim2.new(1, 0, 1, 0),
Visible = false,
Image = "rbxasset://textures/ui/InspectMenu/Button_white.png",
ImageColor3 = Colors.Pumice,
}),
TryOnTextKey = Roact.createElement("TextButton", {
BackgroundTransparency = 1,
Size = UDim2.new(1, 0, 1, 0),
Text = RobloxTranslator:FormatByKeyForLocale(tryOnTextKey, locale),
Font = Enum.Font.Gotham,
TextSize = TEXT_SIZE,
TextColor3 = Colors.White,
SelectionImageObject = self.selectedImage,
ZIndex = 2,
[Roact.Event.SelectionGained] = function(rbx)
rbx.Parent.SelectionGainedImage.Visible = true
rbx.TextColor3 = Colors.Carbon
end,
[Roact.Event.SelectionLost] = function(rbx)
rbx.Parent.SelectionGainedImage.Visible = false
rbx.TextColor3 = Colors.White
end,
[Roact.Ref] = tryOnButtonRef,
[Roact.Event.Activated] = function()
if tryingOn then
takeOffItem()
else
tryOnItem(true, assetInfo.assetId, assetInfo.assetTypeId, partOfBundle, bundleId)
end
end,
})
})
end
return RoactRodux.UNSTABLE_connect2(
function(state, props)
local assetId = state.detailsInformation.assetId
return {
locale = state.locale,
view = state.view,
assetInfo = state.assets[assetId],
bundleInfo = state.bundles,
tryingOnInfo = state.tryingOnInfo,
}
end,
function(dispatch)
return {
tryOnItem = function(tryingOn, assetId, assetTypeId, partOfBundle, bundleId)
dispatch(TryOnItem(tryingOn, assetId, assetTypeId, partOfBundle, bundleId))
end,
takeOffItem = function()
dispatch(SetTryingOnInfo(false, nil, nil))
end,
}
end
)(TryOnButton)
@@ -0,0 +1,20 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local InspectAndBuyFolder = script.Parent.Parent
local TestContainer = require(InspectAndBuyFolder.Test.TestContainer)
local TryOnButton = require(script.Parent.TryOnButton)
it("should create and destroy without errors", function()
local element = Roact.createElement(TestContainer, nil, {
Roact.createElement(TryOnButton, {
tryOnButtonRef = Roact.createRef(),
})
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,118 @@
local CorePackages = game:GetService("CorePackages")
local Cryo = require(CorePackages.Cryo)
local InspectAndBuyFolder = script.Parent.Parent
local Roact = require(CorePackages.Roact)
local RoactRodux = require(CorePackages.RoactRodux)
local Constants = require(InspectAndBuyFolder.Constants)
local AvatarViewport = require(InspectAndBuyFolder.Components.AvatarViewport)
local GetHumanoidDescriptionFromCostumeId = require(InspectAndBuyFolder.Thunks.GetHumanoidDescriptionFromCostumeId)
local TryOnViewport = Roact.PureComponent:extend("TryOnViewport")
local function isPartOfBundle(assetInfo)
return assetInfo and assetInfo.bundlesAssetIsIn and #assetInfo.bundlesAssetIsIn == 1
end
function TryOnViewport:init()
self.model = self.props.localPlayerModel
self.model.HumanoidRootPart.Anchored = true
self.humanoidDescriptions = {}
self.humanoidDescriptionForLocalPlayer = self.model.Humanoid.HumanoidDescription
self.state = {
obtainedHumanoidDescriptions = {}
}
end
function TryOnViewport:didMount()
self.isMounted = true
end
function TryOnViewport:didUpdate(prevProps)
local assetInfo = self.props.assetInfo
local bundles = self.props.bundles
local tryingOnInfo = self.props.tryingOnInfo
local prevTryingOnInfo = prevProps.tryingOnInfo
if tryingOnInfo ~= prevTryingOnInfo and tryingOnInfo.tryingOn and isPartOfBundle(assetInfo) then
local bundleId = assetInfo.bundlesAssetIsIn[1]
local costumeId = bundles[bundleId].costumeId
-- Get the humanoid description of this bundle's costume.
self.props.getHumanoidDescriptionFromCostumeId(costumeId, function(humanoidDescription)
if self and self.isMounted then
self.humanoidDescriptions[costumeId] = humanoidDescription
self:setState({
obtainedHumanoidDescriptions = Cryo.Dictionary.join(self.state.obtainedHumanoidDescriptions, {[costumeId] = true})
})
end
end)
end
end
function TryOnViewport:render()
local assetInfo = self.props.assetInfo
local bundles = self.props.bundles
local tryingOnInfo = self.props.tryingOnInfo
local humanoidDescription = self.humanoidDescriptionForLocalPlayer:Clone()
local visible = tryingOnInfo.tryingOn
local setScrollingEnabled = self.props.setScrollingEnabled
if tryingOnInfo and tryingOnInfo.tryingOn then
if isPartOfBundle(assetInfo) then
local bundleId = assetInfo.bundlesAssetIsIn[1]
local costumeId = bundles[bundleId].costumeId
local costumeHumanoidDescription = self.humanoidDescriptions[costumeId]
visible = self.state.obtainedHumanoidDescriptions[costumeId] == true
if costumeHumanoidDescription then
-- Overwrite the inspecter's assets with any asset from the costume.
for _, value in pairs(Constants.HumanoidDescriptionAssetNames) do
if tonumber(costumeHumanoidDescription[value]) and tostring(costumeHumanoidDescription[value]) ~= "0" then
humanoidDescription[value] = costumeHumanoidDescription[value]
end
end
end
else
humanoidDescription[Constants.HumanoidDescriptionIdToName[tryingOnInfo.assetTypeId]] = tryingOnInfo.assetId
end
end
return Roact.createElement("Frame", {
BackgroundTransparency = 1,
Size = UDim2.new(1, 0, 0, 300),
LayoutOrder = 2,
Visible = visible,
}, {
AvatarViewport = Roact.createElement(AvatarViewport, {
size = UDim2.new(0.75, 0, 1, 0),
position = UDim2.new(0.5, 0, 0.5, 0),
humanoidDescription = humanoidDescription,
model = self.model,
visible = true,
backgroundTransparency = 1,
setScrollingEnabled = setScrollingEnabled,
})
})
end
function TryOnViewport:willUnmount()
self.isMounted = false
end
return RoactRodux.UNSTABLE_connect2(
function(state, props)
local assetId = state.detailsInformation.assetId
return {
assetInfo = state.assets[assetId],
bundles = state.bundles,
tryingOnInfo = state.tryingOnInfo,
}
end,
function(dispatch)
return {
getHumanoidDescriptionFromCostumeId = function(costumeId, callback)
return dispatch(GetHumanoidDescriptionFromCostumeId(costumeId, callback))
end,
}
end
)(TryOnViewport)
@@ -0,0 +1,22 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local InspectAndBuyFolder = script.Parent.Parent
local TestContainer = require(InspectAndBuyFolder.Test.TestContainer)
local TryOnViewport = require(script.Parent.TryOnViewport)
local mockModel = require(InspectAndBuyFolder.Test.getMockModel)()
it("should create and destroy without errors", function()
local element = Roact.createElement(TestContainer, nil, {
Roact.createElement(TryOnViewport, {
localPlayerModel = mockModel,
})
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,88 @@
local Constants = {
NotificationKeys = {
MultipleBundleNoticeKey = "InGame.InspectMenu.Description.MultipleBundlesNotice",
SingleBundleNoticeKey = "InGame.InspectMenu.Description.SingleBundleNotice",
LimitedItemNoticeKey = "InGame.InspectMenu.Description.LimitedNotice",
},
ItemType = {
Asset = "Asset",
Bundle = "Bundle",
},
View = {
Compact = 0,
Wide = 1,
WideLandscape = 2,
},
HumanoidDescriptionAssetNames = {
BackAccessory = "BackAccessory",
FaceAccessory = "FaceAccessory",
FrontAccessory = "FrontAccessory",
HairAccessory = "HairAccessory",
HatAccessory = "HatAccessory",
NeckAccessory = "NeckAccessory",
ShouldersAccessory = "ShouldersAccessory",
WaistAccessory = "WaistAccessory",
Face = "Face",
Head = "Head",
LeftArm = "LeftArm",
LeftLeg = "LeftLeg",
RightArm = "RightArm",
RightLeg = "RightLeg",
Torso = "Torso",
GraphicTShirt = "GraphicTShirt",
Pants = "Pants",
Shirt = "Shirt",
ClimbAnimation = "ClimbAnimation",
FallAnimation = "FallAnimation",
IdleAnimation = "IdleAnimation",
JumpAnimation = "JumpAnimation",
RunAnimation = "RunAnimation",
SwimAnimation = "SwimAnimation",
WalkAnimation = "WalkAnimation",
},
HumanoidDescriptionIdToName = {
["2"] = "Shirt",
["8"] = "HatAccessory",
["41"] = "HairAccessory",
["42"] = "FaceAccessory",
["43"] = "NeckAccessory",
["44"] = "ShouldersAccessory",
["45"] = "FrontAccessory",
["46"] = "BackAccessory",
["47"] = "WaistAccessory",
["11"] = "Shirt",
["12"] = "Pants",
["19"] = "Gear",
["17"] = "Head",
["18"] = "Face",
["27"] = "Torso",
["28"] = "RightArm",
["29"] = "LeftArm",
["30"] = "LeftLeg",
["31"] = "RightLeg",
["48"] = "ClimbAnimation",
["50"] = "FallAnimation",
["51"] = "IdleAnimation",
["52"] = "JumpAnimation",
["53"] = "RunAnimation",
["54"] = "SwimAnimation",
["55"] = "WalkAnimation",
},
AnimationAssetTypes = {
["48"] = 48,
["50"] = 50,
["51"] = 51,
["52"] = 52,
["53"] = 53,
["54"] = 54,
["55"] = 55,
["61"] = 61, -- Emotes
},
}
return Constants
@@ -0,0 +1 @@
return game:DefineFastFlag("FixInspectAndBuyPerformFetch", false)
@@ -0,0 +1,10 @@
--[[
A function to return a fake ID, used for testing
]]
local lastId = 0
return function()
lastId = lastId + 1
return ("MOCK-%d"):format(lastId)
end
@@ -0,0 +1,5 @@
{
"lint": {
"ImplicitReturn": "fatal"
}
}
@@ -0,0 +1,118 @@
--[[
Model for an Asset (e.g. Hat).
{
name = string,
assetId = string,
assetTypeId = string,
creatorId = string,
creatorName = string,
owned = bool,
isForSale = bool,
description = string,
price = string,
productId = string,
isLimited = bool,
bundlesAssetIsIn = table,
numFavorites = int,
}
]]
local CoreGui = game:GetService("CoreGui")
local MockId = require(script.Parent.Parent.MockId)
local GetFFlagLuaPremiumCatalogIGIAB
= require(CoreGui.RobloxGui.Modules.Flags.GetFFlagLuaPremiumCatalogIGIAB)
local AssetInfo = {}
function AssetInfo.new()
local self = {}
return self
end
function AssetInfo.mock()
local self = AssetInfo.new()
self.name = ""
self.assetId = MockId()
self.assetTypeId = ""
self.creatorId = ""
self.creatorName = ""
self.owned = false
self.isForSale = false
self.description = ""
self.price = ""
self.productId = ""
self.isLimited = false
self.bundlesAssetIsIn = {}
self.numFavorites = 0
self.minimumMembershipLevel = 0
return self
end
function AssetInfo.fromGetProductInfo(assetInfo)
local newAsset = AssetInfo.new()
newAsset.name = assetInfo.Name
newAsset.description = assetInfo.Description
newAsset.price = assetInfo.PriceInRobux
newAsset.creatorId = tostring(assetInfo.Creator.Id)
newAsset.creatorName = assetInfo.Creator.Name
newAsset.assetId = tostring(assetInfo.AssetId)
newAsset.assetTypeId = tostring(assetInfo.AssetTypeId)
newAsset.productId = tostring(assetInfo.ProductId)
newAsset.isForSale = assetInfo.IsForSale
newAsset.isLimited = assetInfo.IsLimited or assetInfo.IsLimitedUnique
return newAsset
end
function AssetInfo.fromHumanoidDescription(id)
local newAsset = AssetInfo.new()
newAsset.assetId = tostring(id)
return newAsset
end
function AssetInfo.fromGetAssetBundles(assetId, bundleIds)
local newAsset = AssetInfo.new()
newAsset.assetId = tostring(assetId)
newAsset.bundlesAssetIsIn = bundleIds
return newAsset
end
function AssetInfo.fromGetAssetFavoriteCount(assetId, numFavorites)
local newAsset = AssetInfo.new()
newAsset.assetId = tostring(assetId)
newAsset.numFavorites = numFavorites
return newAsset
end
function AssetInfo.fromGetEconomyProductInfo(asset, isOwned, price, isForSale, premiumPricing)
local newAsset = AssetInfo.new()
newAsset.assetId = tostring(asset.assetId)
newAsset.owned = isOwned
if price then
newAsset.price = price
end
newAsset.isForSale = isForSale
if GetFFlagLuaPremiumCatalogIGIAB() then
newAsset.premiumPricing = premiumPricing
end
return newAsset
end
function AssetInfo.fromPurchaseSuccess(assetId)
local newAsset = AssetInfo.new()
newAsset.assetId = tostring(assetId)
newAsset.owned = true
return newAsset
end
return AssetInfo
@@ -0,0 +1,94 @@
--[[
Model for a Bundle
{
name = string,
bundleId = string,
description = string,
bundleType = string,
creatorId = string,
creatorName = string,
productId = string,
isForSale = bool,
price = string,
assetIds = table,
numFavorites = int,
costumeId = string,
owned = bool
}
]]
local MockId = require(script.Parent.Parent.MockId)
local BundleInfo = {}
function BundleInfo.new()
local self = {}
return self
end
function BundleInfo.mock()
local self = BundleInfo.new()
self.name = ""
self.bundleId = MockId()
self.description = ""
self.bundleType = ""
self.creatorId = ""
self.creatorName = ""
self.productId = ""
self.isForSale = false
self.price = ""
self.assetIds = {}
self.numFavorites = 0
self.costumeId = ""
self.owned = false
return self
end
function BundleInfo.fromGetAssetBundles(bundleInfo)
local newBundle = BundleInfo.new()
newBundle.name = bundleInfo.name
newBundle.bundleId = tostring(bundleInfo.id)
newBundle.description = bundleInfo.description
newBundle.bundleType = bundleInfo.bundleType
newBundle.creatorId = tostring(bundleInfo.creator.id)
newBundle.creatorName = bundleInfo.creator.name
newBundle.productId = tostring(bundleInfo.product.id)
newBundle.assetIds = {}
for _, asset in pairs(bundleInfo.items) do
if asset.type == "UserOutfit" then
newBundle.costumeId = tostring(asset.id)
else
table.insert(newBundle.assetIds, tostring(asset.id))
end
end
return newBundle
end
function BundleInfo.fromGetBundleFavoriteCount(bundleId, numFavorites)
local newBundle = BundleInfo.new()
newBundle.bundleId = tostring(bundleId)
newBundle.numFavorites = numFavorites
return newBundle
end
function BundleInfo.fromGetEconomyProductInfo(isOwned, bundleId, price, isForSale)
local newBundle = BundleInfo.new()
newBundle.bundleId = tostring(bundleId)
newBundle.owned = isOwned
newBundle.price = price
newBundle.isForSale = isForSale
return newBundle
end
function BundleInfo.fromPurchaseSuccess(bundleId)
local newBundle = BundleInfo.new()
newBundle.bundleId = tostring(bundleId)
newBundle.owned = true
return newBundle
end
return BundleInfo
@@ -0,0 +1,5 @@
{
"lint": {
"ImplicitReturn": "fatal"
}
}
@@ -0,0 +1,42 @@
local CorePackages = game:GetService("CorePackages")
local Cryo = require(CorePackages.Cryo)
local Rodux = require(CorePackages.Rodux)
local InspectAndBuyFolder = script.Parent.Parent
local AssetInfo = require(InspectAndBuyFolder.Models.AssetInfo)
local SetAssets = require(InspectAndBuyFolder.Actions.SetAssets)
local SetBundlesAssetIsPartOf = require(InspectAndBuyFolder.Actions.SetBundlesAssetIsPartOf)
return Rodux.createReducer(
{}
, {
--[[
Set a group of assets, joining with any existing assets.
]]
[SetAssets.name] = function(state, action)
local assets = {}
for _, asset in ipairs(action.assets) do
assert(asset.assetId ~= nil, "Expected an asset id when setting an asset's information.")
local currentAsset = state[asset.assetId] or {}
assets[asset.assetId] = Cryo.Dictionary.join(currentAsset, asset)
end
assets = Cryo.Dictionary.join(state, assets)
return assets
end,
--[[
Sets the list of bundles an asset is part of. At this point
the asset should already exist in the store. This is called
after the user navigates to the details page.
]]
[SetBundlesAssetIsPartOf.name] = function(state, action)
local assetId = tostring(action.assetId)
local bundles = action.bundleIds
local currentAsset = state[assetId] or {}
local asset = AssetInfo.fromGetAssetBundles(assetId, bundles)
asset = Cryo.Dictionary.join(currentAsset, asset)
return Cryo.Dictionary.join(state, {[assetId] = asset })
end,
})
@@ -0,0 +1,70 @@
return function()
local InspectAndBuyFolder = script.Parent.Parent
local SetAssets = require(InspectAndBuyFolder.Actions.SetAssets)
local SetBundlesAssetIsPartOf = require(InspectAndBuyFolder.Actions.SetBundlesAssetIsPartOf)
local Assets = require(script.Parent.Assets)
local AssetInfo = require(InspectAndBuyFolder.Models.AssetInfo)
local MOCK_ASSET_1 = AssetInfo.mock()
MOCK_ASSET_1.assetId = "123"
local MOCK_ASSET_2 = AssetInfo.mock()
MOCK_ASSET_2.assetId = "456"
local MOCK_NAME = "Mock Name"
local function countKeys(t)
local count = 0
for _ in pairs(t) do
count = count + 1
end
return count
end
describe("SetAssets", function()
it("should set a single asset", function()
local newState = Assets(nil, SetAssets({MOCK_ASSET_1}))
expect(newState[MOCK_ASSET_1.assetId].assetId).to.equal(MOCK_ASSET_1.assetId)
expect(countKeys(newState)).to.equal(1)
end)
it("should set a list of assets", function()
local newState = Assets(nil, SetAssets({MOCK_ASSET_1, MOCK_ASSET_2}))
expect(newState[MOCK_ASSET_1.assetId].assetId).to.equal(MOCK_ASSET_1.assetId)
expect(newState[MOCK_ASSET_2.assetId].assetId).to.equal(MOCK_ASSET_2.assetId)
expect(countKeys(newState)).to.equal(2)
end)
it("should update an asset", function()
local newState = Assets(nil, SetAssets({MOCK_ASSET_1}))
expect(newState[MOCK_ASSET_1.assetId].assetId).to.equal(MOCK_ASSET_1.assetId)
local updatedAsset = MOCK_ASSET_1
updatedAsset.name = MOCK_NAME
newState = Assets(newState, SetAssets({updatedAsset}))
expect(newState[MOCK_ASSET_1.assetId].assetId).to.equal(MOCK_ASSET_1.assetId)
expect(newState[MOCK_ASSET_1.assetId].name).to.equal(MOCK_NAME)
expect(countKeys(newState)).to.equal(1)
end)
end)
describe("SetBundlesAssetIsPartOf", function()
it("should set the bundles an asset is part of", function()
local newState = Assets(nil, SetAssets({MOCK_ASSET_1}))
newState = Assets(newState, SetBundlesAssetIsPartOf(
MOCK_ASSET_1.assetId,
{"1", "2", "3"}
))
expect(newState[MOCK_ASSET_1.assetId].assetId).to.equal(MOCK_ASSET_1.assetId)
expect(newState[MOCK_ASSET_1.assetId].bundlesAssetIsIn[1]).to.equal("1")
expect(#newState[MOCK_ASSET_1.assetId].bundlesAssetIsIn).to.equal(3)
end)
end)
end
@@ -0,0 +1,31 @@
local CorePackages = game:GetService("CorePackages")
local Cryo = require(CorePackages.Cryo)
local Rodux = require(CorePackages.Rodux)
local InspectAndBuyFolder = script.Parent.Parent
local SetBundles = require(InspectAndBuyFolder.Actions.SetBundles)
--[[
For V1, in order to prevent going through all pages of the
/v1/assets/{assetId}/bundles endpoint for Rthro assets we
manually populate Rthro animation bundle data. In the future
we only need to get a few bundles at a time as a user scrolls
through the list of bundles.
]]
return Rodux.createReducer(
{}
, {
--[[
Set/update bundle information.
]]
[SetBundles.name] = function(state, action)
local bundles = {}
for _, bundle in ipairs(action.bundles) do
assert(bundle.bundleId ~= nil, "Expected a bundle id when setting a bundle's information.")
local currentBundle = state[bundle.bundleId] or {}
bundles[bundle.bundleId] = Cryo.Dictionary.join(currentBundle, bundle)
end
return Cryo.Dictionary.join(state, bundles)
end,
})
@@ -0,0 +1,60 @@
return function()
local InspectAndBuyFolder = script.Parent.Parent
local SetBundles = require(InspectAndBuyFolder.Actions.SetBundles)
local Bundles = require(script.Parent.Bundles)
local BundleInfo = require(InspectAndBuyFolder.Models.BundleInfo)
local MOCK_BUNDLE_1 = BundleInfo.mock()
MOCK_BUNDLE_1.bundleId = "123"
local MOCK_BUNDLE_2 = BundleInfo.mock()
MOCK_BUNDLE_2.bundleId = "456"
local function countKeys(t)
local count = 0
for _ in pairs(t) do
count = count + 1
end
return count
end
describe("SetBundles", function()
it("should set a bundle's information", function()
local newState = Bundles(nil, SetBundles({
[1] = MOCK_BUNDLE_1,
}))
expect(newState[MOCK_BUNDLE_1.bundleId].bundleId).to.equal(MOCK_BUNDLE_1.bundleId)
expect(countKeys(newState)).to.equal(1)
end)
it("should set multiple bundles information", function()
local newState = Bundles(nil, SetBundles({
[1] = MOCK_BUNDLE_1,
[2] = MOCK_BUNDLE_2,
}))
expect(newState[MOCK_BUNDLE_1.bundleId].bundleId).to.equal(MOCK_BUNDLE_1.bundleId)
expect(newState[MOCK_BUNDLE_2.bundleId].bundleId).to.equal(MOCK_BUNDLE_2.bundleId)
expect(countKeys(newState)).to.equal(2)
end)
it("should update a bundle's information", function()
local bundle1 = MOCK_BUNDLE_1
local numFavorites = 500
local newState = Bundles(nil, SetBundles({
[1] = bundle1,
}))
expect(newState[bundle1.bundleId].bundleId).to.equal(bundle1.bundleId)
bundle1.numFavorites = numFavorites
newState = Bundles(newState, SetBundles({
[1] = bundle1,
}))
expect(newState[bundle1.bundleId].numFavorites).to.equal(numFavorites)
expect(countKeys(newState)).to.equal(1)
end)
end)
end
@@ -0,0 +1,22 @@
local CorePackages = game:GetService("CorePackages")
local Rodux = require(CorePackages.Rodux)
local InspectAndBuyFolder = script.Parent.Parent
local SetDetailsInformation = require(InspectAndBuyFolder.Actions.SetDetailsInformation)
local DEFAULT_INFORMATION = {
viewingDetails = false,
assetId = nil,
}
return Rodux.createReducer(
DEFAULT_INFORMATION
, {
[SetDetailsInformation.name] = function(state, action)
local newInformation = {
viewingDetails = action.viewingDetails,
assetId = action.assetId,
}
return newInformation
end,
})
@@ -0,0 +1,27 @@
return function()
local InspectAndBuyFolder = script.Parent.Parent
local SetDetailsInformation = require(InspectAndBuyFolder.Actions.SetDetailsInformation)
local DetailsInformation = require(script.Parent.DetailsInformation)
local function countKeys(t)
local count = 0
for _ in pairs(t) do
count = count + 1
end
return count
end
--[[
Note: Rthro Animation Bundle is by default in the Bundles reducer.
]]
describe("SetDetailsInformation", function()
it("should set the details information", function()
local assetId = "123"
local newState = DetailsInformation(nil, SetDetailsInformation(true, assetId))
expect(newState.viewingDetails).to.equal(true)
expect(newState.assetId).to.equal(assetId)
expect(countKeys(newState)).to.equal(2)
end)
end)
end
@@ -0,0 +1,20 @@
local CorePackages = game:GetService("CorePackages")
-- local Cryo = require(CorePackages.Cryo)
local Rodux = require(CorePackages.Rodux)
local InspectAndBuyFolder = script.Parent.Parent
local SetEquippedAssets = require(InspectAndBuyFolder.Actions.SetEquippedAssets)
return Rodux.createReducer(
{}
, {
--[[
Set the inspector's equipped assets.
]]
[SetEquippedAssets.name] = function(state, action)
local equippedAssets = {}
for _, asset in ipairs(action.equippedAssets) do
equippedAssets[tostring(asset.assetId)] = true
end
return equippedAssets
end,
})
@@ -0,0 +1,34 @@
return function()
local InspectAndBuyFolder = script.Parent.Parent
local SetEquippedAssets = require(InspectAndBuyFolder.Actions.SetEquippedAssets)
local EquippedAssets = require(script.Parent.EquippedAssets)
local MOCK_ASSET = {
assetId = "123"
}
local MOCK_ASSET2 = {
assetId = "456"
}
local function countKeys(t)
local count = 0
for _ in pairs(t) do
count = count + 1
end
return count
end
describe("SetEquippedAssets", function()
it("should set a list of assets as equipped", function()
local equippedAssets = {
[1] = MOCK_ASSET,
[2] = MOCK_ASSET2,
}
local newState = EquippedAssets(nil, SetEquippedAssets(equippedAssets))
expect(newState[MOCK_ASSET.assetId]).to.equal(true)
expect(newState[MOCK_ASSET2.assetId]).to.equal(true)
expect(newState["TEST"]).to.never.equal(true)
expect(countKeys(newState)).to.equal(2)
end)
end)
end
@@ -0,0 +1,15 @@
local CorePackages = game:GetService("CorePackages")
local Rodux = require(CorePackages.Rodux)
local Cryo = require(CorePackages.Cryo)
local InspectAndBuyFolder = script.Parent.Parent
local SetFavoriteAsset = require(InspectAndBuyFolder.Actions.SetFavoriteAsset)
return Rodux.createReducer(
{}
, {
[SetFavoriteAsset.name] = function(state, action)
local id = action.id
local isFavorite = action.isFavorite
return Cryo.Dictionary.join(state, {[id] = isFavorite})
end
})
@@ -0,0 +1,31 @@
return function()
local InspectAndBuyFolder = script.Parent.Parent
local SetFavoriteAsset = require(InspectAndBuyFolder.Actions.SetFavoriteAsset)
local FavoriteAssets = require(script.Parent.FavoriteAssets)
local MOCK_ID = "123"
local function countKeys(t)
local count = 0
for _ in pairs(t) do
count = count + 1
end
return count
end
describe("SetFavoriteAsset", function()
it("should add a new asset as favorited/unfavorited", function()
local newState = FavoriteAssets(nil, SetFavoriteAsset(MOCK_ID, true))
expect(newState[MOCK_ID]).to.equal(true)
expect(countKeys(newState)).to.equal(1)
end)
it("should update the favorite status of an existing asset", function()
local newState = FavoriteAssets(nil, SetFavoriteAsset(MOCK_ID, true))
expect(newState[MOCK_ID]).to.equal(true)
newState = FavoriteAssets(newState, SetFavoriteAsset(MOCK_ID, false))
expect(newState[MOCK_ID]).to.equal(false)
expect(countKeys(newState)).to.equal(1)
end)
end)
end
@@ -0,0 +1,15 @@
local CorePackages = game:GetService("CorePackages")
local Rodux = require(CorePackages.Rodux)
local Cryo = require(CorePackages.Cryo)
local InspectAndBuyFolder = script.Parent.Parent
local SetFavoriteBundle = require(InspectAndBuyFolder.Actions.SetFavoriteBundle)
return Rodux.createReducer(
{}
, {
[SetFavoriteBundle.name] = function(state, action)
local id = action.id
local isFavorite = action.isFavorite
return Cryo.Dictionary.join(state, {[id] = isFavorite})
end
})
@@ -0,0 +1,31 @@
return function()
local InspectAndBuyFolder = script.Parent.Parent
local SetFavoriteBundle = require(InspectAndBuyFolder.Actions.SetFavoriteBundle)
local FavoriteBundles = require(script.Parent.FavoriteBundles)
local MOCK_ID = "123"
local function countKeys(t)
local count = 0
for _ in pairs(t) do
count = count + 1
end
return count
end
describe("SetFavoriteBundle", function()
it("should add a new bundle as favorited/unfavorited", function()
local newState = FavoriteBundles(nil, SetFavoriteBundle(MOCK_ID, true))
expect(newState[MOCK_ID]).to.equal(true)
expect(countKeys(newState)).to.equal(1)
end)
it("should update the favorite status of an existing bundle", function()
local newState = FavoriteBundles(nil, SetFavoriteBundle(MOCK_ID, true))
expect(newState[MOCK_ID]).to.equal(true)
newState = FavoriteBundles(newState, SetFavoriteBundle(MOCK_ID, false))
expect(newState[MOCK_ID]).to.equal(false)
expect(countKeys(newState)).to.equal(1)
end)
end)
end
@@ -0,0 +1,10 @@
local CorePackages = game:GetService("CorePackages")
local Reducers = script.Parent
local Rodux = require(CorePackages.Rodux)
local FavoriteAssets = require(Reducers.FavoriteAssets)
local FavoriteBundles = require(Reducers.FavoriteBundles)
return Rodux.combineReducers({
assets = FavoriteAssets,
bundles = FavoriteBundles,
})
@@ -0,0 +1,20 @@
return function()
local Reducer = require(script.Parent.Favorites)
it("has the expected fields, and only the expected fields", function()
local state = Reducer(nil, {})
local expectedKeys = {
assets = true,
bundles = true,
}
for key in pairs(expectedKeys) do
assert(state[key] ~= nil, string.format("Expected field %q", key))
end
for key in pairs(state) do
assert(expectedKeys[key] ~= nil, string.format("Did not expect field %q", key))
end
end)
end
@@ -0,0 +1,13 @@
local CorePackages = game:GetService("CorePackages")
local Rodux = require(CorePackages.Rodux)
local InspectAndBuyFolder = script.Parent.Parent
local SetGamepadEnabled = require(InspectAndBuyFolder.Actions.SetGamepadEnabled)
local GamepadEnabled = Rodux.createReducer(false, {
[SetGamepadEnabled.name] = function(state, action)
return action.gamepadEnabled
end,
})
return GamepadEnabled
@@ -0,0 +1,38 @@
local CorePackages = game:GetService("CorePackages")
local FetchingStatus = require(CorePackages.AppTempCommon.LuaApp.Reducers.FetchingStatus)
local Reducers = script.Parent
local Rodux = require(CorePackages.Rodux)
local View = require(Reducers.View)
local PlayerId = require(Reducers.PlayerId)
local PlayerName = require(Reducers.PlayerName)
local Assets = require(Reducers.Assets)
local Bundles = require(Reducers.Bundles)
local EquippedAssets = require(Reducers.EquippedAssets)
local DetailsInformation = require(Reducers.DetailsInformation)
local TryingOnInfo = require(Reducers.TryingOnInfo)
local Favorites = require(Reducers.Favorites)
local Locale = require(Reducers.Locale)
local Visible = require(Reducers.Visible)
local ItemBeingPurchased = require(Reducers.ItemBeingPurchased)
local IsLoaded = require(Reducers.IsLoaded)
local GamepadEnabled = require(Reducers.GamepadEnabled)
local StoreId = require(Reducers.StoreId)
return Rodux.combineReducers({
view = View,
playerId = PlayerId,
playerName = PlayerName,
assets = Assets,
bundles = Bundles,
equippedAssets = EquippedAssets,
detailsInformation = DetailsInformation,
tryingOnInfo = TryingOnInfo,
favorites = Favorites,
locale = Locale,
visible = Visible,
itemBeingPurchased = ItemBeingPurchased,
gamepadEnabled = GamepadEnabled,
isLoaded = IsLoaded,
FetchingStatus = FetchingStatus,
storeId = StoreId,
})
@@ -0,0 +1,34 @@
return function()
local Reducer = require(script.Parent.InspectAndBuyReducer)
it("has the expected fields, and only the expected fields", function()
local state = Reducer(nil, {})
local expectedKeys = {
view = true,
playerId = true,
playerName = true,
assets = true,
bundles = true,
equippedAssets = true,
detailsInformation = true,
tryingOnInfo = true,
favorites = true,
locale = true,
visible = true,
itemBeingPurchased = true,
gamepadEnabled = true,
isLoaded = true,
FetchingStatus = true,
storeId = true,
}
for key in pairs(expectedKeys) do
assert(state[key] ~= nil, string.format("Expected field %q", key))
end
for key in pairs(state) do
assert(expectedKeys[key] ~= nil, string.format("Did not expect field %q", key))
end
end)
end
@@ -0,0 +1,13 @@
local CorePackages = game:GetService("CorePackages")
local Rodux = require(CorePackages.Rodux)
local InspectAndBuyFolder = script.Parent.Parent
local SetAssets = require(InspectAndBuyFolder.Actions.SetAssets)
local IsLoaded = Rodux.createReducer(false, {
[SetAssets.name] = function(state, action)
return true
end,
})
return IsLoaded
@@ -0,0 +1,18 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Rodux = require(CorePackages.Rodux)
local InspectAndBuyFolder = script.Parent.Parent
local SetAssets = require(InspectAndBuyFolder.Actions.SetAssets)
local IsLoaded = require(script.Parent.IsLoaded)
describe("IsLoaded", function()
it("should mark the Inspect Menu as loaded or not", function()
local store = Rodux.Store.new(IsLoaded)
local newState = store:getState()
expect(newState).to.equal(false)
newState = IsLoaded(newState, SetAssets({}))
expect(newState).to.equal(true)
end)
end)
end
@@ -0,0 +1,22 @@
local CorePackages = game:GetService("CorePackages")
local Rodux = require(CorePackages.Rodux)
local InspectAndBuyFolder = script.Parent.Parent
local SetItemBeingPurchased = require(InspectAndBuyFolder.Actions.SetItemBeingPurchased)
local DEFAULT_INFORMATION = {
itemType = nil,
itemId = nil,
}
return Rodux.createReducer(
DEFAULT_INFORMATION
, {
[SetItemBeingPurchased.name] = function(state, action)
local newInformation = {
itemType = action.itemType,
itemId = action.itemId,
}
return newInformation
end,
})
@@ -0,0 +1,14 @@
local CorePackages = game:GetService("CorePackages")
local Rodux = require(CorePackages.Rodux)
local InspectAndBuyFolder = script.Parent.Parent
local SetLocale = require(InspectAndBuyFolder.Actions.SetLocale)
local FALL_BACK_LOCALE = "en-us"
return Rodux.createReducer(
FALL_BACK_LOCALE
, {
[SetLocale.name] = function(state, action)
return action.locale
end,
})
@@ -0,0 +1,14 @@
return function()
local InspectAndBuyFolder = script.Parent.Parent
local SetLocale = require(InspectAndBuyFolder.Actions.SetLocale)
local Locale = require(script.Parent.Locale)
local MOCK_LOCALE = "en-uk"
describe("SetLocale", function()
it("should set the locale", function()
local newState = Locale(nil, SetLocale(MOCK_LOCALE))
expect(newState).to.equal(MOCK_LOCALE)
end)
end)
end
@@ -0,0 +1,12 @@
local CorePackages = game:GetService("CorePackages")
local Rodux = require(CorePackages.Rodux)
local InspectAndBuyFolder = script.Parent.Parent
local SetPlayerId = require(InspectAndBuyFolder.Actions.SetPlayerId)
return Rodux.createReducer(
""
, {
[SetPlayerId.name] = function(state, action)
return action.playerId
end,
})
@@ -0,0 +1,12 @@
local CorePackages = game:GetService("CorePackages")
local Rodux = require(CorePackages.Rodux)
local InspectAndBuyFolder = script.Parent.Parent
local SetPlayerName = require(InspectAndBuyFolder.Actions.SetPlayerName)
return Rodux.createReducer(
""
, {
[SetPlayerName.name] = function(state, action)
return action.playerName
end,
})
@@ -0,0 +1,17 @@
local CorePackages = game:GetService("CorePackages")
local Rodux = require(CorePackages.Rodux)
local InspectAndBuyFolder = script.Parent.Parent
local UpdateStoreId = require(InspectAndBuyFolder.Actions.UpdateStoreId)
local storeIdCounter = 0
return Rodux.createReducer(
"0"
, {
[UpdateStoreId.name] = function(state, action)
storeIdCounter = storeIdCounter + 1
return tostring(storeIdCounter)
end,
})
@@ -0,0 +1,24 @@
local CorePackages = game:GetService("CorePackages")
local Rodux = require(CorePackages.Rodux)
local InspectAndBuyFolder = script.Parent.Parent
local SetTryingOnInfo = require(InspectAndBuyFolder.Actions.SetTryingOnInfo)
local DEFAULT_INFO = {
tryingOn = false,
assetId = nil,
assetTypeId = nil,
}
return Rodux.createReducer(
DEFAULT_INFO
, {
[SetTryingOnInfo.name] = function(state, action)
local newInfo = {
tryingOn = action.tryingOn,
assetId = action.assetId,
assetTypeId = action.assetTypeId,
}
return newInfo
end,
})
@@ -0,0 +1,29 @@
return function()
local InspectAndBuyFolder = script.Parent.Parent
local SetTryingOnInfo = require(InspectAndBuyFolder.Actions.SetTryingOnInfo)
local TryingOnInfo = require(script.Parent.TryingOnInfo)
local function countKeys(t)
local count = 0
for _ in pairs(t) do
count = count + 1
end
return count
end
--[[
Note: Rthro Animation Bundle is by default in the Bundles reducer.
]]
describe("SetTryingOnInfo", function()
it("should set the trying on information", function()
local assetId = "123"
local assetTypeId = "8"
local newState = TryingOnInfo(nil, SetTryingOnInfo(true, assetId, assetTypeId))
expect(newState.tryingOn).to.equal(true)
expect(newState.assetId).to.equal(assetId)
expect(newState.assetTypeId).to.equal(assetTypeId)
expect(countKeys(newState)).to.equal(3)
end)
end)
end
@@ -0,0 +1,13 @@
local CorePackages = game:GetService("CorePackages")
local Rodux = require(CorePackages.Rodux)
local InspectAndBuyFolder = script.Parent.Parent
local SetView = require(InspectAndBuyFolder.Actions.SetView)
local Constants = require(InspectAndBuyFolder.Constants)
return Rodux.createReducer(
Constants.View.Wide
, {
[SetView.name] = function(state, action)
return action.view
end,
})
@@ -0,0 +1,16 @@
local CorePackages = game:GetService("CorePackages")
local Rodux = require(CorePackages.Rodux)
local InspectAndBuyFolder = script.Parent.Parent
local ShowMenu = require(InspectAndBuyFolder.Actions.ShowMenu)
local HideMenu = require(InspectAndBuyFolder.Actions.HideMenu)
return Rodux.createReducer(
true
, {
[ShowMenu.name] = function(state, action)
return true
end,
[HideMenu.name] = function(state, action)
return false
end,
})
@@ -0,0 +1,5 @@
{
"lint": {
"ImplicitReturn": "fatal"
}
}

Some files were not shown because too many files have changed in this diff Show More