add gs
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"lint": {
|
||||
"LocalShadow": "fatal"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"language": {
|
||||
"mode": "nonstrict"
|
||||
},
|
||||
"lint": {
|
||||
"ImplicitReturn": "fatal"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Action = require(CorePackages.AppTempCommon.Common.Action)
|
||||
|
||||
return Action(script.Name, function(user)
|
||||
return {
|
||||
user = user
|
||||
}
|
||||
end)
|
||||
@@ -0,0 +1,8 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Action = require(CorePackages.AppTempCommon.Common.Action)
|
||||
|
||||
return Action(script.Name, function(users)
|
||||
return {
|
||||
users = users
|
||||
}
|
||||
end)
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Action = require(CorePackages.AppTempCommon.Common.Action)
|
||||
|
||||
return Action(script.Name, function(userId)
|
||||
return {
|
||||
userId = userId
|
||||
}
|
||||
end)
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Action = require(CorePackages.AppTempCommon.Common.Action)
|
||||
|
||||
return Action(script.Name, function(userId, response)
|
||||
return {
|
||||
userId = userId,
|
||||
response = response
|
||||
}
|
||||
end)
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Action = require(CorePackages.AppTempCommon.Common.Action)
|
||||
|
||||
return Action(script.Name, function(userId)
|
||||
return {
|
||||
userId = userId
|
||||
}
|
||||
end)
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Action = require(CorePackages.AppTempCommon.Common.Action)
|
||||
|
||||
return Action(script.Name, function(placesInfos)
|
||||
return {
|
||||
placesInfos = placesInfos,
|
||||
}
|
||||
end)
|
||||
@@ -0,0 +1,8 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Action = require(CorePackages.AppTempCommon.Common.Action)
|
||||
|
||||
return Action(script.Name, function(userId)
|
||||
return {
|
||||
userId = userId,
|
||||
}
|
||||
end)
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Action = require(CorePackages.AppTempCommon.Common.Action)
|
||||
|
||||
return Action(script.Name, function(deviceOrientation)
|
||||
return {
|
||||
deviceOrientation = deviceOrientation,
|
||||
}
|
||||
end)
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Common = CorePackages.AppTempCommon.Common
|
||||
|
||||
local Action = require(Common.Action)
|
||||
|
||||
return Action(script.Name, function(count)
|
||||
return {
|
||||
count = count,
|
||||
}
|
||||
end)
|
||||
@@ -0,0 +1,14 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Action = require(CorePackages.AppTempCommon.Common.Action)
|
||||
local ArgCheck = require(CorePackages.ArgCheck)
|
||||
|
||||
--[[
|
||||
Each entry in the table is a type of GameIcon with the universe id as key
|
||||
]]
|
||||
return Action(script.Name, function(iconsTable)
|
||||
ArgCheck.isType(iconsTable, "table", "iconsTable")
|
||||
|
||||
return {
|
||||
gameIcons = iconsTable
|
||||
}
|
||||
end)
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
return function()
|
||||
local SetGameIcons = require(script.Parent.SetGameIcons)
|
||||
|
||||
it("should assert if given a non-table for thumbnailsTable", function()
|
||||
SetGameIcons({})
|
||||
|
||||
expect(function()
|
||||
SetGameIcons("string")
|
||||
end).to.throw()
|
||||
|
||||
expect(function()
|
||||
SetGameIcons(0)
|
||||
end).to.throw()
|
||||
|
||||
expect(function()
|
||||
SetGameIcons(nil)
|
||||
end).to.throw()
|
||||
|
||||
expect(function()
|
||||
SetGameIcons(false)
|
||||
end).to.throw()
|
||||
|
||||
expect(function()
|
||||
SetGameIcons(function() end)
|
||||
end).to.throw()
|
||||
end)
|
||||
end
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Action = require(CorePackages.AppTempCommon.Common.Action)
|
||||
|
||||
--[[
|
||||
Passes a table that looks like this : { "universeId" : {json}, ... }
|
||||
|
||||
{
|
||||
"26034470" : {
|
||||
universeId : "26034470",
|
||||
placeId : "70542190",
|
||||
url : https://t5.rbxcdn.com/ed422c6fbb22280971cfb289f40ac814,
|
||||
final : true
|
||||
}, {...}, ...
|
||||
}
|
||||
|
||||
]]
|
||||
|
||||
--TODO MOBLUAPP-778 Refactor improper Setter Actions.
|
||||
return Action(script.Name, function(thumbnailsTable)
|
||||
assert(type(thumbnailsTable) == "table",
|
||||
string.format("SetGameThumbnails action expects thumbnailsTable to be a table, was %s", type(thumbnailsTable)))
|
||||
|
||||
return {
|
||||
thumbnails = thumbnailsTable
|
||||
}
|
||||
end)
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Common = CorePackages.AppTempCommon.Common
|
||||
|
||||
local Action = require(Common.Action)
|
||||
|
||||
return Action(script.Name, function(userId, universeId)
|
||||
return {
|
||||
userId = userId,
|
||||
universeId = universeId,
|
||||
}
|
||||
end)
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Action = require(CorePackages.AppTempCommon.Common.Action)
|
||||
|
||||
return Action(script.Name, function(userId, isFriend)
|
||||
return {
|
||||
userId = userId,
|
||||
isFriend = isFriend,
|
||||
}
|
||||
end)
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Common = CorePackages.AppTempCommon.Common
|
||||
|
||||
local Action = require(Common.Action)
|
||||
|
||||
return Action(script.Name, function(userId, membershipType)
|
||||
return {
|
||||
userId = userId,
|
||||
membershipType = membershipType,
|
||||
}
|
||||
end)
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Action = require(CorePackages.AppTempCommon.Common.Action)
|
||||
|
||||
return Action(script.Name, function(userId, presence, lastLocation)
|
||||
return {
|
||||
userId = tostring(userId),
|
||||
presence = presence,
|
||||
lastLocation = lastLocation,
|
||||
}
|
||||
end)
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Common = CorePackages.AppTempCommon.Common
|
||||
|
||||
local Action = require(Common.Action)
|
||||
|
||||
return Action(script.Name, function(userId, image, thumbnailType, thumbnailSize)
|
||||
return {
|
||||
userId = userId,
|
||||
image = image,
|
||||
thumbnailType = thumbnailType,
|
||||
thumbnailSize = thumbnailSize,
|
||||
}
|
||||
end)
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Action = require(CorePackages.AppTempCommon.Common.Action)
|
||||
|
||||
return Action(script.Name, function(key, status)
|
||||
return {
|
||||
key = key,
|
||||
status = status
|
||||
}
|
||||
end)
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local UpdateFetchingStatus = require(CorePackages.AppTempCommon.LuaApp.Actions.UpdateFetchingStatus)
|
||||
|
||||
describe("Action UpdateFetchingStatus", function()
|
||||
it("should return correct action name", function()
|
||||
expect(UpdateFetchingStatus.name).to.equal("UpdateFetchingStatus")
|
||||
end)
|
||||
|
||||
it("should return correct action type name", function()
|
||||
local action = UpdateFetchingStatus()
|
||||
expect(action.type).to.equal(UpdateFetchingStatus.name)
|
||||
end)
|
||||
|
||||
it("should return a table with the correct key and status", function()
|
||||
local action = UpdateFetchingStatus("key", "status")
|
||||
expect(action.key).to.equal("key")
|
||||
expect(action.status).to.equal("status")
|
||||
end)
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"language": {
|
||||
"mode": "nonstrict"
|
||||
},
|
||||
"lint": {
|
||||
"ImplicitReturn": "fatal"
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
local Workspace = game:GetService("Workspace")
|
||||
local RunService = game:GetService('RunService')
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
local BAR_SLICE_CENTER = Rect.new(1, 0, 2, 3)
|
||||
local BAR_MAX_SIZE = 15
|
||||
local BAR_MAX_AMPLITUDE = 40
|
||||
local BAR_DIAMETER = 4
|
||||
local BAR_PERIOD = 1.25
|
||||
|
||||
local LoadingBar = Roact.Component:extend("LoadingBar")
|
||||
|
||||
function LoadingBar:init()
|
||||
self.barRef = Roact.createRef()
|
||||
end
|
||||
|
||||
function LoadingBar:render()
|
||||
local zIndex = self.props.ZIndex
|
||||
|
||||
return Roact.createElement("ImageLabel", {
|
||||
Image = "rbxasset://textures/ui/LuaApp/9-slice/gr-loading-indicator.png",
|
||||
ScaleType = "Slice",
|
||||
SliceCenter = BAR_SLICE_CENTER,
|
||||
BackgroundTransparency = 1,
|
||||
BorderSizePixel = 0,
|
||||
ZIndex = zIndex,
|
||||
[Roact.Ref] = self.barRef,
|
||||
})
|
||||
end
|
||||
|
||||
function LoadingBar:didMount()
|
||||
self.connection = RunService.RenderStepped:Connect(function()
|
||||
local t = Workspace.DistributedGameTime
|
||||
local instance = self.barRef.current
|
||||
local period = 2.0 * math.pi / BAR_PERIOD
|
||||
|
||||
local width = (BAR_MAX_SIZE/2) * (1 - math.cos(2*t*period))
|
||||
instance.Size = UDim2.new(0, BAR_DIAMETER + width, 0, BAR_DIAMETER)
|
||||
|
||||
local x = BAR_MAX_AMPLITUDE * math.cos(t*period)
|
||||
instance.Position = UDim2.new(0.5, x - width/2 - BAR_DIAMETER/2, 0.5, 0)
|
||||
end)
|
||||
end
|
||||
|
||||
function LoadingBar:willUnmount()
|
||||
self.connection:Disconnect()
|
||||
end
|
||||
|
||||
return LoadingBar
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
return function()
|
||||
local LoadingBar = require(script.Parent.LoadingBar)
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local element = Roact.createElement(LoadingBar, {
|
||||
Position = UDim2.new(0.5, 0, 0.5, 5),
|
||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"language": {
|
||||
"mode": "nonstrict"
|
||||
},
|
||||
"lint": {
|
||||
"ImplicitReturn": "fatal"
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
return {
|
||||
AvatarThumbnail = "AvatarThumbnail",
|
||||
HeadShot = "HeadShot",
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
local RetrievalStatus = {}
|
||||
|
||||
local EnumValues =
|
||||
{
|
||||
NotStarted = "NotStarted",
|
||||
Fetching = "Fetching",
|
||||
Done = "Done",
|
||||
Failed = "Failed",
|
||||
}
|
||||
|
||||
setmetatable(RetrievalStatus,
|
||||
{
|
||||
__newindex = function(t, key, index)
|
||||
end,
|
||||
__index = function(t, index)
|
||||
assert(EnumValues[index] ~= nil, ("RetrievalStatus Enum has no value: " .. tostring(index)))
|
||||
return EnumValues[index]
|
||||
end
|
||||
})
|
||||
|
||||
return RetrievalStatus
|
||||
@@ -0,0 +1,10 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local User = require(CorePackages.AppTempCommon.LuaApp.Models.User)
|
||||
|
||||
return {
|
||||
[0] = User.PresenceType.OFFLINE,
|
||||
[1] = User.PresenceType.ONLINE,
|
||||
[2] = User.PresenceType.IN_GAME,
|
||||
[3] = User.PresenceType.IN_STUDIO,
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"language": {
|
||||
"mode": "nonstrict"
|
||||
},
|
||||
"lint": {
|
||||
"ImplicitReturn": "fatal"
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local ThrottleUserId = require(CorePackages.AppTempCommon.LuaApp.Utils.ThrottleUserId)
|
||||
|
||||
local FIntAvatarEditorNewCatalogButton = settings():GetFVariable("AvatarEditorNewCatalogButton2")
|
||||
|
||||
return function(userId)
|
||||
if tonumber(userId) then
|
||||
local throttleNumber = tonumber(FIntAvatarEditorNewCatalogButton)
|
||||
local id = tonumber(userId)
|
||||
return ThrottleUserId(throttleNumber, id)
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
-- TODO: Delete this file when deleting the flag: LuaAppConvertUniverseIdToStringV364
|
||||
local FFlagLuaAppConvertUniverseIdToString = settings():GetFFlag("LuaAppConvertUniverseIdToStringV364")
|
||||
|
||||
return function(universeId)
|
||||
-- When the flag is on, we've converted the universe id to string at the place we received it
|
||||
if FFlagLuaAppConvertUniverseIdToString then
|
||||
return universeId
|
||||
else
|
||||
return tostring(universeId)
|
||||
end
|
||||
end
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
local ThrottleUserId = require(CorePackages.AppTempCommon.LuaApp.Utils.ThrottleUserId)
|
||||
|
||||
local FIntEnableFriendFooterOnHomePage = settings():GetFVariable("EnableFriendFooterOnHomePageV369")
|
||||
|
||||
-- Don't call this function globally because we cannot get the userId
|
||||
-- Reason: The LocalPlayer wouldn't be ready if we called it globally.
|
||||
return function()
|
||||
local throttleNumber = tonumber(FIntEnableFriendFooterOnHomePage)
|
||||
local userId = Players.LocalPlayer.UserId
|
||||
|
||||
return ThrottleUserId(throttleNumber, userId)
|
||||
end
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
game:DefineFastFlag("LuaAppFixLightTheme", false)
|
||||
|
||||
return function()
|
||||
return game:GetFastFlag("LuaAppFixLightTheme")
|
||||
end
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
return function()
|
||||
return settings():GetFFlag("UseDateTimeType3")
|
||||
end
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"language": {
|
||||
"mode": "nonstrict"
|
||||
},
|
||||
"lint": {
|
||||
"ImplicitReturn": "fatal"
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local HttpService = game:GetService("HttpService")
|
||||
|
||||
local Url = require(CorePackages.AppTempCommon.LuaApp.Http.Url)
|
||||
|
||||
return function(requestImpl, conversationId, universeId, decorators)
|
||||
assert(requestImpl, "requestImpl is required")
|
||||
assert(conversationId, "conversationId is required")
|
||||
assert(universeId, "universeId is required")
|
||||
|
||||
local payload = HttpService:JSONEncode({
|
||||
conversationId = conversationId,
|
||||
universeId = universeId,
|
||||
decorators = decorators
|
||||
})
|
||||
local url = string.format("%s/send-game-link-message", Url.CHAT_URL)
|
||||
|
||||
return requestImpl(url, "POST", { postBody = payload })
|
||||
end
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local HttpService = game:GetService("HttpService")
|
||||
|
||||
local Url = require(CorePackages.AppTempCommon.LuaApp.Http.Url)
|
||||
|
||||
return function(requestImpl, conversationId, messageText, decorators)
|
||||
local payload = HttpService:JSONEncode({
|
||||
conversationId = conversationId,
|
||||
message = messageText,
|
||||
decorators = decorators
|
||||
})
|
||||
|
||||
local url = string.format("%s/send-message", Url.CHAT_URL)
|
||||
|
||||
return requestImpl(url, "POST", { postBody = payload })
|
||||
end
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local HttpService = game:GetService("HttpService")
|
||||
|
||||
local Url = require(CorePackages.AppTempCommon.LuaApp.Http.Url)
|
||||
|
||||
return function(requestImpl, userId, clientId)
|
||||
local payload = HttpService:JSONEncode({
|
||||
participantuserId = userId
|
||||
})
|
||||
|
||||
local url = string.format("%s/start-one-to-one-conversation", Url.CHAT_URL)
|
||||
|
||||
return requestImpl(url, "POST", { postBody = payload })
|
||||
end
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Url = require(CorePackages.AppTempCommon.LuaApp.Http.Url)
|
||||
|
||||
--[[
|
||||
Docs: https://thumbnails.roblox.com/docs#!/Games/get_v1_games_icons
|
||||
This resolves to
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"targetId": 0,
|
||||
"state": "Error",
|
||||
"imageUrl": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
]]
|
||||
return function (requestImpl, universeIds, size)
|
||||
local qs = Url:makeQueryString({
|
||||
universeIds = table.concat(universeIds, ","),
|
||||
format = "png",
|
||||
size = size,
|
||||
})
|
||||
local url = string.format("%sv1/games/icons?%s", Url.THUMBNAILS_URL, qs)
|
||||
return requestImpl(url, "GET")
|
||||
end
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
--[[
|
||||
*** DEPRECATED ***
|
||||
TODO: removed this file after new thumbnail API is being in use without any flags
|
||||
RELATED: GAMEDISC-27 GAMEDISC-126 FIntLuaAppPercentRollOutNewThumbnailsApiV3
|
||||
]]
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Url = require(CorePackages.AppTempCommon.LuaApp.Http.Url)
|
||||
|
||||
--[[
|
||||
This endpoint returns a promise that resolves to:
|
||||
[
|
||||
{
|
||||
"final": true,
|
||||
"url": "string",
|
||||
"retryToken": "string",
|
||||
"universeId": 0,
|
||||
"placeId": 0
|
||||
}, {...}, ...
|
||||
]
|
||||
]]
|
||||
|
||||
-- requestImpl - (function<promise<HttpResponse>>(url, requestMethod, options))
|
||||
-- imageTokens - (array<long>) the placeIds of the places you want to get thumbnails for
|
||||
-- height - (int) the height of the asset to render
|
||||
-- width - (int) the width of the asset to render
|
||||
return function(requestImpl, imageTokens, height, width)
|
||||
local args = {}
|
||||
|
||||
if height then
|
||||
table.insert(args, string.format("height=%d", height))
|
||||
end
|
||||
|
||||
if width then
|
||||
table.insert(args, string.format("width=%d", width))
|
||||
end
|
||||
|
||||
-- append all of the thumbnail tokens
|
||||
local totalTokens = 0
|
||||
for _, value in pairs(imageTokens) do
|
||||
totalTokens = totalTokens + 1
|
||||
table.insert(args, string.format("imageTokens=%s", value))
|
||||
end
|
||||
if totalTokens == 0 then
|
||||
error("cannot fetch thumbnails without tokens")
|
||||
end
|
||||
|
||||
-- construct the url
|
||||
local url = string.format("%sv1/games/game-thumbnails?%s", Url.GAME_URL, table.concat(args, "&"))
|
||||
|
||||
-- return a promise of the result listed above
|
||||
return requestImpl(url, "GET")
|
||||
end
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Url = require(CorePackages.AppTempCommon.LuaApp.Http.Url)
|
||||
|
||||
--[[
|
||||
This endpoint returns games' information with a batches of place ids
|
||||
Doc: https://games.roblox.com/docs#!/Games/get_v1_games_multiget_place_details
|
||||
{
|
||||
"placeId": 0,
|
||||
"name": "string",
|
||||
"description": "string",
|
||||
"url": "string",
|
||||
"builder": "string",
|
||||
"builderId": 0,
|
||||
"isPlayable": true,
|
||||
"reasonProhibited": "string",
|
||||
"universeId": 0,
|
||||
"universeRootPlaceId": 0,
|
||||
"price": 0,
|
||||
"imageToken": "string"
|
||||
}
|
||||
]]--
|
||||
|
||||
return function(requestImpl, placeIds)
|
||||
local argTable = {
|
||||
placeIds = placeIds,
|
||||
}
|
||||
|
||||
local args = Url:makeQueryString(argTable)
|
||||
local url = string.format("%s/v1/games/multiget-place-details?%s", Url.GAME_URL, args)
|
||||
|
||||
return requestImpl(url, "GET")
|
||||
end
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Url = require(CorePackages.AppTempCommon.LuaApp.Http.Url)
|
||||
|
||||
return function(requestImpl, placeIds)
|
||||
local argTable = {
|
||||
placeIds = placeIds,
|
||||
}
|
||||
|
||||
-- construct the url
|
||||
local args = Url:makeQueryString(argTable)
|
||||
local url = string.format("%s/v1/games/multiget-place-details?%s",
|
||||
Url.GAME_URL, args
|
||||
)
|
||||
|
||||
return requestImpl(url, "GET")
|
||||
end
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Url = require(CorePackages.AppTempCommon.LuaApp.Http.Url)
|
||||
|
||||
--[[
|
||||
Documentation of endpoint:
|
||||
https://thumbnails.roblox.com/docs#!/Avatar/get_v1_users_avatar
|
||||
|
||||
input:
|
||||
userIds
|
||||
thumbnailSize
|
||||
output:
|
||||
[
|
||||
{
|
||||
"targetId": number,
|
||||
"state": string,
|
||||
"imageUrl": string,
|
||||
},
|
||||
]
|
||||
]]
|
||||
|
||||
local MAX_USER_IDS = 100
|
||||
|
||||
return function (networkImpl, userIds, thumbnailSize)
|
||||
assert(type(userIds) == "table", "ThumbnailsGetAvatar expects userIds to be a table")
|
||||
|
||||
if #userIds == 0 or #userIds > MAX_USER_IDS then
|
||||
error(string.format("ThumbnailsGetAvatar request expects userIds count between 1-%d", MAX_USER_IDS))
|
||||
end
|
||||
|
||||
local queryString = Url:makeQueryString({
|
||||
userIds = table.concat(userIds, ","),
|
||||
size = thumbnailSize,
|
||||
format = "png",
|
||||
})
|
||||
|
||||
local url = string.format("%sv1/users/avatar?%s", Url.THUMBNAILS_URL, queryString)
|
||||
|
||||
return networkImpl(url, "GET")
|
||||
end
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Url = require(CorePackages.AppTempCommon.LuaApp.Http.Url)
|
||||
|
||||
--[[
|
||||
Documentation of endpoint:
|
||||
https://thumbnails.roblox.com/docs#!/Avatar/get_v1_users_avatar_headshot
|
||||
|
||||
input:
|
||||
userIds
|
||||
thumbnailSize
|
||||
output:
|
||||
[
|
||||
{
|
||||
"targetId": number,
|
||||
"state": string,
|
||||
"imageUrl": string,
|
||||
},
|
||||
]
|
||||
]]
|
||||
|
||||
local MAX_USER_IDS = 100
|
||||
|
||||
return function (networkImpl, userIds, thumbnailSize)
|
||||
assert(type(userIds) == "table", "ThumbnailsGetAvatarHeadshot expects userIds to be a table")
|
||||
|
||||
if #userIds == 0 or #userIds > MAX_USER_IDS then
|
||||
error(string.format("ThumbnailsGetAvatarHeadshot request expects userIds count between 1-%d", MAX_USER_IDS))
|
||||
end
|
||||
|
||||
local queryString = Url:makeQueryString({
|
||||
userIds = table.concat(userIds, ","),
|
||||
size = thumbnailSize,
|
||||
format = "png",
|
||||
})
|
||||
|
||||
local url = string.format("%sv1/users/avatar-headshot?%s", Url.THUMBNAILS_URL, queryString)
|
||||
|
||||
return networkImpl(url, "GET")
|
||||
end
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
local Url = require(CorePackages.AppTempCommon.LuaApp.Http.Url)
|
||||
|
||||
local isNewFriendsEndpointsEnabled = require(CorePackages.AppTempCommon.LuaChat.Flags.isNewFriendsEndpointsEnabled)
|
||||
|
||||
--[[
|
||||
This endpoint returns a promise that resolves to:
|
||||
|
||||
[
|
||||
{
|
||||
"success:" true,
|
||||
"count": "0"
|
||||
},
|
||||
]
|
||||
]]--
|
||||
|
||||
-- requestImpl - (function<promise<HttpResponse>>(url, requestMethod, options))
|
||||
return function(requestImpl)
|
||||
|
||||
local url = string.format("%s/user/get-friendship-count?%s",
|
||||
Url.API_URL, tostring(Players.LocalPlayer.UserId)
|
||||
)
|
||||
|
||||
if isNewFriendsEndpointsEnabled() then
|
||||
url = string.format("%s/my/friends/count", Url.FRIEND_URL)
|
||||
end
|
||||
|
||||
return requestImpl(url, "GET")
|
||||
end
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Url = require(CorePackages.AppTempCommon.LuaApp.Http.Url)
|
||||
|
||||
return function(requestImpl, userId)
|
||||
local url = string.format("%s/users/%s/friends",
|
||||
Url.FRIEND_URL, userId
|
||||
)
|
||||
|
||||
return requestImpl(url, "GET")
|
||||
end
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local HttpService = game:GetService("HttpService")
|
||||
|
||||
local Url = require(CorePackages.AppTempCommon.LuaApp.Http.Url)
|
||||
|
||||
-- Endpoint documented here:
|
||||
-- https://presence.roblox.com/docs
|
||||
|
||||
return function(requestImpl, userIds)
|
||||
local userIdsToNumber = {}
|
||||
for _, id in pairs(userIds) do
|
||||
local idToNumber = tonumber(id)
|
||||
if idToNumber then
|
||||
table.insert(userIdsToNumber, idToNumber)
|
||||
end
|
||||
end
|
||||
|
||||
local payload = HttpService:JSONEncode({
|
||||
userIds = userIdsToNumber,
|
||||
})
|
||||
|
||||
local url = string.format("%s/presence/users", Url.PRESENCE_URL)
|
||||
|
||||
return requestImpl(url, "POST", { postBody = payload })
|
||||
end
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
local Promise = require(CorePackages.AppTempCommon.LuaApp.Promise)
|
||||
|
||||
local THUMBNAIL_TYPE_BY_NAME = {
|
||||
AvatarThumbnail = Enum.ThumbnailType.AvatarThumbnail,
|
||||
HeadShot = Enum.ThumbnailType.HeadShot,
|
||||
}
|
||||
|
||||
local THUMBNAIL_SIZE_BY_NAME = {
|
||||
Size48x48 = Enum.ThumbnailSize.Size48x48,
|
||||
Size60x60 = Enum.ThumbnailSize.Size60x60,
|
||||
Size100x100 = Enum.ThumbnailSize.Size100x100,
|
||||
Size150x150 = Enum.ThumbnailSize.Size150x150,
|
||||
Size352x352 = Enum.ThumbnailSize.Size352x352
|
||||
}
|
||||
|
||||
return function(userId, thumbnailType, thumbnailSize)
|
||||
return Promise.new(function(resolve, reject)
|
||||
--Async methods will yield the thread
|
||||
spawn(function()
|
||||
local result = {success = false}
|
||||
local success, message = pcall(function()
|
||||
local image, isFinal = Players:GetUserThumbnailAsync(
|
||||
tonumber(userId), THUMBNAIL_TYPE_BY_NAME[thumbnailType], THUMBNAIL_SIZE_BY_NAME[thumbnailSize]
|
||||
)
|
||||
|
||||
result = {
|
||||
success = true,
|
||||
id = userId,
|
||||
thumbnailType = thumbnailType,
|
||||
thumbnailSize = thumbnailSize,
|
||||
|
||||
image = isFinal and image or nil,
|
||||
isFinal = isFinal,
|
||||
}
|
||||
end)
|
||||
|
||||
if success then
|
||||
resolve(result)
|
||||
else
|
||||
result.message = message
|
||||
reject(result)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,181 @@
|
||||
--[[
|
||||
Url Constructor
|
||||
|
||||
Provides a single location for base urls.
|
||||
|
||||
]]--
|
||||
local ContentProvider = game:GetService("ContentProvider")
|
||||
|
||||
local FFlagLuaFixEconomyCreatorStatsUrl = game:DefineFastFlag("LuaFixEconomyCreatorStatsUrl", false)
|
||||
|
||||
-- helper functions
|
||||
local function parseBaseUrlInformation()
|
||||
-- get the current base url from the current configuration
|
||||
local baseUrl = ContentProvider.BaseUrl
|
||||
|
||||
-- keep a copy of the base url (https://www.roblox.com/)
|
||||
-- append a trailing slash if there isn't one
|
||||
if baseUrl:sub(#baseUrl) ~= "/" then
|
||||
baseUrl = baseUrl .. "/"
|
||||
end
|
||||
|
||||
-- parse out scheme (http, https)
|
||||
local _, schemeEnd = baseUrl:find("://")
|
||||
|
||||
-- parse out the prefix (www, kyle, ying, etc.)
|
||||
local prefixIndex, prefixEnd = baseUrl:find("%.", schemeEnd + 1)
|
||||
local basePrefix = baseUrl:sub(schemeEnd + 1, prefixIndex - 1)
|
||||
|
||||
-- parse out the domain (roblox.com/, sitetest1.robloxlabs.com/, etc.)
|
||||
local baseDomain = baseUrl:sub(prefixEnd + 1)
|
||||
|
||||
return baseUrl, basePrefix, baseDomain
|
||||
end
|
||||
local function preventTableModification(aTable, key, value)
|
||||
error("Attempt to modify read-only table")
|
||||
end
|
||||
local function createReadOnlyTable(aTable)
|
||||
return setmetatable({}, {
|
||||
__index = aTable,
|
||||
__newindex = preventTableModification,
|
||||
__metatable = false
|
||||
});
|
||||
end
|
||||
|
||||
|
||||
-- url construction building blocks
|
||||
local _baseUrl, _basePrefix, _baseDomain = parseBaseUrlInformation()
|
||||
|
||||
-- construct urls once
|
||||
local _baseApiUrl = string.format("https://api.%s", _baseDomain)
|
||||
local _baseApisUrl = string.format("https://apis.%s", _baseDomain)
|
||||
local _baseAuthUrl = string.format("https://auth.%s", _baseDomain)
|
||||
local _baseAccountSettingsUrl = string.format("https://accountsettings.%s", _baseDomain)
|
||||
local _baseAvatarUrl = string.format("https://avatar.%s", _baseDomain)
|
||||
local _baseCatalogUrl = string.format("https://catalog.%s", _baseDomain)
|
||||
local _baseInventoryUrl = string.format("https://inventory.%s", _baseDomain)
|
||||
local _baseChatUrl = string.format("https://chat.%sv2", _baseDomain)
|
||||
local _baseFriendUrl = string.format("https://friends.%sv1", _baseDomain)
|
||||
local _baseGameAssetUrl = string.format("https://assetgame.%s", _baseDomain)
|
||||
local _baseGamesUrl = string.format("https://games.%s", _baseDomain)
|
||||
local _baseGroupsUrl = string.format("https://groups.%s", _baseDomain)
|
||||
local _baseNotificationUrl = string.format("https://notifications.%s", _baseDomain)
|
||||
local _basePresenceUrl = string.format("https://presence.%sv1", _baseDomain)
|
||||
local _baseRealtimeUrl = string.format("https://realtime.%s", _baseDomain)
|
||||
local _baseWebUrl = string.format("https://web.%s", _baseDomain)
|
||||
local _baseWwwUrl = string.format("https://www.%s", _baseDomain)
|
||||
local _baseAdsUrl = string.format("https://ads.%s", _baseDomain)
|
||||
local _baseFollowingsUrl = string.format("https://followings.%s", _baseDomain)
|
||||
local _baseEconomyUrl = string.format("https://economy.%s", _baseDomain)
|
||||
local _baseThumbnailsUrl = string.format("https://thumbnails.%s", _baseDomain)
|
||||
local _baseAccountSettings = string.format("https://accountsettings.%s", _baseDomain)
|
||||
local _basePremiumFeatures = string.format("https://premiumfeatures.%s", _baseDomain)
|
||||
local _baseLocale = string.format("https://locale.%s", _baseDomain)
|
||||
local _baseBadgesUrl = string.format("https://badges.%s", _baseDomain)
|
||||
local _baseMetricsUrl = string.format("https://metrics.%sv1", _baseDomain)
|
||||
local _baseApisRcsUrl = string.format("https://apis.rcs.%s", _baseDomain)
|
||||
local _baseDiscussionsUrl = string.format("https://discussions.%s", _baseDomain)
|
||||
local _baseContactsUrl = string.format("https://contacts.%s", _baseDomain)
|
||||
local _baseSearchUrl = string.format("https://search.%s", _baseDomain)
|
||||
local _baseStaticUrl = string.format("https://static.%s", _baseDomain)
|
||||
local _baseGameSearchUITreatments = string.format("https://gamesearchuitreatments.api.%s", _baseDomain)
|
||||
local _baseEconomyCreatorStats = FFlagLuaFixEconomyCreatorStatsUrl
|
||||
and string.format("https://economycreatorstats.%s", _baseDomain)
|
||||
or string.format("https://economycreatorstats.api.%s", _baseDomain)
|
||||
local _baseUrlSecure = string.gsub(_baseUrl, "http://", "https://")
|
||||
|
||||
-- public api
|
||||
local Url = {
|
||||
DOMAIN = _baseDomain,
|
||||
PREFIX = _basePrefix,
|
||||
BASE_URL = _baseUrl,
|
||||
BASE_URL_SECURE = _baseUrlSecure,
|
||||
API_URL = _baseApiUrl,
|
||||
APIS_URL = _baseApisUrl,
|
||||
AUTH_URL = _baseAuthUrl,
|
||||
ACCOUNT_SETTINGS_URL = _baseAccountSettingsUrl,
|
||||
AVATAR_URL = _baseAvatarUrl,
|
||||
CATALOG_URL = _baseCatalogUrl,
|
||||
INVENTORY_URL = _baseInventoryUrl,
|
||||
GAME_URL = _baseGamesUrl,
|
||||
GAME_ASSET_URL = _baseGameAssetUrl,
|
||||
GROUPS_URL = _baseGroupsUrl,
|
||||
CHAT_URL = _baseChatUrl,
|
||||
FRIEND_URL = _baseFriendUrl,
|
||||
PRESENCE_URL = _basePresenceUrl,
|
||||
NOTIFICATION_URL = _baseNotificationUrl,
|
||||
REALTIME_URL = _baseRealtimeUrl,
|
||||
WEB_URL = _baseWebUrl,
|
||||
WWW_URL = _baseWwwUrl,
|
||||
ADS_URL = _baseAdsUrl,
|
||||
SEARCH_URL = _baseSearchUrl,
|
||||
GAME_SEARCH_UI_TREATMENTS = _baseGameSearchUITreatments,
|
||||
FOLLOWINGS_URL = _baseFollowingsUrl,
|
||||
ECONOMY_URL = _baseEconomyUrl,
|
||||
THUMBNAILS_URL = _baseThumbnailsUrl,
|
||||
BADGES_URL = _baseBadgesUrl,
|
||||
ACCOUNT_SETTINGS = _baseAccountSettings,
|
||||
PREMIUM_FEATURES = _basePremiumFeatures,
|
||||
LOCALE = _baseLocale,
|
||||
METRICS_URL = _baseMetricsUrl,
|
||||
APIS_RCS_URL = _baseApisRcsUrl,
|
||||
DISCUSSIONS_URL = _baseDiscussionsUrl,
|
||||
CONTACTS_URL = _baseContactsUrl,
|
||||
STATIC_URL = _baseStaticUrl,
|
||||
BLOG_URL = "https://blog.roblox.com/",
|
||||
CORP_URL = "https://corp.roblox.com/",
|
||||
ECNOMY_CREATOR_STATS = _baseEconomyCreatorStats,
|
||||
}
|
||||
|
||||
function Url:getUserProfileUrl(userId)
|
||||
return string.format("%susers/%s/profile", self.BASE_URL, userId)
|
||||
end
|
||||
|
||||
function Url:getUserFriendsUrl(userId)
|
||||
return string.format("%susers/%s/friends", self.BASE_URL, userId)
|
||||
end
|
||||
|
||||
function Url:getUserInventoryUrl(userId)
|
||||
return string.format("%susers/%s/inventory", self.BASE_URL, userId)
|
||||
end
|
||||
|
||||
function Url:getPlaceDefaultThumbnailUrl(placeId, width, height)
|
||||
return string.format(
|
||||
"%sThumbs/Asset.ashx?width=%d&height=%d&assetId=%s&ignorePlaceMediaItems=true",
|
||||
self.BASE_URL,
|
||||
width,
|
||||
height,
|
||||
tostring(placeId))
|
||||
end
|
||||
|
||||
function Url:isVanitySite()
|
||||
return self.PREFIX ~= "www"
|
||||
end
|
||||
|
||||
-- data - (table<string, string>) a table of key/value pairs to format
|
||||
function Url:makeQueryString(data)
|
||||
--NOTE - This function can be used to create a query string of parameters
|
||||
-- at the end of url query, or create a application/form-url-encoded post body string
|
||||
local params = {}
|
||||
|
||||
-- NOTE - Arrays are handled, but generally data is expected to be flat.
|
||||
for key, value in pairs(data) do
|
||||
if value ~= nil then --for optional params
|
||||
if type(value) == "table" then
|
||||
for i = 1, #value do
|
||||
table.insert(params, key .. "=" .. value[i])
|
||||
end
|
||||
else
|
||||
table.insert(params, key .. "=" .. tostring(value))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return table.concat(params, "&")
|
||||
end
|
||||
|
||||
|
||||
-- prevent anyone from modifying this table:
|
||||
Url = createReadOnlyTable(Url)
|
||||
|
||||
return Url
|
||||
@@ -0,0 +1,12 @@
|
||||
return function()
|
||||
|
||||
local ContentProvider = game:GetService("ContentProvider")
|
||||
local Url = require(script.Parent.Url)
|
||||
|
||||
it("The base url has not been changed for debugging", function()
|
||||
local baseUrl = ContentProvider.BaseUrl
|
||||
|
||||
expect(baseUrl).to.equal(Url.BASE_URL)
|
||||
end)
|
||||
|
||||
end
|
||||
@@ -0,0 +1,17 @@
|
||||
--[[
|
||||
A function to return a fake ID, used for testing.
|
||||
|
||||
We turn all IDs into strings as we typically use them as keys in the state.
|
||||
It's better to use a string than a number, because a number would indicate
|
||||
an array index.
|
||||
|
||||
Roblox APIs expect to be given integers for IDs however, so just tonumber()
|
||||
the ID in this case.
|
||||
]]
|
||||
|
||||
local lastId = 0
|
||||
|
||||
return function()
|
||||
lastId = lastId + 1
|
||||
return tostring(lastId)
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"language": {
|
||||
"mode": "nonstrict"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
--[[
|
||||
{
|
||||
universeId : string,
|
||||
state : string,
|
||||
url : string,
|
||||
}
|
||||
]]
|
||||
|
||||
local Thumbnail = {}
|
||||
|
||||
function Thumbnail.new()
|
||||
local self = {}
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function Thumbnail.fromThumbnailData(thumbnailData, size)
|
||||
local self = Thumbnail.new()
|
||||
|
||||
self.universeId = tostring(thumbnailData.targetId)
|
||||
self.state = thumbnailData.state
|
||||
self.url = thumbnailData.imageUrl
|
||||
self.size = size
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function Thumbnail.isCompleteThumbnailData(thumbnailData)
|
||||
return type(thumbnailData) == "table"
|
||||
and type(thumbnailData.targetId) == "number"
|
||||
and type(thumbnailData.state) == "string"
|
||||
and (type(thumbnailData.imageUrl) == "string" or thumbnailData.imageUrl == nil)
|
||||
end
|
||||
|
||||
function Thumbnail.checkStateIsFinal(thumbnailState)
|
||||
return thumbnailState ~= "Pending"
|
||||
end
|
||||
|
||||
return Thumbnail
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
return function()
|
||||
local Thumbnail = require(script.Parent.Thumbnail)
|
||||
|
||||
it("should set fields without errors", function()
|
||||
local testData =
|
||||
{
|
||||
targetId = 123456,
|
||||
state = "Completed",
|
||||
imageUrl = "a url",
|
||||
}
|
||||
|
||||
local thumbnail = Thumbnail.fromThumbnailData(testData)
|
||||
|
||||
expect(thumbnail).to.be.a("table")
|
||||
expect(thumbnail.universeId).to.equal("123456")
|
||||
expect(thumbnail.state).to.equal("Completed")
|
||||
expect(thumbnail.url).to.equal("a url")
|
||||
end)
|
||||
|
||||
end
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
local ThumbnailRequest = {}
|
||||
|
||||
function ThumbnailRequest.new()
|
||||
local self = {}
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function ThumbnailRequest.fromData(thumbnailType, thumbnailSize)
|
||||
local self = ThumbnailRequest.new()
|
||||
|
||||
self.thumbnailType = thumbnailType
|
||||
self.thumbnailSize = thumbnailSize
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
return ThumbnailRequest
|
||||
@@ -0,0 +1,134 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
local MockId = require(CorePackages.AppTempCommon.LuaApp.MockId)
|
||||
|
||||
local User = {}
|
||||
|
||||
User.PresenceType = {
|
||||
OFFLINE = "OFFLINE",
|
||||
ONLINE = "ONLINE",
|
||||
IN_GAME = "IN_GAME",
|
||||
IN_STUDIO = "IN_STUDIO",
|
||||
}
|
||||
|
||||
function User.new()
|
||||
local self = {}
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function User.mock()
|
||||
local self = User.new()
|
||||
|
||||
self.id = MockId()
|
||||
|
||||
self.isFetching = false
|
||||
self.isFriend = false
|
||||
self.lastLocation = nil
|
||||
self.name = "USER NAME"
|
||||
self.universeId = nil
|
||||
self.placeId = nil
|
||||
self.rootPlaceId = nil
|
||||
self.gameInstanceId = nil
|
||||
self.presence = User.PresenceType.OFFLINE
|
||||
self.membership = nil
|
||||
self.thumbnails = nil
|
||||
self.lastOnline = nil
|
||||
self.displayName = "DN+" .. self.name
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-- Note: Going forward, leverage User.fromDataTable() instead.
|
||||
-- It accepts a more flexible parameter than User.fromData() and constructs the same User model
|
||||
function User.fromData(id, name, isFriend)
|
||||
local self = User.new()
|
||||
|
||||
self.id = tostring(id)
|
||||
|
||||
self.isFetching = false
|
||||
self.isFriend = isFriend
|
||||
self.lastLocation = nil
|
||||
self.name = name
|
||||
self.universeId = nil
|
||||
self.placeId = nil
|
||||
self.rootPlaceId = nil
|
||||
self.gameInstanceId = nil
|
||||
|
||||
self.presence = (Players.LocalPlayer and self.id == tostring(Players.LocalPlayer.UserId))
|
||||
and User.PresenceType.ONLINE or nil
|
||||
self.thumbnails = nil
|
||||
self.lastOnline = nil
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function User.fromDataTable(data)
|
||||
local self = User.new()
|
||||
|
||||
self.id = tostring(data.id)
|
||||
self.isFriend = data.isFriend
|
||||
self.presence = (Players.LocalPlayer
|
||||
and self.id == tostring(Players.LocalPlayer.UserId)) and User.PresenceType.ONLINE or nil
|
||||
self.isFetching = false
|
||||
self.lastLocation = nil
|
||||
self.name = data.name
|
||||
self.displayName = data.displayName or data.name
|
||||
self.universeId = nil
|
||||
self.placeId = nil
|
||||
self.rootPlaceId = nil
|
||||
self.gameInstanceId = nil
|
||||
self.thumbnails = nil
|
||||
self.lastOnline = nil
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function User.compare(user1, user2)
|
||||
assert(not(user1 == nil and user2 == nil))
|
||||
assert(user1 == nil or typeof(user1) == "table")
|
||||
assert(user2 == nil or typeof(user2) == "table")
|
||||
|
||||
-- Return false if any of the provided input is nil(empty).
|
||||
if not user1 or not user2 then
|
||||
return false
|
||||
end
|
||||
|
||||
for field, valueInUser2 in pairs(user2) do
|
||||
if user1[field] ~= valueInUser2 then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
for field, valueInUser1 in pairs(user1) do
|
||||
if user2[field] ~= valueInUser1 then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function User.userPresenceToText(localization, user)
|
||||
local presence = user.presence
|
||||
local lastLocation = user.lastLocation
|
||||
|
||||
if not presence then
|
||||
return ''
|
||||
end
|
||||
|
||||
if presence == User.PresenceType.OFFLINE then
|
||||
return localization:Format("Common.Presence.Label.Offline")
|
||||
elseif presence == User.PresenceType.ONLINE then
|
||||
return localization:Format("Common.Presence.Label.Online")
|
||||
elseif (presence == User.PresenceType.IN_GAME) or (presence == User.PresenceType.IN_STUDIO) then
|
||||
if lastLocation ~= nil then
|
||||
return lastLocation
|
||||
else
|
||||
return localization:Format("Common.Presence.Label.Online")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return User
|
||||
@@ -0,0 +1,98 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Immutable = require(CorePackages.AppTempCommon.Common.Immutable)
|
||||
local User = require(CorePackages.AppTempCommon.LuaApp.Models.User)
|
||||
|
||||
it("should detect if provided users are identical", function()
|
||||
local clone1 = User.fromData(1, "Andy", true)
|
||||
local clone2 = Immutable.Set(clone1, "isFriend", true)
|
||||
|
||||
local result = User.compare(clone1, clone2)
|
||||
expect(result).to.equal(true)
|
||||
|
||||
result = User.compare(clone2, clone1)
|
||||
expect(result).to.equal(true)
|
||||
end)
|
||||
|
||||
it("should detect when there is one or more fields with different values", function()
|
||||
local andy = User.fromData(1, "Andy", true)
|
||||
local ollie = Immutable.Set(andy, "name", "Ollie")
|
||||
|
||||
local result = User.compare(andy, ollie)
|
||||
expect(result).to.equal(false)
|
||||
|
||||
result = User.compare(ollie, andy)
|
||||
expect(result).to.equal(false)
|
||||
end)
|
||||
|
||||
it("should detect descrepancy when one user model contains more fields than the other", function()
|
||||
local andy = User.fromData(1, "Andy", true)
|
||||
local secretlyNotAndy = Immutable.Set(andy, "someDifferentField", "I'm Ollie!")
|
||||
|
||||
local result = User.compare(andy, secretlyNotAndy)
|
||||
expect(result).to.equal(false)
|
||||
|
||||
result = User.compare(secretlyNotAndy, andy)
|
||||
expect(result).to.equal(false)
|
||||
end)
|
||||
|
||||
it("should throw if invalid input is provided", function()
|
||||
local aString = "I'm not a table."
|
||||
local teddy = User.fromData(1, "Teddy", true)
|
||||
|
||||
expect(function() User.compare(nil, nil) end).to.throw()
|
||||
expect(function() User.compare(aString, nil) end).to.throw()
|
||||
expect(function() User.compare(nil, aString) end).to.throw()
|
||||
expect(function() User.compare(aString, aString) end).to.throw()
|
||||
expect(function() User.compare(teddy, aString) end).to.throw()
|
||||
expect(function() User.compare(aString, teddy) end).to.throw()
|
||||
end)
|
||||
|
||||
it("should return false if any one of the input is empty or nil)", function()
|
||||
local emptyTable = {}
|
||||
local teddy = User.fromData(1, "Teddy", true)
|
||||
|
||||
local result = User.compare(teddy, nil)
|
||||
expect(result).to.equal(false)
|
||||
|
||||
result = User.compare(nil, teddy)
|
||||
expect(result).to.equal(false)
|
||||
|
||||
result = User.compare(teddy, emptyTable)
|
||||
expect(result).to.equal(false)
|
||||
|
||||
result = User.compare(emptyTable, teddy)
|
||||
expect(result).to.equal(false)
|
||||
end)
|
||||
|
||||
describe("fromDataTable", function()
|
||||
it("should properly set user data", function()
|
||||
local data = {
|
||||
id = 1,
|
||||
name = "FooBar",
|
||||
displayName = "FooBar+DN",
|
||||
isFriend = false,
|
||||
}
|
||||
local user = User.fromDataTable(data)
|
||||
|
||||
expect(user.id).to.equal("1")
|
||||
expect(user.name).to.equal("FooBar")
|
||||
expect(user.displayName).to.equal("FooBar+DN")
|
||||
expect(user.isFriend).to.equal(false)
|
||||
end)
|
||||
|
||||
it("should still set user data without a displayName property", function()
|
||||
local data = {
|
||||
id = 1,
|
||||
name = "FooBar",
|
||||
isFriend = false,
|
||||
}
|
||||
local user = User.fromDataTable(data)
|
||||
|
||||
expect(user.id).to.equal("1")
|
||||
expect(user.name).to.equal("FooBar")
|
||||
expect(user.displayName).to.equal("FooBar")
|
||||
expect(user.isFriend).to.equal(false)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,51 @@
|
||||
local percentReporting = tonumber(settings():GetFVariable("PercentReportingNetworkProfileAfterStartup"))
|
||||
|
||||
local FEATURE_NAME = "NetworkProfileDuringStartup"
|
||||
local QUEUED_MEASURE_NAME = "Queued"
|
||||
local NAME_LOOKUP_MEASURE_NAME = "NameLookup"
|
||||
local CONNECT_MEASURE_NAME = "Connect"
|
||||
local SSL_HANDSHAKE_MEASURE_NAME = "SSLHandshake"
|
||||
local MAKE_REQUEST_MEASURE_NAME = "MakeRequest"
|
||||
local RECEIVE_RESPONSE_MEASURE_NAME = "ReceiveResponse"
|
||||
|
||||
local NetworkProfiler = {}
|
||||
NetworkProfiler.__index = NetworkProfiler
|
||||
|
||||
NetworkProfiler.aggregate = {
|
||||
queued = 0.0,
|
||||
nameLookup = 0.0,
|
||||
connect = 0.0,
|
||||
sslHandshake = 0.0,
|
||||
makeRequest = 0.0,
|
||||
receiveResponse = 0.0,
|
||||
}
|
||||
|
||||
function NetworkProfiler:track(timeProfile)
|
||||
self.aggregate.queued = self.aggregate.queued + timeProfile.queued
|
||||
if timeProfile.nameLookup >= 0 then
|
||||
self.aggregate.nameLookup = self.aggregate.nameLookup + timeProfile.nameLookup
|
||||
end
|
||||
if timeProfile.connect >= 0 then
|
||||
self.aggregate.connect = self.aggregate.connect + timeProfile.connect
|
||||
end
|
||||
if timeProfile.sslHandshake >= 0 then
|
||||
self.aggregate.sslHandshake = self.aggregate.sslHandshake + timeProfile.sslHandshake
|
||||
end
|
||||
if timeProfile.makeRequest >= 0 then
|
||||
self.aggregate.makeRequest = self.aggregate.makeRequest + timeProfile.makeRequest
|
||||
end
|
||||
if timeProfile.receiveResponse >= 0 then
|
||||
self.aggregate.receiveResponse = self.aggregate.receiveResponse + timeProfile.receiveResponse
|
||||
end
|
||||
end
|
||||
|
||||
function NetworkProfiler:report(reportToDiag)
|
||||
reportToDiag(FEATURE_NAME, QUEUED_MEASURE_NAME, self.aggregate.queued, percentReporting)
|
||||
reportToDiag(FEATURE_NAME, NAME_LOOKUP_MEASURE_NAME, self.aggregate.nameLookup, percentReporting)
|
||||
reportToDiag(FEATURE_NAME, CONNECT_MEASURE_NAME, self.aggregate.connect, percentReporting)
|
||||
reportToDiag(FEATURE_NAME, SSL_HANDSHAKE_MEASURE_NAME, self.aggregate.sslHandshake, percentReporting)
|
||||
reportToDiag(FEATURE_NAME, MAKE_REQUEST_MEASURE_NAME, self.aggregate.makeRequest, percentReporting)
|
||||
reportToDiag(FEATURE_NAME, RECEIVE_RESPONSE_MEASURE_NAME, self.aggregate.receiveResponse, percentReporting)
|
||||
end
|
||||
|
||||
return NetworkProfiler
|
||||
@@ -0,0 +1,8 @@
|
||||
-----------------------------------------------------------------------------
|
||||
--- ---
|
||||
--- Under Migration to CorePackages ---
|
||||
--- ---
|
||||
-----------------------------------------------------------------------------
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
return require(CorePackages.Promise)
|
||||
@@ -0,0 +1,91 @@
|
||||
--[[
|
||||
Provides utility functions for Promises
|
||||
]]
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Result = require(CorePackages.AppTempCommon.LuaApp.Result)
|
||||
local Promise = require(CorePackages.AppTempCommon.LuaApp.Promise)
|
||||
|
||||
local PromiseUtilities = {}
|
||||
|
||||
--[[
|
||||
Accept a table of promises;
|
||||
promises = {
|
||||
[1] = Promise.resolve(),
|
||||
["Home"] = Promise.reject(),
|
||||
...
|
||||
}
|
||||
Returns a new promise that:
|
||||
* is resolved when all input promises are finished.
|
||||
returns the results of each individual promises in a list of Results
|
||||
results = {
|
||||
[1] = Result1,
|
||||
["Home"] = Result2,
|
||||
...
|
||||
}
|
||||
* is never rejected.
|
||||
]]
|
||||
function PromiseUtilities.Batch(promises)
|
||||
assert(type(promises) == "table", "PromiseUtilities expects a list of Promises!")
|
||||
|
||||
local numberOfPromises = 0
|
||||
|
||||
for _, promise in pairs(promises) do
|
||||
assert(Promise.is(promise), "PromiseUtilities expects a list of Promises!")
|
||||
numberOfPromises = numberOfPromises + 1
|
||||
end
|
||||
|
||||
return Promise.new(function(resolve, reject)
|
||||
local totalCompleted = 0
|
||||
local results = {}
|
||||
|
||||
local function promiseCompleted(key, success, value)
|
||||
results[key] = Result.new(success, value)
|
||||
totalCompleted = totalCompleted + 1
|
||||
|
||||
if totalCompleted == numberOfPromises then
|
||||
resolve(results)
|
||||
end
|
||||
end
|
||||
|
||||
if next(promises) == nil then
|
||||
resolve(results)
|
||||
end
|
||||
|
||||
for key, promise in pairs(promises) do
|
||||
promise:andThen(
|
||||
function(result, ...)
|
||||
if select("#", ...) > 0 then
|
||||
warn("Promises in PromiseUtilities.Batch should not return tuple")
|
||||
end
|
||||
promiseCompleted(key, true, result)
|
||||
end,
|
||||
function(reason)
|
||||
promiseCompleted(key, false, reason)
|
||||
end
|
||||
)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function PromiseUtilities.CountResults(batchPromiseResults)
|
||||
local totalCount = 0
|
||||
local failureCount = 0
|
||||
|
||||
for _, result in pairs(batchPromiseResults) do
|
||||
local success, _ = result:unwrap()
|
||||
if not success then
|
||||
failureCount = failureCount + 1
|
||||
end
|
||||
totalCount = totalCount + 1
|
||||
end
|
||||
|
||||
return {
|
||||
successCount = totalCount - failureCount,
|
||||
failureCount = failureCount,
|
||||
totalCount = totalCount,
|
||||
}
|
||||
end
|
||||
|
||||
return PromiseUtilities
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PromiseUtilities = require(CorePackages.AppTempCommon.LuaApp.PromiseUtilities)
|
||||
local Promise = require(CorePackages.AppTempCommon.LuaApp.Promise)
|
||||
local Result = require(CorePackages.AppTempCommon.LuaApp.Result)
|
||||
local TableUtilities = require(CorePackages.AppTempCommon.LuaApp.TableUtilities)
|
||||
|
||||
describe("PromiseUtilities.Batch", function()
|
||||
it("should assert if input is not a list of Promises", function()
|
||||
expect(function()
|
||||
PromiseUtilities.Batch()
|
||||
end).to.throw()
|
||||
|
||||
expect(function()
|
||||
PromiseUtilities.Batch(Promise.resolve(), Promise.resolve())
|
||||
end).to.throw()
|
||||
|
||||
expect(function()
|
||||
PromiseUtilities.Batch({
|
||||
Promise.resolve(),
|
||||
"something else"
|
||||
})
|
||||
end).to.throw()
|
||||
end)
|
||||
|
||||
it("should invoke the given resolve callback when all promises are finished", function()
|
||||
local promises = {
|
||||
[1] = Promise.resolve(),
|
||||
["Home"] = Promise.resolve()
|
||||
}
|
||||
local callCount = 0
|
||||
|
||||
local batchedPromise = PromiseUtilities.Batch(promises):andThen(
|
||||
function()
|
||||
callCount = callCount + 1
|
||||
end
|
||||
)
|
||||
|
||||
expect(batchedPromise).to.be.ok()
|
||||
expect(callCount).to.equal(1)
|
||||
expect(batchedPromise._status).to.equal(Promise.Status.Resolved)
|
||||
end)
|
||||
|
||||
it("should not invoke any callbacks when one of the promises are not finished", function()
|
||||
local promises = {
|
||||
[1] = Promise.resolve(),
|
||||
["Home"] = Promise.new(function() end)
|
||||
}
|
||||
local callCount = 0
|
||||
|
||||
local batchedPromise = PromiseUtilities.Batch(promises):andThen(
|
||||
function()
|
||||
callCount = callCount + 1
|
||||
end,
|
||||
|
||||
function()
|
||||
callCount = callCount + 1
|
||||
end
|
||||
)
|
||||
|
||||
expect(batchedPromise).to.be.ok()
|
||||
expect(callCount).to.equal(0)
|
||||
expect(batchedPromise._status).to.equal(Promise.Status.Started)
|
||||
end)
|
||||
|
||||
it("should return the correct results of each individual promise", function()
|
||||
local promises = {
|
||||
[1] = Promise.resolve(5),
|
||||
["Home"] = Promise.reject("failed")
|
||||
}
|
||||
local promiseResults = nil
|
||||
|
||||
local batchedPromise = PromiseUtilities.Batch(promises):andThen(
|
||||
function(results)
|
||||
promiseResults = results
|
||||
end
|
||||
)
|
||||
|
||||
expect(batchedPromise).to.be.ok()
|
||||
expect(batchedPromise._status).to.equal(Promise.Status.Resolved)
|
||||
expect(TableUtilities.FieldCount(promiseResults)).to.equal(2)
|
||||
|
||||
expect(Result.is(promiseResults[1])).to.equal(true)
|
||||
local success1, value1 = promiseResults[1]:unwrap()
|
||||
expect(success1).to.equal(true)
|
||||
expect(value1).to.equal(5)
|
||||
local isMatchCalled1 = false
|
||||
promiseResults[1]:match(function(result)
|
||||
expect(result).to.equal(5)
|
||||
isMatchCalled1 = true
|
||||
end,
|
||||
function()
|
||||
error("should not be called")
|
||||
end)
|
||||
expect(isMatchCalled1).to.equal(true)
|
||||
|
||||
|
||||
expect(Result.is(promiseResults["Home"])).to.equal(true)
|
||||
local success2, value2 = promiseResults["Home"]:unwrap()
|
||||
expect(success2).to.equal(false)
|
||||
expect(value2).to.equal("failed")
|
||||
local isMatchCalled2 = false
|
||||
promiseResults["Home"]:match(function()
|
||||
error("should not be called")
|
||||
end,
|
||||
function(err)
|
||||
expect(err).to.equal("failed")
|
||||
isMatchCalled2 = true
|
||||
end)
|
||||
expect(isMatchCalled2).to.equal(true)
|
||||
end)
|
||||
|
||||
it("should return the correct results of each individual promise that resolved later", function()
|
||||
local resolveLater
|
||||
local rejectLater
|
||||
|
||||
local promises = {
|
||||
[1] = Promise.new(function(resolve)
|
||||
resolveLater = resolve
|
||||
end),
|
||||
["Home"] = Promise.new(function(_, reject)
|
||||
rejectLater = reject
|
||||
end)
|
||||
}
|
||||
local promiseResults = nil
|
||||
|
||||
local batchedPromise = PromiseUtilities.Batch(promises):andThen(
|
||||
function(results)
|
||||
promiseResults = results
|
||||
end
|
||||
)
|
||||
|
||||
resolveLater(5)
|
||||
rejectLater("failed")
|
||||
|
||||
expect(batchedPromise).to.be.ok()
|
||||
expect(batchedPromise._status).to.equal(Promise.Status.Resolved)
|
||||
expect(TableUtilities.FieldCount(promiseResults)).to.equal(2)
|
||||
|
||||
expect(Result.is(promiseResults[1])).to.equal(true)
|
||||
local success1, value1 = promiseResults[1]:unwrap()
|
||||
expect(success1).to.equal(true)
|
||||
expect(value1).to.equal(5)
|
||||
|
||||
expect(Result.is(promiseResults["Home"])).to.equal(true)
|
||||
local success2, value2 = promiseResults["Home"]:unwrap()
|
||||
expect(success2).to.equal(false)
|
||||
expect(value2).to.equal("failed")
|
||||
end)
|
||||
|
||||
it("should resolve if given an empty list of promises", function()
|
||||
local emptyPromises = {}
|
||||
local callCount = 0
|
||||
|
||||
local batchedPromise = PromiseUtilities.Batch(emptyPromises):andThen(
|
||||
function(results)
|
||||
callCount = callCount + 1
|
||||
end
|
||||
)
|
||||
|
||||
expect(batchedPromise).to.be.ok()
|
||||
expect(callCount).to.equal(1)
|
||||
expect(batchedPromise._status).to.equal(Promise.Status.Resolved)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("PromiseUtilities.CountResults", function()
|
||||
it("should count the results correctly", function()
|
||||
local emptyResults = {}
|
||||
|
||||
local countResult = PromiseUtilities.CountResults(emptyResults)
|
||||
|
||||
expect(countResult).to.be.ok()
|
||||
expect(countResult.successCount).to.equal(0)
|
||||
expect(countResult.failureCount).to.equal(0)
|
||||
expect(countResult.totalCount).to.equal(0)
|
||||
|
||||
local promiseResults = { Result.success(0), Result.success(0), Result.error(1) }
|
||||
|
||||
countResult = PromiseUtilities.CountResults(promiseResults)
|
||||
|
||||
expect(countResult).to.be.ok()
|
||||
expect(countResult.successCount).to.equal(2)
|
||||
expect(countResult.failureCount).to.equal(1)
|
||||
expect(countResult.totalCount).to.equal(3)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"language": {
|
||||
"mode": "nonstrict"
|
||||
},
|
||||
"lint": {
|
||||
"ImplicitReturn": "fatal"
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local UpdateFetchingStatus = require(CorePackages.AppTempCommon.LuaApp.Actions.UpdateFetchingStatus)
|
||||
local Cryo = require(CorePackages.Cryo)
|
||||
|
||||
return function(state, action)
|
||||
state = state or {}
|
||||
|
||||
if action.type == UpdateFetchingStatus.name then
|
||||
local key = action.key
|
||||
local status = action.status
|
||||
local value
|
||||
if status ~= nil then
|
||||
value = status
|
||||
else
|
||||
value = Cryo.None
|
||||
end
|
||||
|
||||
state = Cryo.Dictionary.join(
|
||||
state,
|
||||
{
|
||||
[key] = value,
|
||||
}
|
||||
)
|
||||
|
||||
end
|
||||
|
||||
return state
|
||||
end
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local UpdateFetchingStatus = require(CorePackages.AppTempCommon.LuaApp.Actions.UpdateFetchingStatus)
|
||||
local FetchingStatusReducer = require(CorePackages.AppTempCommon.LuaApp.Reducers.FetchingStatus)
|
||||
local RetrievalStatus = require(CorePackages.AppTempCommon.LuaApp.Enum.RetrievalStatus)
|
||||
local TableUtilities = require(CorePackages.AppTempCommon.LuaApp.TableUtilities)
|
||||
|
||||
local KEY_1 = "key_1"
|
||||
local KEY_2 = "key_2"
|
||||
|
||||
describe("FetchingStatus", function()
|
||||
it("should be empty by default", function()
|
||||
local state = FetchingStatusReducer(nil, {})
|
||||
|
||||
expect(TableUtilities.FieldCount(state)).to.equal(0)
|
||||
end)
|
||||
|
||||
it("should not be modified by other actions", function()
|
||||
local oldState = FetchingStatusReducer(nil, {})
|
||||
local newState = FetchingStatusReducer(oldState, { type = "not a real action" })
|
||||
|
||||
expect(newState).to.equal(oldState)
|
||||
end)
|
||||
|
||||
it("should be changed using UpdateFetchingStatus", function()
|
||||
local state = FetchingStatusReducer(nil, {})
|
||||
|
||||
state = FetchingStatusReducer(state, UpdateFetchingStatus(KEY_1, RetrievalStatus.Fetching))
|
||||
expect(state[KEY_1]).to.equal(RetrievalStatus.Fetching)
|
||||
|
||||
state = FetchingStatusReducer(state, UpdateFetchingStatus(KEY_1, RetrievalStatus.Failed))
|
||||
expect(state[KEY_1]).to.equal(RetrievalStatus.Failed)
|
||||
end)
|
||||
|
||||
it("should store different values for different keys", function()
|
||||
local state = FetchingStatusReducer(nil, {})
|
||||
|
||||
state = FetchingStatusReducer(state, UpdateFetchingStatus(KEY_1, RetrievalStatus.Failed))
|
||||
state = FetchingStatusReducer(state, UpdateFetchingStatus(KEY_2, RetrievalStatus.Done))
|
||||
|
||||
expect(state[KEY_1]).to.equal(RetrievalStatus.Failed)
|
||||
expect(state[KEY_2]).to.equal(RetrievalStatus.Done)
|
||||
end)
|
||||
|
||||
it("should clear values for nil keys", function()
|
||||
local state = { [KEY_1] = RetrievalStatus.Fetching }
|
||||
|
||||
state = FetchingStatusReducer(state, UpdateFetchingStatus(KEY_1, nil))
|
||||
expect(state[KEY_1]).to.equal(nil)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,43 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Immutable = require(CorePackages.AppTempCommon.Common.Immutable)
|
||||
local RetrievalStatus = require(CorePackages.AppTempCommon.LuaApp.Enum.RetrievalStatus)
|
||||
|
||||
local FetchUserFriendsStarted = require(CorePackages.AppTempCommon.LuaApp.Actions.FetchUserFriendsStarted)
|
||||
local FetchUserFriendsFailed = require(CorePackages.AppTempCommon.LuaApp.Actions.FetchUserFriendsFailed)
|
||||
local FetchUserFriendsCompleted = require(CorePackages.AppTempCommon.LuaApp.Actions.FetchUserFriendsCompleted)
|
||||
|
||||
local function setFieldPerUser(state, fieldName, userId, value)
|
||||
local field = state[fieldName] or {}
|
||||
return Immutable.JoinDictionaries(state, {
|
||||
[fieldName] = Immutable.JoinDictionaries(field, {
|
||||
[userId] = value
|
||||
})
|
||||
})
|
||||
end
|
||||
|
||||
local function setRetrievalStatus(state, userId, status)
|
||||
return setFieldPerUser(state, "retrievalStatus", userId, status)
|
||||
end
|
||||
|
||||
local function setRetrievalFailureResponse(state, userId, response)
|
||||
return setFieldPerUser(state, "retrievalFailureResponse", userId, response)
|
||||
end
|
||||
|
||||
return function(state, action)
|
||||
state = state or {
|
||||
retrievalStatus = {},
|
||||
retrievalFailureResponse = {},
|
||||
}
|
||||
|
||||
if action.type == FetchUserFriendsStarted.name then
|
||||
state = setRetrievalStatus(state, action.userId, RetrievalStatus.Fetching)
|
||||
elseif action.type == FetchUserFriendsFailed.name then
|
||||
state = setRetrievalStatus(state, action.userId, RetrievalStatus.Failed)
|
||||
state = setRetrievalFailureResponse(state, action.userId, action.response)
|
||||
elseif action.type == FetchUserFriendsCompleted.name then
|
||||
state = setRetrievalStatus(state, action.userId, RetrievalStatus.Done)
|
||||
end
|
||||
|
||||
return state
|
||||
end
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Immutable = require(CorePackages.AppTempCommon.Common.Immutable)
|
||||
local ReceivedPlacesInfos = require(CorePackages.AppTempCommon.LuaApp.Actions.ReceivedPlacesInfos)
|
||||
|
||||
local LuaAppFlags = CorePackages.AppTempCommon.LuaApp.Flags
|
||||
local convertUniverseIdToString = require(LuaAppFlags.ConvertUniverseIdToString)
|
||||
|
||||
return function(state, action)
|
||||
state = state or {}
|
||||
|
||||
if action.type == ReceivedPlacesInfos.name then
|
||||
for _, placeInfo in pairs(action.placesInfos) do
|
||||
local universeId = convertUniverseIdToString(placeInfo.universeId)
|
||||
|
||||
state = Immutable.Set(state, universeId, placeInfo)
|
||||
end
|
||||
end
|
||||
|
||||
return state
|
||||
end
|
||||
@@ -0,0 +1,98 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Immutable = require(CorePackages.AppTempCommon.Common.Immutable)
|
||||
|
||||
local AddUser = require(CorePackages.AppTempCommon.LuaApp.Actions.AddUser)
|
||||
local AddUsers = require(CorePackages.AppTempCommon.LuaApp.Actions.AddUsers)
|
||||
local ReceivedUserPresence = require(CorePackages.AppTempCommon.LuaChat.Actions.ReceivedUserPresence)
|
||||
local RemoveUser = require(CorePackages.AppTempCommon.LuaApp.Actions.RemoveUser)
|
||||
local SetUserIsFriend = require(CorePackages.AppTempCommon.LuaApp.Actions.SetUserIsFriend)
|
||||
local SetUserMembershipType = require(CorePackages.AppTempCommon.LuaApp.Actions.SetUserMembershipType)
|
||||
local SetUserPresence = require(CorePackages.AppTempCommon.LuaApp.Actions.SetUserPresence)
|
||||
local SetUserThumbnail = require(CorePackages.AppTempCommon.LuaApp.Actions.SetUserThumbnail)
|
||||
|
||||
return function(state, action)
|
||||
state = state or {}
|
||||
|
||||
if action.type == AddUser.name then
|
||||
local user = action.user
|
||||
state = Immutable.Set(state, user.id, user)
|
||||
elseif action.type == AddUsers.name then
|
||||
local addedUsers = action.users
|
||||
local usersUpdate = {}
|
||||
for userId, addedUser in pairs(addedUsers) do
|
||||
local existingUser = state[userId]
|
||||
if existingUser then
|
||||
usersUpdate[userId] = Immutable.JoinDictionaries(existingUser, addedUser)
|
||||
else
|
||||
usersUpdate[userId] = addedUser
|
||||
end
|
||||
end
|
||||
|
||||
state = Immutable.JoinDictionaries(state, usersUpdate)
|
||||
|
||||
elseif action.type == SetUserIsFriend.name then
|
||||
local user = state[action.userId]
|
||||
if user then
|
||||
local newUser = Immutable.Set(user, "isFriend", action.isFriend)
|
||||
state = Immutable.Set(state, user.id, newUser)
|
||||
else
|
||||
warn("Setting isFriend on user", action.userId, "who doesn't exist yet")
|
||||
end
|
||||
elseif action.type == SetUserPresence.name then
|
||||
local user = state[action.userId]
|
||||
if user then
|
||||
local newUser = Immutable.JoinDictionaries(user, {
|
||||
presence = action.presence,
|
||||
lastLocation = action.lastLocation,
|
||||
})
|
||||
state = Immutable.Set(state, user.id, newUser)
|
||||
else
|
||||
warn("Setting presence on user", action.userId, "who doesn't exist yet")
|
||||
end
|
||||
elseif action.type == ReceivedUserPresence.name then
|
||||
local user = state[action.userId]
|
||||
if user then
|
||||
state = Immutable.JoinDictionaries(state, {
|
||||
[action.userId] = Immutable.JoinDictionaries(user, {
|
||||
presence = action.presence,
|
||||
lastLocation = action.lastLocation,
|
||||
placeId = action.placeId,
|
||||
rootPlaceId = action.rootPlaceId,
|
||||
gameInstanceId = action.gameInstanceId,
|
||||
lastOnline = action.lastOnline,
|
||||
universeId = action.universeId,
|
||||
}),
|
||||
})
|
||||
end
|
||||
elseif action.type == SetUserThumbnail.name then
|
||||
local user = state[action.userId]
|
||||
if user then
|
||||
local thumbnails = user.thumbnails or {}
|
||||
state = Immutable.JoinDictionaries(state, {
|
||||
[action.userId] = Immutable.JoinDictionaries(user, {
|
||||
thumbnails = Immutable.JoinDictionaries(thumbnails, {
|
||||
[action.thumbnailType] = Immutable.JoinDictionaries(thumbnails[action.thumbnailType] or {}, {
|
||||
[action.thumbnailSize] = action.image,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
end
|
||||
elseif action.type == SetUserMembershipType.name then
|
||||
local user = state[action.userId]
|
||||
if user then
|
||||
state = Immutable.JoinDictionaries(state, {
|
||||
[action.userId] = Immutable.JoinDictionaries(user, {
|
||||
membership = action.membershipType,
|
||||
}),
|
||||
})
|
||||
end
|
||||
elseif action.type == RemoveUser.name then
|
||||
if state[action.userId] then
|
||||
state = Immutable.RemoveFromDictionary(state, action.userId)
|
||||
end
|
||||
end
|
||||
|
||||
return state
|
||||
end
|
||||
@@ -0,0 +1,106 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local MockId = require(CorePackages.AppTempCommon.LuaApp.MockId)
|
||||
local User = require(CorePackages.AppTempCommon.LuaApp.Models.User)
|
||||
local Users = require(CorePackages.AppTempCommon.LuaApp.Reducers.Users)
|
||||
|
||||
local AddUser = require(CorePackages.AppTempCommon.LuaApp.Actions.AddUser)
|
||||
local ReceivedUserPresence = require(CorePackages.AppTempCommon.LuaChat.Actions.ReceivedUserPresence)
|
||||
local SetUserIsFriend = require(CorePackages.AppTempCommon.LuaApp.Actions.SetUserIsFriend)
|
||||
local SetUserMembershipType = require(CorePackages.AppTempCommon.LuaApp.Actions.SetUserMembershipType)
|
||||
local SetUserPresence = require(CorePackages.AppTempCommon.LuaApp.Actions.SetUserPresence)
|
||||
|
||||
describe("initial state", function()
|
||||
it("should return an initial table when passed nil", function()
|
||||
local state = Users(nil, {})
|
||||
expect(state).to.be.a("table")
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("AddUser", function()
|
||||
it("should add a user to the store", function()
|
||||
local user = User.mock()
|
||||
local state = {}
|
||||
|
||||
state = Users(state, AddUser(user))
|
||||
|
||||
expect(state[user.id]).to.equal(user)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("SetUserIsFriend", function()
|
||||
it("should set isFriend on an existing user", function()
|
||||
local user = User.mock()
|
||||
local state = {
|
||||
[user.id] = user
|
||||
}
|
||||
|
||||
expect(state[user.id].isFriend).to.equal(false)
|
||||
|
||||
state = Users(state, SetUserIsFriend(user.id, true))
|
||||
expect(state[user.id].isFriend).to.equal(true)
|
||||
|
||||
state = Users(state, SetUserIsFriend(user.id, false))
|
||||
expect(state[user.id].isFriend).to.equal(false)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("SetUserPresence", function()
|
||||
it("should set presence on an existing user", function()
|
||||
local user = User.mock()
|
||||
local state = {
|
||||
[user.id] = user
|
||||
}
|
||||
|
||||
expect(state[user.id].presence).to.equal(User.PresenceType.OFFLINE)
|
||||
|
||||
state = Users(state, SetUserPresence(user.id, User.PresenceType.ONLINE))
|
||||
expect(state[user.id].presence).to.equal(User.PresenceType.ONLINE)
|
||||
|
||||
state = Users(state, SetUserPresence(user.id, User.PresenceType.IN_GAME))
|
||||
expect(state[user.id].presence).to.equal(User.PresenceType.IN_GAME)
|
||||
|
||||
state = Users(state, SetUserPresence(user.id, User.PresenceType.IN_STUDIO))
|
||||
expect(state[user.id].presence).to.equal(User.PresenceType.IN_STUDIO)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("ReceivedUserPresence", function()
|
||||
it("should set presence on an existing user", function()
|
||||
local user = User.mock()
|
||||
local state = {
|
||||
[user.id] = user
|
||||
}
|
||||
|
||||
local existingPresence = user.presence
|
||||
local newPresence = 'ONLINE'
|
||||
local lastLocation = MockId()
|
||||
local newPlaceId = MockId()
|
||||
|
||||
state = Users(state, ReceivedUserPresence(user.id, newPresence, lastLocation, newPlaceId))
|
||||
|
||||
expect(user.presence).to.equal(existingPresence)
|
||||
expect(state[user.id].presence).to.equal(newPresence)
|
||||
expect(state[user.id].lastLocation).to.equal(lastLocation)
|
||||
expect(state[user.id].placeId).to.equal(newPlaceId)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("SetUserMembershipType", function()
|
||||
it("should set membership on an existing user", function()
|
||||
local user = User.mock()
|
||||
local state = {
|
||||
[user.id] = user
|
||||
}
|
||||
|
||||
local existingMembership = user.membership
|
||||
local newMembership = Enum.MembershipType.BuildersClub
|
||||
|
||||
state = Users(state, SetUserMembershipType(user.id, newMembership))
|
||||
|
||||
expect(user.membership).to.equal(existingMembership)
|
||||
expect(state[user.id].membership).to.equal(newMembership)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,8 @@
|
||||
-----------------------------------------------------------------------------
|
||||
--- ---
|
||||
--- Under Migration to CorePackages ---
|
||||
--- ---
|
||||
-----------------------------------------------------------------------------
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
return require(CorePackages.Result)
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"language": {
|
||||
"mode": "nonstrict"
|
||||
},
|
||||
"lint": {
|
||||
"ImplicitReturn": "fatal"
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
--[[
|
||||
The is a wrapper for the style provider for apps.
|
||||
props:
|
||||
style : table - Includes the name of the theme and font being used.
|
||||
{
|
||||
themeName : string - The name of the theme being used.
|
||||
fontName : string - The name of the font being used.
|
||||
}
|
||||
]]
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local ArgCheck = require(CorePackages.ArgCheck)
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local UIBlox = require(CorePackages.UIBlox)
|
||||
local StyleProvider = UIBlox.Style.Provider
|
||||
local StylePalette = require(script.Parent.StylePalette)
|
||||
|
||||
local AppStyleProvider = Roact.Component:extend("AppStyleProvider")
|
||||
|
||||
function AppStyleProvider:render()
|
||||
local style = self.props.style
|
||||
ArgCheck.isNotNil(style, "style prop for AppStyleProvider")
|
||||
local themeName = style.themeName
|
||||
local fontName = style.fontName
|
||||
local stylePalette = StylePalette.new()
|
||||
stylePalette:updateTheme(themeName)
|
||||
stylePalette:updateFont(fontName)
|
||||
local appStyle = stylePalette:currentStyle()
|
||||
|
||||
return Roact.createElement(StyleProvider,{
|
||||
style = appStyle,
|
||||
}, self.props[Roact.Children])
|
||||
end
|
||||
|
||||
return AppStyleProvider
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local AppStyleProvider = require(script.Parent.AppStyleProvider)
|
||||
local Constants = require(script.Parent.Constants)
|
||||
local appStyle = {
|
||||
themeName = Constants.ThemeName.Dark,
|
||||
fontName = Constants.FontName.Gotham,
|
||||
}
|
||||
it("should create and destroy without errors", function()
|
||||
local element = Roact.createElement("Frame")
|
||||
local appStyleProvider = Roact.createElement(AppStyleProvider, {
|
||||
style = appStyle,
|
||||
},{
|
||||
Element = element,
|
||||
})
|
||||
|
||||
local instance = Roact.mount(appStyleProvider)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
|
||||
it("should throw when style prop is nil", function()
|
||||
local element = Roact.createElement("Frame")
|
||||
local appStyleProvider = Roact.createElement(AppStyleProvider, {},{
|
||||
Element = element,
|
||||
})
|
||||
expect(function()
|
||||
local instance = Roact.mount(appStyleProvider)
|
||||
Roact.unmount(instance)
|
||||
end).to.throw()
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,23 @@
|
||||
local Colors = {
|
||||
--Common colors
|
||||
Black = Color3.fromRGB(0, 0, 0),
|
||||
White = Color3.fromRGB(255, 255, 255),
|
||||
Green = Color3.fromRGB(0, 176, 111),
|
||||
Red = Color3.fromRGB(247, 75, 82),
|
||||
|
||||
--Dark theme colors
|
||||
Carbon = Color3.fromRGB(31, 33, 35),
|
||||
Flint = Color3.fromRGB(57, 59, 61),
|
||||
Graphite = Color3.fromRGB(101, 102, 104),
|
||||
Obsidian = Color3.fromRGB(24, 25, 27),
|
||||
Pumice = Color3.fromRGB(189, 190, 190),
|
||||
Slate = Color3.fromRGB(35, 37, 39),
|
||||
|
||||
--Light theme colors
|
||||
Alabaster = Color3.fromRGB(242, 244, 245),
|
||||
Ash = Color3.fromRGB(234, 237, 239),
|
||||
Chalk = Color3.fromRGB(216, 219, 222),
|
||||
Smoke = Color3.fromRGB(96, 97, 98),
|
||||
}
|
||||
|
||||
return Colors
|
||||
@@ -0,0 +1,12 @@
|
||||
local Constants = {}
|
||||
|
||||
Constants.ThemeName = {
|
||||
Dark = "dark",
|
||||
Light = "light",
|
||||
}
|
||||
|
||||
Constants.FontName = {
|
||||
Gotham = "gotham",
|
||||
}
|
||||
|
||||
return Constants
|
||||
@@ -0,0 +1,54 @@
|
||||
local baseSize = 16
|
||||
-- Nominal size conversion
|
||||
-- https://confluence.rbx.com/display/PX/Font+Metrics
|
||||
local nominalSizeFactor = 1.2
|
||||
local font = {
|
||||
BaseSize = baseSize * nominalSizeFactor,
|
||||
Title = {
|
||||
Font = Enum.Font.GothamBlack,
|
||||
RelativeSize = 32 / baseSize,
|
||||
RelativeMinSize = 24 / baseSize,
|
||||
},
|
||||
Header1 = {
|
||||
Font = Enum.Font.GothamSemibold,
|
||||
RelativeSize = 20 / baseSize,
|
||||
RelativeMinSize = 16 / baseSize,
|
||||
},
|
||||
Header2 = {
|
||||
Font = Enum.Font.GothamSemibold,
|
||||
RelativeSize = 16 / baseSize,
|
||||
RelativeMinSize = 12 / baseSize,
|
||||
},
|
||||
SubHeader1 = {
|
||||
Font = Enum.Font.GothamSemibold,
|
||||
RelativeSize = 16 / baseSize,
|
||||
RelativeMinSize = 12 / baseSize,
|
||||
},
|
||||
Body = {
|
||||
Font = Enum.Font.Gotham,
|
||||
RelativeSize = 16 / baseSize,
|
||||
RelativeMinSize = 12 / baseSize,
|
||||
},
|
||||
CaptionHeader = {
|
||||
Font = Enum.Font.GothamSemibold,
|
||||
RelativeSize = 12 / baseSize,
|
||||
RelativeMinSize = 9 / baseSize,
|
||||
},
|
||||
CaptionSubHeader = {
|
||||
Font = Enum.Font.GothamSemibold,
|
||||
RelativeSize = 12 / baseSize,
|
||||
RelativeMinSize = 9 / baseSize,
|
||||
},
|
||||
CaptionBody = {
|
||||
Font = Enum.Font.Gotham,
|
||||
RelativeSize = 12 / baseSize,
|
||||
RelativeMinSize = 9 / baseSize,
|
||||
},
|
||||
Footer = {
|
||||
Font = Enum.Font.GothamSemibold,
|
||||
RelativeSize = 10 / baseSize,
|
||||
RelativeMinSize = 8 / baseSize,
|
||||
},
|
||||
}
|
||||
|
||||
return font
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
return function()
|
||||
it("should be valid font palette without errors", function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local UIBlox = require(CorePackages.UIBlox)
|
||||
local validateFont = UIBlox.Style.Validator.validateFont
|
||||
local Gotham = require(script.Parent.Gotham)
|
||||
assert(validateFont(Gotham))
|
||||
end)
|
||||
end
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local ArgCheck = require(CorePackages.ArgCheck)
|
||||
local Logging = require(CorePackages.Logging)
|
||||
local UIBlox = require(CorePackages.UIBlox)
|
||||
local validateFont = UIBlox.Style.Validator.validateFont
|
||||
|
||||
return function (fontName, defaultFont, fontMap)
|
||||
local mappedFont
|
||||
if fontName ~= nil and #fontName > 0 then
|
||||
mappedFont = fontMap[string.lower(fontName)]
|
||||
end
|
||||
|
||||
if mappedFont == nil then
|
||||
mappedFont = fontMap[defaultFont]
|
||||
Logging.warn(string.format("Unrecognized font name: `%s`", tostring(fontName)))
|
||||
end
|
||||
|
||||
ArgCheck.assert(validateFont(mappedFont))
|
||||
return mappedFont
|
||||
end
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
return function()
|
||||
local getFontFromName = require(script.Parent.getFontFromName)
|
||||
local Constants = require(script.Parent.Parent.Constants)
|
||||
it("should be able to get a font palette without errors", function()
|
||||
local fontMap = {
|
||||
[Constants.FontName.Gotham] = require(script.Parent.Gotham),
|
||||
}
|
||||
local fontTable = getFontFromName(Constants.FontName.Gotham, Constants.FontName.Gotham, fontMap)
|
||||
expect(fontTable).to.be.a("table")
|
||||
end)
|
||||
|
||||
it("should be able to get a font palette using default without errors", function()
|
||||
local fontMap = {
|
||||
[Constants.FontName.Gotham] = require(script.Parent.Gotham),
|
||||
}
|
||||
local fontTable = getFontFromName("sourceSans", Constants.FontName.Gotham, fontMap)
|
||||
expect(fontTable).to.be.a("table")
|
||||
end)
|
||||
|
||||
it("should throw the font palette is invalid", function()
|
||||
expect(function()
|
||||
local fontMap = {
|
||||
[Constants.FontName.Gotham] = {
|
||||
Font = {
|
||||
Font = Enum.Font.Gotham,
|
||||
RelativeSize = 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
getFontFromName(Constants.FontName.Gotham, Constants.FontName.Gotham, fontMap)
|
||||
end).to.throw()
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,49 @@
|
||||
local getThemeFromName = require(script.Parent.Themes.getThemeFromName)
|
||||
local getFontFromName = require(script.Parent.Fonts.getFontFromName)
|
||||
local Constants = require(script.Parent.Constants)
|
||||
|
||||
local StylePalette = {}
|
||||
StylePalette.__index = StylePalette
|
||||
|
||||
local DEFAULT_FONT = Constants.FontName.Gotham
|
||||
local FONT_MAP = {
|
||||
[Constants.FontName.Gotham] = require(script.Parent.Fonts.Gotham),
|
||||
}
|
||||
|
||||
local DEFAULT_THEME = Constants.ThemeName.Light
|
||||
local THEME_MAP = {
|
||||
[Constants.ThemeName.Dark] = require(script.Parent.Themes.DarkTheme),
|
||||
[Constants.ThemeName.Light] = require(script.Parent.Themes.LightTheme),
|
||||
}
|
||||
|
||||
function StylePalette.new(style)
|
||||
--By default a new style will be empty.
|
||||
-- This will allow the font and theme to be merged independently even when one is empty.
|
||||
local self = {}
|
||||
|
||||
if style ~= nil then
|
||||
self.Font = style.Font
|
||||
self.Theme = style.Theme
|
||||
end
|
||||
|
||||
setmetatable(self, StylePalette)
|
||||
return self
|
||||
end
|
||||
|
||||
function StylePalette:updateFont(fontName)
|
||||
self.Font = getFontFromName(fontName, DEFAULT_FONT, FONT_MAP)
|
||||
end
|
||||
|
||||
function StylePalette:updateTheme(themeName)
|
||||
self.Theme = getThemeFromName(themeName, DEFAULT_THEME, THEME_MAP)
|
||||
end
|
||||
|
||||
function StylePalette:currentStyle()
|
||||
local style = {
|
||||
Font = self.Font,
|
||||
Theme = self.Theme,
|
||||
}
|
||||
return style
|
||||
end
|
||||
|
||||
return StylePalette
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local UIBlox = require(CorePackages.UIBlox)
|
||||
local StylePalette = require(script.Parent.StylePalette)
|
||||
local validateStye = UIBlox.Style.Validator.validateStyle
|
||||
|
||||
it("should be able to create a style palette", function()
|
||||
local stylePalette = StylePalette.new()
|
||||
stylePalette:updateTheme("dark")
|
||||
stylePalette:updateFont("gotham")
|
||||
local appStyle = stylePalette:currentStyle()
|
||||
expect(validateStye(appStyle)).equal(true)
|
||||
end)
|
||||
|
||||
it("should be able to create a style palette and be able to update theme 1", function()
|
||||
local stylePalette = StylePalette.new()
|
||||
stylePalette:updateTheme("dark")
|
||||
stylePalette:updateFont("gotham")
|
||||
local appStyle = stylePalette:currentStyle()
|
||||
expect(validateStye(appStyle)).equal(true)
|
||||
|
||||
stylePalette:updateTheme("light")
|
||||
local newAppStyle = stylePalette:currentStyle()
|
||||
expect(validateStye(newAppStyle)).equal(true)
|
||||
end)
|
||||
|
||||
it("should be able to create a style palette and be able to update theme 2", function()
|
||||
local stylePalette = StylePalette.new()
|
||||
stylePalette:updateTheme("dark")
|
||||
stylePalette:updateFont("gotham")
|
||||
local appStyle = stylePalette:currentStyle()
|
||||
expect(validateStye(appStyle)).equal(true)
|
||||
|
||||
stylePalette:updateFont("gotham")
|
||||
local newAppStyle = stylePalette:currentStyle()
|
||||
expect(validateStye(newAppStyle)).equal(true)
|
||||
end)
|
||||
|
||||
it("should be able to create a style palette and be able to merge an old one in", function()
|
||||
local stylePalette = StylePalette.new()
|
||||
stylePalette:updateTheme("dark")
|
||||
stylePalette:updateFont("gotham")
|
||||
local appStyle = stylePalette:currentStyle()
|
||||
expect(validateStye(appStyle)).equal(true)
|
||||
|
||||
local newstylePalette = StylePalette.new(stylePalette)
|
||||
local newAppStyle = newstylePalette:currentStyle()
|
||||
expect(validateStye(newAppStyle)).equal(true)
|
||||
end)
|
||||
|
||||
it("should be able to create a empty style palette", function()
|
||||
local stylePalette = StylePalette.new()
|
||||
local appStyle = stylePalette:currentStyle()
|
||||
expect(appStyle.Font).equal(nil)
|
||||
expect(appStyle.Theme).equal(nil)
|
||||
expect(validateStye(appStyle)).equal(false)
|
||||
end)
|
||||
end
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
local ThemesRoot = script.Parent
|
||||
local StylesRoot = ThemesRoot.Parent
|
||||
local Colors = require(StylesRoot.Colors)
|
||||
|
||||
local theme = {
|
||||
BackgroundDefault = {
|
||||
Color = Colors.Slate,
|
||||
Transparency = 0,
|
||||
},
|
||||
BackgroundContrast = {
|
||||
Color = Colors.Carbon,
|
||||
Transparency = 0,
|
||||
},
|
||||
BackgroundMuted = {
|
||||
Color = Colors.Obsidian,
|
||||
Transparency = 0,
|
||||
},
|
||||
BackgroundUIDefault = {
|
||||
Color = Colors.Flint,
|
||||
Transparency = 0,
|
||||
},
|
||||
BackgroundUIContrast = {
|
||||
Color = Colors.Black,
|
||||
Transparency = 0.3, -- Alpha 0.7
|
||||
},
|
||||
BackgroundOnHover = {
|
||||
Color = Colors.White,
|
||||
Transparency = 0.9, -- Alpha 0.1
|
||||
},
|
||||
BackgroundOnPress = {
|
||||
Color = Colors.Black,
|
||||
Transparency = 0.7, -- Alpha 0.3
|
||||
},
|
||||
|
||||
UIDefault = {
|
||||
Color = Colors.Graphite,
|
||||
Transparency = 0,
|
||||
},
|
||||
UIMuted = {
|
||||
Color = Colors.Obsidian,
|
||||
Transparency = 0.2, -- Alpha 0.8
|
||||
},
|
||||
UIEmphasis = {
|
||||
Color = Colors.White,
|
||||
Transparency = 0.7, -- Alpha 0.3
|
||||
},
|
||||
|
||||
ContextualPrimaryDefault = {
|
||||
Color = Colors.Green,
|
||||
Transparency = 0,
|
||||
},
|
||||
ContextualPrimaryOnHover = {
|
||||
Color = Colors.Green,
|
||||
Transparency = 0,
|
||||
},
|
||||
ContextualPrimaryContent = {
|
||||
Color = Colors.White,
|
||||
Transparency = 0,
|
||||
},
|
||||
|
||||
SystemPrimaryDefault = {
|
||||
Color = Colors.White,
|
||||
Transparency = 0,
|
||||
},
|
||||
SystemPrimaryOnHover = {
|
||||
Color = Colors.White,
|
||||
Transparency = 0,
|
||||
},
|
||||
SystemPrimaryContent = {
|
||||
Color = Colors.Flint,
|
||||
Transparency = 0,
|
||||
},
|
||||
|
||||
SecondaryDefault = {
|
||||
Color = Colors.White,
|
||||
Transparency = 0.3, -- 0.7 Alpha
|
||||
},
|
||||
SecondaryOnHover = {
|
||||
Color = Colors.White,
|
||||
Transparency = 0,
|
||||
},
|
||||
SecondaryContent = {
|
||||
Color = Colors.White,
|
||||
Transparency = 0.3, -- 0.7 Alpha
|
||||
},
|
||||
|
||||
IconDefault = {
|
||||
Color = Colors.White,
|
||||
Transparency = 0.3, -- 0.7 alpha
|
||||
},
|
||||
IconEmphasis = {
|
||||
Color = Colors.White,
|
||||
Transparency = 0,
|
||||
},
|
||||
IconOnHover = {
|
||||
Color = Colors.White,
|
||||
Transparency = 0,
|
||||
},
|
||||
|
||||
TextEmphasis = {
|
||||
Color = Colors.White,
|
||||
Transparency = 0,
|
||||
},
|
||||
TextDefault = {
|
||||
Color = Colors.Pumice,
|
||||
Transparency = 0,
|
||||
},
|
||||
TextMuted = {
|
||||
Color = Colors.White,
|
||||
Transparency = 0.3, -- 0.7 Alpha
|
||||
},
|
||||
|
||||
Divider = {
|
||||
Color = Colors.White,
|
||||
Transparency = 0.8, -- 0.2 Alpha
|
||||
},
|
||||
Overlay = {
|
||||
Color = Colors.Black,
|
||||
Transparency = 0.5, -- 0.5 Alpha
|
||||
},
|
||||
DropShadow = {
|
||||
Color = Colors.Black,
|
||||
Transparency = 0,
|
||||
},
|
||||
NavigationBar = {
|
||||
Color = Colors.Carbon,
|
||||
Transparency = 0,
|
||||
},
|
||||
PlaceHolder = {
|
||||
Color = Colors.Flint,
|
||||
Transparency = 0.5, -- 0.5 Alpha
|
||||
},
|
||||
|
||||
OnlineStatus = {
|
||||
Color = Colors.Green,
|
||||
Transparency = 0,
|
||||
},
|
||||
OfflineStatus = {
|
||||
Color = Colors.White,
|
||||
Transparency = 0.3, -- 0.7 Alpha
|
||||
},
|
||||
|
||||
Success = {
|
||||
Color = Colors.Green,
|
||||
Transparency = 0,
|
||||
},
|
||||
Alert = {
|
||||
Color = Colors.Red,
|
||||
Transparency = 0,
|
||||
},
|
||||
|
||||
Badge = {
|
||||
Color = Colors.White,
|
||||
Transparency = 0,
|
||||
},
|
||||
BadgeContent = {
|
||||
Color = Colors.Flint,
|
||||
Transparency = 0,
|
||||
},
|
||||
}
|
||||
|
||||
return theme
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
return function()
|
||||
it("should be a valid theme palette.", function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local UIBlox = require(CorePackages.UIBlox)
|
||||
local validateTheme = UIBlox.Style.Validator.validateTheme
|
||||
local DarkTheme = require(script.Parent.DarkTheme)
|
||||
assert(validateTheme(DarkTheme))
|
||||
end)
|
||||
end
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
local ThemesRoot = script.Parent
|
||||
local StylesRoot = ThemesRoot.Parent
|
||||
local LuaAppRoot = StylesRoot.Parent
|
||||
|
||||
local Colors = require(StylesRoot.Colors)
|
||||
local GetFFlagLuaAppFixLightTheme = require(LuaAppRoot.Flags.GetFFlagLuaAppFixLightTheme)
|
||||
|
||||
local theme = {
|
||||
BackgroundDefault = {
|
||||
Color = Colors.Alabaster,
|
||||
Transparency = 0,
|
||||
},
|
||||
BackgroundContrast = {
|
||||
Color = Colors.Ash,
|
||||
Transparency = 0,
|
||||
},
|
||||
BackgroundMuted = {
|
||||
Color = Colors.Chalk,
|
||||
Transparency = 0,
|
||||
},
|
||||
BackgroundUIDefault = {
|
||||
Color = Colors.White,
|
||||
Transparency = 0,
|
||||
},
|
||||
BackgroundUIContrast = {
|
||||
Color = Colors.White,
|
||||
Transparency = 0.1, -- Alpha 0.9
|
||||
},
|
||||
BackgroundOnHover = GetFFlagLuaAppFixLightTheme() and {
|
||||
Color = Colors.Black,
|
||||
Transparency = 0.9, -- Alpha 0.1
|
||||
} or {
|
||||
Color = Colors.White,
|
||||
Transparency = 0.7, -- Alpha 0.3
|
||||
},
|
||||
BackgroundOnPress = {
|
||||
Color = Colors.Black,
|
||||
Transparency = 0.9, -- Alpha 0.1
|
||||
},
|
||||
|
||||
UIDefault = {
|
||||
Color = Colors.Pumice,
|
||||
Transparency = 0,
|
||||
},
|
||||
UIMuted = {
|
||||
Color = Colors.Black,
|
||||
Transparency = 0.9, -- Alpha 0.1
|
||||
},
|
||||
UIEmphasis = {
|
||||
Color = Colors.Black,
|
||||
Transparency = 0.7, -- Alpha 0.3
|
||||
},
|
||||
|
||||
ContextualPrimaryDefault = {
|
||||
Color = Colors.Green,
|
||||
Transparency = 0,
|
||||
},
|
||||
ContextualPrimaryOnHover = {
|
||||
Color = Colors.Green,
|
||||
Transparency = 0,
|
||||
},
|
||||
ContextualPrimaryContent = {
|
||||
Color = Colors.White,
|
||||
Transparency = 0,
|
||||
},
|
||||
|
||||
SystemPrimaryDefault = {
|
||||
Color = Colors.Flint,
|
||||
Transparency = 0,
|
||||
},
|
||||
SystemPrimaryOnHover = {
|
||||
Color = Colors.Flint,
|
||||
Transparency = 0,
|
||||
},
|
||||
SystemPrimaryContent = {
|
||||
Color = Colors.White,
|
||||
Transparency = 0,
|
||||
},
|
||||
|
||||
SecondaryDefault = {
|
||||
Color = Colors.Black,
|
||||
Transparency = 0.5, -- 0.5 Alpha
|
||||
},
|
||||
SecondaryOnHover = {
|
||||
Color = Colors.Flint,
|
||||
Transparency = 0,
|
||||
},
|
||||
SecondaryContent = {
|
||||
Color = Colors.Black,
|
||||
Transparency = 0.5, -- 0.5 Alpha
|
||||
},
|
||||
|
||||
IconDefault = {
|
||||
Color = Colors.Black,
|
||||
Transparency = 0.4, -- 0.6 alpha
|
||||
},
|
||||
IconEmphasis = {
|
||||
Color = Colors.Flint,
|
||||
Transparency = 0,
|
||||
},
|
||||
IconOnHover = {
|
||||
Color = Colors.Flint,
|
||||
Transparency = 0,
|
||||
},
|
||||
|
||||
TextEmphasis = {
|
||||
Color = Colors.Flint,
|
||||
Transparency = 0,
|
||||
},
|
||||
TextDefault = {
|
||||
Color = Colors.Smoke,
|
||||
Transparency = 0,
|
||||
},
|
||||
TextMuted = {
|
||||
Color = Colors.Black,
|
||||
Transparency = 0.4, -- 0.6 Alpha
|
||||
},
|
||||
|
||||
Divider = {
|
||||
Color = Colors.Pumice,
|
||||
Transparency = 0,
|
||||
},
|
||||
Overlay = {
|
||||
Color = Colors.Black,
|
||||
Transparency = 0.7, -- 0.3 Alpha
|
||||
},
|
||||
DropShadow = {
|
||||
Color = Colors.Black,
|
||||
Transparency = 0,
|
||||
},
|
||||
NavigationBar = {
|
||||
Color = Colors.White,
|
||||
Transparency = 0,
|
||||
},
|
||||
PlaceHolder = {
|
||||
Color = Colors.Chalk,
|
||||
Transparency = 0.3, -- 0.7 Alpha
|
||||
},
|
||||
|
||||
OnlineStatus = {
|
||||
Color = Colors.Green,
|
||||
Transparency = 0,
|
||||
},
|
||||
OfflineStatus = {
|
||||
Color = Colors.Black,
|
||||
Transparency = 0.5, -- 0.5 Alpha
|
||||
},
|
||||
|
||||
Success = {
|
||||
Color = Colors.Green,
|
||||
Transparency = 0,
|
||||
},
|
||||
Alert = {
|
||||
Color = Colors.Red,
|
||||
Transparency = 0,
|
||||
},
|
||||
|
||||
Badge = {
|
||||
Color = Colors.Flint,
|
||||
Transparency = 0,
|
||||
},
|
||||
BadgeContent = {
|
||||
Color = Colors.White,
|
||||
Transparency = 0,
|
||||
},
|
||||
}
|
||||
|
||||
return theme
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
return function()
|
||||
it("should be a valid theme palette.", function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local UIBlox = require(CorePackages.UIBlox)
|
||||
local validateTheme = UIBlox.Style.Validator.validateTheme
|
||||
local LightTheme = require(script.Parent.DarkTheme)
|
||||
assert(validateTheme(LightTheme))
|
||||
end)
|
||||
end
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local ArgCheck = require(CorePackages.ArgCheck)
|
||||
local Logging = require(CorePackages.Logging)
|
||||
local UIBlox = require(CorePackages.UIBlox)
|
||||
local validateTheme = UIBlox.Style.Validator.validateTheme
|
||||
|
||||
return function (themeName, defaultTheme, themeMap)
|
||||
local mappedTheme
|
||||
if themeName ~= nil and #themeName > 0 then
|
||||
mappedTheme = themeMap[string.lower(themeName)]
|
||||
end
|
||||
|
||||
if mappedTheme == nil then
|
||||
mappedTheme = themeMap[defaultTheme]
|
||||
Logging.warn(string.format("Unrecognized theme name: `%s`", tostring(themeName)))
|
||||
end
|
||||
ArgCheck.assert(validateTheme(mappedTheme))
|
||||
return mappedTheme
|
||||
end
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
return function()
|
||||
local getThemeFromName = require(script.Parent.getThemeFromName)
|
||||
local Constants = require(script.Parent.Parent.Constants)
|
||||
it("should be able to get a theme palette without errors", function()
|
||||
local themeMap = {
|
||||
[Constants.ThemeName.Dark] = require(script.Parent.DarkTheme),
|
||||
}
|
||||
local themeTable = getThemeFromName(Constants.ThemeName.Dark, Constants.ThemeName.Dark,themeMap)
|
||||
expect(themeTable).to.be.a("table")
|
||||
end)
|
||||
|
||||
it("should be able to get a theme palette using default without errors", function()
|
||||
local themeMap = {
|
||||
[Constants.ThemeName.Dark] = require(script.Parent.DarkTheme),
|
||||
}
|
||||
local themeTable = getThemeFromName("classic", Constants.ThemeName.Dark, themeMap)
|
||||
expect(themeTable).to.be.a("table")
|
||||
end)
|
||||
|
||||
it("should throw with invalid theme palette", function()
|
||||
expect(function()
|
||||
local themeMap = {
|
||||
[Constants.ThemeName.Dark] = {
|
||||
Background = {
|
||||
Color = Color3.fromRGB(0, 0, 0),
|
||||
Transparency = 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
getThemeFromName(Constants.ThemeName.Dark, Constants.ThemeName.Dark, themeMap)
|
||||
end).to.throw()
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,21 @@
|
||||
-----------------------------------------------------------------------------
|
||||
--- ---
|
||||
--- Under Migration to CorePackages ---
|
||||
--- ---
|
||||
--- This file is a compatibility bridge between old and new api ---
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local tutils = require(CorePackages.tutils)
|
||||
|
||||
return {
|
||||
CheckListConsistency = tutils.checkListConsistency,
|
||||
DeepEqual = tutils.deepEqual,
|
||||
EqualKey = tutils.equalKey,
|
||||
FieldCount = tutils.fieldCount,
|
||||
ListDifference = tutils.listDifferences,
|
||||
Print = tutils.print,
|
||||
RecursiveToString = tutils.toString,
|
||||
ShallowEqual = tutils.shallowEqual,
|
||||
TableDifference = tutils.tableDifference,
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
return function()
|
||||
local TableUtilities = require(script.Parent.TableUtilities)
|
||||
local NotForProductionUse = game:GetService("CoreGui").RobloxGui.Modules.NotForProductionUse
|
||||
local expectedFields = require(NotForProductionUse.UnitTestHelpers.expectedFields)
|
||||
|
||||
describe("alias wrapper", function()
|
||||
it("SHOULD have all required fields", function()
|
||||
expectedFields(TableUtilities, {
|
||||
"CheckListConsistency",
|
||||
"DeepEqual",
|
||||
"EqualKey",
|
||||
"FieldCount",
|
||||
"ListDifference",
|
||||
"Print",
|
||||
"RecursiveToString",
|
||||
"ShallowEqual",
|
||||
"TableDifference",
|
||||
})
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("legacy tests", function()
|
||||
it("should return whether tables are equal to each other", function()
|
||||
local tableA = nil
|
||||
local tableB = nil
|
||||
expect(TableUtilities.ShallowEqual(tableA, tableB)).to.equal(false)
|
||||
|
||||
tableA = nil
|
||||
tableB = {}
|
||||
expect(TableUtilities.ShallowEqual(tableA, tableB)).to.equal(false)
|
||||
|
||||
tableA = {}
|
||||
tableB = nil
|
||||
expect(TableUtilities.ShallowEqual(tableA, tableB)).to.equal(false)
|
||||
|
||||
tableA = {}
|
||||
tableB = {}
|
||||
expect(TableUtilities.ShallowEqual(tableA, tableB)).to.equal(true)
|
||||
|
||||
tableA = {
|
||||
key1 = "value1",
|
||||
}
|
||||
tableB = {
|
||||
key1 = "value1",
|
||||
}
|
||||
expect(TableUtilities.ShallowEqual(tableA, tableB)).to.equal(true)
|
||||
|
||||
tableA = {
|
||||
key1 = "value1",
|
||||
}
|
||||
tableB = {
|
||||
key1 = "value2",
|
||||
}
|
||||
expect(TableUtilities.ShallowEqual(tableA, tableB)).to.equal(false)
|
||||
|
||||
tableA = {
|
||||
key1 = "value1",
|
||||
}
|
||||
tableB = {
|
||||
key2 = "value1",
|
||||
}
|
||||
expect(TableUtilities.ShallowEqual(tableA, tableB)).to.equal(false)
|
||||
|
||||
tableA = {
|
||||
key1 = "value1",
|
||||
}
|
||||
tableB = {
|
||||
key2 = "value2",
|
||||
}
|
||||
expect(TableUtilities.ShallowEqual(tableA, tableB)).to.equal(false)
|
||||
|
||||
tableA = {
|
||||
key1 = "value1",
|
||||
}
|
||||
tableB = {
|
||||
key1 = "value1",
|
||||
key2 = "value2",
|
||||
}
|
||||
expect(TableUtilities.ShallowEqual(tableA, tableB)).to.equal(false)
|
||||
end)
|
||||
|
||||
it("should return whether tables are equal to each other at key", function()
|
||||
local tableA = nil
|
||||
local tableB = nil
|
||||
expect(TableUtilities.EqualKey(tableA, tableB)).to.equal(false)
|
||||
expect(TableUtilities.EqualKey(tableA, tableB, "")).to.equal(false)
|
||||
expect(TableUtilities.EqualKey(tableA, tableB, "key1")).to.equal(false)
|
||||
|
||||
tableA = nil
|
||||
tableB = {}
|
||||
expect(TableUtilities.EqualKey(tableA, tableB)).to.equal(false)
|
||||
expect(TableUtilities.EqualKey(tableA, tableB, "")).to.equal(false)
|
||||
expect(TableUtilities.EqualKey(tableA, tableB, "key1")).to.equal(false)
|
||||
|
||||
tableA = {}
|
||||
tableB = nil
|
||||
expect(TableUtilities.EqualKey(tableA, tableB)).to.equal(false)
|
||||
expect(TableUtilities.EqualKey(tableA, tableB, "")).to.equal(false)
|
||||
expect(TableUtilities.EqualKey(tableA, tableB, "key1")).to.equal(false)
|
||||
|
||||
tableA = {}
|
||||
tableB = {}
|
||||
expect(TableUtilities.EqualKey(tableA, tableB)).to.equal(false)
|
||||
expect(TableUtilities.EqualKey(tableA, tableB, "")).to.equal(false)
|
||||
expect(TableUtilities.EqualKey(tableA, tableB, "key1")).to.equal(false)
|
||||
|
||||
tableA = {
|
||||
key1 = "value1",
|
||||
}
|
||||
tableB = {
|
||||
key1 = "value1",
|
||||
}
|
||||
expect(TableUtilities.EqualKey(tableA, tableB)).to.equal(false)
|
||||
expect(TableUtilities.EqualKey(tableA, tableB, "")).to.equal(false)
|
||||
expect(TableUtilities.EqualKey(tableA, tableB, "key1")).to.equal(true)
|
||||
|
||||
tableA = {
|
||||
key1 = "value1",
|
||||
}
|
||||
tableB = {
|
||||
key1 = "value2",
|
||||
}
|
||||
expect(TableUtilities.EqualKey(tableA, tableB)).to.equal(false)
|
||||
expect(TableUtilities.EqualKey(tableA, tableB, "")).to.equal(false)
|
||||
expect(TableUtilities.EqualKey(tableA, tableB, "key1")).to.equal(false)
|
||||
|
||||
tableA = {
|
||||
key1 = "value1",
|
||||
}
|
||||
tableB = {
|
||||
key2 = "value1",
|
||||
}
|
||||
expect(TableUtilities.EqualKey(tableA, tableB)).to.equal(false)
|
||||
expect(TableUtilities.EqualKey(tableA, tableB, "")).to.equal(false)
|
||||
expect(TableUtilities.EqualKey(tableA, tableB, "key1")).to.equal(false)
|
||||
|
||||
tableA = {
|
||||
key1 = "value1",
|
||||
}
|
||||
tableB = {
|
||||
key2 = "value2",
|
||||
}
|
||||
expect(TableUtilities.EqualKey(tableA, tableB)).to.equal(false)
|
||||
expect(TableUtilities.EqualKey(tableA, tableB, "")).to.equal(false)
|
||||
expect(TableUtilities.EqualKey(tableA, tableB, "key1")).to.equal(false)
|
||||
|
||||
tableA = {
|
||||
key1 = "value1",
|
||||
}
|
||||
tableB = {
|
||||
key1 = "value1",
|
||||
key2 = "value2",
|
||||
}
|
||||
expect(TableUtilities.EqualKey(tableA, tableB)).to.equal(false)
|
||||
expect(TableUtilities.EqualKey(tableA, tableB, "")).to.equal(false)
|
||||
expect(TableUtilities.EqualKey(tableA, tableB, "key1")).to.equal(true)
|
||||
expect(TableUtilities.EqualKey(tableA, tableB, "key2")).to.equal(false)
|
||||
end)
|
||||
|
||||
it("should return table's field count", function()
|
||||
local t = {}
|
||||
expect(TableUtilities.FieldCount(t)).to.equal(0)
|
||||
|
||||
t = {
|
||||
key1 = "value1",
|
||||
}
|
||||
expect(TableUtilities.FieldCount(t)).to.equal(1)
|
||||
|
||||
t = {
|
||||
key1 = "value1",
|
||||
key2 = "value2",
|
||||
}
|
||||
expect(TableUtilities.FieldCount(t)).to.equal(2)
|
||||
end)
|
||||
|
||||
describe("TableUtilities.DeepEqual", function()
|
||||
it("works for primitve data types", function()
|
||||
expect(TableUtilities.DeepEqual(1, 1)).to.equal(true)
|
||||
expect(TableUtilities.DeepEqual("str1", "str1")).to.equal(true)
|
||||
expect(TableUtilities.DeepEqual(1, 2)).to.equal(false)
|
||||
expect(TableUtilities.DeepEqual("str1", "str2")).to.equal(false)
|
||||
end)
|
||||
it("correctly identifies deeply-equal tables", function()
|
||||
local table1 = {
|
||||
num = 1,
|
||||
innerTable = {
|
||||
innerString = "str"
|
||||
}
|
||||
}
|
||||
local table2 = {
|
||||
num = 1,
|
||||
innerTable = {
|
||||
innerString = "str"
|
||||
}
|
||||
}
|
||||
expect(TableUtilities.DeepEqual(table1, table2)).to.equal(true)
|
||||
end)
|
||||
it("correctly rejects non-deeply-equal tables", function()
|
||||
local table1 = {
|
||||
num = 1,
|
||||
innerTable = {
|
||||
innerString = "str"
|
||||
}
|
||||
}
|
||||
local table2 = {
|
||||
num = 1,
|
||||
innerTable = {
|
||||
innerString = "differentStr"
|
||||
}
|
||||
}
|
||||
expect(TableUtilities.DeepEqual(table1, table2)).to.equal(false)
|
||||
local table3 = {
|
||||
num = 1,
|
||||
innerTable = {
|
||||
innerString = "str"
|
||||
}
|
||||
}
|
||||
local table4 = {
|
||||
num = 1,
|
||||
innerTableWithDifferentKey = {
|
||||
innerString = "str"
|
||||
}
|
||||
}
|
||||
expect(TableUtilities.DeepEqual(table3, table4)).to.equal(false)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"lint": {
|
||||
"ImplicitReturn": "fatal"
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local AppTempCommon = CorePackages.AppTempCommon
|
||||
local Promise = require(AppTempCommon.LuaApp.Promise)
|
||||
local ApiFetchThumbnails = require(AppTempCommon.LuaApp.Utils.ApiFetchThumbnails)
|
||||
local GamesGetIcons = require(AppTempCommon.LuaApp.Http.Requests.GamesGetIcons)
|
||||
local SetGameIcons = require(AppTempCommon.LuaApp.Actions.SetGameIcons)
|
||||
|
||||
local DEFAULT_ICON_SIZE = "150x150"
|
||||
|
||||
return function (networkImpl, universeIds, imageSize)
|
||||
return function(store)
|
||||
local state = store:getState()
|
||||
local stateToCheckForDuplicates = state.GameIcons
|
||||
|
||||
-- Filter out the icons that are already in the store.
|
||||
local idsToGet = {}
|
||||
for _, targetId in pairs(universeIds) do
|
||||
if stateToCheckForDuplicates[targetId] == nil then
|
||||
table.insert(idsToGet, targetId)
|
||||
end
|
||||
end
|
||||
|
||||
if #idsToGet == 0 then
|
||||
return Promise.resolve()
|
||||
else
|
||||
return ApiFetchThumbnails.Fetch(networkImpl,
|
||||
idsToGet, imageSize or DEFAULT_ICON_SIZE, "Game", GamesGetIcons, SetGameIcons, store)
|
||||
end
|
||||
end
|
||||
end
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local LuaApp = CorePackages.AppTempCommon.LuaApp
|
||||
|
||||
local GamesGetThumbnails = require(LuaApp.Http.Requests.GamesGetThumbnails)
|
||||
local SetGameThumbnails = require(LuaApp.Actions.SetGameThumbnails)
|
||||
|
||||
local Functional = require(CorePackages.AppTempCommon.Common.Functional)
|
||||
local Promise = require(LuaApp.Promise)
|
||||
local Result = require(LuaApp.Result)
|
||||
|
||||
local TableUtilities = require(LuaApp.TableUtilities)
|
||||
|
||||
local THUMBNAIL_PAGE_COUNT = 20
|
||||
local THUMBNAIL_SIZE = 150
|
||||
local RETRY_MAX_COUNT = math.max(0, settings():GetFVariable("LuaAppNonFinalThumbnailMaxRetries"))
|
||||
local RETRY_TIME_MULTIPLIER = 2 -- seconds
|
||||
|
||||
local function convertToId(value)
|
||||
if type(value) ~= "number" and type(value) ~= "string" then
|
||||
return Result.error("convertToId expects value passed in to be a number or a string")
|
||||
end
|
||||
|
||||
return Result.success(tostring(value))
|
||||
end
|
||||
|
||||
local function subdivideThumbnailTokenArray(thumbnailTokens, tokenLimit)
|
||||
local someTokens = {}
|
||||
for i = 1, #thumbnailTokens, tokenLimit do
|
||||
local subArray = Functional.Take(thumbnailTokens, tokenLimit, i)
|
||||
table.insert(someTokens, subArray)
|
||||
end
|
||||
|
||||
return someTokens
|
||||
end
|
||||
|
||||
local function fetchThumbnailBatch(networkImpl, store, thumbnailTokens)
|
||||
return GamesGetThumbnails(networkImpl, thumbnailTokens, THUMBNAIL_SIZE, THUMBNAIL_SIZE):andThen(function(result)
|
||||
local thumbnails = {}
|
||||
local unfinalizedThumbnails = {}
|
||||
|
||||
for _,image in pairs(result.responseBody) do
|
||||
local convertToIdResult = convertToId(image.universeId)
|
||||
|
||||
convertToIdResult:match(function(universeId)
|
||||
if image.final == false then
|
||||
unfinalizedThumbnails[universeId] = image.retryToken
|
||||
else
|
||||
-- index all of the thumbnails by universeId
|
||||
thumbnails[universeId] = image
|
||||
end
|
||||
end, function(convertToIdError)
|
||||
warn(convertToIdError)
|
||||
end)
|
||||
end
|
||||
|
||||
store:dispatch(SetGameThumbnails(thumbnails))
|
||||
return Promise.resolve(unfinalizedThumbnails)
|
||||
end)
|
||||
end
|
||||
|
||||
local function fetchSubdividedThumbnailsArray(networkImpl, store, thumbnailTokens)
|
||||
return fetchThumbnailBatch(networkImpl, store, thumbnailTokens):andThen(function(unfinalizedThumbnails)
|
||||
local remainingUnfinalizedThumbnails = unfinalizedThumbnails
|
||||
|
||||
for retryCount = 1, RETRY_MAX_COUNT do
|
||||
if TableUtilities.FieldCount(remainingUnfinalizedThumbnails) == 0 then
|
||||
return -- Bail out, we're done!
|
||||
end
|
||||
|
||||
wait(RETRY_TIME_MULTIPLIER * math.pow(2, retryCount - 1))
|
||||
remainingUnfinalizedThumbnails = fetchThumbnailBatch(networkImpl, store, remainingUnfinalizedThumbnails):await()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
local function fetchThumbnails(networkImpl, thumbnailTokens)
|
||||
return function(store)
|
||||
-- NOTE : because the size of each thumbnail token, me must limit the number we can fetch at a time.
|
||||
-- So break apart the array of tokens we get into smaller, more manageable pieces.
|
||||
local fetchPromises = {}
|
||||
local someTokens = subdivideThumbnailTokenArray(thumbnailTokens, THUMBNAIL_PAGE_COUNT)
|
||||
for _, thumbsArr in ipairs(someTokens) do
|
||||
local promise = fetchSubdividedThumbnailsArray(networkImpl, store, thumbsArr)
|
||||
table.insert(fetchPromises, promise)
|
||||
end
|
||||
|
||||
return Promise.all(fetchPromises)
|
||||
end
|
||||
end
|
||||
|
||||
return fetchThumbnails
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local ApiFetchGameIcons = require(CorePackages.AppTempCommon.LuaApp.Thunks.ApiFetchGameIcons)
|
||||
local Functional = require(CorePackages.AppTempCommon.Common.Functional)
|
||||
local GamesMultigetPlaceDetails = require(CorePackages.AppTempCommon.LuaApp.Http.Requests.GamesMultigetPlaceDetails)
|
||||
local PlaceInfoModel = require(CorePackages.AppTempCommon.LuaChat.Models.PlaceInfoModel)
|
||||
local ReceivedPlacesInfos = require(CorePackages.AppTempCommon.LuaApp.Actions.ReceivedPlacesInfos)
|
||||
|
||||
local LuaAppFlags = CorePackages.AppTempCommon.LuaApp.Flags
|
||||
local convertUniverseIdToString = require(LuaAppFlags.ConvertUniverseIdToString)
|
||||
|
||||
return function(networkImpl, placeIds)
|
||||
return function(store)
|
||||
if not placeIds or #placeIds == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
return GamesMultigetPlaceDetails(networkImpl, placeIds):andThen(function(result)
|
||||
local data = result.responseBody
|
||||
|
||||
local thumbnailUniverseIds = {}
|
||||
local placeInfos = Functional.Map(data, function(placeInfoData)
|
||||
local placeInfo = PlaceInfoModel.fromWeb(placeInfoData)
|
||||
local universeId = convertUniverseIdToString(placeInfo.universeId)
|
||||
table.insert(thumbnailUniverseIds, universeId)
|
||||
return placeInfo
|
||||
end)
|
||||
|
||||
store:dispatch(ReceivedPlacesInfos(placeInfos))
|
||||
|
||||
if #thumbnailUniverseIds > 0 then
|
||||
store:dispatch(ApiFetchGameIcons(networkImpl, thumbnailUniverseIds))
|
||||
end
|
||||
|
||||
return placeInfos
|
||||
end)
|
||||
end
|
||||
end
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Functional = require(CorePackages.AppTempCommon.Common.Functional)
|
||||
local GetPlaceInfos = require(CorePackages.AppTempCommon.LuaApp.Http.Requests.GetPlaceInfos)
|
||||
|
||||
-- LuaChat
|
||||
local PlaceInfoModel = require(CorePackages.AppTempCommon.LuaChat.Models.PlaceInfoModel)
|
||||
local ReceivedMultiplePlaceInfos = require(CorePackages.AppTempCommon.LuaChat.Actions.ReceivedMultiplePlaceInfos)
|
||||
|
||||
return function(networkImpl, placeIds)
|
||||
return function(store)
|
||||
return GetPlaceInfos(networkImpl, placeIds):andThen(function(result)
|
||||
local data = result.responseBody
|
||||
|
||||
local placeInfos = Functional.Map(data, function(placeInfoData)
|
||||
return PlaceInfoModel.fromWeb(placeInfoData)
|
||||
end)
|
||||
|
||||
store:dispatch(ReceivedMultiplePlaceInfos(placeInfos))
|
||||
|
||||
return placeInfos
|
||||
end)
|
||||
end
|
||||
end
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Actions = CorePackages.AppTempCommon.LuaApp.Actions
|
||||
local Requests = CorePackages.AppTempCommon.LuaApp.Http.Requests
|
||||
|
||||
local UsersGetFriendCount = require(Requests.UsersGetFriendCount)
|
||||
local SetFriendCount = require(Actions.SetFriendCount)
|
||||
|
||||
local isNewFriendsEndpointsEnabled = require(CorePackages.AppTempCommon.LuaChat.Flags.isNewFriendsEndpointsEnabled)
|
||||
|
||||
return function(networkImpl)
|
||||
return function(store)
|
||||
return UsersGetFriendCount(networkImpl):andThen(function(result)
|
||||
local data = result.responseBody
|
||||
|
||||
if isNewFriendsEndpointsEnabled() then
|
||||
if data.count then
|
||||
store:dispatch(SetFriendCount(data.count))
|
||||
end
|
||||
else
|
||||
if data.success and data.count then
|
||||
store:dispatch(SetFriendCount(data.count))
|
||||
end
|
||||
end
|
||||
|
||||
return data.count
|
||||
end)
|
||||
end
|
||||
end
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Requests = CorePackages.AppTempCommon.LuaApp.Http.Requests
|
||||
|
||||
local Promise = require(CorePackages.AppTempCommon.LuaApp.Promise)
|
||||
local ApiFetchUsersPresences = require(CorePackages.AppTempCommon.LuaApp.Thunks.ApiFetchUsersPresences)
|
||||
local ApiFetchUsersThumbnail = require(CorePackages.AppTempCommon.LuaApp.Thunks.ApiFetchUsersThumbnail)
|
||||
local UsersGetFriends = require(Requests.UsersGetFriends)
|
||||
|
||||
local FetchUserFriendsStarted = require(CorePackages.AppTempCommon.LuaApp.Actions.FetchUserFriendsStarted)
|
||||
local FetchUserFriendsFailed = require(CorePackages.AppTempCommon.LuaApp.Actions.FetchUserFriendsFailed)
|
||||
local FetchUserFriendsCompleted = require(CorePackages.AppTempCommon.LuaApp.Actions.FetchUserFriendsCompleted)
|
||||
local UserModel = require(CorePackages.AppTempCommon.LuaApp.Models.User)
|
||||
local UpdateUsers = require(CorePackages.AppTempCommon.LuaApp.Thunks.UpdateUsers)
|
||||
|
||||
return function(requestImpl, userId, thumbnailRequest, checkPoints)
|
||||
return function(store)
|
||||
store:dispatch(FetchUserFriendsStarted(userId))
|
||||
|
||||
if checkPoints ~= nil and checkPoints.startFetchUserFriends ~= nil then
|
||||
checkPoints:startFetchUserFriends()
|
||||
end
|
||||
|
||||
local fetchedUserIds = {}
|
||||
return UsersGetFriends(requestImpl, userId):andThen(function(response)
|
||||
local responseBody = response.responseBody
|
||||
|
||||
local newUsers = {}
|
||||
for _, userData in pairs(responseBody.data) do
|
||||
local id = tostring(userData.id)
|
||||
|
||||
userData.isFriend = true
|
||||
local newUser = UserModel.fromDataTable(userData)
|
||||
|
||||
table.insert(fetchedUserIds, id)
|
||||
newUsers[newUser.id] = newUser
|
||||
end
|
||||
store:dispatch(UpdateUsers(newUsers))
|
||||
|
||||
if checkPoints ~= nil and checkPoints.finishFetchUserFriends ~= nil then
|
||||
checkPoints:finishFetchUserFriends()
|
||||
end
|
||||
|
||||
return fetchedUserIds
|
||||
end):andThen(function(userIds)
|
||||
if checkPoints ~= nil and checkPoints.startFetchUsersPresences ~= nil then
|
||||
checkPoints:startFetchUsersPresences()
|
||||
end
|
||||
-- Asynchronously fetch friend thumbnails so we don't block display of UI
|
||||
store:dispatch(ApiFetchUsersThumbnail.Fetch(requestImpl, userIds, thumbnailRequest))
|
||||
|
||||
return store:dispatch(ApiFetchUsersPresences(requestImpl, userIds))
|
||||
end):andThen(
|
||||
function(result)
|
||||
store:dispatch(FetchUserFriendsCompleted(userId))
|
||||
|
||||
if checkPoints ~= nil and checkPoints.finishFetchUsersPresences ~= nil then
|
||||
checkPoints:finishFetchUsersPresences()
|
||||
end
|
||||
|
||||
return Promise.resolve(fetchedUserIds)
|
||||
end,
|
||||
function(response)
|
||||
store:dispatch(FetchUserFriendsFailed(userId, response))
|
||||
return Promise.reject(response)
|
||||
end
|
||||
)
|
||||
end
|
||||
end
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local LuaApp = CorePackages.AppTempCommon.LuaApp
|
||||
local ChatUtils = CorePackages.AppTempCommon.LuaChat.Utils
|
||||
|
||||
local getPlaceIds = require(ChatUtils.getFriendsActiveGamesPlaceIdsFromUsersPresence)
|
||||
local receiveUsersPresence = require(ChatUtils.receiveUsersPresence)
|
||||
|
||||
local ApiFetchGamesDataByPlaceIds = require(LuaApp.Thunks.ApiFetchGamesDataByPlaceIds)
|
||||
local UsersGetPresence = require(LuaApp.Http.Requests.UsersGetPresence)
|
||||
|
||||
return function(networkImpl, userIds)
|
||||
return function(store)
|
||||
return UsersGetPresence(networkImpl, userIds):andThen(function(result)
|
||||
local userPresences = result.responseBody.userPresences
|
||||
receiveUsersPresence(userPresences, store)
|
||||
|
||||
local placeIds = getPlaceIds(userPresences, store)
|
||||
store:dispatch(ApiFetchGamesDataByPlaceIds(networkImpl, placeIds))
|
||||
end)
|
||||
end
|
||||
end
|
||||
+192
@@ -0,0 +1,192 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Cryo = require(CorePackages.Cryo)
|
||||
|
||||
local Actions = CorePackages.AppTempCommon.LuaApp.Actions
|
||||
local TableUtilities = require(CorePackages.AppTempCommon.LuaApp.TableUtilities)
|
||||
local PromiseUtilities = require(CorePackages.AppTempCommon.LuaApp.PromiseUtilities)
|
||||
|
||||
local ThumbnailsGetAvatar = require(CorePackages.AppTempCommon.LuaApp.Http.Requests.ThumbnailsGetAvatar)
|
||||
local ThumbnailsGetAvatarHeadshot = require(CorePackages.AppTempCommon.LuaApp.Http.Requests.ThumbnailsGetAvatarHeadshot)
|
||||
|
||||
local AvatarThumbnailTypes = require(CorePackages.AppTempCommon.LuaApp.Enum.AvatarThumbnailTypes)
|
||||
|
||||
local SetUserThumbnail = require(Actions.SetUserThumbnail)
|
||||
local Promise = require(CorePackages.AppTempCommon.LuaApp.Promise)
|
||||
local PerformFetch = require(CorePackages.AppTempCommon.LuaApp.Thunks.Networking.Util.PerformFetch)
|
||||
local Result = require(CorePackages.AppTempCommon.LuaApp.Result)
|
||||
|
||||
local RETRY_MAX_COUNT = math.max(0, settings():GetFVariable("LuaAppNonFinalThumbnailMaxRetries"))
|
||||
local RETRY_TIME_MULTIPLIER = math.max(0, settings():GetFVariable("LuaAppThumbnailsApiRetryTimeMultiplier"))
|
||||
|
||||
local MAX_REQUEST_COUNT = 100
|
||||
|
||||
local ThumbnailsTypeToApiMap = {
|
||||
[AvatarThumbnailTypes.AvatarThumbnail] = ThumbnailsGetAvatar,
|
||||
[AvatarThumbnailTypes.HeadShot] = ThumbnailsGetAvatarHeadshot,
|
||||
}
|
||||
|
||||
local function subdivideEntries(entries, limit)
|
||||
local subArrays = {}
|
||||
for i = 1, #entries, limit do
|
||||
local subArray = Cryo.List.getRange(entries, i, i + limit - 1)
|
||||
table.insert(subArrays, subArray)
|
||||
end
|
||||
return subArrays
|
||||
end
|
||||
|
||||
local function keyMapper(userId, thumbnailType, thumbnailSize)
|
||||
return "luaapp.usersthumbnailsapi." .. userId .. "." .. thumbnailType .. "." .. thumbnailSize
|
||||
end
|
||||
|
||||
local function isCompleteThumbnailData(entry)
|
||||
return type(entry) == "table"
|
||||
and type(entry.targetId) == "number"
|
||||
and type(entry.state) == "string"
|
||||
and type(entry.imageUrl) == "string"
|
||||
end
|
||||
|
||||
local ApiFetchUsersThumbnail = {}
|
||||
|
||||
function ApiFetchUsersThumbnail.getThumbnailsSizeArgForSize(thumbnailSize)
|
||||
assert(typeof(thumbnailSize) == "string",
|
||||
string.format("ApiFetchUsersThumbnail expects a string for thumbnailSize. Type: %s", typeof(thumbnailSize))
|
||||
)
|
||||
|
||||
assert(string.match(thumbnailSize, 'Size.+x'),
|
||||
string.format(
|
||||
"ApiFetchUsersThumbnail expects thumbnailSize to follow format \"Size..x..\" Current thumbnailSize: ",
|
||||
thumbnailSize
|
||||
)
|
||||
)
|
||||
return string.gsub(thumbnailSize, "Size", "")
|
||||
end
|
||||
|
||||
function ApiFetchUsersThumbnail._fetch(networkImpl, listOfUserIds, thumbnailRequest)
|
||||
local thumbnailSize = thumbnailRequest.thumbnailSize
|
||||
local thumbnailType = thumbnailRequest.thumbnailType
|
||||
|
||||
local thumbnailSizeRequestArg = ApiFetchUsersThumbnail.getThumbnailsSizeArgForSize(thumbnailSize)
|
||||
local thumbnailsApiForThumbnailType = ThumbnailsTypeToApiMap[thumbnailType]
|
||||
|
||||
assert(typeof(thumbnailType) == "string",
|
||||
"ApiFetchUsersThumbnail expects thumbnailType to be a string")
|
||||
assert(typeof(thumbnailsApiForThumbnailType) == "function",
|
||||
"ApiFetchUsersThumbnail failed to find api for given type: ", thumbnailType)
|
||||
|
||||
local function keyMapperForCurrentTypeAndSize(userId)
|
||||
return keyMapper(userId, thumbnailType, thumbnailSize)
|
||||
end
|
||||
|
||||
local function getTableOfFailedResults(userIds)
|
||||
local results = {}
|
||||
for _, userId in pairs(userIds) do
|
||||
local key = keyMapperForCurrentTypeAndSize(userId)
|
||||
results[key] = Result.new(false, {
|
||||
targetId = userId,
|
||||
})
|
||||
end
|
||||
return results
|
||||
end
|
||||
|
||||
return PerformFetch.Batch(listOfUserIds, keyMapperForCurrentTypeAndSize, function(store, userIdsToFetch)
|
||||
local function fetchThumbnails(userIdsProvided)
|
||||
return thumbnailsApiForThumbnailType(networkImpl, userIdsProvided, thumbnailSizeRequestArg):andThen(
|
||||
function(result)
|
||||
local results = getTableOfFailedResults(userIdsProvided)
|
||||
local data = result and result.responseBody and result.responseBody.data
|
||||
if typeof(data) == "table" then
|
||||
for _, entry in pairs(data) do
|
||||
if isCompleteThumbnailData(entry) then
|
||||
local userId = tostring(entry.targetId)
|
||||
local key = keyMapperForCurrentTypeAndSize(userId)
|
||||
local success = false
|
||||
if entry.state == "Completed" then
|
||||
store:dispatch(SetUserThumbnail(tostring(entry.targetId), entry.imageUrl, thumbnailType, thumbnailSize))
|
||||
success = true
|
||||
end
|
||||
results[key] = Result.new(success, entry)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return Promise.resolve(results)
|
||||
end,
|
||||
function(err)
|
||||
local results = getTableOfFailedResults(userIdsProvided)
|
||||
return Promise.resolve(results)
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
return fetchThumbnails(userIdsToFetch):andThen(function(results)
|
||||
local completedThumbnails = {}
|
||||
local thumbnailResults = results
|
||||
|
||||
if _G.__TESTEZ_RUNNING_TEST__ then
|
||||
RETRY_MAX_COUNT = 1
|
||||
RETRY_TIME_MULTIPLIER = 0.001
|
||||
end
|
||||
|
||||
local function retry(retryCount)
|
||||
local remainingUserIdsToFetch = {}
|
||||
|
||||
for key, result in pairs(thumbnailResults) do
|
||||
local isSuccessful, thumbnailInfo = result:unwrap()
|
||||
|
||||
if isSuccessful and thumbnailInfo.state == "Completed" then
|
||||
completedThumbnails[key] = result
|
||||
elseif isSuccessful and thumbnailInfo.state == "Pending" then
|
||||
table.insert(remainingUserIdsToFetch, thumbnailInfo.targetId)
|
||||
end
|
||||
end
|
||||
|
||||
if TableUtilities.FieldCount(remainingUserIdsToFetch) == 0 then
|
||||
return Promise.resolve(completedThumbnails)
|
||||
end
|
||||
|
||||
local delayPromise = Promise.new(function(resolve, reject)
|
||||
coroutine.wrap(function()
|
||||
wait(RETRY_TIME_MULTIPLIER * math.pow(2, retryCount - 1))
|
||||
resolve()
|
||||
end)()
|
||||
end)
|
||||
|
||||
return delayPromise:andThen(function()
|
||||
return fetchThumbnails(remainingUserIdsToFetch)
|
||||
end):andThen(function(newResults)
|
||||
thumbnailResults = newResults
|
||||
if retryCount > 1 then
|
||||
return retry(retryCount - 1)
|
||||
else
|
||||
return Promise.resolve(completedThumbnails)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
return retry(RETRY_MAX_COUNT)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
function ApiFetchUsersThumbnail.Fetch(networkImpl, userIds, thumbnailRequests)
|
||||
return function(store)
|
||||
local allPromises = {}
|
||||
local subArraysOfUserIds = subdivideEntries(userIds, MAX_REQUEST_COUNT)
|
||||
|
||||
for _, thumbnailRequest in pairs(thumbnailRequests) do
|
||||
for _, limitedListOfUserIds in pairs(subArraysOfUserIds) do
|
||||
local promise = store:dispatch(ApiFetchUsersThumbnail._fetch(networkImpl, limitedListOfUserIds, thumbnailRequest))
|
||||
table.insert(allPromises, promise)
|
||||
end
|
||||
end
|
||||
|
||||
return PromiseUtilities.Batch(allPromises)
|
||||
end
|
||||
end
|
||||
|
||||
function ApiFetchUsersThumbnail.GetFetchingStatus(state, userId, thumbnailType, thumbnailSize)
|
||||
return PerformFetch.GetStatus(state, keyMapper(userId, thumbnailType, thumbnailSize))
|
||||
end
|
||||
|
||||
return ApiFetchUsersThumbnail
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
local Requests = CorePackages.AppTempCommon.LuaApp.Http.Requests
|
||||
|
||||
local ChatSendMessage = require(Requests.ChatSendMessage)
|
||||
local ChatStartOneToOneConversation = require(Requests.ChatStartOneToOneConversation)
|
||||
|
||||
local CoreGui = game:GetService("CoreGui")
|
||||
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
|
||||
local RobloxTranslator = require(RobloxGui.Modules.RobloxTranslator)
|
||||
|
||||
|
||||
local ChatSendGameLinkMessage = require(Requests.ChatSendGameLinkMessage)
|
||||
|
||||
return function(networkImpl, userId, placeInfo)
|
||||
local clientId = Players.LocalPlayer.UserId
|
||||
|
||||
-- Construct the invite messages based on place info
|
||||
local inviteTextMessage
|
||||
inviteTextMessage = RobloxTranslator:FormatByKey(
|
||||
"Feature.SettingsHub.Message.InviteToGameTitle", { PLACENAME = placeInfo.name }
|
||||
)
|
||||
|
||||
return function(store)
|
||||
return ChatStartOneToOneConversation(networkImpl, userId, clientId):andThen(function(conversationResult)
|
||||
local conversation = conversationResult.responseBody.conversation
|
||||
|
||||
return ChatSendMessage(networkImpl, conversation.id, inviteTextMessage):andThen(function()
|
||||
local function handleResult(inviteResult)
|
||||
local data = inviteResult.responseBody
|
||||
|
||||
return {
|
||||
resultType = data.resultType,
|
||||
conversationId = conversation.id,
|
||||
placeId = placeInfo.universeRootPlaceId,
|
||||
}
|
||||
end
|
||||
|
||||
return ChatSendGameLinkMessage(networkImpl, conversation.id, placeInfo.universeId):andThen(handleResult)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
end
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"language": {
|
||||
"mode": "nonstrict"
|
||||
}
|
||||
}
|
||||
+245
@@ -0,0 +1,245 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Result = require(CorePackages.AppTempCommon.LuaApp.Result)
|
||||
local Promise = require(CorePackages.AppTempCommon.LuaApp.Promise)
|
||||
|
||||
local PromiseUtilities = require(CorePackages.AppTempCommon.LuaApp.PromiseUtilities)
|
||||
local RetrievalStatus = require(CorePackages.AppTempCommon.LuaApp.Enum.RetrievalStatus)
|
||||
local UpdateFetchingStatus = require(CorePackages.AppTempCommon.LuaApp.Actions.UpdateFetchingStatus)
|
||||
|
||||
--[[
|
||||
PerformFetch wraps the notion of a network request together with its fetching status
|
||||
so that it is easier to de-duplicate concurrent requests for the same resource. The
|
||||
fetching status for individual fetching operations are available in the store as:
|
||||
|
||||
storeState.FetchingStatus[key]
|
||||
|
||||
When you use one of the methods in this helper, you provide a key (or keymap), and
|
||||
supply a functor that will only be called when a fetch actually needs to be performed.
|
||||
|
||||
Any follow-up andThen/catch clauses will be correctly daisy-chained onto the original
|
||||
ongoing fetch request if one is already underway.
|
||||
]]
|
||||
local PerformFetch = {}
|
||||
|
||||
local batchPromises = {} -- fetch key = outstanding promise from PerformFetch.Batch
|
||||
|
||||
--[[
|
||||
Helper function for unit tests to be able to clean up batchPromises created from
|
||||
previous test case. This is because unit tests don't wait until the mock requests
|
||||
are resolved and moves onto the next test. If tests happen to generate duplicate
|
||||
fetchStatusKey, unresolved batchPromise will throw thinking that the promise does
|
||||
not have the correct status.
|
||||
]]
|
||||
function PerformFetch.ClearOutstandingPromiseStatus()
|
||||
batchPromises = {}
|
||||
end
|
||||
|
||||
local function singleFetchKeymapper(item)
|
||||
-- Single fetch keys are used directly
|
||||
return item
|
||||
end
|
||||
|
||||
--[[
|
||||
Get the fetching status for a given status key. Defaults to
|
||||
RetrievalStatus.NotStarted for missing keys.
|
||||
]]
|
||||
function PerformFetch.GetStatus(state, fetchStatusKey)
|
||||
assert(typeof(state) == "table")
|
||||
assert(typeof(fetchStatusKey) == "string")
|
||||
assert(#fetchStatusKey > 0)
|
||||
return state.FetchingStatus[fetchStatusKey] or RetrievalStatus.NotStarted
|
||||
end
|
||||
|
||||
--[[
|
||||
Perform a fetch operation for a single resource.
|
||||
|
||||
Args:
|
||||
fetchStatusKey - String key for the fetching status to index the Rodux store.
|
||||
fetchFunctor - Functor to call when a fetch needs to be performed for fetchStatusKey.
|
||||
|
||||
Returns:
|
||||
A Promise that resolves or rejects in accordance with the result of fetchFunctor, or the
|
||||
promise for the original fetch if one is already ongoing.
|
||||
|
||||
Usage:
|
||||
In your main thunk, wrap your inner store function with this thunk, like this:
|
||||
|
||||
return function(arg1, arg2)
|
||||
return PerformFetch.Single("mykey", function(store)
|
||||
return doYourLogicHere() -- Must return a Promise!!!
|
||||
end)
|
||||
end
|
||||
|
||||
Please note that in order for single fetches to integrate well with batch fetches,
|
||||
your promise must NEVER resolve or reject with multiple arguments! Wrap your results
|
||||
in a table instead.
|
||||
]]
|
||||
function PerformFetch.Single(fetchStatusKey, fetchFunctor)
|
||||
assert(typeof(fetchStatusKey) == "string")
|
||||
assert(typeof(fetchFunctor) == "function")
|
||||
assert(#fetchStatusKey > 0)
|
||||
|
||||
return function(store)
|
||||
-- Call batch API to handle the individual fetch
|
||||
return PerformFetch.Batch({ fetchStatusKey }, singleFetchKeymapper, function(batchStore, itemsToFetch)
|
||||
assert(#itemsToFetch == 1)
|
||||
|
||||
local functorPromise = fetchFunctor(batchStore)
|
||||
assert(Promise.is(functorPromise))
|
||||
|
||||
return functorPromise:andThen(function(...)
|
||||
assert(#{...} <= 1)
|
||||
return Promise.resolve({ [fetchStatusKey] = Result.new(true, (...)) })
|
||||
end, function(...)
|
||||
assert(#{...} <= 1)
|
||||
return Promise.resolve({ [fetchStatusKey] = Result.new(false, (...)) })
|
||||
end)
|
||||
end)(store):andThen(function(batchResults)
|
||||
local success, value = batchResults[fetchStatusKey]:unwrap()
|
||||
if success then
|
||||
return Promise.resolve(value)
|
||||
else
|
||||
return Promise.reject(value)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
Perform a fetch operation for multiple resources at once (batching).
|
||||
|
||||
Args:
|
||||
items - The list of item ids that need to be fetched.
|
||||
keyMapper - A function that maps items to string keys for the Rodux store.
|
||||
fetchFunctor - A function that will be called when at least one item needs to be fetched.
|
||||
|
||||
Returns:
|
||||
A Promise that always resolves. Result data is returned in a single table to the
|
||||
andThen() clause, where item fetch keys are the keys and the results for each key
|
||||
are encoded using a Result object.
|
||||
|
||||
Usage:
|
||||
In your main thunk, wrap your inner store function with this thunk, like this:
|
||||
|
||||
local MAPPER = function(item)
|
||||
return doSomething(item) -- Each key must be unique
|
||||
end
|
||||
|
||||
return function(arg1, arg2)
|
||||
local allItems = makeYourItemsList()
|
||||
return PerformFetch.Batch(allItems, MAPPER, function(store, itemsToFetch)
|
||||
return doYourLogicHere(itemsToFetch) -- Must return a Promise!!!
|
||||
end)
|
||||
end
|
||||
|
||||
Your implementation of fetchFunctor should return a promise that resolves
|
||||
according to the structure of PromiseUtilities.Batch, ex:
|
||||
|
||||
return Promise.resolve({
|
||||
itemFetchKey1 = Result.new(true, payload1),
|
||||
itemFetchKey2 = Result.new(false, payload2), -- failed
|
||||
})
|
||||
|
||||
Any other resolving arguments will be dropped for consistency and safety of the API.
|
||||
Since this is a batching API, your implementation should NOT reject().
|
||||
|
||||
Please keep in mind that batching calls have to fit into an environment where they may
|
||||
be daisy chained onto other batching calls, and those results have to be amalgamated
|
||||
at the end of the chain into unique tables for each of the callers!
|
||||
]]
|
||||
function PerformFetch.Batch(items, keyMapper, fetchFunctor)
|
||||
assert(typeof(items) == "table")
|
||||
assert(typeof(keyMapper) == "function")
|
||||
assert(typeof(fetchFunctor) == "function")
|
||||
|
||||
return function(store)
|
||||
local itemsToFetch = {}
|
||||
local itemsToFetchKeyMap = {}
|
||||
local batchPromisesForItemsAlreadyBeingFetched = {}
|
||||
|
||||
-- Filter out items that do not need to be fetched
|
||||
for _, item in ipairs(items) do
|
||||
local fetchStatusKey = keyMapper(item)
|
||||
local fetchingStatus = PerformFetch.GetStatus(store:getState(), fetchStatusKey)
|
||||
local batchPromise = batchPromises[fetchStatusKey]
|
||||
|
||||
if batchPromise then
|
||||
assert(fetchingStatus == RetrievalStatus.Fetching)
|
||||
|
||||
batchPromisesForItemsAlreadyBeingFetched[fetchStatusKey] = batchPromise
|
||||
else
|
||||
assert(fetchingStatus ~= RetrievalStatus.Fetching)
|
||||
|
||||
table.insert(itemsToFetch, item)
|
||||
itemsToFetchKeyMap[item] = fetchStatusKey
|
||||
end
|
||||
end
|
||||
|
||||
local doResolve
|
||||
local batchFetchingPromise = Promise.new(function(resolve)
|
||||
doResolve = resolve
|
||||
end)
|
||||
|
||||
-- Call functor if there are items to fetch, otherwise short-circuit it
|
||||
-- We want to call it FIRST because we need to kick off async fetch before blocking
|
||||
-- on other responses.
|
||||
local functorPromise
|
||||
if #itemsToFetch > 0 then
|
||||
-- Place remaining items into fetching state and make entry in table before
|
||||
-- we kick off functor just in case it returns already-completed promise
|
||||
for _, fetchStatusKey in pairs(itemsToFetchKeyMap) do
|
||||
store:dispatch(UpdateFetchingStatus(fetchStatusKey, RetrievalStatus.Fetching))
|
||||
batchPromises[fetchStatusKey] = batchFetchingPromise
|
||||
end
|
||||
|
||||
functorPromise = fetchFunctor(store, itemsToFetch)
|
||||
assert(Promise.is(functorPromise))
|
||||
else
|
||||
functorPromise = Promise.resolve({})
|
||||
end
|
||||
|
||||
functorPromise:andThen(function(myResults)
|
||||
myResults = myResults or {} -- No resolve args = empty table for ease of use
|
||||
|
||||
return PromiseUtilities.Batch(batchPromisesForItemsAlreadyBeingFetched):andThen(function(batchResults)
|
||||
local filteredResults = {}
|
||||
for batchKey, batchResult in pairs(batchResults) do
|
||||
-- Extract only the result for the key we care about from the batch results
|
||||
local _, value = batchResult:unwrap()
|
||||
filteredResults[batchKey] = value[batchKey]
|
||||
end
|
||||
|
||||
return myResults, filteredResults
|
||||
end)
|
||||
end,
|
||||
function()
|
||||
assert(false, "PerformFetch fetchFunctor should never reject")
|
||||
end):andThen(function(myResults, batchResults)
|
||||
-- Iterate on requested items rather than on actual result set
|
||||
-- so that we are sure to check all our keys and ignore extra ones
|
||||
for _, fetchKey in pairs(itemsToFetchKeyMap) do
|
||||
local resultObj = myResults[fetchKey]
|
||||
if Result.is(resultObj) then
|
||||
batchResults[fetchKey] = resultObj
|
||||
else
|
||||
batchResults[fetchKey] = Result.error()
|
||||
end
|
||||
|
||||
-- Update fetching status in store from Result object status
|
||||
-- (The extra parens unwrap a multi-return value!)
|
||||
local itemStatus = (batchResults[fetchKey]:unwrap()) and RetrievalStatus.Done or RetrievalStatus.Failed
|
||||
store:dispatch(UpdateFetchingStatus(fetchKey, itemStatus))
|
||||
batchPromises[fetchKey] = nil
|
||||
end
|
||||
|
||||
return batchResults
|
||||
end):andThen(function(joinedResults)
|
||||
doResolve(joinedResults)
|
||||
end)
|
||||
|
||||
return batchFetchingPromise
|
||||
end
|
||||
end
|
||||
|
||||
return PerformFetch
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user