add gs
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"lint": {
|
||||
"SameLineStatement": "fatal",
|
||||
"FunctionUnused": "fatal",
|
||||
//"LocalShadow": "fatal",
|
||||
//"LocalUnused": "fatal",
|
||||
"ImportUnused": "fatal",
|
||||
"ImplicitReturn": "fatal"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
--[[
|
||||
A helper function to define a Rodux action creator with an associated name.
|
||||
|
||||
Normally when creating a Rodux action, you can just create a function:
|
||||
|
||||
return function(value)
|
||||
return {
|
||||
type = "MyAction",
|
||||
value = value,
|
||||
}
|
||||
end
|
||||
|
||||
And then when you check for it in your reducer, you either use a constant,
|
||||
or type out the string name:
|
||||
|
||||
if action.type == "MyAction" then
|
||||
-- change some state
|
||||
end
|
||||
|
||||
Typos here are a remarkably common bug. We also have the issue that there's
|
||||
no link between reducers and the actions that they respond to!
|
||||
|
||||
`Action` (this helper) provides a utility that makes this a bit cleaner.
|
||||
|
||||
Instead, define your Rodux action like this:
|
||||
|
||||
return Action("MyAction", function(value)
|
||||
return {
|
||||
value = value,
|
||||
}
|
||||
end)
|
||||
|
||||
We no longer need to add the `type` field manually.
|
||||
|
||||
Additionally, the returned action creator now has a 'name' property that can
|
||||
be checked by your reducer:
|
||||
|
||||
local MyAction = require(Reducers.MyAction)
|
||||
|
||||
...
|
||||
|
||||
if action.type == MyAction.name then
|
||||
-- change some state!
|
||||
end
|
||||
|
||||
Now we have a clear link between our reducers and the actions they use, and
|
||||
if we ever typo a name, we'll get a warning in LuaCheck as well as an error
|
||||
at runtime!
|
||||
]]
|
||||
|
||||
return function(name, fn)
|
||||
assert(type(name) == "string", "A name must be provided to create an Action")
|
||||
assert(type(fn) == "function", "A function must be provided to create an Action")
|
||||
|
||||
return setmetatable({
|
||||
name = name,
|
||||
}, {
|
||||
__call = function(self, ...)
|
||||
local result = fn(...)
|
||||
|
||||
assert(type(result) == "table", "An action must return a table")
|
||||
|
||||
result.type = name
|
||||
|
||||
return result
|
||||
end
|
||||
})
|
||||
end
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
local Action = require(script.Parent.Parent.Action)
|
||||
|
||||
return Action("ActionBindingsUpdateSearchFilter", function(searchTerm)
|
||||
return {
|
||||
searchTerm = searchTerm
|
||||
}
|
||||
end)
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
local Action = require(script.Parent.Parent.Action)
|
||||
|
||||
return Action("ChangeDevConsoleSize", function(newSize)
|
||||
return {
|
||||
newSize = newSize
|
||||
}
|
||||
end)
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
local Action = require(script.Parent.Parent.Action)
|
||||
|
||||
return Action("ClientLogUpdateSearchFilter", function(searchTerm, filterTypes)
|
||||
return {
|
||||
searchTerm = searchTerm,
|
||||
filterTypes = filterTypes
|
||||
}
|
||||
end)
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
local Action = require(script.Parent.Parent.Action)
|
||||
|
||||
return Action("ClientMemoryUpdateSearchFilter", function(searchTerm, filterTypes)
|
||||
return {
|
||||
searchTerm = searchTerm,
|
||||
filterTypes = filterTypes
|
||||
}
|
||||
end)
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
local Action = require(script.Parent.Parent.Action)
|
||||
|
||||
return Action("ClientNetworkUpdateSearchFilter", function(searchTerm, filterTypes)
|
||||
return {
|
||||
searchTerm = searchTerm,
|
||||
filterTypes = filterTypes
|
||||
}
|
||||
end)
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
local Action = require(script.Parent.Parent.Action)
|
||||
|
||||
return Action("ClientScriptsUpdateSearchFilter", function(searchTerm, filterTypes)
|
||||
return {
|
||||
searchTerm = searchTerm,
|
||||
filterTypes = filterTypes
|
||||
}
|
||||
end)
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
local Action = require(script.Parent.Parent.Action)
|
||||
|
||||
return Action("DataStoresUpdateSearchFilter", function(searchTerm, filterTypes)
|
||||
return {
|
||||
searchTerm = searchTerm,
|
||||
filterTypes = filterTypes
|
||||
}
|
||||
end)
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
local Action = require(script.Parent.Parent.Action)
|
||||
|
||||
return Action("ServerJobsUpdateSearchFilter", function(searchTerm, filterTypes)
|
||||
return {
|
||||
searchTerm = searchTerm,
|
||||
filterTypes = filterTypes
|
||||
}
|
||||
end)
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
local Action = require(script.Parent.Parent.Action)
|
||||
|
||||
return Action("ServerLogUpdateSearchFilter", function(searchTerm, filterTypes)
|
||||
return {
|
||||
searchTerm = searchTerm,
|
||||
filterTypes = filterTypes
|
||||
}
|
||||
end)
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
local Action = require(script.Parent.Parent.Action)
|
||||
|
||||
return Action("ServerMemoryUpdateSearchFilter", function(searchTerm, filterTypes)
|
||||
return {
|
||||
searchTerm = searchTerm,
|
||||
filterTypes = filterTypes
|
||||
}
|
||||
end)
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
local Action = require(script.Parent.Parent.Action)
|
||||
|
||||
return Action("ServerNetworkUpdateSearchFilter", function(searchTerm, filterTypes)
|
||||
|
||||
return {
|
||||
searchTerm = searchTerm,
|
||||
filterTypes = filterTypes
|
||||
}
|
||||
end)
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
local Action = require(script.Parent.Parent.Action)
|
||||
|
||||
return Action("ServerScriptsUpdateSearchFilter", function(searchTerm, filterTypes)
|
||||
return {
|
||||
searchTerm = searchTerm,
|
||||
filterTypes = filterTypes
|
||||
}
|
||||
end)
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
local Action = require(script.Parent.Parent.Action)
|
||||
|
||||
return Action("ServerStatsUpdateSearchFilter", function(searchTerm, filterTypes)
|
||||
return {
|
||||
searchTerm = searchTerm,
|
||||
filterTypes = filterTypes
|
||||
}
|
||||
end)
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
local Action = require(script.Parent.Parent.Action)
|
||||
|
||||
return Action("SetActiveTab", function(tabListIndex, isClientView)
|
||||
return {
|
||||
newTabIndex = tabListIndex,
|
||||
isClientView = isClientView
|
||||
}
|
||||
end)
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
local Action = require(script.Parent.Parent.Action)
|
||||
|
||||
return Action("SetDevConsoleMinimized", function(minimize)
|
||||
return {
|
||||
isMinimized = minimize
|
||||
}
|
||||
end)
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
local Action = require(script.Parent.Parent.Action)
|
||||
|
||||
return Action("SetDevConsolePosition", function(pos)
|
||||
return {
|
||||
position = pos
|
||||
}
|
||||
end)
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
local Action = require(script.Parent.Parent.Action)
|
||||
|
||||
return Action("SetDevConsoleVisibility", function(visibility)
|
||||
return {
|
||||
isVisible = visibility
|
||||
}
|
||||
end)
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
local Action = require(script.Parent.Parent.Action)
|
||||
|
||||
return Action(script.Name, function(waitingForRecording, lastFileOutputLocation)
|
||||
|
||||
return {
|
||||
waitingForRecording = waitingForRecording,
|
||||
lastFileOutputLocation = lastFileOutputLocation or "",
|
||||
}
|
||||
end)
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
local Action = require(script.Parent.Parent.Action)
|
||||
|
||||
return Action("SetTabList", function(tabList, initIndex, isDeveloperView)
|
||||
return {
|
||||
tabList = tabList,
|
||||
initIndex = initIndex,
|
||||
isDeveloperView = isDeveloperView,
|
||||
}
|
||||
end)
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
local Action = require(script.Parent.Parent.Action)
|
||||
|
||||
return Action("UpdateAveragePing", function(newAveragePing)
|
||||
return {
|
||||
AveragePing = newAveragePing
|
||||
}
|
||||
end)
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
local CircularBuffer = {}
|
||||
CircularBuffer.__index = CircularBuffer
|
||||
|
||||
function CircularBuffer.new(size)
|
||||
assert(size, "Cannot initialize CircularBuffer with nil")
|
||||
assert(size > 0, "Cannot initialize CircularBuffer to size < 1")
|
||||
|
||||
local self = {}
|
||||
setmetatable(self, CircularBuffer)
|
||||
|
||||
self._data = {}
|
||||
self._backIndex = 0
|
||||
self._maxSize = size
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function CircularBuffer:reset()
|
||||
self._data = {}
|
||||
self._backIndex = 0
|
||||
end
|
||||
|
||||
function CircularBuffer:getSize()
|
||||
return #self._data
|
||||
end
|
||||
|
||||
function CircularBuffer:getMaxSize()
|
||||
return self._maxSize
|
||||
end
|
||||
|
||||
function CircularBuffer:setSize(newSize)
|
||||
assert(newSize, "Cannot set CircularBuffer with nil")
|
||||
assert(newSize > 0, "Cannot set CircularBuffer to size < 1")
|
||||
if newSize == self._maxSize then
|
||||
return
|
||||
end
|
||||
|
||||
local it = self:iterator()
|
||||
local msg = it:next()
|
||||
local sorted = {}
|
||||
local ind = 0
|
||||
while msg and ind < newSize do
|
||||
local nextInd = ind + 1
|
||||
|
||||
sorted[nextInd] = {
|
||||
entry = msg
|
||||
}
|
||||
|
||||
if sorted[ind] then
|
||||
sorted[ind]._next = sorted[nextInd]
|
||||
end
|
||||
|
||||
ind = nextInd
|
||||
msg = it:next()
|
||||
end
|
||||
|
||||
self._data = sorted
|
||||
self._backIndex = ind
|
||||
self._maxSize = newSize
|
||||
end
|
||||
|
||||
function CircularBuffer:getFrontIndex()
|
||||
local front = self._backIndex + 1
|
||||
if not self._data[front] then
|
||||
return 1
|
||||
end
|
||||
return front
|
||||
end
|
||||
|
||||
function CircularBuffer:front()
|
||||
local front = self:getFrontIndex()
|
||||
if self._data[front] then
|
||||
return self._data[front].entry
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function CircularBuffer:back()
|
||||
if self._data[self._backIndex] then
|
||||
return self._data[self._backIndex].entry
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function CircularBuffer:iterator()
|
||||
local front = self._data[self:getFrontIndex()]
|
||||
|
||||
local iterator = {
|
||||
data = front,
|
||||
next = function (self)
|
||||
local retVal = self.data
|
||||
if retVal then
|
||||
self.data = self.data._next
|
||||
end
|
||||
return retVal and retVal.entry
|
||||
end
|
||||
}
|
||||
|
||||
return iterator
|
||||
end
|
||||
|
||||
function CircularBuffer:getData()
|
||||
return self._data
|
||||
end
|
||||
|
||||
function CircularBuffer:at(ind)
|
||||
assert(ind, "Cannot index CircularBuffer with nil")
|
||||
|
||||
local index = self:getFrontIndex()
|
||||
index = (index + ind - 2) % self._maxSize + 1
|
||||
if self._data[index] then
|
||||
return self._data[index].entry
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function CircularBuffer:reverseAt(ind)
|
||||
local index = self._backIndex
|
||||
index = (index - ind) % self._maxSize + 1
|
||||
if self._data[index] then
|
||||
return self._data[index].entry
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- returns the ejected element if newData overwrites
|
||||
-- the previous front element
|
||||
function CircularBuffer:push_back(newData)
|
||||
local currBackIndex = self._backIndex
|
||||
local newBackIndex = self._backIndex + 1
|
||||
if newBackIndex > self._maxSize then
|
||||
newBackIndex = 1
|
||||
end
|
||||
|
||||
local overwrittenData = self._data[newBackIndex]
|
||||
self._data[newBackIndex] = {
|
||||
entry = newData
|
||||
}
|
||||
|
||||
if currBackIndex > 0 then
|
||||
self._data[currBackIndex]._next = self._data[newBackIndex]
|
||||
if overwrittenData then
|
||||
overwrittenData._next = nil
|
||||
end
|
||||
end
|
||||
|
||||
self._backIndex = newBackIndex
|
||||
return overwrittenData and overwrittenData.entry
|
||||
end
|
||||
|
||||
return CircularBuffer
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
return function()
|
||||
local CircularBuffer = require(script.Parent.CircularBuffer)
|
||||
|
||||
it("should not allow initialize to size 0", function()
|
||||
expect(function()
|
||||
local buffer = CircularBuffer.new(0)
|
||||
end).to.throw()
|
||||
|
||||
expect(function()
|
||||
local buffer = CircularBuffer.new()
|
||||
end).to.throw()
|
||||
end)
|
||||
|
||||
it("should push_back() past its max size and loop over itself", function()
|
||||
local buffer = CircularBuffer.new(1)
|
||||
buffer:push_back("test1")
|
||||
buffer:push_back("test2")
|
||||
buffer:push_back("test3")
|
||||
expect(buffer:getSize()).to.equal(1)
|
||||
expect(buffer:front()).to.equal("test3")
|
||||
|
||||
buffer:setSize(5)
|
||||
buffer:reset()
|
||||
expect(buffer:getSize()).to.equal(0)
|
||||
|
||||
local expectedData = { 1, 2, 3, 4, 5 }
|
||||
for _, v in ipairs(expectedData) do
|
||||
buffer:push_back(v)
|
||||
end
|
||||
|
||||
expect(buffer:front()).to.equal(1)
|
||||
buffer:push_back(6)
|
||||
expect(buffer:front()).to.equal(2)
|
||||
end)
|
||||
|
||||
it("should sort and clip data during setSize() where applicable", function()
|
||||
local buffer = CircularBuffer.new(100)
|
||||
for i = 1, 100 do
|
||||
buffer:push_back(i)
|
||||
end
|
||||
|
||||
expect(buffer:getSize()).to.equal(100)
|
||||
|
||||
buffer:setSize(1)
|
||||
expect(buffer:getSize()).to.equal(1)
|
||||
|
||||
buffer:setSize(100)
|
||||
expect(buffer:getSize()).to.equal(1)
|
||||
end)
|
||||
|
||||
it("should maintain the same front() value until it loops over itself", function()
|
||||
local buffer = CircularBuffer.new(10)
|
||||
expect(buffer:front()).to.equal(nil)
|
||||
|
||||
for i = 1, 10 do
|
||||
buffer:push_back(i)
|
||||
expect(buffer:front()).to.equal(1)
|
||||
end
|
||||
|
||||
buffer:push_back(11)
|
||||
expect(buffer:front()).to.equal(2)
|
||||
|
||||
buffer:push_back(12)
|
||||
expect(buffer:front()).to.equal(3)
|
||||
end)
|
||||
|
||||
it("should always return the last push_back() value when calling back()", function()
|
||||
local buffer = CircularBuffer.new(10)
|
||||
expect(buffer:back()).to.equal(nil)
|
||||
|
||||
for i = 1, 20 do
|
||||
buffer:push_back(i)
|
||||
expect(buffer:back()).to.equal(i)
|
||||
end
|
||||
end)
|
||||
|
||||
it("should iterate via iterator and terminate with a nil", function()
|
||||
local buffer = CircularBuffer.new(100)
|
||||
for i = 1, 100 do
|
||||
buffer:push_back(i)
|
||||
end
|
||||
|
||||
local it = buffer:iterator()
|
||||
local val = it:next()
|
||||
local count = 1
|
||||
while val do
|
||||
expect(val).to.equal(count)
|
||||
|
||||
val = it:next()
|
||||
count = count + 1
|
||||
end
|
||||
|
||||
expect(val).to.equal(nil)
|
||||
end)
|
||||
|
||||
it("should maintain expected getData() after push_back()", function()
|
||||
local buffer = CircularBuffer.new(5)
|
||||
local expectedData = {1, 2, 3, 4, 5}
|
||||
|
||||
for _, v in ipairs(expectedData) do
|
||||
buffer:push_back(v)
|
||||
end
|
||||
|
||||
local data = buffer:getData()
|
||||
|
||||
for i,v in ipairs(expectedData) do
|
||||
expect(data[i].entry).to.equal(v)
|
||||
end
|
||||
|
||||
buffer:push_back(6)
|
||||
local newExpectedData = {6, 2, 3, 4, 5}
|
||||
data = buffer:getData()
|
||||
for i,v in ipairs(newExpectedData) do
|
||||
expect(data[i].entry).to.equal(v)
|
||||
end
|
||||
end)
|
||||
|
||||
it("should loop to access the correct values when using at() and reverseAt", function()
|
||||
local buffer = CircularBuffer.new(10)
|
||||
local expectedData = {2, 4, 6, 8, 0, 9, 7, 5, 3, 1}
|
||||
for _,v in ipairs(expectedData) do
|
||||
buffer:push_back(v)
|
||||
end
|
||||
|
||||
for i, v in ipairs(expectedData) do
|
||||
expect(buffer:at(i)).to.equal(v)
|
||||
end
|
||||
|
||||
|
||||
expect(buffer:at(0)).to.equal(1)
|
||||
expect(buffer:at(100)).to.equal(1)
|
||||
expect(buffer:at(-100)).to.equal(1)
|
||||
|
||||
expect(buffer:at(0)).to.equal(1)
|
||||
expect(buffer:at(100)).to.equal(1)
|
||||
expect(buffer:at(-100)).to.equal(1)
|
||||
|
||||
expect(buffer:reverseAt(1)).to.equal(1)
|
||||
expect(buffer:reverseAt(5)).to.equal(9)
|
||||
expect(buffer:reverseAt(0)).to.equal(2)
|
||||
expect(buffer:reverseAt(100)).to.equal(2)
|
||||
expect(buffer:reverseAt(-100)).to.equal(2)
|
||||
end)
|
||||
end
|
||||
+350
@@ -0,0 +1,350 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
local Components = script.Parent.Parent.Parent.Components
|
||||
local DataConsumer = require(Components.DataConsumer)
|
||||
local HeaderButton = require(Components.HeaderButton)
|
||||
local CellLabel = require(Components.CellLabel)
|
||||
|
||||
local Constants = require(script.Parent.Parent.Parent.Constants)
|
||||
local GeneralFormatting = Constants.GeneralFormatting
|
||||
local LINE_WIDTH = GeneralFormatting.LineWidth
|
||||
local LINE_COLOR = GeneralFormatting.LineColor
|
||||
|
||||
local ActionBindingsFormatting = Constants.ActionBindingsFormatting
|
||||
local HEADER_NAMES = ActionBindingsFormatting.ChartHeaderNames
|
||||
local CELL_WIDTHS = ActionBindingsFormatting.ChartCellWidths
|
||||
local HEADER_HEIGHT = ActionBindingsFormatting.HeaderFrameHeight
|
||||
local ENTRY_HEIGHT = ActionBindingsFormatting.EntryFrameHeight
|
||||
local CELL_PADDING = ActionBindingsFormatting.CellPadding
|
||||
local MIN_FRAME_WIDTH = ActionBindingsFormatting.MinFrameWidth
|
||||
|
||||
local IS_CORE_STR = "Core"
|
||||
local IS_DEVELOPER_STR = "Developer"
|
||||
local NON_FOUND_ENTRIES_STR = "No ActionBindings Found"
|
||||
|
||||
-- create table of offsets and sizes for each cell
|
||||
local totalCellWidth = 0
|
||||
for _, cellWidth in ipairs(CELL_WIDTHS) do
|
||||
totalCellWidth = totalCellWidth + cellWidth
|
||||
end
|
||||
|
||||
local currOffset = -totalCellWidth
|
||||
local cellOffset = {}
|
||||
local headerCellSize = {}
|
||||
local entryCellSize = {}
|
||||
|
||||
currOffset = currOffset / 2
|
||||
table.insert(cellOffset, UDim2.new(0, CELL_PADDING, 0, 0))
|
||||
table.insert(headerCellSize, UDim2.new(.5, currOffset - CELL_PADDING, 0, HEADER_HEIGHT))
|
||||
table.insert(entryCellSize, UDim2.new(.5, currOffset - CELL_PADDING, 0, ENTRY_HEIGHT))
|
||||
|
||||
for _, cellWidth in ipairs(CELL_WIDTHS) do
|
||||
table.insert(cellOffset,UDim2.new(.5, currOffset + CELL_PADDING, 0, 0))
|
||||
table.insert(headerCellSize, UDim2.new(0, cellWidth - CELL_PADDING, 0, HEADER_HEIGHT))
|
||||
table.insert(entryCellSize, UDim2.new(0, cellWidth - CELL_PADDING, 0, ENTRY_HEIGHT))
|
||||
currOffset = currOffset + cellWidth
|
||||
end
|
||||
|
||||
table.insert(cellOffset,UDim2.new(.5, currOffset + CELL_PADDING, 0, 0))
|
||||
table.insert(headerCellSize, UDim2.new(.5, (-totalCellWidth / 2) - CELL_PADDING, 0, HEADER_HEIGHT))
|
||||
table.insert(entryCellSize, UDim2.new(.5, (-totalCellWidth / 2) - CELL_PADDING, 0, ENTRY_HEIGHT))
|
||||
|
||||
local verticalOffsets = {}
|
||||
for i, offset in ipairs(cellOffset) do
|
||||
verticalOffsets[i] = UDim2.new(
|
||||
offset.X.Scale,
|
||||
offset.X.Offset - CELL_PADDING,
|
||||
offset.Y.Scale,
|
||||
offset.Y.Offset)
|
||||
end
|
||||
|
||||
local ActionBindingsChart = Roact.Component:extend("ActionBindingsChart")
|
||||
|
||||
local function constructHeader(onSortChanged, width)
|
||||
local header = {}
|
||||
|
||||
for ind, name in ipairs(HEADER_NAMES) do
|
||||
header[name] = Roact.createElement(HeaderButton, {
|
||||
text = name,
|
||||
size = headerCellSize[ind],
|
||||
pos = cellOffset[ind],
|
||||
sortfunction = onSortChanged,
|
||||
})
|
||||
end
|
||||
|
||||
header["upperHorizontalLine"] = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, LINE_WIDTH),
|
||||
Position = UDim2.new(0, 0, 0, 0),
|
||||
BackgroundColor3 = LINE_COLOR,
|
||||
BorderSizePixel = 0,
|
||||
})
|
||||
|
||||
header["lowerHorizontalLine"] = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, LINE_WIDTH),
|
||||
Position = UDim2.new(0, 0, 1, -LINE_WIDTH),
|
||||
BackgroundColor3 = LINE_COLOR,
|
||||
BorderSizePixel = 0,
|
||||
})
|
||||
|
||||
for ind = 2, #verticalOffsets do
|
||||
local key = string.format("VerticalLine_%d",ind)
|
||||
header[key] = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(0, LINE_WIDTH, 1, 0),
|
||||
Position = verticalOffsets[ind],
|
||||
BackgroundColor3 = LINE_COLOR,
|
||||
BorderSizePixel = 0,
|
||||
})
|
||||
end
|
||||
|
||||
return Roact.createElement("Frame", {
|
||||
Size = UDim2.new(0, width, 0, HEADER_HEIGHT),
|
||||
BackgroundTransparency = 1,
|
||||
}, header)
|
||||
end
|
||||
|
||||
local function constructEntry(entry, width, layoutOrder)
|
||||
local name = entry.name
|
||||
local actionInfo = entry.actionInfo
|
||||
|
||||
-- the last element is special cased because the data in the
|
||||
-- string is passed in as value in the table
|
||||
-- use tostring to convert the enum into an actual string also because it's used twice
|
||||
local enumStr = tostring(actionInfo["inputTypes"][1])
|
||||
|
||||
local isCoreString = IS_CORE_STR
|
||||
if actionInfo["isCore"] then
|
||||
isCoreString = IS_DEVELOPER_STR
|
||||
end
|
||||
|
||||
local row = {}
|
||||
for i = 2,#verticalOffsets do
|
||||
local key = string.format("line_%d",i)
|
||||
row[key] = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(0,LINE_WIDTH,1,0),
|
||||
Position = verticalOffsets[i],
|
||||
BackgroundColor3 = LINE_COLOR,
|
||||
BorderSizePixel = 0,
|
||||
})
|
||||
end
|
||||
|
||||
row[name] = Roact.createElement(CellLabel, {
|
||||
text = enumStr,
|
||||
size = entryCellSize[1],
|
||||
pos = cellOffset[1],
|
||||
})
|
||||
|
||||
row.priorityLevel = Roact.createElement(CellLabel, {
|
||||
text = actionInfo["priorityLevel"],
|
||||
size = entryCellSize[2],
|
||||
pos = cellOffset[2],
|
||||
})
|
||||
|
||||
row.isCore = Roact.createElement(CellLabel, {
|
||||
text = isCoreString,
|
||||
size = entryCellSize[3],
|
||||
pos = cellOffset[3],
|
||||
})
|
||||
|
||||
row.actionName = Roact.createElement(CellLabel, {
|
||||
text = name,
|
||||
size = entryCellSize[4],
|
||||
pos = cellOffset[4],
|
||||
})
|
||||
|
||||
row.inputTypes = Roact.createElement(CellLabel, {
|
||||
text = enumStr,
|
||||
size = entryCellSize[5],
|
||||
pos = cellOffset[5],
|
||||
})
|
||||
|
||||
row.lowerHorizontalLine = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, LINE_WIDTH),
|
||||
Position = UDim2.new(0, 0, 1, 0),
|
||||
BackgroundColor3 = LINE_COLOR,
|
||||
BorderSizePixel = 0,
|
||||
})
|
||||
|
||||
|
||||
return Roact.createElement("Frame", {
|
||||
Size = UDim2.new(0, width, 0, ENTRY_HEIGHT),
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = layoutOrder,
|
||||
},row)
|
||||
end
|
||||
|
||||
function ActionBindingsChart:init(props)
|
||||
local initBindings = props.ActionBindingsData:getCurrentData()
|
||||
|
||||
self.onSortChanged = function(sortType)
|
||||
local currSortType = props.ActionBindingsData:getSortType()
|
||||
if sortType == currSortType then
|
||||
self:setState({
|
||||
reverseSort = not self.state.reverseSort
|
||||
})
|
||||
else
|
||||
props.ActionBindingsData:setSortType(sortType)
|
||||
self:setState({
|
||||
reverseSort = false,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
self.onCanvasPosChanged = function()
|
||||
local canvasPos = self.scrollingRef.current.CanvasPosition
|
||||
if self.state.canvasPos ~= canvasPos then
|
||||
self:setState({
|
||||
canvasPos = canvasPos,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
self.scrollingRef = Roact.createRef()
|
||||
|
||||
self.state = {
|
||||
actionBindingEntries = initBindings,
|
||||
reverseSort = false,
|
||||
}
|
||||
end
|
||||
|
||||
function ActionBindingsChart:willUpdate()
|
||||
if self.canvasPosConnector then
|
||||
self.canvasPosConnector:Disconnect()
|
||||
end
|
||||
end
|
||||
|
||||
function ActionBindingsChart:didUpdate()
|
||||
if self.scrollingRef.current then
|
||||
local signal = self.scrollingRef.current:GetPropertyChangedSignal("CanvasPosition")
|
||||
self.canvasPosConnector = signal:Connect(self.onCanvasPosChanged)
|
||||
|
||||
local absSize = self.scrollingRef.current.AbsoluteSize
|
||||
local currAbsSize = self.state.absScrollSize
|
||||
if absSize.X ~= currAbsSize.X or
|
||||
absSize.Y ~= currAbsSize.Y then
|
||||
self:setState({
|
||||
absScrollSize = absSize,
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ActionBindingsChart:didMount()
|
||||
self.bindingsUpdated = self.props.ActionBindingsData:Signal():Connect(function(bindingsData)
|
||||
self:setState({
|
||||
actionBindingEntries = bindingsData
|
||||
})
|
||||
end)
|
||||
|
||||
if self.scrollingRef.current then
|
||||
local signal = self.scrollingRef.current:GetPropertyChangedSignal("CanvasPosition")
|
||||
self.canvasPosConnector = signal:Connect(self.onCanvasPosChanged)
|
||||
|
||||
self:setState({
|
||||
absScrollSize = self.scrollingRef.current.AbsoluteSize,
|
||||
canvasPos = self.scrollingRef.current.CanvasPosition,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function ActionBindingsChart:willUnmount()
|
||||
self.bindingsUpdated:Disconnect()
|
||||
self.bindingsUpdated = nil
|
||||
self.canvasPosConnector:Disconnect()
|
||||
self.canvasPosConnector = nil
|
||||
end
|
||||
|
||||
function ActionBindingsChart:render()
|
||||
local entries = {}
|
||||
local searchTerm = self.props.searchTerm
|
||||
local size = self.props.size
|
||||
local layoutOrder = self.props.layoutOrder
|
||||
|
||||
local entryList = self.state.actionBindingEntries
|
||||
local reverseSort = self.state.reverseSort
|
||||
|
||||
local canvasPos = self.state.canvasPos
|
||||
local absScrollSize = self.state.absScrollSize
|
||||
local frameWidth = absScrollSize and math.max(absScrollSize.X, MIN_FRAME_WIDTH) or MIN_FRAME_WIDTH
|
||||
|
||||
entries["UIListLayout"] = Roact.createElement("UIListLayout", {
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Left,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Top,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
})
|
||||
|
||||
local totalEntries = #entryList
|
||||
local canvasHeight = 0
|
||||
|
||||
if absScrollSize and canvasPos then
|
||||
local paddingHeight = -1
|
||||
local usedFrameSpace = 0
|
||||
local count = 0
|
||||
|
||||
for ind, entry in ipairs(entryList) do
|
||||
local foundTerm = false
|
||||
if searchTerm then
|
||||
local enumStr = tostring(entry.actionInfo["inputTypes"][1])
|
||||
foundTerm = string.find(enumStr:lower(), searchTerm:lower()) ~= nil
|
||||
foundTerm = foundTerm or string.find(entry.name:lower(), searchTerm:lower()) ~= nil
|
||||
end
|
||||
|
||||
if not searchTerm or foundTerm then
|
||||
if canvasHeight + ENTRY_HEIGHT >= canvasPos.Y then
|
||||
if usedFrameSpace < absScrollSize.Y then
|
||||
local entryLayoutOrder = reverseSort and (totalEntries - ind) or ind
|
||||
entries[ind] = constructEntry(entry, frameWidth, entryLayoutOrder + 1)
|
||||
end
|
||||
if paddingHeight < 0 then
|
||||
paddingHeight = canvasHeight
|
||||
else
|
||||
usedFrameSpace = usedFrameSpace + ENTRY_HEIGHT
|
||||
end
|
||||
count = count + 1
|
||||
end
|
||||
|
||||
canvasHeight = canvasHeight + ENTRY_HEIGHT
|
||||
end
|
||||
end
|
||||
|
||||
if count == 0 then
|
||||
entries["NoneFound"] = Roact.createElement("TextLabel", {
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
Text = NON_FOUND_ENTRIES_STR,
|
||||
TextColor3 = LINE_COLOR,
|
||||
|
||||
BackgroundTransparency = 1,
|
||||
|
||||
LayoutOrder = 1,
|
||||
})
|
||||
else
|
||||
|
||||
entries["WindowingPadding"] = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, paddingHeight),
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 1,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
return Roact.createElement("Frame", {
|
||||
Size = size,
|
||||
BackgroundTransparency = 1,
|
||||
ClipsDescendants = true,
|
||||
LayoutOrder = layoutOrder,
|
||||
}, {
|
||||
Header = constructHeader(self.onSortChanged, frameWidth),
|
||||
MainChart = Roact.createElement("ScrollingFrame", {
|
||||
Position = UDim2.new(0, 0, 0, HEADER_HEIGHT),
|
||||
Size = UDim2.new(1, 0, 1, - HEADER_HEIGHT),
|
||||
CanvasSize = UDim2.new(0, frameWidth, 0, canvasHeight),
|
||||
ScrollBarThickness = 6,
|
||||
BackgroundColor3 = Constants.Color.BaseGray,
|
||||
BackgroundTransparency = 1,
|
||||
|
||||
[Roact.Ref] = self.scrollingRef
|
||||
}, entries),
|
||||
})
|
||||
end
|
||||
|
||||
return DataConsumer(ActionBindingsChart, "ActionBindingsData")
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local RoactRodux = require(CorePackages.RoactRodux)
|
||||
local Store = require(CorePackages.Rodux).Store
|
||||
|
||||
local DataProvider = require(script.Parent.Parent.DataProvider)
|
||||
local ActionBindingsChart = require(script.Parent.ActionBindingsChart)
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local store = Store.new(function()
|
||||
return {
|
||||
MainView = {
|
||||
currTabIndex = 0,
|
||||
isDeveloperView = true,
|
||||
},
|
||||
}
|
||||
end)
|
||||
|
||||
local element = Roact.createElement(RoactRodux.StoreProvider, {
|
||||
store = store,
|
||||
}, {
|
||||
DataProvider = Roact.createElement(DataProvider, {}, {
|
||||
ActionBindingsChart = Roact.createElement(ActionBindingsChart)
|
||||
})
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
local ContextActionService = game:GetService("ContextActionService")
|
||||
local Signal = require(script.Parent.Parent.Parent.Signal)
|
||||
|
||||
local Constants = require(script.Parent.Parent.Parent.Constants)
|
||||
local HEADER_NAMES = Constants.ActionBindingsFormatting.ChartHeaderNames
|
||||
|
||||
local SORT_COMPARATOR = {
|
||||
[HEADER_NAMES[1]] = function(a,b) -- "Name"
|
||||
return a.counter < b.counter
|
||||
end,
|
||||
[HEADER_NAMES[2]] = function(a,b) -- "Priority"
|
||||
if a.actionInfo.priorityLevel == b.actionInfo.priorityLevel then
|
||||
return a.counter < b.counter
|
||||
end
|
||||
return a.actionInfo.priorityLevel < b.actionInfo.priorityLevel
|
||||
end,
|
||||
[HEADER_NAMES[3]] = function(a,b) -- "Security"
|
||||
if a.actionInfo.isCore == b.actionInfo.isCore then
|
||||
return a.counter < b.counter
|
||||
else
|
||||
return a.actionInfo.isCore
|
||||
end
|
||||
end,
|
||||
[HEADER_NAMES[4]] = function(a,b) -- "Action Name"
|
||||
return a.name:lower() < b.name:lower()
|
||||
end,
|
||||
[HEADER_NAMES[5]] = function(a,b) -- "Input Types"
|
||||
return tostring(a.actionInfo.inputTypes[1]) < tostring(b.actionInfo.inputTypes[1])
|
||||
end,
|
||||
}
|
||||
|
||||
local ActionBindingsData = {}
|
||||
ActionBindingsData.__index = ActionBindingsData
|
||||
|
||||
function ActionBindingsData.new()
|
||||
local self = {}
|
||||
setmetatable(self, ActionBindingsData)
|
||||
|
||||
self._bindingsUpdated = Signal.new()
|
||||
self._bindingsData = {}
|
||||
self._bindingCounter = 0
|
||||
self._sortedBindingData = {}
|
||||
self._sortType = HEADER_NAMES[1] -- Name
|
||||
self._isRunning = false
|
||||
return self
|
||||
end
|
||||
|
||||
function ActionBindingsData:setSortType(sortType)
|
||||
if SORT_COMPARATOR[sortType] then
|
||||
self._sortType = sortType
|
||||
table.sort(self._sortedBindingData, SORT_COMPARATOR[self._sortType])
|
||||
self._bindingsUpdated:Fire(self._sortedBindingData)
|
||||
else
|
||||
error(string.format("attempted to pass invalid sortType: %s", tostring(sortType)), 2)
|
||||
end
|
||||
end
|
||||
|
||||
function ActionBindingsData:getSortType()
|
||||
return self._sortType
|
||||
end
|
||||
|
||||
function ActionBindingsData:Signal()
|
||||
return self._bindingsUpdated
|
||||
end
|
||||
|
||||
function ActionBindingsData:getCurrentData()
|
||||
return self._sortedBindingData
|
||||
end
|
||||
|
||||
-- this funciton will require some extra work to handle the
|
||||
-- case a entry insert occurs during the end of the list
|
||||
function ActionBindingsData:updateBindingDataEntry(name, info)
|
||||
if info == nil then
|
||||
--remove element and clean up sorted
|
||||
self._bindingsData[name] = nil
|
||||
for i, v in pairs(self._sortedBindingData) do
|
||||
if v.name == name then
|
||||
table.remove(self._sortedBindingData, i)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
elseif not self._bindingsData[name] then
|
||||
self._bindingCounter = self._bindingCounter + 1
|
||||
self._bindingsData[name] = info
|
||||
local newEntry = {
|
||||
name = name,
|
||||
actionInfo = self._bindingsData[name],
|
||||
counter = self._bindingCounter,
|
||||
}
|
||||
table.insert(self._sortedBindingData, newEntry)
|
||||
else
|
||||
self._bindingsData[name] = info
|
||||
end
|
||||
end
|
||||
|
||||
function ActionBindingsData:isRunning()
|
||||
return self._isRunning
|
||||
end
|
||||
|
||||
function ActionBindingsData:start()
|
||||
local boundActions = ContextActionService:GetAllBoundActionInfo()
|
||||
for actionName, actionInfo in pairs(boundActions) do
|
||||
actionInfo.isCore = false
|
||||
self:updateBindingDataEntry(actionName, actionInfo)
|
||||
end
|
||||
|
||||
local boundCoreActions = ContextActionService:GetAllBoundCoreActionInfo()
|
||||
for actionName, actionInfo in pairs(boundCoreActions) do
|
||||
actionInfo.isCore = true
|
||||
self:updateBindingDataEntry(actionName, actionInfo)
|
||||
end
|
||||
|
||||
if not self._actionChangedConnection then
|
||||
self._actionChangedConnection = ContextActionService.BoundActionChanged:connect(
|
||||
function(actionName, changeName, changeTable)
|
||||
self:updateBindingDataEntry(actionName, nil)
|
||||
self:updateBindingDataEntry(changeName, changeTable)
|
||||
self._bindingsUpdated:Fire(self._sortedBindingData)
|
||||
end)
|
||||
end
|
||||
|
||||
if not self._actionAddedConnection then
|
||||
self._actionAddedConnection = ContextActionService.BoundActionAdded:connect(
|
||||
function(actionName, createTouchButton, actionInfo, isCore)
|
||||
actionInfo.isCore = isCore
|
||||
self:updateBindingDataEntry(actionName, actionInfo)
|
||||
self._bindingsUpdated:Fire(self._sortedBindingData)
|
||||
end)
|
||||
|
||||
end
|
||||
if not self._actionRemovedConnection then
|
||||
self._actionRemovedConnection = ContextActionService.BoundActionRemoved:connect(
|
||||
function(actionName, actionInfo, isCore)
|
||||
self:updateBindingDataEntry(actionName, nil)
|
||||
self._bindingsUpdated:Fire(self._sortedBindingData)
|
||||
end)
|
||||
end
|
||||
self._isRunning = true
|
||||
end
|
||||
|
||||
function ActionBindingsData:stop()
|
||||
if self.actionChangedConnector then
|
||||
self.actionChangedConnector:Disconnect()
|
||||
self.actionChangedConnector = nil
|
||||
end
|
||||
if self.actionAddedConnector then
|
||||
self.actionAddedConnector:Disconnect()
|
||||
self.actionAddedConnector = nil
|
||||
end
|
||||
if self.actionRemovedConnector then
|
||||
self.actionRemovedConnector:Disconnect()
|
||||
self.actionRemovedConnector = nil
|
||||
end
|
||||
self._isRunning = false
|
||||
end
|
||||
|
||||
return ActionBindingsData
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local RoactRodux = require(CorePackages.RoactRodux)
|
||||
|
||||
local Components = script.Parent.Parent.Parent.Components
|
||||
local ActionBindingsChart = require(Components.ActionBindings.ActionBindingsChart)
|
||||
local UtilAndTab = require(Components.UtilAndTab)
|
||||
|
||||
local Actions = script.Parent.Parent.Parent.Actions
|
||||
local ActionBindingsUpdateSearchFilter = require(Actions.ActionBindingsUpdateSearchFilter)
|
||||
|
||||
local Constants = require(script.Parent.Parent.Parent.Constants)
|
||||
local PADDING = Constants.GeneralFormatting.MainRowPadding
|
||||
|
||||
local MainViewActionBindings = Roact.Component:extend("MainViewActionBindings")
|
||||
|
||||
function MainViewActionBindings:init()
|
||||
self.onUtilTabHeightChanged = function(utilTabHeight)
|
||||
self:setState({
|
||||
utilTabHeight = utilTabHeight
|
||||
})
|
||||
end
|
||||
|
||||
self.onSearchTermChanged = function(newSearchTerm)
|
||||
self.props.dispatchActionBindingsUpdateSearchFilter(newSearchTerm, {})
|
||||
end
|
||||
|
||||
self.utilRef = Roact.createRef()
|
||||
|
||||
self.state = {
|
||||
utilTabHeight = 0
|
||||
}
|
||||
end
|
||||
|
||||
function MainViewActionBindings:didMount()
|
||||
local utilSize = self.utilRef.current.Size
|
||||
self:setState({
|
||||
utilTabHeight = utilSize.Y.Offset
|
||||
})
|
||||
end
|
||||
|
||||
function MainViewActionBindings:didUpdate()
|
||||
local utilSize = self.utilRef.current.Size
|
||||
if utilSize.Y.Offset ~= self.state.utilTabHeight then
|
||||
self:setState({
|
||||
utilTabHeight = utilSize.Y.Offset
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function MainViewActionBindings:render()
|
||||
local size = self.props.size
|
||||
local formFactor = self.props.formFactor
|
||||
local tabList = self.props.tabList
|
||||
local searchTerm = self.props.bindingsSearchTerm
|
||||
|
||||
local utilTabHeight = self.state.utilTabHeight
|
||||
|
||||
return Roact.createElement("Frame", {
|
||||
Size = size,
|
||||
BackgroundColor3 = Constants.Color.BaseGray,
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 3,
|
||||
}, {
|
||||
UIListLayout = Roact.createElement("UIListLayout", {
|
||||
Padding = UDim.new(0, PADDING),
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
}),
|
||||
|
||||
UtilAndTab = Roact.createElement(UtilAndTab, {
|
||||
windowWidth = size.X.Offset,
|
||||
formFactor = formFactor,
|
||||
tabList = tabList,
|
||||
searchTerm = searchTerm,
|
||||
layoutOrder = 1,
|
||||
|
||||
refForParent = self.utilRef,
|
||||
|
||||
onHeightChanged = self.onUtilTabHeightChanged,
|
||||
onSearchTermChanged = self.onSearchTermChanged,
|
||||
}),
|
||||
|
||||
ActionBindings = utilTabHeight > 0 and Roact.createElement(ActionBindingsChart, {
|
||||
size = UDim2.new(1, 0, 1, -utilTabHeight),
|
||||
searchTerm = searchTerm,
|
||||
layoutOrder = 2,
|
||||
}),
|
||||
})
|
||||
end
|
||||
|
||||
local function mapStateToProps(state, props)
|
||||
return {
|
||||
bindingsSearchTerm = state.ActionBindingsData.bindingsSearchTerm,
|
||||
}
|
||||
end
|
||||
|
||||
local function mapDispatchToProps(dispatch)
|
||||
return {
|
||||
dispatchActionBindingsUpdateSearchFilter = function(searchTerm, filters)
|
||||
dispatch(ActionBindingsUpdateSearchFilter(searchTerm, filters))
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
return RoactRodux.UNSTABLE_connect2(mapStateToProps, mapDispatchToProps)(MainViewActionBindings)
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local RoactRodux = require(CorePackages.RoactRodux)
|
||||
local Store = require(CorePackages.Rodux).Store
|
||||
|
||||
local DataProvider = require(script.Parent.Parent.DataProvider)
|
||||
local MainViewActionBindings = require(script.Parent.MainViewActionBindings)
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local store = Store.new(function()
|
||||
return {
|
||||
MainView = {
|
||||
currTabIndex = 0,
|
||||
isDeveloperView = true,
|
||||
},
|
||||
ActionBindingsData = {
|
||||
bindingsSearchTerm = ""
|
||||
}
|
||||
}
|
||||
end)
|
||||
|
||||
local element = Roact.createElement(RoactRodux.StoreProvider, {
|
||||
store = store,
|
||||
}, {
|
||||
DataProvider = Roact.createElement(DataProvider, {}, {
|
||||
MainViewActionBindings = Roact.createElement(MainViewActionBindings, {
|
||||
size = UDim2.new(),
|
||||
tabList = {},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
local Immutable = require(script.Parent.Parent.Immutable)
|
||||
local Constants = require(script.Parent.Parent.Constants)
|
||||
local LINE_WIDTH = Constants.GeneralFormatting.LineWidth
|
||||
local LINE_COLOR = Constants.GeneralFormatting.LineColor
|
||||
local ARROW_WIDTH = Constants.GeneralFormatting.ArrowWidth
|
||||
local CLOSE_ARROW = Constants.Image.RightArrow
|
||||
local OPEN_ARROW = Constants.Image.DownArrow
|
||||
|
||||
local BannerButton = Roact.Component:extend("BannerButton")
|
||||
|
||||
function BannerButton:render()
|
||||
local children = self.props[Roact.Children] or {}
|
||||
|
||||
local size = self.props.size
|
||||
local pos = self.props.pos
|
||||
local isExpanded = self.props.isExpanded
|
||||
local layoutOrder = self.props.layoutOrder
|
||||
|
||||
local onButtonPress = self.props.onButtonPress
|
||||
|
||||
local bannerElements = {
|
||||
BannerButtonArrow = onButtonPress and Roact.createElement("ImageLabel", {
|
||||
Image = isExpanded and OPEN_ARROW or CLOSE_ARROW,
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.new(0, ARROW_WIDTH, 0, ARROW_WIDTH),
|
||||
Position = UDim2.new(0, 0, .5, -ARROW_WIDTH / 2),
|
||||
}),
|
||||
|
||||
HorizontalLineTop = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, LINE_WIDTH),
|
||||
BackgroundColor3 = LINE_COLOR,
|
||||
BorderSizePixel = 0,
|
||||
}),
|
||||
|
||||
HorizontalLineBottom = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, LINE_WIDTH),
|
||||
Position = UDim2.new(0, 0, 1, -LINE_WIDTH),
|
||||
BackgroundColor3 = LINE_COLOR,
|
||||
BorderSizePixel = 0,
|
||||
}),
|
||||
}
|
||||
|
||||
return Roact.createElement("ImageButton", {
|
||||
Size = size,
|
||||
Position = pos,
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = layoutOrder,
|
||||
|
||||
[Roact.Event.Activated] = onButtonPress,
|
||||
}, Immutable.JoinDictionaries(bannerElements, children))
|
||||
end
|
||||
|
||||
return BannerButton
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
local Constants = require(script.Parent.Parent.Constants)
|
||||
local TEXT_SIZE = Constants.DefaultFontSize.MainWindow
|
||||
local TEXT_COLOR = Constants.Color.Text
|
||||
local MAIN_FONT = Constants.Font.MainWindow
|
||||
local MAIN_FONT_BOLD = Constants.Font.MainWindowBold
|
||||
|
||||
local function CellLabel(props)
|
||||
local text = props.text
|
||||
local size = props.size
|
||||
local pos = props.pos
|
||||
local bold = props.bold
|
||||
local layoutOrder = props.layoutOrder
|
||||
|
||||
return Roact.createElement("TextLabel", {
|
||||
Text = text,
|
||||
TextSize = TEXT_SIZE,
|
||||
TextColor3 = TEXT_COLOR,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextWrapped = true,
|
||||
Font = bold and MAIN_FONT_BOLD or MAIN_FONT,
|
||||
|
||||
Size = size,
|
||||
Position = pos,
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = layoutOrder,
|
||||
})
|
||||
end
|
||||
|
||||
return CellLabel
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local TextService = game:GetService("TextService")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
local Constants = require(script.Parent.Parent.Constants)
|
||||
local PADDING = Constants.UtilityBarFormatting.CheckBoxInnerPadding
|
||||
|
||||
local CheckBox = Roact.Component:extend("CheckBox")
|
||||
|
||||
function CheckBox:render()
|
||||
local checkBoxHeight = self.props.checkBoxHeight
|
||||
local frameHeight = self.props.frameHeight
|
||||
local layoutOrder = self.props.layoutOrder
|
||||
|
||||
local name = self.props.name
|
||||
local font = self.props.font
|
||||
local fontSize = self.props.fontSize
|
||||
|
||||
local isSelected = self.props.isSelected
|
||||
local selectedColor = self.props.selectedColor
|
||||
local unselectedColor = self.props.unselectedColor
|
||||
local onCheckBoxClicked = self.props.onCheckBoxClicked
|
||||
|
||||
-- this can be replaced with default values once that releases
|
||||
local image = ""
|
||||
local borderSize = 1
|
||||
local backgroundColor = unselectedColor
|
||||
|
||||
if isSelected then
|
||||
image = Constants.Image.Check
|
||||
borderSize = 0
|
||||
backgroundColor = selectedColor
|
||||
end
|
||||
|
||||
local textVector = TextService:GetTextSize(name, fontSize, font, Vector2.new(0, frameHeight))
|
||||
local textWidth = textVector.X
|
||||
|
||||
return Roact.createElement("ImageButton", {
|
||||
Size = UDim2.new(0, checkBoxHeight + textWidth + (PADDING * 2), 0, frameHeight),
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = layoutOrder,
|
||||
|
||||
[Roact.Event.Activated] = function(rbx)
|
||||
onCheckBoxClicked(name, not isSelected)
|
||||
end,
|
||||
}, {
|
||||
Icon = Roact.createElement("ImageLabel", {
|
||||
Image = image,
|
||||
Size = UDim2.new(0, checkBoxHeight, 0, checkBoxHeight),
|
||||
Position = UDim2.new(0, 0, .5, -checkBoxHeight / 2),
|
||||
BackgroundColor3 = backgroundColor,
|
||||
BackgroundTransparency = 0,
|
||||
BorderColor3 = Constants.Color.Text,
|
||||
BorderSizePixel = borderSize,
|
||||
}),
|
||||
Text = Roact.createElement("TextLabel", {
|
||||
Text = name,
|
||||
TextColor3 = Constants.Color.Text,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
Font = font,
|
||||
TextSize = fontSize,
|
||||
|
||||
Size = UDim2.new(1, -frameHeight, 1, 0),
|
||||
Position = UDim2.new(0, checkBoxHeight + PADDING, 0, 0),
|
||||
BackgroundTransparency = 1,
|
||||
})
|
||||
})
|
||||
end
|
||||
|
||||
return CheckBox
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
local CheckBox = require(script.Parent.CheckBox)
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local element = Roact.createElement(CheckBox, {
|
||||
name = "",
|
||||
fontSize = 0,
|
||||
font = 0,
|
||||
frameHeight = 0,
|
||||
checkBoxHeight = 0,
|
||||
})
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local TextService = game:GetService("TextService")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
local Components = script.Parent.Parent.Components
|
||||
local CheckBox = require(Components.CheckBox)
|
||||
local CheckBoxDropDown = require(Components.CheckBoxDropDown)
|
||||
|
||||
local Constants = require(script.Parent.Parent.Constants)
|
||||
local CHECK_BOX_HEIGHT = Constants.UtilityBarFormatting.CheckBoxHeight
|
||||
local CHECK_BOX_PADDING = Constants.UtilityBarFormatting.CheckBoxInnerPadding * 2
|
||||
local FILTER_ICON_UNFILLED = Constants.Image.FilterUnfilled
|
||||
local FILTER_ICON_FILLED = Constants.Image.FilterFilled
|
||||
|
||||
local DROP_DOWN_Y_ADJUST = 3
|
||||
|
||||
local CheckBoxContainer = Roact.PureComponent:extend("CheckBoxContainer")
|
||||
|
||||
function CheckBoxContainer:init()
|
||||
self.onCheckBoxClicked = function(field, newState)
|
||||
local onCheckBoxChanged = self.props.onCheckBoxChanged
|
||||
onCheckBoxChanged(field, newState)
|
||||
end
|
||||
|
||||
-- this is part of the dropdown logic
|
||||
self.onCheckBoxExpanded = function(rbx, input)
|
||||
if input.UserInputType == Enum.UserInputType.MouseButton1 or
|
||||
(input.UserInputType == Enum.UserInputType.Touch and
|
||||
input.UserInputState == Enum.UserInputState.End) then
|
||||
self:setState({
|
||||
expanded = true,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
-- this is part of the dropdown logic
|
||||
self.onCloseCheckBox = function(rbx, input)
|
||||
if input.UserInputType == Enum.UserInputType.MouseButton1 or
|
||||
(input.UserInputType == Enum.UserInputType.Touch and
|
||||
input.UserInputState == Enum.UserInputState.End) then
|
||||
self:setState({
|
||||
expanded = false
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
if not self.props.orderedCheckBoxState then
|
||||
warn("CheckBoxContainer must be passed a list of Box Names or else it only creates an empty frame")
|
||||
end
|
||||
|
||||
local textWidths = {}
|
||||
local totalLength = 0
|
||||
local count = 0
|
||||
for ind, box in ipairs(self.props.orderedCheckBoxState) do
|
||||
local textVector = TextService:GetTextSize(
|
||||
box.name,
|
||||
Constants.DefaultFontSize.UtilBar,
|
||||
Constants.Font.UtilBar,
|
||||
Vector2.new(0, 0)
|
||||
)
|
||||
textWidths[ind] = textVector.X
|
||||
totalLength = totalLength + textVector.X + CHECK_BOX_HEIGHT + CHECK_BOX_PADDING
|
||||
count = count + 1
|
||||
end
|
||||
|
||||
self.ref = Roact.createRef()
|
||||
|
||||
self.state = {
|
||||
expanded = false,
|
||||
textWidths = textWidths,
|
||||
numCheckBoxes = count,
|
||||
minFullLength = totalLength,
|
||||
}
|
||||
end
|
||||
|
||||
function CheckBoxContainer:render()
|
||||
local elements = {}
|
||||
local frameWidth = self.props.frameWidth
|
||||
local frameHeight = self.props.frameHeight
|
||||
local pos = self.props.pos
|
||||
|
||||
local layoutOrder = self.props.layoutOrder
|
||||
local orderedCheckBoxState = self.props.orderedCheckBoxState
|
||||
|
||||
local minFullLength = self.state.minFullLength
|
||||
local expanded = self.state.expanded
|
||||
local numCheckBoxes = self.state.numCheckBoxes
|
||||
|
||||
local anySelected = false
|
||||
for layoutOrder, box in ipairs(orderedCheckBoxState) do
|
||||
elements[box.name] = Roact.createElement(CheckBox, {
|
||||
name = box.name,
|
||||
font = Constants.Font.UtilBar,
|
||||
fontSize = Constants.DefaultFontSize.UtilBar,
|
||||
checkBoxHeight = CHECK_BOX_HEIGHT,
|
||||
frameHeight = frameHeight,
|
||||
layoutOrder = layoutOrder,
|
||||
|
||||
isSelected = box.state,
|
||||
selectedColor = Constants.Color.SelectedBlue,
|
||||
unselectedColor = Constants.Color.UnselectedGray,
|
||||
|
||||
onCheckBoxClicked = self.onCheckBoxClicked,
|
||||
})
|
||||
anySelected = anySelected or box.state
|
||||
end
|
||||
|
||||
if frameWidth < minFullLength then
|
||||
elements["CheckBoxLayout"] = Roact.createElement("UIListLayout", {
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Left,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Top,
|
||||
FillDirection = Enum.FillDirection.Vertical,
|
||||
})
|
||||
|
||||
local showDropDown = self.ref.current and expanded
|
||||
local dropDownPos
|
||||
if showDropDown then
|
||||
local absPos = self.ref.current.AbsolutePosition
|
||||
-- adding slight y offset to nudge dropdown enough to see button border
|
||||
dropDownPos = UDim2.new(0, absPos.X, 0, absPos.Y + frameHeight + DROP_DOWN_Y_ADJUST)
|
||||
end
|
||||
|
||||
return Roact.createElement("ImageButton", {
|
||||
Size = UDim2.new(0, frameHeight, 0, frameHeight),
|
||||
LayoutOrder = layoutOrder,
|
||||
|
||||
Image = showDropDown and FILTER_ICON_FILLED or FILTER_ICON_UNFILLED,
|
||||
BackgroundTransparency = 1,
|
||||
BorderColor3 = Constants.Color.Text,
|
||||
|
||||
[Roact.Event.InputEnded] = self.onCheckBoxExpanded,
|
||||
[Roact.Ref] = self.ref,
|
||||
}, {
|
||||
DropDown = showDropDown and Roact.createElement(CheckBoxDropDown, {
|
||||
absolutePosition = dropDownPos,
|
||||
frameWidth = frameWidth,
|
||||
elementHeight = frameHeight,
|
||||
numElements = numCheckBoxes,
|
||||
|
||||
onCloseCheckBox = self.onCloseCheckBox
|
||||
}, elements)
|
||||
})
|
||||
else
|
||||
elements["CheckBoxLayout"] = Roact.createElement("UIListLayout", {
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Left,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Top,
|
||||
FillDirection = Enum.FillDirection.Horizontal,
|
||||
})
|
||||
|
||||
return Roact.createElement("Frame", {
|
||||
Size = UDim2.new(0,frameWidth,0,frameHeight),
|
||||
Position = pos,
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = layoutOrder,
|
||||
}, elements)
|
||||
end
|
||||
end
|
||||
|
||||
return CheckBoxContainer
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
local CheckBoxContainer = require(script.Parent.CheckBoxContainer)
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local element = Roact.createElement(CheckBoxContainer, {
|
||||
orderedCheckBoxState = {},
|
||||
frameWidth = 0,
|
||||
})
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local RobloxGui = game:GetService("CoreGui").RobloxGui
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
local Constants = require(script.Parent.Parent.Constants)
|
||||
local INNER_FRAME_PADDING = 12
|
||||
|
||||
local CheckBoxDropDown = Roact.Component:extend("CheckBoxDropDown")
|
||||
|
||||
function CheckBoxDropDown:render()
|
||||
local children = self.props[Roact.Children] or {}
|
||||
local absolutePosition = self.props.absolutePosition
|
||||
local frameWidth = self.props.frameWidth
|
||||
local elementHeight = self.props.elementHeight
|
||||
local numElements = self.props.numElements
|
||||
local dropdownTargetGui = self.props.dropdownTargetGui
|
||||
|
||||
local onCloseCheckBox = self.props.onCloseCheckBox
|
||||
|
||||
local frameHeight = elementHeight * numElements
|
||||
local outerFrameSize = UDim2.new( 0, frameWidth, 0, (2 * INNER_FRAME_PADDING) + frameHeight)
|
||||
|
||||
return Roact.createElement(Roact.Portal, {
|
||||
-- render the portal into the same ScreenGui as the DevConsole
|
||||
target = dropdownTargetGui ~= nil and dropdownTargetGui or game:GetService("CoreGui").DevConsoleMaster,
|
||||
}, {
|
||||
InputCatcher = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
Position = UDim2.new(0, 0, 0, 0),
|
||||
BackgroundTransparency = 1,
|
||||
|
||||
[Roact.Event.InputEnded] = onCloseCheckBox,
|
||||
}, {
|
||||
OuterFrame = Roact.createElement("ImageButton", {
|
||||
Size = outerFrameSize,
|
||||
AutoButtonColor = false,
|
||||
Position = absolutePosition,
|
||||
BackgroundColor3 = Constants.Color.TextBoxGray,
|
||||
BackgroundTransparency = 0,
|
||||
}, {
|
||||
innerFrame = Roact.createElement("Frame", {
|
||||
Position = UDim2.new(0, INNER_FRAME_PADDING, 0 , INNER_FRAME_PADDING),
|
||||
Size = UDim2.new(0,frameWidth, 0, frameHeight),
|
||||
BackgroundTransparency = 1,
|
||||
}, children)
|
||||
})
|
||||
}),
|
||||
})
|
||||
end
|
||||
|
||||
return CheckBoxDropDown
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
local CheckBoxDropDown = require(script.Parent.CheckBoxDropDown)
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local element = Roact.createElement(CheckBoxDropDown, {
|
||||
elementHeight = 0,
|
||||
numElements = 0,
|
||||
dropdownTargetGui = Instance.new("ScreenGui"),
|
||||
})
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
local Constants = require(script.Parent.Parent.Constants)
|
||||
local FRAME_HEIGHT = Constants.UtilityBarFormatting.FrameHeight
|
||||
local SMALL_FRAME_HEIGHT = Constants.UtilityBarFormatting.SmallFrameHeight
|
||||
local CS_BUTTON_WIDTH = Constants.UtilityBarFormatting.ClientServerButtonWidth
|
||||
local SMALL_CS_BUTTON_WIDTH = Constants.UtilityBarFormatting.ClientServerDropDownWidth
|
||||
local FONT = Constants.Font.UtilBar
|
||||
local FONT_SIZE = Constants.DefaultFontSize.UtilBar
|
||||
local FONT_COLOR = Constants.Color.Text
|
||||
|
||||
local FullScreenDropDownButton = require(script.Parent.FullScreenDropDownButton)
|
||||
local DropDown = require(script.Parent.DropDown)
|
||||
|
||||
local BUTTON_SIZE = UDim2.new(0, CS_BUTTON_WIDTH, 0, FRAME_HEIGHT)
|
||||
local CLIENT_SERVER_NAMES = {"Client", "Server"}
|
||||
|
||||
local ClientServerButton = Roact.Component:extend("ClientServerButton")
|
||||
|
||||
function ClientServerButton:init()
|
||||
self.dropDownCallback = function(index)
|
||||
if index == 1 then
|
||||
self.props.onClientButton()
|
||||
elseif index == 2 then
|
||||
self.props.onServerButton()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ClientServerButton:render()
|
||||
local formFactor = self.props.formFactor
|
||||
local useDropDown = self.props.useDropDown
|
||||
local isClientView = self.props.isClientView
|
||||
local layoutOrder = self.props.layoutOrder
|
||||
local onServerButton = self.props.onServerButton
|
||||
local onClientButton = self.props.onClientButton
|
||||
|
||||
local serverButtonColor = Constants.Color.SelectedBlue
|
||||
local clientButtonColor = Constants.Color.UnselectedGray
|
||||
|
||||
if isClientView then
|
||||
clientButtonColor = Constants.Color.SelectedBlue
|
||||
serverButtonColor = Constants.Color.UnselectedGray
|
||||
end
|
||||
|
||||
if formFactor == Constants.FormFactor.Small then
|
||||
return Roact.createElement(FullScreenDropDownButton, {
|
||||
buttonSize = UDim2.new(0, SMALL_CS_BUTTON_WIDTH, 0, SMALL_FRAME_HEIGHT),
|
||||
dropDownList = CLIENT_SERVER_NAMES,
|
||||
selectedIndex = isClientView and 1 or 2,
|
||||
onSelection = self.dropDownCallback,
|
||||
layoutOrder = layoutOrder,
|
||||
})
|
||||
|
||||
elseif useDropDown then
|
||||
return Roact.createElement(DropDown, {
|
||||
buttonSize = UDim2.new(0, SMALL_CS_BUTTON_WIDTH, 0, SMALL_FRAME_HEIGHT),
|
||||
dropDownList = CLIENT_SERVER_NAMES,
|
||||
selectedIndex = isClientView and 1 or 2,
|
||||
onSelection = self.dropDownCallback,
|
||||
layoutOrder = layoutOrder,
|
||||
})
|
||||
else
|
||||
return Roact.createElement("Frame", {
|
||||
Size = UDim2.new(0, 2 * CS_BUTTON_WIDTH, 0, FRAME_HEIGHT),
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = layoutOrder,
|
||||
}, {
|
||||
ClientButton = Roact.createElement('TextButton', {
|
||||
Text = CLIENT_SERVER_NAMES[1],
|
||||
TextSize = FONT_SIZE,
|
||||
TextColor3 = FONT_COLOR,
|
||||
Font = FONT,
|
||||
Size = BUTTON_SIZE,
|
||||
AutoButtonColor = false,
|
||||
BackgroundColor3 = clientButtonColor,
|
||||
BackgroundTransparency = 0,
|
||||
LayoutOrder = 1,
|
||||
|
||||
[Roact.Event.Activated] = onClientButton,
|
||||
}),
|
||||
ServerButton = Roact.createElement('TextButton', {
|
||||
Text = CLIENT_SERVER_NAMES[2],
|
||||
TextSize = FONT_SIZE,
|
||||
TextColor3 = FONT_COLOR,
|
||||
Font = FONT,
|
||||
Size = BUTTON_SIZE,
|
||||
AutoButtonColor = false,
|
||||
Position = UDim2.new(0, CS_BUTTON_WIDTH, 0, 0),
|
||||
BackgroundColor3 = serverButtonColor,
|
||||
BackgroundTransparency = 0,
|
||||
LayoutOrder = 2,
|
||||
|
||||
[Roact.Event.Activated] = onServerButton,
|
||||
})
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
return ClientServerButton
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
local ClientServerButton = require(script.Parent.ClientServerButton)
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local element = Roact.createElement(ClientServerButton
|
||||
)
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local Immutable = require(script.Parent.Parent.Immutable)
|
||||
|
||||
return function(component, ...)
|
||||
if not component then
|
||||
error("Expected component to be passed to connection, got nil.")
|
||||
end
|
||||
|
||||
local targetList = {}
|
||||
for i = 1, select("#", ...) do
|
||||
targetList[i] = select(i, ...)
|
||||
end
|
||||
|
||||
local name = string.format("Consumer(%s)_DependsOn_%s",tostring(component), targetList[1] )
|
||||
local DataConsumer = Roact.Component:extend(name)
|
||||
|
||||
function DataConsumer:init()
|
||||
local contextTable = {}
|
||||
for _,dataName in pairs(targetList) do
|
||||
local contextualData = self._context.DevConsoleData[dataName]
|
||||
if not contextualData then
|
||||
local errorStr = string.format("%s %s",tostring(dataName),
|
||||
"could not be found. Make sure DataProvider is above this consumer"
|
||||
)
|
||||
error(errorStr)
|
||||
return
|
||||
end
|
||||
contextTable[dataName] = contextualData
|
||||
end
|
||||
|
||||
self.state = contextTable
|
||||
end
|
||||
|
||||
function DataConsumer:render()
|
||||
local props = Immutable.JoinDictionaries(self.props, self.state)
|
||||
return Roact.createElement(component, props)
|
||||
end
|
||||
|
||||
return DataConsumer
|
||||
end
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local RoactRodux = require(CorePackages.RoactRodux)
|
||||
|
||||
|
||||
local Components = script.Parent.Parent.Components
|
||||
local LogData = require(Components.Log.LogData)
|
||||
local ClientMemoryData = require(Components.Memory.ClientMemoryData)
|
||||
local ServerMemoryData = require(Components.Memory.ServerMemoryData)
|
||||
local NetworkData = require(Components.Network.NetworkData)
|
||||
local ServerScriptsData = require(Components.Scripts.ServerScriptsData)
|
||||
local DataStoresData = require(Components.DataStores.DataStoresData)
|
||||
local ServerStatsData = require(Components.ServerStats.ServerStatsData)
|
||||
local ActionBindingsData = require(Components.ActionBindings.ActionBindingsData)
|
||||
local ServerJobsData = require(Components.ServerJobs.ServerJobsData)
|
||||
|
||||
local FFlagAdminServerLogs = settings():GetFFlag("AdminServerLogs")
|
||||
|
||||
local DataProvider = Roact.Component:extend("DataProvider")
|
||||
|
||||
function DataProvider:init()
|
||||
self._context.DevConsoleData = {
|
||||
ClientLogData = LogData.new(true),
|
||||
ServerLogData = LogData.new(false),
|
||||
ClientMemoryData = ClientMemoryData.new(),
|
||||
ServerMemoryData = ServerMemoryData.new(),
|
||||
ClientNetworkData = NetworkData.new(true),
|
||||
ServerNetworkData = NetworkData.new(false),
|
||||
ServerScriptsData = ServerScriptsData.new(),
|
||||
DataStoresData = DataStoresData.new(),
|
||||
ServerStatsData = ServerStatsData.new(),
|
||||
ActionBindingsData = ActionBindingsData.new(),
|
||||
ServerJobsData = ServerJobsData.new(),
|
||||
}
|
||||
end
|
||||
|
||||
function DataProvider:didMount()
|
||||
if self.props.isDeveloperView and not FFlagAdminServerLogs then
|
||||
for _, dataProvider in pairs(self._context.DevConsoleData) do
|
||||
dataProvider:start()
|
||||
end
|
||||
else
|
||||
self._context.DevConsoleData.ClientLogData:start()
|
||||
self._context.DevConsoleData.ClientMemoryData:start()
|
||||
end
|
||||
end
|
||||
|
||||
function DataProvider:willUpdate(nextProps, nextState)
|
||||
if FFlagAdminServerLogs then
|
||||
if nextProps.isDeveloperView and
|
||||
not self.props.isDeveloperView then
|
||||
for _, dataProvider in pairs(self._context.DevConsoleData) do
|
||||
if not dataProvider:isRunning() then
|
||||
dataProvider:start()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function DataProvider:render()
|
||||
return Roact.oneChild(self.props[Roact.Children])
|
||||
end
|
||||
|
||||
local function mapStateToProps(state, props)
|
||||
return {
|
||||
isDeveloperView = state.MainView.isDeveloperView,
|
||||
}
|
||||
end
|
||||
|
||||
return RoactRodux.UNSTABLE_connect2(mapStateToProps, nil)(DataProvider)
|
||||
+239
@@ -0,0 +1,239 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
local Components = script.Parent.Parent.Parent.Components
|
||||
local DataConsumer = require(Components.DataConsumer)
|
||||
local CellLabel = require(Components.CellLabel)
|
||||
local BannerButton = require(Components.BannerButton)
|
||||
local LineGraph = require(Components.LineGraph)
|
||||
|
||||
local Constants = require(script.Parent.Parent.Parent.Constants)
|
||||
local LINE_WIDTH = Constants.GeneralFormatting.LineWidth
|
||||
local LINE_COLOR = Constants.GeneralFormatting.LineColor
|
||||
local HEADER_NAMES = Constants.DataStoresFormatting.ChartHeaderNames
|
||||
local VALUE_CELL_WIDTH = Constants.DataStoresFormatting.ValueCellWidth
|
||||
local CELL_PADDING = Constants.DataStoresFormatting.CellPadding
|
||||
local ARROW_PADDING = Constants.DataStoresFormatting.ExpandArrowPadding
|
||||
local HEADER_HEIGHT = Constants.DataStoresFormatting.HeaderFrameHeight
|
||||
local ENTRY_HEIGHT = Constants.DataStoresFormatting.EntryFrameHeight
|
||||
|
||||
local GRAPH_HEIGHT = Constants.GeneralFormatting.LineGraphHeight
|
||||
|
||||
local NO_DATA_MSG = "Initialize DataStoresService to view DataStore Budget."
|
||||
local NO_RESULT_SEARCH_STR = Constants.GeneralFormatting.NoResultSearchStr
|
||||
|
||||
local convertTimeStamp = require(script.Parent.Parent.Parent.Util.convertTimeStamp)
|
||||
|
||||
local DataStoresChart = Roact.Component:extend("DataStoresChart")
|
||||
|
||||
local function getX(entry)
|
||||
return entry.time
|
||||
end
|
||||
|
||||
local function getY(entry)
|
||||
return entry.value
|
||||
end
|
||||
|
||||
local function stringFormatY(value)
|
||||
return math.ceil(value)
|
||||
end
|
||||
|
||||
function DataStoresChart:init(props)
|
||||
local currStoresData, currStoresDataCount = props.DataStoresData:getCurrentData()
|
||||
|
||||
self.getOnButtonPress = function (name)
|
||||
return function(rbx, input)
|
||||
self:setState({
|
||||
expandedEntry = self.state.expandedEntry ~= name and name
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
self.state = {
|
||||
dataStoresData = currStoresData,
|
||||
dataStoresDataCount = currStoresDataCount,
|
||||
expandedEntry = nil
|
||||
}
|
||||
end
|
||||
|
||||
function DataStoresChart:didMount()
|
||||
self.statsConnector = self.props.DataStoresData:Signal():Connect(function(data, count)
|
||||
self:setState({
|
||||
dataStoresData = data,
|
||||
dataStoresDataCount = count,
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
function DataStoresChart:willUnmount()
|
||||
self.statsConnector:Disconnect()
|
||||
self.statsConnector = nil
|
||||
end
|
||||
|
||||
function DataStoresChart:render()
|
||||
local elements = {}
|
||||
local searchTerm = self.props.searchTerm
|
||||
local size = self.props.size
|
||||
local layoutOrder = self.props.layoutOrder
|
||||
|
||||
local expandedEntry = self.state.expandedEntry
|
||||
|
||||
elements["UIListLayout"] = Roact.createElement("UIListLayout", {
|
||||
FillDirection = Enum.FillDirection.Vertical,
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Left,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Top,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
})
|
||||
|
||||
local componentHeight = HEADER_HEIGHT
|
||||
|
||||
local datastoreBudget = self.state.dataStoresData
|
||||
local totalCount = 0
|
||||
local currLayoutOrder = 1
|
||||
if datastoreBudget then
|
||||
for name, data in pairs(datastoreBudget) do
|
||||
totalCount = totalCount + 1
|
||||
if not searchTerm or string.find(name:lower(), searchTerm:lower()) ~= nil then
|
||||
currLayoutOrder = currLayoutOrder + 1
|
||||
|
||||
local showGraph = expandedEntry == name
|
||||
local frameHeight = showGraph and ENTRY_HEIGHT + GRAPH_HEIGHT or ENTRY_HEIGHT
|
||||
|
||||
elements[name] = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, frameHeight),
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = currLayoutOrder,
|
||||
}, {
|
||||
|
||||
DataButton = Roact.createElement(BannerButton, {
|
||||
size = UDim2.new(1, 0, 0, ENTRY_HEIGHT),
|
||||
pos = UDim2.new(),
|
||||
isExpanded = showGraph,
|
||||
|
||||
onButtonPress = self.getOnButtonPress(name),
|
||||
}, {
|
||||
[name] = Roact.createElement(CellLabel, {
|
||||
text = name,
|
||||
size = UDim2.new(1 - VALUE_CELL_WIDTH, -CELL_PADDING - ARROW_PADDING, 1, 0),
|
||||
pos = UDim2.new(0, CELL_PADDING + ARROW_PADDING, 0, 0),
|
||||
}),
|
||||
|
||||
Data = Roact.createElement(CellLabel, {
|
||||
text = data.dataSet:back().value,
|
||||
size = UDim2.new(VALUE_CELL_WIDTH , -CELL_PADDING, 1, 0),
|
||||
pos = UDim2.new(1 - VALUE_CELL_WIDTH, CELL_PADDING, 0, 0),
|
||||
}),
|
||||
|
||||
VerticalLine = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(0, LINE_WIDTH, 0, ENTRY_HEIGHT),
|
||||
Position = UDim2.new(1 - VALUE_CELL_WIDTH, 0, 0, 0),
|
||||
BackgroundColor3 = LINE_COLOR,
|
||||
BorderSizePixel = 0,
|
||||
}),
|
||||
|
||||
lowerHorizontalLine = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, LINE_WIDTH),
|
||||
Position = UDim2.new(0, 0, 1, 0),
|
||||
BackgroundColor3 = LINE_COLOR,
|
||||
BorderSizePixel = 0,
|
||||
}),
|
||||
}),
|
||||
|
||||
Graph = showGraph and Roact.createElement(LineGraph, {
|
||||
pos = UDim2.new(0, 0, 0, ENTRY_HEIGHT),
|
||||
size = UDim2.new(1, 0, 1, -ENTRY_HEIGHT),
|
||||
graphData = data.dataSet,
|
||||
maxY = data.max,
|
||||
minY = data.min,
|
||||
|
||||
getX = getX,
|
||||
getY = getY,
|
||||
|
||||
axisLabelX = "Timestamp",
|
||||
axisLabelY = name,
|
||||
|
||||
stringFormatX = convertTimeStamp,
|
||||
stringFormatY = stringFormatY,
|
||||
|
||||
}),
|
||||
})
|
||||
componentHeight = componentHeight + ENTRY_HEIGHT
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if currLayoutOrder == 1 then
|
||||
if totalCount == 0 then
|
||||
return Roact.createElement("TextLabel", {
|
||||
Size = size,
|
||||
Text = NO_DATA_MSG,
|
||||
TextColor3 = Constants.Color.Text,
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = layoutOrder,
|
||||
})
|
||||
else
|
||||
local noResultSearchStr = string.format(NO_RESULT_SEARCH_STR, searchTerm)
|
||||
elements["emptyResult"] = Roact.createElement("TextLabel", {
|
||||
Size = size,
|
||||
Text = noResultSearchStr,
|
||||
TextColor3 = Constants.Color.Text,
|
||||
BackgroundTransparency = 1,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
return Roact.createElement("Frame", {
|
||||
Size = size,
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = layoutOrder,
|
||||
}, {
|
||||
Header = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, HEADER_HEIGHT),
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 1,
|
||||
}, {
|
||||
[HEADER_NAMES[1]] = Roact.createElement(CellLabel, {
|
||||
text = HEADER_NAMES[1],
|
||||
size = UDim2.new(1 - VALUE_CELL_WIDTH, -CELL_PADDING - ARROW_PADDING, 1, 0),
|
||||
pos = UDim2.new(0, CELL_PADDING + ARROW_PADDING, 0, 0),
|
||||
}),
|
||||
|
||||
[HEADER_NAMES[2]] = Roact.createElement(CellLabel, {
|
||||
text = HEADER_NAMES[2],
|
||||
size = UDim2.new(VALUE_CELL_WIDTH , -CELL_PADDING, 1, 0),
|
||||
pos = UDim2.new(1 - VALUE_CELL_WIDTH, CELL_PADDING, 0, 0),
|
||||
}),
|
||||
|
||||
upperHorizontalLine = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, LINE_WIDTH),
|
||||
BackgroundColor3 = LINE_COLOR,
|
||||
BorderSizePixel = 0,
|
||||
}),
|
||||
|
||||
vertical = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(0, LINE_WIDTH, 1, 0),
|
||||
Position = UDim2.new(1 - VALUE_CELL_WIDTH, 0, 0, 0),
|
||||
BackgroundColor3 = LINE_COLOR,
|
||||
BorderSizePixel = 0,
|
||||
}),
|
||||
|
||||
lowerHorizontalLine = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, LINE_WIDTH),
|
||||
Position = UDim2.new(0, 0, 1, 0),
|
||||
BackgroundColor3 = LINE_COLOR,
|
||||
BorderSizePixel = 0,
|
||||
}),
|
||||
}),
|
||||
mainFrame = Roact.createElement("ScrollingFrame", {
|
||||
Position = UDim2.new(0, 0, 0, HEADER_HEIGHT),
|
||||
Size = UDim2.new(1, 0, 1, -HEADER_HEIGHT),
|
||||
ScrollBarThickness = 5,
|
||||
|
||||
CanvasSize = UDim2.new(1, 0, 0, componentHeight),
|
||||
|
||||
BackgroundTransparency = 1,
|
||||
}, elements),
|
||||
})
|
||||
end
|
||||
|
||||
return DataConsumer(DataStoresChart, "DataStoresData")
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local RoactRodux = require(CorePackages.RoactRodux)
|
||||
local Store = require(CorePackages.Rodux).Store
|
||||
|
||||
local DataProvider = require(script.Parent.Parent.DataProvider)
|
||||
local DataStoresChart = require(script.Parent.DataStoresChart)
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local element = Roact.createElement(DataProvider, {}, {
|
||||
DataStoresChart = Roact.createElement(DataStoresChart)
|
||||
})
|
||||
|
||||
local store = Store.new(function()
|
||||
return {
|
||||
MainView = {
|
||||
currTabIndex = 0,
|
||||
isDeveloperView = true,
|
||||
},
|
||||
}
|
||||
end)
|
||||
|
||||
local element = Roact.createElement(RoactRodux.StoreProvider, {
|
||||
store = store,
|
||||
}, {
|
||||
DataProvider = Roact.createElement(DataProvider, {}, {
|
||||
DataStoresChart = Roact.createElement(DataStoresChart)
|
||||
})
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
local CircularBuffer = require(script.Parent.Parent.Parent.CircularBuffer)
|
||||
local Signal = require(script.Parent.Parent.Parent.Signal)
|
||||
|
||||
local MAX_DATASET_COUNT = tonumber(settings():GetFVariable("NewDevConsoleMaxGraphCount"))
|
||||
|
||||
local getClientReplicator = require(script.Parent.Parent.Parent.Util.getClientReplicator)
|
||||
|
||||
local DataStoresData = {}
|
||||
DataStoresData.__index = DataStoresData
|
||||
|
||||
function DataStoresData.new()
|
||||
local self = {}
|
||||
setmetatable(self, DataStoresData)
|
||||
|
||||
self._dataStoresUpdated = Signal.new()
|
||||
self._dataStoresData = {}
|
||||
self._dataStoresDataCount = 0
|
||||
self._lastUpdateTime = 0
|
||||
self._isRunning = false
|
||||
return self
|
||||
end
|
||||
|
||||
function DataStoresData:Signal()
|
||||
return self._dataStoresUpdated
|
||||
end
|
||||
|
||||
function DataStoresData:getCurrentData()
|
||||
return self._dataStoresData, self._dataStoresDataCount
|
||||
end
|
||||
|
||||
function DataStoresData:updateValue(key, value)
|
||||
if not self._dataStoresData[key] then
|
||||
local newBuffer = CircularBuffer.new(MAX_DATASET_COUNT)
|
||||
newBuffer:push_back({
|
||||
value = value,
|
||||
time = self._lastUpdateTime
|
||||
})
|
||||
|
||||
self._dataStoresData[key] = {
|
||||
max = value,
|
||||
min = value,
|
||||
dataSet = newBuffer,
|
||||
}
|
||||
else
|
||||
local dataEntry = self._dataStoresData[key]
|
||||
local currMax = dataEntry.max
|
||||
local currMin = dataEntry.min
|
||||
|
||||
local update = {
|
||||
value = value,
|
||||
time = self._lastUpdateTime
|
||||
}
|
||||
local overwrittenEntry = self._dataStoresData[key].dataSet:push_back(update)
|
||||
|
||||
if overwrittenEntry then
|
||||
local iter = self._dataStoresData[key].dataSet:iterator()
|
||||
local dat = iter:next()
|
||||
if currMax == overwrittenEntry.value then
|
||||
currMax = currMin
|
||||
while dat do
|
||||
currMax = dat.value < currMax and currMax or dat.value
|
||||
dat = iter:next()
|
||||
end
|
||||
end
|
||||
if currMin == overwrittenEntry.value then
|
||||
currMin = currMax
|
||||
while dat do
|
||||
currMin = currMin < dat.value and currMin or dat.value
|
||||
dat = iter:next()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self._dataStoresData[key].max = currMax < value and value or currMax
|
||||
self._dataStoresData[key].min = currMin < value and currMin or value
|
||||
end
|
||||
end
|
||||
|
||||
function DataStoresData:isRunning()
|
||||
return self._isRunning
|
||||
end
|
||||
|
||||
function DataStoresData:start()
|
||||
local clientReplicator = getClientReplicator()
|
||||
if clientReplicator and not self._statsListenerConnection then
|
||||
self._statsListenerConnection = clientReplicator.StatsReceived:connect(function(stats)
|
||||
local dataStoreBudget = stats.DataStoreBudget
|
||||
self._lastUpdateTime = os.time()
|
||||
if dataStoreBudget then
|
||||
local count = 0
|
||||
for k, v in pairs(dataStoreBudget) do
|
||||
if type(v) == 'number' then
|
||||
self:updateValue(k,v)
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
self._dataStoresDataCount = count
|
||||
self._dataStoresUpdated:Fire(self._dataStoresData, self._dataStoresDataCount)
|
||||
end
|
||||
end)
|
||||
clientReplicator:RequestServerStats(true)
|
||||
self._isRunning = true
|
||||
end
|
||||
end
|
||||
|
||||
function DataStoresData:stop()
|
||||
-- listeners are responsible for disconnecting themselves
|
||||
if self._statsListenerConnection then
|
||||
self._statsListenerConnection:Disconnect()
|
||||
self._statsListenerConnection = nil
|
||||
end
|
||||
self._isRunning = false
|
||||
end
|
||||
|
||||
return DataStoresData
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local RoactRodux = require(CorePackages.RoactRodux)
|
||||
|
||||
local Components = script.Parent.Parent.Parent.Components
|
||||
local DataStoresChart = require(Components.DataStores.DataStoresChart)
|
||||
local UtilAndTab = require(Components.UtilAndTab)
|
||||
|
||||
local Actions = script.Parent.Parent.Parent.Actions
|
||||
local DataStoresUpdateSearchFilter = require(Actions.DataStoresUpdateSearchFilter)
|
||||
|
||||
local Constants = require(script.Parent.Parent.Parent.Constants)
|
||||
local PADDING = Constants.GeneralFormatting.MainRowPadding
|
||||
|
||||
local MainViewDataStores = Roact.PureComponent:extend("MainViewDataStores")
|
||||
|
||||
function MainViewDataStores:init()
|
||||
self.onUtilTabHeightChanged = function(utilTabHeight)
|
||||
self:setState({
|
||||
utilTabHeight = utilTabHeight
|
||||
})
|
||||
end
|
||||
|
||||
self.onSearchTermChanged = function(newSearchTerm)
|
||||
self.props.dispatchDataStoresUpdateSearchFilter(newSearchTerm, {})
|
||||
end
|
||||
|
||||
self.utilRef = Roact.createRef()
|
||||
|
||||
self.state = {
|
||||
utilTabHeight = 0
|
||||
}
|
||||
end
|
||||
|
||||
function MainViewDataStores:didMount()
|
||||
local utilSize = self.utilRef.current.Size
|
||||
self:setState({
|
||||
utilTabHeight = utilSize.Y.Offset
|
||||
})
|
||||
end
|
||||
|
||||
function MainViewDataStores:didUpdate()
|
||||
local utilSize = self.utilRef.current.Size
|
||||
if utilSize.Y.Offset ~= self.state.utilTabHeight then
|
||||
self:setState({
|
||||
utilTabHeight = utilSize.Y.Offset
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function MainViewDataStores:render()
|
||||
local size = self.props.size
|
||||
local formFactor = self.props.formFactor
|
||||
local tabList = self.props.tabList
|
||||
local searchTerm = self.props.storesSearchTerm
|
||||
|
||||
local utilTabHeight = self.state.utilTabHeight
|
||||
|
||||
return Roact.createElement("Frame", {
|
||||
Size = size,
|
||||
BackgroundColor3 = Constants.Color.BaseGray,
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 3,
|
||||
}, {
|
||||
UIListLayout = Roact.createElement("UIListLayout", {
|
||||
Padding = UDim.new(0, PADDING),
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
}),
|
||||
|
||||
UtilAndTab = Roact.createElement(UtilAndTab, {
|
||||
windowWidth = size.X.Offset,
|
||||
formFactor = formFactor,
|
||||
tabList = tabList,
|
||||
searchTerm = searchTerm,
|
||||
layoutOrder = 1,
|
||||
|
||||
refForParent = self.utilRef,
|
||||
|
||||
onHeightChanged = self.onUtilTabHeightChanged,
|
||||
onSearchTermChanged = self.onSearchTermChanged,
|
||||
}),
|
||||
|
||||
DataStores = utilTabHeight > 0 and Roact.createElement(DataStoresChart, {
|
||||
size = UDim2.new(1, 0, 1, -utilTabHeight),
|
||||
searchTerm = searchTerm,
|
||||
layoutOrder = 2,
|
||||
})
|
||||
})
|
||||
end
|
||||
|
||||
local function mapStateToProps(state, props)
|
||||
return {
|
||||
storesSearchTerm = state.DataStoresData.storesSearchTerm,
|
||||
}
|
||||
end
|
||||
|
||||
local function mapDispatchToProps(dispatch)
|
||||
return {
|
||||
dispatchDataStoresUpdateSearchFilter = function(searchTerm, filters)
|
||||
dispatch(DataStoresUpdateSearchFilter(searchTerm, filters))
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
return RoactRodux.UNSTABLE_connect2(mapStateToProps, mapDispatchToProps)(MainViewDataStores)
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local RoactRodux = require(CorePackages.RoactRodux)
|
||||
local Store = require(CorePackages.Rodux).Store
|
||||
|
||||
local DataProvider = require(script.Parent.Parent.DataProvider)
|
||||
local MainViewDataStores = require(script.Parent.MainViewDataStores)
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local store = Store.new(function()
|
||||
return {
|
||||
MainView = {
|
||||
currTabIndex = 0,
|
||||
isDeveloperView = true,
|
||||
},
|
||||
DataStoresData = {
|
||||
storesSearchTerm = ""
|
||||
}
|
||||
}
|
||||
end)
|
||||
|
||||
local element = Roact.createElement(RoactRodux.StoreProvider, {
|
||||
store = store,
|
||||
}, {
|
||||
DataProvider = Roact.createElement(DataProvider, {}, {
|
||||
MainViewDataStores = Roact.createElement(MainViewDataStores, {
|
||||
size = UDim2.new(),
|
||||
tabList = {},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+203
@@ -0,0 +1,203 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local RobloxGui = game:GetService("CoreGui").RobloxGui
|
||||
local TextService = game:GetService("TextService")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local RoactRodux = require(CorePackages.RoactRodux)
|
||||
|
||||
local Constants = require(script.Parent.Parent.Constants)
|
||||
local FRAME_HEIGHT = Constants.TopBarFormatting.FrameHeight
|
||||
local ICON_SIZE = .5 * FRAME_HEIGHT
|
||||
local ICON_PADDING = (FRAME_HEIGHT - ICON_SIZE) / 2
|
||||
|
||||
local UserInputService = game:GetService("UserInputService")
|
||||
|
||||
local DEVCONSOLE_TEXT = "Developer Console"
|
||||
local DEVCONSOLE_TEXT_X_PADDING = 4
|
||||
local DEVCONSOLE_TEXT_FRAMESIZE = TextService:GetTextSize(DEVCONSOLE_TEXT, Constants.DefaultFontSize.TopBar,
|
||||
Constants.Font.TopBar, Vector2.new(0, 0))
|
||||
|
||||
local LiveUpdateElement = require(script.Parent.Parent.Components.LiveUpdateElement)
|
||||
local SetDevConsolePosition = require(script.Parent.Parent.Actions.SetDevConsolePosition)
|
||||
|
||||
local DevConsoleTopBar = Roact.Component:extend("DevConsoleTopBar")
|
||||
|
||||
local FFlagDevConsoleIsVeryStickyWhyWillItNotLetGo = settings():GetFFlag("DevConsoleIsVeryStickyWhyWillItNotLetGo")
|
||||
|
||||
function DevConsoleTopBar:init()
|
||||
self.inputBegan = function(rbx,input)
|
||||
if self.props.isMinimized then
|
||||
return
|
||||
end
|
||||
|
||||
if input.UserInputType == Enum.UserInputType.MouseButton1 then
|
||||
local absPos = self.ref.current.AbsolutePosition
|
||||
local startPos = Vector3.new(absPos.X, absPos.Y, 0)
|
||||
self:setState({
|
||||
startPos = startPos,
|
||||
startOffset = input.Position,
|
||||
moving = true,
|
||||
})
|
||||
|
||||
if FFlagDevConsoleIsVeryStickyWhyWillItNotLetGo then
|
||||
local inputEndedConn
|
||||
local function onInputEnded(input)
|
||||
if input.UserInputType == Enum.UserInputType.MouseButton1 then
|
||||
self.inputEnded(nil, input)
|
||||
inputEndedConn:Disconnect()
|
||||
end
|
||||
end
|
||||
inputEndedConn = UserInputService.InputEnded:Connect(onInputEnded)
|
||||
end
|
||||
end
|
||||
end
|
||||
self.inputChanged = function(rbx,input)
|
||||
if self.state.moving then
|
||||
local offset = self.state.startPos - self.state.startOffset
|
||||
offset = offset + input.Position
|
||||
local position = UDim2.new(0, offset.X, 0, offset.Y)
|
||||
self.props.dispatchSetDevConsolePosition(position)
|
||||
end
|
||||
end
|
||||
self.inputEnded = function(rbx,input)
|
||||
if input.UserInputType == Enum.UserInputType.MouseButton1 then
|
||||
self:setState({
|
||||
moving = false,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
self.ref = Roact.createRef()
|
||||
end
|
||||
|
||||
function DevConsoleTopBar:render()
|
||||
local isMinimized = self.props.isMinimized
|
||||
local formFactor = self.props.formFactor
|
||||
|
||||
local onMinimizeClicked = self.props.onMinimizeClicked
|
||||
local onMaximizeClicked = self.props.onMaximizeClicked
|
||||
local onCloseClicked = self.props.onCloseClicked
|
||||
|
||||
local moving = self.state.moving
|
||||
|
||||
local elements = {}
|
||||
|
||||
|
||||
elements["WindowTitle"] = Roact.createElement("TextLabel", {
|
||||
Text = DEVCONSOLE_TEXT,
|
||||
TextSize = Constants.DefaultFontSize.TopBar,
|
||||
TextColor3 = Color3.new(1, 1, 1),
|
||||
Font = Constants.Font.TopBar,
|
||||
Size = UDim2.new(0, DEVCONSOLE_TEXT_FRAMESIZE.X, 0, FRAME_HEIGHT),
|
||||
Position = UDim2.new(0, DEVCONSOLE_TEXT_X_PADDING, 0, 0),
|
||||
BackgroundColor3 = Constants.Color.BaseGray,
|
||||
BackgroundTransparency = 1,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
})
|
||||
|
||||
-- padding multiplied by two for the padding on the left and the right
|
||||
local devConsoleTextSizeAndPadding = DEVCONSOLE_TEXT_FRAMESIZE.X + (DEVCONSOLE_TEXT_X_PADDING * 2)
|
||||
local liveStatsModulePos = UDim2.new(0, devConsoleTextSizeAndPadding, 0, 0)
|
||||
local liveStatsModuleSize = UDim2.new(1, -2 * devConsoleTextSizeAndPadding, 0, FRAME_HEIGHT)
|
||||
|
||||
if isMinimized then
|
||||
liveStatsModulePos = UDim2.new(0, 0, 1, 0)
|
||||
liveStatsModuleSize = UDim2.new(1, 0, 1, 0)
|
||||
|
||||
elseif self.ref.current then
|
||||
liveStatsModuleSize = UDim2.new(
|
||||
0,
|
||||
self.ref.current.AbsoluteSize.X - (2 * DEVCONSOLE_TEXT_FRAMESIZE.X),
|
||||
0,
|
||||
FRAME_HEIGHT
|
||||
)
|
||||
end
|
||||
|
||||
local topBarLiveUpdate = self.props.topBarLiveUpdate
|
||||
|
||||
elements["LiveStatsModule"] = Roact.createElement(LiveUpdateElement, {
|
||||
topBarLiveUpdate = topBarLiveUpdate,
|
||||
size = liveStatsModuleSize,
|
||||
position = liveStatsModulePos,
|
||||
})
|
||||
|
||||
-- minimize and maximize buttons should only appear on desktop
|
||||
if formFactor == Constants.FormFactor.Large then
|
||||
if not isMinimized then
|
||||
elements["MinButton"] = Roact.createElement("ImageButton", {
|
||||
Size = UDim2.new(0, ICON_SIZE, 0, ICON_SIZE),
|
||||
Position = UDim2.new(1, -2 * FRAME_HEIGHT + ICON_PADDING, 0, ICON_PADDING),
|
||||
BorderColor3 = Color3.new(1, 0, 0),
|
||||
BackgroundColor3 = Constants.Color.BaseGray,
|
||||
BackgroundTransparency = 1,
|
||||
Image = Constants.Image.Minimize,
|
||||
|
||||
[Roact.Event.Activated] = onMinimizeClicked,
|
||||
})
|
||||
else
|
||||
elements["MaxButton"] = Roact.createElement("ImageButton", {
|
||||
Size = UDim2.new(0, ICON_SIZE, 0, ICON_SIZE),
|
||||
Position = UDim2.new(1, -2 * FRAME_HEIGHT + ICON_PADDING, 0, ICON_PADDING),
|
||||
BorderColor3 = Color3.new(0, 0, 1),
|
||||
BackgroundColor3 = Constants.Color.BaseGray,
|
||||
BackgroundTransparency = 1,
|
||||
Image = Constants.Image.Maximize,
|
||||
|
||||
[Roact.Event.Activated] = onMaximizeClicked,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
elements["CloseButton"] = Roact.createElement("ImageButton", {
|
||||
Size = UDim2.new(0, ICON_SIZE, 0, ICON_SIZE),
|
||||
Position = UDim2.new(1, -FRAME_HEIGHT + ICON_PADDING, 0, ICON_PADDING),
|
||||
BorderColor3 = Color3.new(0, 1, 0),
|
||||
BackgroundColor3 = Constants.Color.BaseGray,
|
||||
BackgroundTransparency = 1,
|
||||
Image = Constants.Image.Close,
|
||||
[Roact.Event.Activated] = onCloseClicked,
|
||||
})
|
||||
|
||||
--[[ we do this to catch all inputchanged events
|
||||
if we can handle LARGE distances of continuous MouseMovmement input events
|
||||
for dragging then we might be able to remove the portal
|
||||
]]--
|
||||
elements["MovmentCatchAll"] = moving and Roact.createElement(Roact.Portal, {
|
||||
target = RobloxGui,
|
||||
}, {
|
||||
InputCatcher = Roact.createElement("ScreenGui", {
|
||||
OnTopOfCoreBlur = true,
|
||||
}, {
|
||||
GreyOutFrame = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
BackgroundColor3 = Constants.Color.Black,
|
||||
BackgroundTransparency = .99,
|
||||
Active = true,
|
||||
|
||||
[Roact.Event.InputChanged] = self.inputChanged,
|
||||
[Roact.Event.InputEnded] = self.inputEnded,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
return Roact.createElement("ImageButton", {
|
||||
Size = UDim2.new(1, 0, 0, FRAME_HEIGHT),
|
||||
BackgroundColor3 = Constants.Color.Black,
|
||||
BackgroundTransparency = .5,
|
||||
AutoButtonColor = false,
|
||||
LayoutOrder = 1,
|
||||
|
||||
[Roact.Ref] = self.ref,
|
||||
|
||||
[Roact.Event.InputBegan] = self.inputBegan,
|
||||
}, elements)
|
||||
end
|
||||
|
||||
local function mapDispatchToProps(dispatch)
|
||||
return {
|
||||
dispatchSetDevConsolePosition = function (size)
|
||||
dispatch(SetDevConsolePosition(size))
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
return RoactRodux.UNSTABLE_connect2(nil, mapDispatchToProps)(DevConsoleTopBar)
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local RoactRodux = require(CorePackages.RoactRodux)
|
||||
local Store = require(CorePackages.Rodux).Store
|
||||
|
||||
local DataProvider = require(script.Parent.DataProvider)
|
||||
local DevConsoleTopBar = require(script.Parent.DevConsoleTopBar)
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local store = Store.new(function()
|
||||
return {
|
||||
MainView = {
|
||||
currTabIndex = 0,
|
||||
isDeveloperView = true,
|
||||
},
|
||||
|
||||
TopBarLiveUpdate = {
|
||||
LogWarningCount = 0,
|
||||
LogErrorCount = 0
|
||||
}
|
||||
}
|
||||
end)
|
||||
|
||||
local element = Roact.createElement(RoactRodux.StoreProvider, {
|
||||
store = store,
|
||||
}, {
|
||||
DataProvider = Roact.createElement(DataProvider, nil, {
|
||||
DevConsoleTopBar = Roact.createElement(DevConsoleTopBar)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+295
@@ -0,0 +1,295 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local CoreGui = game:GetService("CoreGui").RobloxGui
|
||||
local GuiService = game:GetService("GuiService")
|
||||
local UserInputService = game:GetService("UserInputService")
|
||||
|
||||
local setMouseVisibility = require(script.Parent.Parent.Util.setMouseVisibility)
|
||||
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local RoactRodux = require(CorePackages.RoactRodux)
|
||||
local DevConsole = script.Parent.Parent
|
||||
|
||||
local Constants = require(DevConsole.Constants)
|
||||
local TOPBAR_HEIGHT = Constants.TopBarFormatting.FrameHeight
|
||||
local ROW_PADDING = Constants.Padding.WindowPadding
|
||||
local MIN_SIZE = Constants.MainWindowInit.MinSize
|
||||
|
||||
local Components = DevConsole.Components
|
||||
local DevConsoleTopBar = require(Components.DevConsoleTopBar)
|
||||
|
||||
local Actions = script.Parent.Parent.Actions
|
||||
local ChangeDevConsoleSize = require(Actions.ChangeDevConsoleSize)
|
||||
local SetDevConsoleVisibility = require(Actions.SetDevConsoleVisibility)
|
||||
local SetDevConsoleMinimized = require(Actions.SetDevConsoleMinimized)
|
||||
|
||||
local BORDER_SIZE = 16
|
||||
|
||||
local DevConsoleWindow = Roact.PureComponent:extend("DevConsoleWindow")
|
||||
|
||||
function DevConsoleWindow:onMinimizeClicked()
|
||||
self.props.dispatchSetDevConsoleMinimized(true)
|
||||
end
|
||||
|
||||
function DevConsoleWindow:onMaximizeClicked()
|
||||
self.props.dispatchSetDevConsoleMinimized(false)
|
||||
end
|
||||
|
||||
function DevConsoleWindow:onCloseClicked()
|
||||
self.props.dispatchSetDevConsolVisibility(false)
|
||||
|
||||
end
|
||||
|
||||
function DevConsoleWindow:init()
|
||||
self.setDevConsoleSize = function (self, topLeft, bottomRight)
|
||||
local x = bottomRight.X - topLeft.X
|
||||
local y = bottomRight.Y - topLeft.Y
|
||||
|
||||
x = x < MIN_SIZE.X and MIN_SIZE.X or x
|
||||
y = y < MIN_SIZE.Y and MIN_SIZE.Y or y
|
||||
|
||||
self.props.dispatchChangeDevConsoleSize(UDim2.new(0, x, 0, y))
|
||||
end
|
||||
|
||||
self.resizeInputBegan = function(rbx, input)
|
||||
if input.UserInputType == Enum.UserInputType.MouseButton1 then
|
||||
local inputChangedConn, inputEndedConn
|
||||
|
||||
local function onInputChanged(input)
|
||||
local currPosition = self.ref.current.AbsolutePosition
|
||||
local cornerPos = input.Position
|
||||
self:setDevConsoleSize(currPosition, cornerPos)
|
||||
end
|
||||
|
||||
local function onInputEnded(input)
|
||||
if input.UserInputType == Enum.UserInputType.MouseButton1 then
|
||||
inputChangedConn:Disconnect()
|
||||
inputEndedConn:Disconnect()
|
||||
end
|
||||
end
|
||||
|
||||
inputChangedConn = UserInputService.InputChanged:Connect(onInputChanged)
|
||||
inputEndedConn = UserInputService.InputEnded:Connect(onInputEnded)
|
||||
end
|
||||
end
|
||||
|
||||
self.doGamepadMenuButton = function(input)
|
||||
if self.props.isVisible then
|
||||
local keyToListenTo = input.KeyCode == Enum.KeyCode.ButtonStart or input.KeyCode == Enum.KeyCode.Escape
|
||||
if keyToListenTo then
|
||||
self.props.dispatchSetDevConsolVisibility(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.ref = Roact.createRef()
|
||||
|
||||
self.state = {
|
||||
resizing = false,
|
||||
}
|
||||
end
|
||||
|
||||
function DevConsoleWindow:didMount()
|
||||
-- we need to run this delay before grabbing the size of the frame
|
||||
-- because the DevconsoleWindow is mounted before the ScreenGui
|
||||
-- is resized to the correct screen size.
|
||||
delay(0, function ()
|
||||
if self.ref.current then
|
||||
local absPos1 = self.ref.current.AbsolutePosition
|
||||
local absPos2 = self.ref.current.ResizeButton.AbsolutePosition
|
||||
self:setDevConsoleSize(absPos1, absPos2)
|
||||
end
|
||||
end)
|
||||
setMouseVisibility(self.props.isVisible)
|
||||
|
||||
self.gamepadMenuListener = UserInputService.InputBegan:Connect(self.doGamepadMenuButton)
|
||||
end
|
||||
|
||||
function DevConsoleWindow:willUnmount()
|
||||
if GuiService.SelectedCoreObject == self.ref.current then
|
||||
GuiService.SelectedCoreObject = nil
|
||||
end
|
||||
|
||||
self.gamepadMenuListener:Disconnect()
|
||||
end
|
||||
|
||||
function DevConsoleWindow:didUpdate(previousProps, previousState)
|
||||
setMouseVisibility(self.props.isVisible)
|
||||
|
||||
if self.props.isMinimized and (GuiService.SelectedCoreObject == self.ref.current) then
|
||||
GuiService.SelectedCoreObject = nil
|
||||
|
||||
elseif self.props.isVisible ~= previousProps.isVisible or
|
||||
self.props.currTabIndex ~= previousProps.currTabIndex then
|
||||
local inputTypeEnum = UserInputService:GetLastInputType()
|
||||
local isGamepad = (Enum.UserInputType.Gamepad1 == inputTypeEnum)
|
||||
|
||||
if isGamepad and self.props.isVisible then
|
||||
GuiService.SelectedCoreObject = self.ref.current
|
||||
else
|
||||
GuiService.SelectedCoreObject = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function DevConsoleWindow:render()
|
||||
local isVisible = self.props.isVisible
|
||||
local formFactor = self.props.formFactor
|
||||
local isDeveloperView = self.props.isDeveloperView
|
||||
local currTabIndex = self.props.currTabIndex
|
||||
local tabList = self.props.tabList
|
||||
|
||||
local isMinimized = self.props.isMinimized
|
||||
local pos = self.props.position
|
||||
local size = self.props.size
|
||||
|
||||
local resizing = self.state.resizing
|
||||
|
||||
local windowSize = size
|
||||
local windowPos = UDim2.new()
|
||||
|
||||
local elements = {}
|
||||
local borderSizePixel = BORDER_SIZE
|
||||
|
||||
|
||||
if formFactor ~= Constants.FormFactor.Large then
|
||||
-- none desktop/Large are full screen devconsoles
|
||||
local absSize = CoreGui.AbsoluteSize
|
||||
size = UDim2.new(0, absSize.X, 0, absSize.Y)
|
||||
pos = UDim2.new(0, 0, 0, 0)
|
||||
|
||||
windowPos = UDim2.new(0, 16, 0, 0)
|
||||
windowSize = size + UDim2.new(0, -32, 0, 0)
|
||||
|
||||
borderSizePixel = 0
|
||||
end
|
||||
|
||||
elements["UIListLayout"] = Roact.createElement("UIListLayout", {
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Left,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Top,
|
||||
Padding = UDim.new(0, ROW_PADDING),
|
||||
})
|
||||
|
||||
if isMinimized then
|
||||
elements["TopBar"] = Roact.createElement(DevConsoleTopBar, {
|
||||
LayoutOrder = 1,
|
||||
formFactor = formFactor,
|
||||
isMinimized = true,
|
||||
onMinimizeClicked = function()
|
||||
self:onMinimizeClicked()
|
||||
end,
|
||||
onMaximizeClicked = function()
|
||||
self:onMaximizeClicked()
|
||||
end,
|
||||
onCloseClicked = function()
|
||||
self:onCloseClicked()
|
||||
end,
|
||||
})
|
||||
|
||||
return Roact.createElement("Frame", {
|
||||
Position = UDim2.new(1, -500, 1, -2 * TOPBAR_HEIGHT),
|
||||
Size = UDim2.new(0, 500, 0, 2 * TOPBAR_HEIGHT),
|
||||
BackgroundColor3 = Color3.new(0, 0, 0),
|
||||
Transparency = Constants.MainWindowInit.Transparency,
|
||||
Active = true,
|
||||
AutoLocalize = false,
|
||||
Visible = isVisible,
|
||||
Selectable = true,
|
||||
BorderColor3 = Constants.Color.BaseGray,
|
||||
|
||||
[Roact.Ref] = self.ref,
|
||||
}, elements)
|
||||
else
|
||||
elements["TopBar"] = Roact.createElement(DevConsoleTopBar, {
|
||||
LayoutOrder = 1,
|
||||
formFactor = formFactor,
|
||||
isMinimized = false,
|
||||
onMinimizeClicked = function()
|
||||
self:onMinimizeClicked()
|
||||
end,
|
||||
onMaximizeClicked = function()
|
||||
self:onMaximizeClicked()
|
||||
end,
|
||||
onCloseClicked = function()
|
||||
self:onCloseClicked()
|
||||
end,
|
||||
})
|
||||
|
||||
local mainViewSize = windowSize
|
||||
|
||||
local TopSectionHeight = TOPBAR_HEIGHT + 2 * ROW_PADDING
|
||||
local mainViewSizeOffset = UDim2.new(0, 0, 0, TopSectionHeight)
|
||||
mainViewSize = mainViewSize - mainViewSizeOffset
|
||||
|
||||
|
||||
if self.ref.current and isVisible and tabList then
|
||||
local targetTab = tabList[currTabIndex]
|
||||
if targetTab then
|
||||
elements["MainView"] = Roact.createElement( targetTab.tab, {
|
||||
size = mainViewSize,
|
||||
formFactor = formFactor,
|
||||
isDeveloperView = isDeveloperView,
|
||||
tabList = tabList,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
return Roact.createElement("Frame", {
|
||||
Position = pos,
|
||||
Size = size,
|
||||
Visible = isVisible,
|
||||
BackgroundColor3 = Color3.new(0, 0, 0),
|
||||
Transparency = Constants.MainWindowInit.Transparency,
|
||||
BorderColor3 = Constants.Color.BaseGray,
|
||||
BorderSizePixel = borderSizePixel,
|
||||
Active = true,
|
||||
AutoLocalize = false,
|
||||
Selectable = false,
|
||||
|
||||
[Roact.Ref] = self.ref,
|
||||
}, {
|
||||
DevConsoleUI = Roact.createElement("Frame", {
|
||||
Size = windowSize,
|
||||
Position = windowPos,
|
||||
BackgroundTransparency = 1,
|
||||
},elements),
|
||||
|
||||
ResizeButton = Roact.createElement("ImageButton", {
|
||||
Position = UDim2.new(1, 0, 1, 0),
|
||||
Size = UDim2.new(0, borderSizePixel, 0, borderSizePixel),
|
||||
BackgroundColor3 = Color3.new(0, 0, 0),
|
||||
Modal = true,
|
||||
|
||||
[Roact.Event.InputBegan] = self.resizeInputBegan,
|
||||
}),
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
local function mapStateToProps(state, props)
|
||||
return {
|
||||
isVisible = state.DisplayOptions.isVisible,
|
||||
isMinimized = state.DisplayOptions.isMinimized,
|
||||
position = state.DisplayOptions.position,
|
||||
size = state.DisplayOptions.size,
|
||||
isDeveloperView = state.MainView.isDeveloperView,
|
||||
currTabIndex = state.MainView.currTabIndex,
|
||||
tabList = state.MainView.tabList,
|
||||
}
|
||||
end
|
||||
|
||||
local function mapDispatchToProps(dispatch)
|
||||
return {
|
||||
dispatchChangeDevConsoleSize = function (size)
|
||||
dispatch(ChangeDevConsoleSize(size))
|
||||
end,
|
||||
dispatchSetDevConsolVisibility = function (isVisible)
|
||||
dispatch(SetDevConsoleVisibility(isVisible))
|
||||
end,
|
||||
dispatchSetDevConsoleMinimized = function (isMinimized)
|
||||
dispatch(SetDevConsoleMinimized(isMinimized))
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
return RoactRodux.UNSTABLE_connect2(mapStateToProps, mapDispatchToProps)(DevConsoleWindow)
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local RoactRodux = require(CorePackages.RoactRodux)
|
||||
local Store = require(CorePackages.Rodux).Store
|
||||
|
||||
local DataProvider = require(script.Parent.DataProvider)
|
||||
local DevConsoleWindow = require(script.Parent.DevConsoleWindow)
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local store = Store.new(function()
|
||||
return {
|
||||
DisplayOptions = {
|
||||
isVisible = 0,
|
||||
platform = 0,
|
||||
isMinimized = false,
|
||||
size = UDim2.new(1, 0, 1, 0),
|
||||
},
|
||||
MainView = {
|
||||
currTabIndex = 0,
|
||||
isDeveloperView = true,
|
||||
},
|
||||
TopBarLiveUpdate = {
|
||||
LogWarningCount = 0,
|
||||
LogErrorCount = 0
|
||||
},
|
||||
LogData = {
|
||||
clientData = { },
|
||||
clientDataFiltered = { },
|
||||
clientSearchTerm = "",
|
||||
clientTypeFilters = { },
|
||||
|
||||
serverData = { },
|
||||
serverDataFiltered = { },
|
||||
serverSearchTerm = "",
|
||||
serverTypeFilters = { },
|
||||
}
|
||||
}
|
||||
end)
|
||||
|
||||
local element = Roact.createElement(RoactRodux.StoreProvider, {
|
||||
store = store,
|
||||
}, {
|
||||
DataProvider = Roact.createElement(DataProvider, {}, {
|
||||
DevConsoleWindow = Roact.createElement(DevConsoleWindow)
|
||||
})
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local RobloxGui = game:GetService("CoreGui").RobloxGui
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
local Constants = require(script.Parent.Parent.Constants)
|
||||
local FONT = Constants.Font.UtilBar
|
||||
local FONT_SIZE = Constants.DefaultFontSize.UtilBar
|
||||
local ARROW_SIZE = Constants.GeneralFormatting.DropDownArrowHeight
|
||||
local ARROW_OFFSET = ARROW_SIZE / 2
|
||||
local OPEN_ARROW = Constants.Image.DownArrow
|
||||
local INNER_FRAME_PADDING = 12
|
||||
|
||||
local DropDown = Roact.Component:extend("DropDown")
|
||||
|
||||
function DropDown:init()
|
||||
self.onMainButtonPressed = function(rbx, input)
|
||||
self:setState({
|
||||
showDropDown = true,
|
||||
})
|
||||
end
|
||||
|
||||
self.nonDropDownSelection = function(rbx, input)
|
||||
if input.UserInputType == Enum.UserInputType.MouseButton1 or
|
||||
(input.UserInputType == Enum.UserInputType.Touch and
|
||||
input.UserInputState == Enum.UserInputState.End) then
|
||||
self:setState({
|
||||
showDropDown = false
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
self.state = {
|
||||
showDropDown = false,
|
||||
}
|
||||
|
||||
self.ref = Roact.createRef()
|
||||
end
|
||||
|
||||
function DropDown:render()
|
||||
local buttonSize = self.props.buttonSize
|
||||
local dropDownList = self.props.dropDownList
|
||||
local selectedIndex = self.props.selectedIndex
|
||||
|
||||
local onSelection = self.props.onSelection
|
||||
local layoutOrder = self.props.layoutOrder
|
||||
|
||||
local dropDownTargetParent = self.props.dropDownTargetParent
|
||||
|
||||
local showDropDown = self.ref.current and self.state.showDropDown
|
||||
|
||||
local children = {}
|
||||
local absolutePosition
|
||||
local outerFrameSize
|
||||
local frameHeight = 0
|
||||
local frameWidth = 0
|
||||
|
||||
if self.ref.current and showDropDown then
|
||||
local absolutePos = self.ref.current.AbsolutePosition
|
||||
local absoluteSize = self.ref.current.AbsoluteSize
|
||||
|
||||
frameWidth = absoluteSize.X
|
||||
|
||||
children["UIListLayout"] = Roact.createElement("UIListLayout", {
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Left,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Top,
|
||||
})
|
||||
|
||||
for ind, name in pairs(dropDownList) do
|
||||
local color = (ind == selectedIndex) and Constants.Color.SelectedGray or Constants.Color.UnselectedGray
|
||||
|
||||
children[name] = Roact.createElement("TextButton", {
|
||||
Size = buttonSize,
|
||||
Text = name,
|
||||
TextColor3 = Constants.Color.Text,
|
||||
TextSize = FONT_SIZE,
|
||||
Font = FONT,
|
||||
|
||||
AutoButtonColor = false,
|
||||
BackgroundColor3 = color,
|
||||
BackgroundTransparency = 0,
|
||||
BorderSizePixel = 0,
|
||||
|
||||
LayoutOrder = ind,
|
||||
|
||||
[Roact.Event.Activated] = function()
|
||||
onSelection(ind)
|
||||
self:setState({
|
||||
showDropDown = false
|
||||
})
|
||||
end
|
||||
})
|
||||
frameHeight = frameHeight + absoluteSize.Y
|
||||
end
|
||||
|
||||
local padding = 2 * INNER_FRAME_PADDING
|
||||
outerFrameSize = UDim2.new(0, frameWidth + padding, 0, frameHeight + padding)
|
||||
absolutePosition = UDim2.new(0, absolutePos.X, 0, absolutePos.Y + absoluteSize.Y)
|
||||
end
|
||||
|
||||
|
||||
return Roact.createElement("TextButton", {
|
||||
Size = buttonSize,
|
||||
Text = dropDownList[selectedIndex],
|
||||
TextColor3 = Constants.Color.Text,
|
||||
TextSize = FONT_SIZE,
|
||||
Font = FONT,
|
||||
|
||||
AutoButtonColor = false,
|
||||
BackgroundColor3 = Constants.Color.UnselectedGray,
|
||||
BackgroundTransparency = 0,
|
||||
LayoutOrder = layoutOrder,
|
||||
|
||||
[Roact.Event.Activated] = self.onMainButtonPressed,
|
||||
|
||||
[Roact.Ref] = self.ref,
|
||||
}, {
|
||||
arrow = Roact.createElement("ImageLabel", {
|
||||
Image = OPEN_ARROW,
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.new(0, ARROW_SIZE, 0, ARROW_SIZE),
|
||||
Position = UDim2.new(1, -ARROW_SIZE - ARROW_OFFSET, .5, -ARROW_OFFSET),
|
||||
}),
|
||||
|
||||
DropDown = showDropDown and Roact.createElement(Roact.Portal, {
|
||||
target = dropDownTargetParent ~= nil and dropDownTargetParent or game:GetService("CoreGui").DevConsoleMaster,
|
||||
}, {
|
||||
InputCatcher = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
Position = UDim2.new(0, 0, 0, 0),
|
||||
BackgroundTransparency = 1,
|
||||
|
||||
[Roact.Event.InputEnded] = self.nonDropDownSelection,
|
||||
}, {
|
||||
OuterFrame = Roact.createElement("ImageButton", {
|
||||
Size = outerFrameSize,
|
||||
AutoButtonColor = false,
|
||||
Position = absolutePosition,
|
||||
BackgroundColor3 = Constants.Color.TextBoxGray,
|
||||
BackgroundTransparency = 0,
|
||||
}, {
|
||||
innerFrame = Roact.createElement("Frame", {
|
||||
Position = UDim2.new(0, INNER_FRAME_PADDING, 0 , INNER_FRAME_PADDING),
|
||||
Size = UDim2.new(0, frameWidth, 0, frameHeight),
|
||||
BackgroundTransparency = 1,
|
||||
}, children)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
end
|
||||
|
||||
return DropDown
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
local DropDown = require(script.Parent.DropDown)
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local element = Roact.createElement(DropDown, {
|
||||
dropDownList = {},
|
||||
dropDownTargetParent = Instance.new("ScreenGui"),
|
||||
})
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local RobloxGui = game:GetService("CoreGui").RobloxGui
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
local Constants = require(script.Parent.Parent.Constants)
|
||||
local ENTRY_HEIGHT = Constants.GeneralFormatting.DropDownEntryHeight
|
||||
local ARROW_SIZE = Constants.GeneralFormatting.DropDownArrowHeight
|
||||
local ARROW_OFFSET = ARROW_SIZE / 2
|
||||
local OPEN_ARROW = Constants.Image.DownArrow
|
||||
|
||||
local FULL_SCREEN_WIDTH = 375
|
||||
local INNER_Y_OFFSET = 8
|
||||
local INNER_X_OFFSET = 15
|
||||
|
||||
local FullScreenDropDownButton = Roact.Component:extend("FullScreenDropDownButton")
|
||||
|
||||
function FullScreenDropDownButton:init()
|
||||
self.startDropDownView = function()
|
||||
self:setState({
|
||||
selectionScreenExpanded = true
|
||||
})
|
||||
end
|
||||
|
||||
self.noSelection = function(rbx, input)
|
||||
if input.UserInputType == Enum.UserInputType.MouseButton1 or
|
||||
(input.UserInputType == Enum.UserInputType.Touch and
|
||||
input.UserInputState == Enum.UserInputState.End) then
|
||||
self:setState({
|
||||
selectionScreenExpanded = false
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
self.state = {
|
||||
selectionScreenExpanded = false,
|
||||
}
|
||||
end
|
||||
|
||||
function FullScreenDropDownButton:render()
|
||||
local buttonSize = self.props.buttonSize
|
||||
local dropDownList = self.props.dropDownList
|
||||
local selectedIndex = self.props.selectedIndex
|
||||
local onSelection = self.props.onSelection
|
||||
local layoutOrder = self.props.layoutOrder
|
||||
|
||||
local isSelecting = self.state.selectionScreenExpanded
|
||||
local portalTarget = self.props.portalTarget
|
||||
|
||||
local dropDownItemList = {}
|
||||
local scrollingFrameHeight = 2 * INNER_Y_OFFSET
|
||||
|
||||
if isSelecting then
|
||||
dropDownItemList["UIListLayout"] = Roact.createElement("UIListLayout", {
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Center,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Top,
|
||||
FillDirection = Enum.FillDirection.Vertical,
|
||||
})
|
||||
|
||||
if dropDownList then
|
||||
for ind,name in ipairs(dropDownList) do
|
||||
local color = (ind == selectedIndex) and Constants.Color.SelectedGray or Constants.Color.UnselectedGray
|
||||
|
||||
dropDownItemList[ind] = Roact.createElement("TextButton", {
|
||||
Text = name,
|
||||
Font = Constants.Font.TabBar,
|
||||
TextSize = Constants.DefaultFontSize.DropDownTabBar,
|
||||
TextColor3 = Constants.Color.Text,
|
||||
AutoButtonColor = false,
|
||||
|
||||
Size = UDim2.new(1, 0, 0, ENTRY_HEIGHT),
|
||||
BackgroundColor3 = color,
|
||||
LayoutOrder = ind,
|
||||
BorderSizePixel = 0,
|
||||
|
||||
[Roact.Event.Activated] = function(rbx)
|
||||
self:setState({
|
||||
selectionScreenExpanded = false
|
||||
})
|
||||
onSelection(ind)
|
||||
end,
|
||||
})
|
||||
|
||||
scrollingFrameHeight = scrollingFrameHeight + ENTRY_HEIGHT
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return Roact.createElement("TextButton", {
|
||||
Size = buttonSize,
|
||||
BackgroundColor3 = Constants.Color.UnselectedGray,
|
||||
Text = "",
|
||||
AutoButtonColor = false,
|
||||
LayoutOrder = layoutOrder,
|
||||
|
||||
[Roact.Event.Activated] = self.startDropDownView,
|
||||
}, {
|
||||
text = Roact.createElement("TextLabel", {
|
||||
Size = UDim2.new(1, -ARROW_SIZE - ARROW_OFFSET, 1, 0),
|
||||
Text = dropDownList[selectedIndex],
|
||||
Font = Constants.Font.TabBar,
|
||||
TextSize = Constants.DefaultFontSize.DropDownTabBar,
|
||||
TextXAlignment = Enum.TextXAlignment.Center,
|
||||
TextColor3 = Constants.Color.Text,
|
||||
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
|
||||
arrow = Roact.createElement("ImageLabel", {
|
||||
Image = OPEN_ARROW,
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.new(0, ARROW_SIZE, 0, ARROW_SIZE),
|
||||
Position = UDim2.new(1, -ARROW_SIZE - ARROW_OFFSET, .5, -ARROW_OFFSET),
|
||||
}),
|
||||
|
||||
selectionView = isSelecting and Roact.createElement(Roact.Portal, {
|
||||
target = portalTarget ~= nil and portalTarget or game:GetService("CoreGui").DevConsoleMaster,
|
||||
}, {
|
||||
GreyOutFrame = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
BackgroundColor3 = Constants.Color.Black,
|
||||
BackgroundTransparency = .36,
|
||||
Active = true,
|
||||
|
||||
[Roact.Event.InputEnded] = self.noSelection,
|
||||
}, {
|
||||
BorderFrame = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(0, FULL_SCREEN_WIDTH, 0, scrollingFrameHeight),
|
||||
Position = UDim2.new(.5, -FULL_SCREEN_WIDTH / 2, 0, 0),
|
||||
BackgroundColor3 = Constants.Color.UnselectedGray,
|
||||
BorderSizePixel = 0,
|
||||
}, {
|
||||
SelectionFrame = Roact.createElement("ScrollingFrame", {
|
||||
Size = UDim2.new(1, -2 * INNER_X_OFFSET, 1, -2 * INNER_Y_OFFSET),
|
||||
Position = UDim2.new(0, INNER_X_OFFSET, 0, INNER_Y_OFFSET),
|
||||
BackgroundTransparency = 1,
|
||||
|
||||
-- adding an extra entry's worth of height for easier access to last
|
||||
-- child when trying to select last child
|
||||
CanvasSize = UDim2.new(1, -2 * INNER_X_OFFSET, 1, ENTRY_HEIGHT),
|
||||
BorderSizePixel = 0,
|
||||
|
||||
ScrollBarThickness = 0,
|
||||
}, dropDownItemList)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
end
|
||||
|
||||
return FullScreenDropDownButton
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
local FullScreenDropDownButton = require(script.Parent.FullScreenDropDownButton)
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local element = Roact.createElement(FullScreenDropDownButton, {
|
||||
dropDownList = {},
|
||||
portalTarget = Instance.new("ScreenGui"),
|
||||
})
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
local Constants = require(script.Parent.Parent.Constants)
|
||||
local FONT = Constants.Font.MainWindowHeader
|
||||
local TEXT_SIZE = Constants.DefaultFontSize.MainWindowHeader
|
||||
local TEXT_COLOR = Constants.Color.Text
|
||||
|
||||
local function HeaderButton(props)
|
||||
local text = props.text
|
||||
local size = props.size
|
||||
local pos = props.pos
|
||||
local sortfunction = props.sortfunction
|
||||
|
||||
return Roact.createElement("TextButton", {
|
||||
Text = text,
|
||||
TextSize = TEXT_SIZE,
|
||||
TextColor3 = TEXT_COLOR,
|
||||
Font = FONT,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
|
||||
Size = size,
|
||||
Position = pos,
|
||||
BackgroundTransparency = 1,
|
||||
|
||||
[Roact.Event.Activated] = function()
|
||||
if sortfunction then
|
||||
sortfunction(text)
|
||||
end
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
return HeaderButton
|
||||
+305
@@ -0,0 +1,305 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
local LineGraphHoverDisplay = require(script.Parent.LineGraphHoverDisplay)
|
||||
|
||||
local Constants = require(script.Parent.Parent.Constants)
|
||||
local TEXT_COLOR = Constants.Color.Text
|
||||
local MAIN_LINE_COLOR = Constants.Color.HighlightBlue
|
||||
local LINE_WIDTH = Constants.GeneralFormatting.LineWidth
|
||||
local LINE_COLOR = Constants.GeneralFormatting.LineColor
|
||||
|
||||
local POINT_WIDTH = Constants.Graph.PointWidth
|
||||
local POINT_OFFSET = Constants.Graph.PointOffset
|
||||
local GRAPH_PADDING = Constants.Graph.Padding
|
||||
local GRAPH_SCALE = Constants.Graph.Scale
|
||||
local GRAPH_Y_INNER_PADDING = Constants.Graph.InnerPaddingY
|
||||
local GRAPH_Y_INNER_SCALE = Constants.Graph.InnerScaleY
|
||||
local TEXT_PADDING = Constants.Graph.TextPadding
|
||||
|
||||
local INVIS_LINE_THRESHOLD = 10
|
||||
local LineGraph = Roact.Component:extend("LineGraph")
|
||||
|
||||
function LineGraph:init()
|
||||
self.onGraphInputChanged = function(rbx, input)
|
||||
if input.UserInputType == Enum.UserInputType.MouseMovement then
|
||||
if not self.state.holdPos then
|
||||
self:setState({
|
||||
inputPosition = input.Position,
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.onGraphInputEnded = function(rbx, input)
|
||||
if input.UserInputType == Enum.UserInputType.MouseMovement then
|
||||
if not self.state.holdPos then
|
||||
self:setState({
|
||||
inputPosition = false
|
||||
})
|
||||
end
|
||||
elseif input.UserInputType == Enum.UserInputType.MouseButton1 then
|
||||
self:setState({
|
||||
holdPos = not self.state.holdPos
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
self.graphRef = Roact.createRef()
|
||||
self.state = {
|
||||
selectedTimeStamps = {}
|
||||
}
|
||||
end
|
||||
|
||||
function LineGraph:didUpdate()
|
||||
if self.state.absGraphSize ~= self.graphRef.current.AbsoluteSize then
|
||||
local absSize = self.graphRef.current.AbsoluteSize
|
||||
local absPos = self.graphRef.current.AbsolutePosition
|
||||
|
||||
self:setState({
|
||||
absGraphSize = absSize,
|
||||
absGraphPos = absPos,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function LineGraph:didMount()
|
||||
local absSize = self.graphRef.current.AbsoluteSize
|
||||
local absPos = self.graphRef.current.AbsolutePosition
|
||||
|
||||
self:setState({
|
||||
absGraphSize = absSize,
|
||||
absGraphPos = absPos,
|
||||
})
|
||||
end
|
||||
|
||||
function LineGraph:render()
|
||||
local size = self.props.size
|
||||
local pos = self.props.pos
|
||||
|
||||
local graphData = self.props.graphData
|
||||
|
||||
local getX = self.props.getX
|
||||
local getY = self.props.getY
|
||||
|
||||
local stringFormatX = self.props.stringFormatX
|
||||
local stringFormatY = self.props.stringFormatY
|
||||
|
||||
local axisLabelX = self.props.axisLabelX
|
||||
local axisLabelY = self.props.axisLabelY
|
||||
|
||||
local layoutOrder = self.props.layoutOrder
|
||||
|
||||
local inputPosition = self.state.inputPosition
|
||||
local absGraphSize = self.state.absGraphSize
|
||||
local absGraphPos = self.state.absGraphPos
|
||||
|
||||
|
||||
local maxX = getX(graphData:back())
|
||||
local minX = getX(graphData:front())
|
||||
local maxY = self.props.maxY
|
||||
local minY = self.props.minY
|
||||
|
||||
local hoverLineY
|
||||
|
||||
local elements = {}
|
||||
if absGraphSize then
|
||||
local dataPoints = {}
|
||||
local dataIter = graphData:iterator()
|
||||
local data = dataIter:next()
|
||||
while data do
|
||||
local datapoint = getY(data)
|
||||
local time = getX(data)
|
||||
|
||||
local xdivisor = maxX - minX
|
||||
local xPosition = xdivisor > 0 and (time - minX) / xdivisor or 0
|
||||
|
||||
local ydivisor = maxY - minY
|
||||
local yPosition = ydivisor > 0 and (datapoint - minY) / ydivisor or 1
|
||||
|
||||
local point = {
|
||||
X = xPosition,
|
||||
Y = yPosition,
|
||||
data = data,
|
||||
}
|
||||
|
||||
table.insert(dataPoints, point)
|
||||
|
||||
data = dataIter:next()
|
||||
end
|
||||
|
||||
for i = 2, #dataPoints do
|
||||
local aX = dataPoints[i].X * absGraphSize.X
|
||||
local aY = dataPoints[i].Y * absGraphSize.Y * GRAPH_Y_INNER_SCALE
|
||||
local bX = dataPoints[i - 1].X * absGraphSize.X
|
||||
local bY = dataPoints[i - 1].Y * absGraphSize.Y * GRAPH_Y_INNER_SCALE
|
||||
|
||||
if aX ~= bX then
|
||||
local vecPosX = (aX + bX) / 2
|
||||
local vecPosY = (aY + bY) / 2
|
||||
|
||||
local vecX = aX - bX
|
||||
local vecY = aY - bY
|
||||
|
||||
local length = math.sqrt((vecX * vecX) + (vecY * vecY))
|
||||
local rot = math.deg(math.atan2(vecY, vecX))
|
||||
|
||||
table.insert(elements, Roact.createElement("Frame", {
|
||||
Size = UDim2.new(0, length, 0, LINE_WIDTH),
|
||||
Position = UDim2.new(0, vecPosX - length / 2, 1 - GRAPH_Y_INNER_PADDING, -vecPosY),
|
||||
BackgroundColor3 = MAIN_LINE_COLOR,
|
||||
BorderSizePixel = 0,
|
||||
Rotation = -rot,
|
||||
}))
|
||||
|
||||
table.insert(elements, Roact.createElement("Frame", {
|
||||
Size = UDim2.new(0, POINT_WIDTH, 0, POINT_WIDTH),
|
||||
Position = UDim2.new(0, aX, 1 - GRAPH_Y_INNER_PADDING, -aY - POINT_OFFSET),
|
||||
BackgroundColor3 = MAIN_LINE_COLOR,
|
||||
BorderSizePixel = 0,
|
||||
}))
|
||||
|
||||
if inputPosition then
|
||||
local hoverLineX = inputPosition.X - absGraphPos.X
|
||||
if hoverLineX < aX and bX < hoverLineX then
|
||||
local aDataX = getX(dataPoints[i].data)
|
||||
local bDataX = getX(dataPoints[i - 1].data)
|
||||
local aDataY = getY(dataPoints[i].data)
|
||||
local bDataY = getY(dataPoints[i - 1].data)
|
||||
|
||||
local ratio = (hoverLineX - bX) / vecX
|
||||
hoverLineY = bY + (vecY * ratio)
|
||||
|
||||
local hoverValX = (aDataX - bDataX) * ratio + bDataX
|
||||
local hoverValY = (aDataY - bDataY) * ratio + bDataY
|
||||
|
||||
elements["HoverDetails"] = Roact.createElement(LineGraphHoverDisplay, {
|
||||
hoverLineX = hoverLineX,
|
||||
hoverLineY = hoverLineY,
|
||||
hoverValX = hoverValX,
|
||||
hoverValY = hoverValY,
|
||||
|
||||
stringFormatX = stringFormatX,
|
||||
stringFormatY = stringFormatY,
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if #dataPoints > 0 then
|
||||
local lastEntryHeight = dataPoints[#dataPoints].Y * absGraphSize.Y * GRAPH_Y_INNER_SCALE
|
||||
local currValue = getY(dataPoints[#dataPoints].data)
|
||||
|
||||
-- calc if the hovered Y position is very close to the last input entry (within the invis-threshold).
|
||||
-- If it's NOT within the "invis threshold" then we can show the line and "last entry value"
|
||||
local showCurrValue = not (hoverLineY and math.abs(lastEntryHeight - hoverLineY) < INVIS_LINE_THRESHOLD)
|
||||
|
||||
-- calc if the hovered Y position is very close to the lower Y bound value (within the invis-threshold).
|
||||
-- If it's NOT within the "invis threshold" then we can show the lowerbound Y value
|
||||
local hoverLineCheck = (hoverLineY and math.abs(hoverLineY) < INVIS_LINE_THRESHOLD)
|
||||
local showLeastValue = not (hoverLineCheck or math.abs(lastEntryHeight) < INVIS_LINE_THRESHOLD)
|
||||
|
||||
if showCurrValue then
|
||||
elements["LatestEntryLine"] = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, TEXT_PADDING, 0, LINE_WIDTH),
|
||||
Position = UDim2.new(0, -TEXT_PADDING, 1 - GRAPH_Y_INNER_PADDING, -lastEntryHeight),
|
||||
BackgroundColor3 = LINE_COLOR,
|
||||
BackgroundTransparency = .5,
|
||||
BorderSizePixel = 0,
|
||||
})
|
||||
|
||||
elements["LatestEntryText"] = Roact.createElement("TextLabel", {
|
||||
Text = stringFormatY and stringFormatY(currValue) or currValue,
|
||||
TextColor3 = TEXT_COLOR,
|
||||
TextXAlignment = Enum.TextXAlignment.Right,
|
||||
|
||||
Position = UDim2.new(0, -TEXT_PADDING - 2, 1 - GRAPH_Y_INNER_PADDING, -lastEntryHeight),
|
||||
BackgroundTransparency = 1,
|
||||
})
|
||||
end
|
||||
|
||||
if showLeastValue then
|
||||
elements["AxisTextY0"] = Roact.createElement("TextLabel", {
|
||||
Text = stringFormatY and stringFormatY(minY) or minY,
|
||||
TextColor3 = TEXT_COLOR,
|
||||
TextXAlignment = Enum.TextXAlignment.Right,
|
||||
|
||||
Position = UDim2.new(0, -TEXT_PADDING - 2, 1 - GRAPH_Y_INNER_PADDING,0),
|
||||
BackgroundTransparency = 1,
|
||||
})
|
||||
end
|
||||
|
||||
elements["AxisX"] = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, LINE_WIDTH),
|
||||
Position = UDim2.new(0, 0, 1, 0),
|
||||
BackgroundColor3 = LINE_COLOR,
|
||||
BorderSizePixel = 0,
|
||||
})
|
||||
elements["AxisY"] = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(0, LINE_WIDTH, 1, 0),
|
||||
BackgroundColor3 = LINE_COLOR,
|
||||
BorderSizePixel = 0,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
local axisTextPadding = 2 * TEXT_PADDING + 2
|
||||
|
||||
return Roact.createElement("Frame", {
|
||||
Size = size,
|
||||
Position = pos,
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = layoutOrder,
|
||||
}, {
|
||||
name = Roact.createElement("TextLabel", {
|
||||
Text = axisLabelY,
|
||||
TextColor3 = TEXT_COLOR,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
|
||||
Position = UDim2.new(GRAPH_PADDING, 0, GRAPH_PADDING, -TEXT_PADDING),
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
|
||||
minX = Roact.createElement("TextLabel", {
|
||||
Text = stringFormatX and stringFormatX(minX) or minX,
|
||||
TextColor3 = TEXT_COLOR,
|
||||
TextXAlignment = Enum.TextXAlignment.Center,
|
||||
|
||||
Position = UDim2.new(GRAPH_PADDING, 0, GRAPH_PADDING + GRAPH_SCALE, axisTextPadding),
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
|
||||
maxX = Roact.createElement("TextLabel", {
|
||||
Text = stringFormatX and stringFormatX(maxX) or maxX,
|
||||
TextColor3 = TEXT_COLOR,
|
||||
TextXAlignment = Enum.TextXAlignment.Center,
|
||||
|
||||
Position = UDim2.new(GRAPH_PADDING + GRAPH_SCALE, 0, GRAPH_PADDING + GRAPH_SCALE, axisTextPadding),
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
|
||||
axisLabelX = Roact.createElement("TextLabel", {
|
||||
Text = axisLabelX,
|
||||
TextColor3 = TEXT_COLOR,
|
||||
TextXAlignment = Enum.TextXAlignment.Center,
|
||||
|
||||
-- adding 2 to padding to push the label away from the axis line
|
||||
Position = UDim2.new(.5, 0, GRAPH_PADDING + GRAPH_SCALE, axisTextPadding),
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
|
||||
graph = Roact.createElement("Frame", {
|
||||
Position = UDim2.new(GRAPH_PADDING, 0, GRAPH_PADDING, 0),
|
||||
Size = UDim2.new(GRAPH_SCALE, 0, GRAPH_SCALE, 0),
|
||||
BackgroundTransparency = 1,
|
||||
|
||||
[Roact.Ref] = self.graphRef,
|
||||
|
||||
[Roact.Event.InputChanged] = self.onGraphInputChanged,
|
||||
[Roact.Event.InputEnded] = self.onGraphInputEnded,
|
||||
}, elements)
|
||||
})
|
||||
end
|
||||
|
||||
return LineGraph
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
local Constants = require(script.Parent.Parent.Constants)
|
||||
local HOVER_LINE_COLOR = Constants.Color.HoverGreen
|
||||
local LINE_WIDTH = Constants.GeneralFormatting.LineWidth
|
||||
local TEXT_PADDING = Constants.Graph.TextPadding
|
||||
local GRAPH_Y_INNER_PADDING = Constants.Graph.InnerPaddingY
|
||||
|
||||
return function(props)
|
||||
local hoverLineX = props.hoverLineX
|
||||
local hoverLineY = props.hoverLineY
|
||||
|
||||
local hoverValX = props.hoverValX
|
||||
local hoverValY = props.hoverValY
|
||||
|
||||
local stringFormatX = props.stringFormatX
|
||||
local stringFormatY = props.stringFormatY
|
||||
|
||||
return Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
BackgroundTransparency = 1,
|
||||
}, {
|
||||
hoverLine = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(0, LINE_WIDTH, 1, 0),
|
||||
Position = UDim2.new(0, hoverLineX, 0, 0),
|
||||
BackgroundColor3 = HOVER_LINE_COLOR,
|
||||
BorderSizePixel = 0,
|
||||
}),
|
||||
|
||||
HoverHorizontal = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(0, hoverLineX + TEXT_PADDING, 0, LINE_WIDTH),
|
||||
Position = UDim2.new(0, -TEXT_PADDING, 1 - GRAPH_Y_INNER_PADDING, -hoverLineY),
|
||||
BackgroundColor3 = HOVER_LINE_COLOR,
|
||||
BorderSizePixel = 0,
|
||||
}),
|
||||
|
||||
HoverTextY = Roact.createElement("TextLabel", {
|
||||
Text = stringFormatY and stringFormatY(hoverValY) or hoverValY,
|
||||
TextColor3 = HOVER_LINE_COLOR,
|
||||
TextXAlignment = Enum.TextXAlignment.Right,
|
||||
|
||||
Position = UDim2.new(0, -TEXT_PADDING - 2, 1 - GRAPH_Y_INNER_PADDING, -hoverLineY),
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
|
||||
HoverTextX = Roact.createElement("TextLabel", {
|
||||
Text = stringFormatX and stringFormatX(hoverValX) or hoverValX,
|
||||
TextColor3 = HOVER_LINE_COLOR,
|
||||
TextXAlignment = Enum.TextXAlignment.Center,
|
||||
|
||||
Position = UDim2.new(0, hoverLineX, 1, TEXT_PADDING),
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
})
|
||||
end
|
||||
+319
@@ -0,0 +1,319 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local TextService = game:GetService("TextService")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local RoactRodux = require(CorePackages.RoactRodux)
|
||||
|
||||
local DataConsumer = require(script.Parent.Parent.Components.DataConsumer)
|
||||
|
||||
local Actions = script.Parent.Parent.Actions
|
||||
local SetActiveTab = require(Actions.SetActiveTab)
|
||||
|
||||
local Constants = require(script.Parent.Parent.Constants)
|
||||
local MsgTypeNamesOrdered = Constants.MsgTypeNamesOrdered
|
||||
|
||||
local TOP_BAR_FONT_SIZE = Constants.DefaultFontSize.TopBar
|
||||
local TEXT_COLOR = Constants.Color.Text
|
||||
local FONT = Constants.Font.TopBar
|
||||
|
||||
local IMAGE_SIZE = UDim2.new(0, TOP_BAR_FONT_SIZE, 0, TOP_BAR_FONT_SIZE)
|
||||
|
||||
local MEM_STAT_STR_SMALL = "Client Mem:"
|
||||
local memStatStrSmallWidth = TextService:GetTextSize(MEM_STAT_STR_SMALL, TOP_BAR_FONT_SIZE, FONT, Vector2.new(0, 0))
|
||||
local MEM_STAT_STR = "Client Memory Usage:"
|
||||
local memStatStrWidth = TextService:GetTextSize(MEM_STAT_STR, TOP_BAR_FONT_SIZE, FONT, Vector2.new(0, 0))
|
||||
local AVG_PING_STR = "Avg. Ping:"
|
||||
local avgPingStrWidth = TextService:GetTextSize(AVG_PING_STR, TOP_BAR_FONT_SIZE, FONT, Vector2.new(0, 0))
|
||||
|
||||
-- supposed to be the calculated width of the frame, but
|
||||
-- doing this for now due to time constraints.
|
||||
local MIN_LARGE_FORMFACTOR_WIDTH = 380
|
||||
local INNER_PADDING = 6
|
||||
|
||||
local LiveUpdateElement = Roact.PureComponent:extend("LiveUpdateElement")
|
||||
|
||||
function LiveUpdateElement:didMount()
|
||||
local totalMemSignal = self.props.ClientMemoryData:totalMemSignal()
|
||||
self.totalMemConnector = totalMemSignal:Connect(function(totalClientMemory)
|
||||
self:setState({totalClientMemory = totalClientMemory})
|
||||
end)
|
||||
|
||||
self.avgPingConnector = self.props.ServerStatsData:avgPing():Connect(function(averagePing)
|
||||
self:setState({averagePing = averagePing})
|
||||
end)
|
||||
|
||||
self.logWarningErrorConnector = self.props.ClientLogData:errorWarningSignal():Connect(function(error, warning)
|
||||
self:setState({
|
||||
numErrors = error,
|
||||
numWarnings = warning,
|
||||
})
|
||||
end)
|
||||
|
||||
|
||||
self:doSizeCheck()
|
||||
|
||||
end
|
||||
|
||||
function LiveUpdateElement:didUpdate()
|
||||
self:doSizeCheck()
|
||||
end
|
||||
|
||||
function LiveUpdateElement:doSizeCheck()
|
||||
if self.ref.current then
|
||||
local formFactorThreshold = self.state.formFactorThreshold
|
||||
local isSmallerThanFormFactorThreshold = self.ref.current.AbsoluteSize.X < formFactorThreshold
|
||||
|
||||
if isSmallerThanFormFactorThreshold ~= self.state.isSmallerThanFormFactorThreshold then
|
||||
self:setState({
|
||||
isSmallerThanFormFactorThreshold = isSmallerThanFormFactorThreshold
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function LiveUpdateElement:willUnmount()
|
||||
self.totalMemConnector:Disconnect()
|
||||
self.totalMemConnector = nil
|
||||
|
||||
self.avgPingConnector:Disconnect()
|
||||
self.avgPingConnector = nil
|
||||
|
||||
self.logWarningErrorConnector:Disconnect()
|
||||
self.logWarningErrorConnector = nil
|
||||
end
|
||||
|
||||
function LiveUpdateElement:init()
|
||||
local errorInit, warningInit = self.props.ClientLogData:getErrorWarningCount()
|
||||
self.onLogWarningButton = function()
|
||||
local warningFilters = {}
|
||||
for _, name in pairs(MsgTypeNamesOrdered) do
|
||||
warningFilters[name] = false
|
||||
end
|
||||
|
||||
warningFilters["Warning"] = true
|
||||
self.props.ClientLogData:setFilters(warningFilters)
|
||||
self.props.dispatchChangeTabClientLog()
|
||||
end
|
||||
|
||||
self.onLogErrorButton = function()
|
||||
local errorFilters = {}
|
||||
for _, name in pairs(MsgTypeNamesOrdered) do
|
||||
errorFilters[name] = false
|
||||
end
|
||||
|
||||
errorFilters["Error"] = true
|
||||
self.props.ClientLogData:setFilters(errorFilters)
|
||||
self.props.dispatchChangeTabClientLog()
|
||||
end
|
||||
|
||||
self.ref = Roact.createRef()
|
||||
|
||||
self.state = {
|
||||
numErrors = errorInit,
|
||||
numWarnings = warningInit,
|
||||
totalClientMemory = 0,
|
||||
averagePing = 0,
|
||||
formFactorThreshold = MIN_LARGE_FORMFACTOR_WIDTH,
|
||||
isSmallerThanFormFactorThreshold = false,
|
||||
}
|
||||
end
|
||||
|
||||
function LiveUpdateElement:render()
|
||||
local size = self.props.size
|
||||
local position = self.props.position
|
||||
local formFactor = self.props.formFactor
|
||||
|
||||
local numErrors = self.state.numErrors
|
||||
local numWarnings = self.state.numWarnings
|
||||
local clientMemoryUsage = self.state.totalClientMemory
|
||||
local averagePing = self.state.averagePing
|
||||
local isSmallerThanFormFactorThreshold = self.state.isSmallerThanFormFactorThreshold
|
||||
|
||||
local useSmallForm = false
|
||||
local currMemStrWidth = memStatStrWidth.X
|
||||
local alignment = Enum.HorizontalAlignment.Center
|
||||
|
||||
if formFactor == Constants.FormFactor.Small or isSmallerThanFormFactorThreshold then
|
||||
position = position + UDim2.new(0, INNER_PADDING * 2, 0, 0)
|
||||
currMemStrWidth = memStatStrSmallWidth.X
|
||||
useSmallForm = true
|
||||
alignment = Enum.HorizontalAlignment.Left
|
||||
end
|
||||
|
||||
local logErrorStat = string.format("%d", numErrors)
|
||||
local logErrorStatVector = TextService:GetTextSize(
|
||||
logErrorStat,
|
||||
TOP_BAR_FONT_SIZE,
|
||||
FONT,
|
||||
Vector2.new(0, 0)
|
||||
)
|
||||
|
||||
local logWarningStat = string.format("%d", numWarnings)
|
||||
local logWarningStatVector = TextService:GetTextSize(
|
||||
logWarningStat,
|
||||
TOP_BAR_FONT_SIZE,
|
||||
FONT,
|
||||
Vector2.new(0, 0)
|
||||
)
|
||||
|
||||
local memUsageString = string.format("%d MB", clientMemoryUsage)
|
||||
local memUsageStringVector = TextService:GetTextSize(
|
||||
memUsageString,
|
||||
TOP_BAR_FONT_SIZE,
|
||||
FONT,
|
||||
Vector2.new(0, 0)
|
||||
)
|
||||
|
||||
local avgPingString = string.format("%d ms", averagePing)
|
||||
local avgPingStringVector = TextService:GetTextSize(avgPingString,
|
||||
TOP_BAR_FONT_SIZE,
|
||||
FONT,
|
||||
Vector2.new(0, 0)
|
||||
)
|
||||
|
||||
if formFactor == Constants.FormFactor.Small or isSmallerThanFormFactorThreshold then
|
||||
position = position + UDim2.new(0, INNER_PADDING * 2, 0, 0)
|
||||
currMemStrWidth = memStatStrSmallWidth.X
|
||||
useSmallForm = true
|
||||
alignment = Enum.HorizontalAlignment.Left
|
||||
end
|
||||
|
||||
local showNetworkPing = averagePing > 0
|
||||
|
||||
return Roact.createElement("Frame", {
|
||||
Position = position,
|
||||
Size = size,
|
||||
BackgroundTransparency = 1,
|
||||
|
||||
[Roact.Ref] = self.ref,
|
||||
}, {
|
||||
UIListLayout = Roact.createElement("UIListLayout", {
|
||||
Padding = UDim.new(0, INNER_PADDING),
|
||||
HorizontalAlignment = alignment,
|
||||
FillDirection = Enum.FillDirection.Horizontal,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||
}),
|
||||
|
||||
LogErrorIcon = Roact.createElement("ImageButton", {
|
||||
Image = Constants.Image.Error,
|
||||
Size = IMAGE_SIZE,
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 1,
|
||||
|
||||
[Roact.Event.Activated] = self.onLogErrorButton,
|
||||
}),
|
||||
|
||||
LogErrorCount = Roact.createElement("TextButton", {
|
||||
Text = logErrorStat,
|
||||
TextSize = TOP_BAR_FONT_SIZE,
|
||||
TextColor3 = TEXT_COLOR,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
Font = FONT,
|
||||
Size = UDim2.new(0, logErrorStatVector.X, 1, 0),
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 2,
|
||||
[Roact.Event.Activated] = self.onLogErrorButton,
|
||||
}),
|
||||
|
||||
ErrorWarningPad = Roact.createElement("Frame", {
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 3,
|
||||
}),
|
||||
|
||||
LogWarningIcon = Roact.createElement("ImageButton", {
|
||||
Image = Constants.Image.Warning,
|
||||
Size = IMAGE_SIZE,
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 4,
|
||||
[Roact.Event.Activated] = self.onLogWarningButton,
|
||||
}),
|
||||
|
||||
LogWarningCount = Roact.createElement("TextButton", {
|
||||
Text = logWarningStat,
|
||||
TextSize = TOP_BAR_FONT_SIZE,
|
||||
TextColor3 = TEXT_COLOR,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
Font = FONT,
|
||||
Size = UDim2.new(0, logWarningStatVector.X, 1, 0),
|
||||
BackgroundTransparency = 9,
|
||||
LayoutOrder = 5,
|
||||
[Roact.Event.Activated] = self.onLogWarningButton,
|
||||
}),
|
||||
|
||||
WarningMemoryPad = Roact.createElement("Frame", {
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 6,
|
||||
}),
|
||||
|
||||
MemoryUsage = Roact.createElement("TextButton", {
|
||||
Text = useSmallForm and MEM_STAT_STR_SMALL or MEM_STAT_STR,
|
||||
TextSize = TOP_BAR_FONT_SIZE,
|
||||
TextColor3 = Constants.Color.WarningYellow,
|
||||
TextXAlignment = Enum.TextXAlignment.Right,
|
||||
Font = FONT,
|
||||
Size = UDim2.new(0, currMemStrWidth, 1, 0),
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 7,
|
||||
[Roact.Event.Activated] = self.props.dispatchChangeTabClientMemory,
|
||||
}),
|
||||
|
||||
MemoryUsage_MB = Roact.createElement("TextButton", {
|
||||
Text = memUsageString,
|
||||
TextSize = TOP_BAR_FONT_SIZE,
|
||||
TextColor3 = TEXT_COLOR,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
Font = FONT,
|
||||
Size = UDim2.new(0, memUsageStringVector.X, 1, 0),
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 8,
|
||||
[Roact.Event.Activated] = self.props.dispatchChangeTabClientMemory,
|
||||
}),
|
||||
|
||||
MemoryPingPad = Roact.createElement("Frame", {
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 9,
|
||||
}),
|
||||
|
||||
AvgPing = not useSmallForm and showNetworkPing and Roact.createElement("TextButton", {
|
||||
Text = AVG_PING_STR,
|
||||
TextSize = TOP_BAR_FONT_SIZE,
|
||||
TextColor3 = Constants.Color.WarningYellow,
|
||||
TextXAlignment = Enum.TextXAlignment.Right,
|
||||
Font = FONT,
|
||||
Size = UDim2.new(0, avgPingStrWidth.X, 1, 0),
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 10,
|
||||
[Roact.Event.Activated] = self.props.dispatchChangeTabNetworkPing,
|
||||
}),
|
||||
|
||||
AvgPing_ms = not useSmallForm and showNetworkPing and Roact.createElement("TextButton", {
|
||||
Text = avgPingString,
|
||||
TextSize = TOP_BAR_FONT_SIZE,
|
||||
TextColor3 = TEXT_COLOR,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
Font = FONT,
|
||||
Size = UDim2.new(0, avgPingStringVector.X, 1, 0),
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 11,
|
||||
[Roact.Event.Activated] = self.props.dispatchChangeTabNetworkPing,
|
||||
})
|
||||
})
|
||||
end
|
||||
|
||||
local function mapDispatchToProps(dispatch)
|
||||
return {
|
||||
dispatchChangeTabClientLog = function()
|
||||
dispatch(SetActiveTab("Log", true))
|
||||
end,
|
||||
dispatchChangeTabClientMemory = function()
|
||||
dispatch(SetActiveTab("Memory", true))
|
||||
end,
|
||||
dispatchChangeTabNetworkPing = function()
|
||||
dispatch(SetActiveTab("ServerStats", true))
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
return RoactRodux.UNSTABLE_connect2(nil, mapDispatchToProps)(
|
||||
DataConsumer(LiveUpdateElement, "ServerStatsData", "ClientMemoryData", "ClientLogData" )
|
||||
)
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local RoactRodux = require(CorePackages.RoactRodux)
|
||||
local Store = require(CorePackages.Rodux).Store
|
||||
|
||||
local DataProvider = require(script.Parent.DataProvider)
|
||||
local LiveUpdateElement = require(script.Parent.LiveUpdateElement)
|
||||
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local store = Store.new(function()
|
||||
return {
|
||||
MainView = {
|
||||
isDeveloperView = true,
|
||||
},
|
||||
}
|
||||
end)
|
||||
|
||||
local element = Roact.createElement(RoactRodux.StoreProvider, {
|
||||
store = store,
|
||||
}, {
|
||||
DataProvider = Roact.createElement(DataProvider, nil, {
|
||||
LiveUpdateElement = Roact.createElement(LiveUpdateElement, {
|
||||
size = UDim2.new(),
|
||||
position = UDim2.new(),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
local DataConsumer = require(script.Parent.Parent.DataConsumer)
|
||||
local LogOutput = require(script.Parent.LogOutput)
|
||||
|
||||
local ClientLog = Roact.Component:extend("ClientLog")
|
||||
|
||||
function ClientLog:init()
|
||||
self.initClientLogData = function()
|
||||
return self.props.ClientLogData:getLogData()
|
||||
end
|
||||
end
|
||||
function ClientLog:render()
|
||||
return Roact.createElement(LogOutput, {
|
||||
layoutOrder = self.props.layoutOrder,
|
||||
size = self.props.size,
|
||||
initLogOutput = self.initClientLogData,
|
||||
targetSignal = self.props.ClientLogData:Signal(),
|
||||
})
|
||||
end
|
||||
|
||||
return DataConsumer(ClientLog, "ClientLogData")
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local RoactRodux = require(CorePackages.RoactRodux)
|
||||
local Store = require(CorePackages.Rodux).Store
|
||||
|
||||
local DataProvider = require(script.Parent.Parent.DataProvider)
|
||||
local ClientLog = require(script.Parent.ClientLog)
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local store = Store.new(function()
|
||||
return {
|
||||
MainView = {
|
||||
isDeveloperView = true,
|
||||
},
|
||||
}
|
||||
end)
|
||||
|
||||
local element = Roact.createElement(RoactRodux.StoreProvider, {
|
||||
store = store,
|
||||
}, {
|
||||
DataProvider = Roact.createElement(DataProvider, {}, {
|
||||
ClientLog = Roact.createElement(ClientLog)
|
||||
})
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local UserInputService = game:GetService("UserInputService")
|
||||
local LogService = game:GetService("LogService")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
local DataConsumer = require(script.Parent.Parent.DataConsumer)
|
||||
|
||||
local ScrollingTextBox = require(script.Parent.Parent.ScrollingTextBox)
|
||||
|
||||
local Constants = require(script.Parent.Parent.Parent.Constants)
|
||||
local COMMANDLINE_INDENT = Constants.LogFormatting.CommandLineIndent
|
||||
local COMMANDLINE_FONTSIZE = Constants.DefaultFontSize.CommandLine
|
||||
local FONT = Constants.Font.Log
|
||||
|
||||
local DevConsoleCommandLine = Roact.PureComponent:extend("DevConsoleCommandLine")
|
||||
|
||||
function DevConsoleCommandLine:init()
|
||||
self.onFocusLost = function(rbx, enterPressed, inputThatCausedFocusLoss)
|
||||
if enterPressed then
|
||||
LogService:ExecuteScript(rbx.Text)
|
||||
rbx:CaptureFocus()
|
||||
end
|
||||
end
|
||||
|
||||
self.textbox = Roact.createRef()
|
||||
end
|
||||
|
||||
function DevConsoleCommandLine:didMount()
|
||||
if not self.onFocusConnection then
|
||||
self.onFocusConnection = UserInputService.InputBegan:Connect(function(input)
|
||||
if self.textbox.current and self.textbox.current:IsFocused() then
|
||||
local rbx = self.textbox.current
|
||||
local serverLogData = self.props.ServerLogData
|
||||
local cmdHistory = serverLogData:getCommandLineHistory()
|
||||
local cmdIndex = serverLogData:getCommandLineIndex()
|
||||
|
||||
if input.KeyCode == Enum.KeyCode.Up then
|
||||
local newIndex = cmdIndex + 1
|
||||
newIndex = math.min(cmdHistory:getSize(), newIndex)
|
||||
cmdIndex = newIndex
|
||||
|
||||
rbx.Text = cmdHistory:reverseAt(newIndex) or ""
|
||||
|
||||
elseif input.KeyCode == Enum.KeyCode.Down then
|
||||
local newIndex = cmdIndex - 1
|
||||
newIndex = math.max(0, newIndex)
|
||||
cmdIndex = newIndex
|
||||
|
||||
rbx.Text = cmdHistory:reverseAt(newIndex) or ""
|
||||
|
||||
elseif input.KeyCode == Enum.KeyCode.Return then
|
||||
if #rbx.Text:gsub("%s+", "") > 0 then
|
||||
local prevText = cmdHistory:reverseAt(1)
|
||||
if prevText ~= rbx.Text then
|
||||
cmdHistory:push_back(rbx.Text)
|
||||
end
|
||||
end
|
||||
cmdIndex = 0
|
||||
elseif cmdIndex ~= 0 then
|
||||
cmdIndex = 0
|
||||
end
|
||||
serverLogData:setCommandLineIndex(cmdIndex)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
-- theres an unwanted behavior here
|
||||
-- if you recapture the same textbox in the onFocuslost event,
|
||||
-- the "\r" character that triggerd the onFocusLost will not be consumed yet
|
||||
-- and will subsequently be put into the textbox. This event is meant to fix that.
|
||||
if not self.fixUnwantedReturnCapture then
|
||||
self.fixUnwantedReturnCapture = UserInputService.InputEnded:Connect(function(input)
|
||||
if self.textbox.current and self.textbox.current:IsFocused() then
|
||||
if input.KeyCode == Enum.KeyCode.Return then
|
||||
self.textbox.current.Text = ""
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
function DevConsoleCommandLine:willUnmount()
|
||||
if self.onFocusConnection then
|
||||
self.onFocusConnection:Disconnect()
|
||||
self.onFocusConnection = nil
|
||||
end
|
||||
if self.fixUnwantedReturnCapture then
|
||||
self.fixUnwantedReturnCapture:Disconnect()
|
||||
self.fixUnwantedReturnCapture = nil
|
||||
end
|
||||
end
|
||||
|
||||
function DevConsoleCommandLine:render()
|
||||
local height = self.props.height
|
||||
local pos = self.props.pos
|
||||
|
||||
local initText = ""
|
||||
|
||||
local cmdIndex = self.props.ServerLogData:getCommandLineIndex()
|
||||
if cmdIndex ~= 0 then
|
||||
local cmdHistory = self.props.ServerLogData:getCommandLineHistory()
|
||||
initText = cmdHistory:reverseAt(cmdIndex) or ""
|
||||
end
|
||||
|
||||
return Roact.createElement("Frame", {
|
||||
Position = pos,
|
||||
Size = UDim2.new(1, 0, 0, height),
|
||||
BackgroundTransparency = 0,
|
||||
BackgroundColor3 = Constants.Color.TextBoxGray,
|
||||
BorderColor3 = Constants.Color.BorderGray,
|
||||
BorderSizePixel = 1,
|
||||
}, {
|
||||
Arrow = Roact.createElement("TextLabel", {
|
||||
Size = UDim2.new(0, COMMANDLINE_INDENT, 1, 0),
|
||||
BackgroundTransparency = 1,
|
||||
TextSize = COMMANDLINE_FONTSIZE,
|
||||
Font = FONT,
|
||||
Text = "> ",
|
||||
TextColor3 = Constants.Color.Text,
|
||||
TextXAlignment = Enum.TextXAlignment.Right,
|
||||
}),
|
||||
|
||||
InputField = Roact.createElement(ScrollingTextBox, {
|
||||
Position = UDim2.new(0, COMMANDLINE_INDENT, 0, 0),
|
||||
Size = UDim2.new(1, -COMMANDLINE_INDENT, 0, height),
|
||||
|
||||
ShowNativeInput = true,
|
||||
ClearTextOnFocus = false,
|
||||
TextColor3 = Constants.Color.Text,
|
||||
TextXAlignment = 0,
|
||||
TextSize = COMMANDLINE_FONTSIZE,
|
||||
Text = initText,
|
||||
Font = FONT,
|
||||
PlaceholderText = "command line",
|
||||
|
||||
[Roact.Ref] = self.textbox,
|
||||
TextBoxFocusLost = self.onFocusLost,
|
||||
})
|
||||
|
||||
})
|
||||
end
|
||||
|
||||
return DataConsumer(DevConsoleCommandLine, "ServerLogData")
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local RoactRodux = require(CorePackages.RoactRodux)
|
||||
local Store = require(CorePackages.Rodux).Store
|
||||
|
||||
local DataProvider = require(script.Parent.Parent.DataProvider)
|
||||
local DevConsoleCommandLine = require(script.Parent.DevConsoleCommandLine)
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local store = Store.new(function()
|
||||
return {
|
||||
MainView = {
|
||||
isDeveloperView = true,
|
||||
},
|
||||
}
|
||||
end)
|
||||
|
||||
local element = Roact.createElement(RoactRodux.StoreProvider, {
|
||||
store = store,
|
||||
}, {
|
||||
DataProvider = Roact.createElement(DataProvider, {}, {
|
||||
CmdLine = Roact.createElement(DevConsoleCommandLine),
|
||||
})
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+343
@@ -0,0 +1,343 @@
|
||||
local LogService = game:GetService("LogService")
|
||||
local TextService = game:GetService("TextService")
|
||||
|
||||
local Constants = require(script.Parent.Parent.Parent.Constants)
|
||||
local FONT_SIZE = Constants.DefaultFontSize.MainWindow
|
||||
local FONT = Constants.Font.Log
|
||||
local MAX_STRING_SIZE = Constants.LogFormatting.MaxStringSize
|
||||
|
||||
local MESSAGE_TO_TYPENAME = Constants.EnumToMsgTypeName
|
||||
|
||||
local CircularBuffer = require(script.Parent.Parent.Parent.CircularBuffer)
|
||||
local Signal = require(script.Parent.Parent.Parent.Signal)
|
||||
|
||||
-- 500 is max kept in history in C++ as of 7/2/2018
|
||||
local MAX_LOG_SIZE = tonumber(settings():GetFVariable("NewDevConsoleMaxLogCount"))
|
||||
local WARNING_TO_FILTER = {"ClassDescriptor failed to learn", "EventDescriptor failed to learn", "Type failed to learn"}
|
||||
local MAX_HISTORY = 100
|
||||
|
||||
local convertTimeStamp = require(script.Parent.Parent.Parent.Util.convertTimeStamp)
|
||||
|
||||
local LogData = {}
|
||||
LogData.__index = LogData
|
||||
|
||||
local function messageEntry(msg, timeAsStr, type)
|
||||
local fmtMessage
|
||||
local charCount = #msg
|
||||
if charCount < MAX_STRING_SIZE then
|
||||
fmtMessage = string.format("%s -- %s", timeAsStr, msg)
|
||||
else
|
||||
fmtMessage = string.format("%s -- %s", timeAsStr, string.sub(msg, 1, MAX_STRING_SIZE))
|
||||
end
|
||||
|
||||
local dims = TextService:GetTextSize(fmtMessage, FONT_SIZE, FONT, Vector2.new())
|
||||
|
||||
return {
|
||||
Message = fmtMessage,
|
||||
CharCount = charCount,
|
||||
Type = type,
|
||||
Dims = dims,
|
||||
}
|
||||
end
|
||||
|
||||
-- MOST if not all of this code is copied from the
|
||||
-- Filter "ClassDescriptor failed to learn" errors
|
||||
local function ignoreWarningMessageOnAdd(message)
|
||||
if message.Type ~= Enum.MessageType.MessageWarning.Value then
|
||||
return false
|
||||
end
|
||||
local found = false
|
||||
for _, filterString in ipairs(WARNING_TO_FILTER) do
|
||||
if string.find(message.Message, filterString) ~= nil then
|
||||
found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
return found
|
||||
end
|
||||
|
||||
-- if a message if filtered that means we need to put it into the filtered messages
|
||||
-- this is defined as the messages that we are searching for when the search
|
||||
-- feature is being used
|
||||
local function isMessageFiltered(message, filterTypes, filterTerm)
|
||||
-- if any types are flagged on, then we need to check against it
|
||||
if #filterTerm == 0 and not next(filterTypes) then
|
||||
return false
|
||||
end
|
||||
|
||||
if next(filterTypes) then
|
||||
if not filterTypes[MESSAGE_TO_TYPENAME[message.Type]] then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
if #filterTerm > 0 then
|
||||
if string.find(message.Message:lower(), filterTerm:lower()) == nil then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function validActiveFilters(filterTypes)
|
||||
local validFilters = false
|
||||
for _, filterActive in pairs(filterTypes) do
|
||||
validFilters = validFilters or filterActive
|
||||
end
|
||||
return validFilters
|
||||
end
|
||||
|
||||
local function filterMessages(buffer, msgIter, filterTypes, filterTerm)
|
||||
buffer:reset()
|
||||
|
||||
local validFilters = validActiveFilters(filterTypes)
|
||||
|
||||
if #filterTerm == 0 and not validFilters then
|
||||
return
|
||||
end
|
||||
|
||||
local counter = 0
|
||||
local msg = msgIter:next()
|
||||
while msg do
|
||||
if isMessageFiltered(msg, filterTypes, filterTerm) then
|
||||
counter = counter + 1
|
||||
buffer:push_back(msg)
|
||||
end
|
||||
msg = msgIter:next()
|
||||
end
|
||||
|
||||
if counter == 0 then
|
||||
if #filterTerm > 0 then
|
||||
local errorMsg = messageEntry(string.format ("\"%s\" was not found", filterTerm), "", 0)
|
||||
buffer:push_back(errorMsg)
|
||||
else
|
||||
local errorMsg = messageEntry("No Messages were found", "", 0)
|
||||
buffer:push_back(errorMsg)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function LogData:checkErrorWarningCounter(msgType)
|
||||
if msgType == Enum.MessageType.MessageWarning.Value then
|
||||
self._warningCount = self._warningCount + 1
|
||||
self._errorWarningSignal:Fire(self._errorCount, self._warningCount)
|
||||
elseif msgType == Enum.MessageType.MessageError.Value then
|
||||
self._errorCount = self._errorCount + 1
|
||||
self._errorWarningSignal:Fire(self._errorCount, self._warningCount)
|
||||
end
|
||||
end
|
||||
|
||||
function LogData.new(isClient)
|
||||
local self = {}
|
||||
setmetatable(self, LogData)
|
||||
|
||||
self._initialized = false
|
||||
self._isRunning = false
|
||||
self._isClient = isClient
|
||||
|
||||
self._logData = CircularBuffer.new(MAX_LOG_SIZE)
|
||||
self._logDataSearched = CircularBuffer.new(MAX_LOG_SIZE)
|
||||
self._searchTerm = ""
|
||||
|
||||
self._commandLineHistory = CircularBuffer.new(MAX_HISTORY)
|
||||
self._commandLineIndex = 0
|
||||
|
||||
self._filters = {}
|
||||
for _, v in pairs(MESSAGE_TO_TYPENAME) do
|
||||
self._filters[v] = true
|
||||
end
|
||||
|
||||
self._errorCount = isClient and 0
|
||||
self._warningCount = isClient and 0
|
||||
self._logDataUpdate = Signal.new()
|
||||
self._errorWarningSignal = isClient and Signal.new()
|
||||
self._filterUpdated = Signal.new()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function LogData:Signal()
|
||||
return self._logDataUpdate
|
||||
end
|
||||
|
||||
function LogData:errorWarningSignal()
|
||||
return self._errorWarningSignal
|
||||
end
|
||||
|
||||
function LogData:filterUpdatedSignal()
|
||||
return self._filterUpdated
|
||||
end
|
||||
|
||||
function LogData:setSearchTerm(targetSearchTerm)
|
||||
if self._searchTerm ~= targetSearchTerm then
|
||||
self._searchTerm = targetSearchTerm
|
||||
|
||||
if self._searchTerm == "" then
|
||||
self._logDataSearched:reset()
|
||||
self._logDataUpdate:Fire(self._logData)
|
||||
else
|
||||
filterMessages(
|
||||
self._logDataSearched,
|
||||
self._logData:iterator(),
|
||||
self._filters,
|
||||
self._searchTerm
|
||||
)
|
||||
self._logDataUpdate:Fire(self._logDataSearched)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function LogData:getSearchTerm()
|
||||
return self._searchTerm
|
||||
end
|
||||
|
||||
function LogData:getCommandLineHistory()
|
||||
return self._commandLineHistory
|
||||
end
|
||||
|
||||
function LogData:getCommandLineIndex()
|
||||
return self._commandLineIndex
|
||||
end
|
||||
|
||||
function LogData:setCommandLineIndex(index)
|
||||
self._commandLineIndex = index
|
||||
end
|
||||
|
||||
function LogData:getFilters()
|
||||
return self._filters
|
||||
end
|
||||
|
||||
function LogData:setFilter(name, newState)
|
||||
local oldState = self._filters[name]
|
||||
self._filters[name] = newState
|
||||
|
||||
if not validActiveFilters(self._filters) then
|
||||
self._filters[name] = oldState
|
||||
return
|
||||
end
|
||||
|
||||
filterMessages(
|
||||
self._logDataSearched,
|
||||
self._logData:iterator(),
|
||||
self._filters,
|
||||
self._searchTerm
|
||||
)
|
||||
self._logDataUpdate:Fire(self._logDataSearched)
|
||||
self._filterUpdated:Fire()
|
||||
end
|
||||
|
||||
function LogData:setFilters(filters)
|
||||
self._filters = filters
|
||||
|
||||
if not validActiveFilters(filters) then
|
||||
self._logDataSearched:reset()
|
||||
self._logDataUpdate:Fire(self._logData)
|
||||
return
|
||||
end
|
||||
|
||||
filterMessages(
|
||||
self._logDataSearched,
|
||||
self._logData:iterator(),
|
||||
self._filters,
|
||||
self._searchTerm
|
||||
)
|
||||
self._logDataUpdate:Fire(self._logDataSearched)
|
||||
self._filterUpdated:Fire()
|
||||
end
|
||||
|
||||
function LogData:getLogData()
|
||||
if #self._logDataSearched:getData() > 0 then
|
||||
return self._logDataSearched
|
||||
end
|
||||
return self._logData
|
||||
end
|
||||
|
||||
function LogData:getErrorWarningCount()
|
||||
return self._errorCount, self._warningCount
|
||||
end
|
||||
|
||||
function LogData:isRunning()
|
||||
return self._initialized
|
||||
end
|
||||
|
||||
function LogData:start()
|
||||
if self._isClient then
|
||||
if not self._initialized then
|
||||
self._initialized = true
|
||||
local Messages = {}
|
||||
if #Messages == 0 then
|
||||
local history = LogService:GetLogHistory()
|
||||
for _, msg in ipairs(history) do
|
||||
local message = messageEntry(
|
||||
msg.message or "[DevConsole Error 1]",
|
||||
convertTimeStamp(msg.timestamp),
|
||||
msg.messageType.Value
|
||||
)
|
||||
if not ignoreWarningMessageOnAdd(message) then
|
||||
self:checkErrorWarningCounter(msg.messageType.Value)
|
||||
self._logData:push_back(message)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self._connection = LogService.MessageOut:connect(function(text, messageType)
|
||||
local message = messageEntry(
|
||||
text or "[DevConsole Error 2]",
|
||||
convertTimeStamp(os.time()),
|
||||
messageType.Value
|
||||
)
|
||||
|
||||
if not ignoreWarningMessageOnAdd(message) then
|
||||
self:checkErrorWarningCounter(messageType.Value)
|
||||
self._logData:push_back(message)
|
||||
|
||||
if #self._logDataSearched:getData() > 0 then
|
||||
if isMessageFiltered(message, self._filters, self._searchTerm) then
|
||||
self._logDataSearched:push_back(message)
|
||||
self._logDataUpdate:Fire(self._logDataSearched)
|
||||
end
|
||||
else
|
||||
self._logDataUpdate:Fire(self._logData)
|
||||
end
|
||||
end
|
||||
end)
|
||||
else
|
||||
self._connection = LogService.ServerMessageOut:connect(function(text, messageType, timestamp)
|
||||
local message = messageEntry(
|
||||
text or "[DevConsole Error 3]",
|
||||
convertTimeStamp(timestamp),
|
||||
messageType.Value
|
||||
)
|
||||
|
||||
if not ignoreWarningMessageOnAdd(message) then
|
||||
self._logData:push_back(message)
|
||||
|
||||
if #self._logDataSearched:getData() > 0 then
|
||||
if isMessageFiltered(message, self._filters, self._searchTerm) then
|
||||
self._logDataSearched:push_back(message)
|
||||
self._logDataUpdate:Fire(self._logDataSearched)
|
||||
end
|
||||
else
|
||||
self._logDataUpdate:Fire(self._logData)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
LogService:RequestServerOutput()
|
||||
end
|
||||
|
||||
self._isRunning = true
|
||||
end
|
||||
|
||||
function LogData:stop()
|
||||
self._initialized = false
|
||||
self._isRunning = false
|
||||
|
||||
if self._connection then
|
||||
self._connection:Disconnect()
|
||||
end
|
||||
end
|
||||
|
||||
return LogData
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
return function()
|
||||
local LogData = require(script.Parent.LogData)
|
||||
|
||||
it("should initialize either client or server", function()
|
||||
local clientLogData = LogData.new(true)
|
||||
local serverLogData = LogData.new(false)
|
||||
expect(clientLogData).to.be.ok()
|
||||
expect(serverLogData).to.be.ok()
|
||||
end)
|
||||
|
||||
it("should get and set the filters", function()
|
||||
local clientLogData = LogData.new(true)
|
||||
local key = "Output"
|
||||
local value = false
|
||||
|
||||
local filters = clientLogData:getFilters()
|
||||
expect(filters[key]).to.equal(true)
|
||||
|
||||
clientLogData:setFilter(key, value)
|
||||
filters = clientLogData:getFilters()
|
||||
|
||||
expect(filters[key]).to.equal(value)
|
||||
end)
|
||||
end
|
||||
+263
@@ -0,0 +1,263 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local TextService = game:GetService("TextService")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
local Constants = require(script.Parent.Parent.Parent.Constants)
|
||||
local FONT_SIZE = Constants.DefaultFontSize.MainWindow
|
||||
local FONT = Constants.Font.Log
|
||||
local ICON_PADDING = Constants.LogFormatting.IconHeight
|
||||
local ARROW_OFFSET = Constants.LogFormatting.TextFrameHeight -- reuse this value for offset needed for the "<" char
|
||||
local LINE_PADDING = Constants.LogFormatting.TextFramePadding
|
||||
local MAX_STRING_SIZE = Constants.LogFormatting.MaxStringSize
|
||||
local MAX_STR_MSG = " -- Could not display entire %d character message because message exceeds max displayable length of %d"
|
||||
|
||||
local FFlagDevConsoleLogNewLineFix = settings():GetFFlag("DevConsoleLogNewLineFix")
|
||||
|
||||
local LogOutput = Roact.Component:extend("LogOutput")
|
||||
|
||||
function LogOutput:init(props)
|
||||
local initLogOutput = props.initLogOutput and props.initLogOutput()
|
||||
|
||||
self.onCanvasChange = function()
|
||||
local current = self.ref.current
|
||||
if current then
|
||||
local canvasPos = current.CanvasPosition
|
||||
local absSize = current.AbsoluteSize
|
||||
if self.state.canvasPos ~= canvasPos or
|
||||
self.state.absSize ~= absSize then
|
||||
|
||||
local yTotal = current.CanvasPosition.Y + current.AbsoluteSize.Y
|
||||
local yCanvasSize = current.CanvasSize.Y.Offset
|
||||
local autoScroll = yTotal == yCanvasSize
|
||||
|
||||
self:setState({
|
||||
canvasPos = canvasPos,
|
||||
absSize = absSize,
|
||||
autoScroll = autoScroll,
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.ref = Roact.createRef()
|
||||
|
||||
self.state = {
|
||||
logData = initLogOutput,
|
||||
absSize = Vector2.new(),
|
||||
canvasPos = Vector2.new(),
|
||||
autoScroll = true,
|
||||
wordWrap = true,
|
||||
}
|
||||
end
|
||||
|
||||
function LogOutput:willUpdate(nextProps, nextState)
|
||||
self._canvasSignal:Disconnect()
|
||||
self._absSizeSignal:Disconnect()
|
||||
end
|
||||
|
||||
function LogOutput:didUpdate()
|
||||
self._canvasSignal = self.ref.current:GetPropertyChangedSignal("CanvasPosition"):Connect(self.onCanvasChange)
|
||||
self._absSizeSignal = self.ref.current:GetPropertyChangedSignal("AbsoluteSize"):Connect(self.onCanvasChange)
|
||||
|
||||
if self.state.autoScroll then
|
||||
local current = self.ref.current
|
||||
if current then
|
||||
local newPos = Vector2.new(current.CanvasPosition.X, self.ref.current.CanvasSize.Y.Offset + self.ref.current.AbsoluteSize.Y)
|
||||
current.CanvasPosition = newPos
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function LogOutput:didMount()
|
||||
self.logConnector = self.props.targetSignal:Connect(function(data)
|
||||
if not self.state.autoScroll and
|
||||
data:getSize() == data:getMaxSize() then
|
||||
local canvasPos = self.state.canvasPos
|
||||
local canvasPosY = canvasPos.Y
|
||||
local newestMsg = data:back()
|
||||
|
||||
if newestMsg then
|
||||
local msgDimsY = newestMsg.Dims.Y
|
||||
local frameWidth = self.state.absSize.X - ARROW_OFFSET
|
||||
|
||||
if self.state.wordWrap and frameWidth > 0 then
|
||||
msgDimsY = newestMsg.Dims.Y * math.ceil(newestMsg.Dims.X / frameWidth)
|
||||
end
|
||||
|
||||
canvasPosY = math.max(0, canvasPosY - msgDimsY - LINE_PADDING)
|
||||
end
|
||||
|
||||
self:setState({
|
||||
logData = data,
|
||||
canvasPos = Vector2.new(canvasPos.X, canvasPosY),
|
||||
})
|
||||
else
|
||||
self:setState({
|
||||
logData = data
|
||||
})
|
||||
end
|
||||
end)
|
||||
|
||||
self._canvasSignal = self.ref.current:GetPropertyChangedSignal("CanvasPosition"):Connect(self.onCanvasChange)
|
||||
self._absSizeSignal = self.ref.current:GetPropertyChangedSignal("AbsoluteSize"):Connect(self.onCanvasChange)
|
||||
|
||||
--[[
|
||||
in some cases, the absolute size is not valid at this point. But in the
|
||||
case that it is, we want to update the absolute size here since the
|
||||
absolute size was changed prior to the absSizeSignal being set up
|
||||
--]]
|
||||
local absSize = self.ref.current.AbsoluteSize
|
||||
if absSize.Magnitude > 0 then
|
||||
self:setState({
|
||||
absSize = self.ref.current.AbsoluteSize,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function LogOutput:willUnmount()
|
||||
self.logConnector:Disconnect()
|
||||
self.logConnector = nil
|
||||
end
|
||||
|
||||
function LogOutput:render()
|
||||
local layoutOrder = self.props.layoutOrder
|
||||
local size = self.props.size
|
||||
|
||||
local logData = self.state.logData
|
||||
local absSize = self.state.absSize
|
||||
local canvasPos = self.state.canvasPos
|
||||
local wordWrap = self.state.wordWrap
|
||||
|
||||
local elements = {}
|
||||
|
||||
local messageCount = 1
|
||||
local scrollingFrameHeight = 0
|
||||
|
||||
if self.ref.current and logData then
|
||||
local frameWidth = absSize.X - ARROW_OFFSET
|
||||
local paddingHeight = -1
|
||||
local usedFrameSpace = 0
|
||||
|
||||
local msgIter = logData:iterator()
|
||||
local message = msgIter:next()
|
||||
while message do
|
||||
local fmtMessage = message.Message
|
||||
local charCount = message.CharCount
|
||||
|
||||
local msgDimsY = message.Dims.Y
|
||||
if wordWrap and frameWidth > 0 then
|
||||
if FFlagDevConsoleLogNewLineFix then
|
||||
-- this fix doesn't fully solve the problem, but it does prevent the white space
|
||||
-- error to 2 lines at most, which should be acceptable until we can refactor
|
||||
-- this whole hot mess
|
||||
|
||||
-- one full message height per time the message width fits in the container
|
||||
msgDimsY = message.Dims.Y * message.Dims.X / absSize.X
|
||||
-- round to the nearest line height
|
||||
msgDimsY = math.ceil(msgDimsY / FONT_SIZE) * FONT_SIZE
|
||||
else
|
||||
msgDimsY = message.Dims.Y * math.ceil(message.Dims.X / frameWidth)
|
||||
end
|
||||
end
|
||||
|
||||
messageCount = messageCount + 1
|
||||
|
||||
if scrollingFrameHeight + msgDimsY >= canvasPos.Y then
|
||||
if usedFrameSpace < absSize.Y then
|
||||
local color = Constants.Color.Text
|
||||
local image = ""
|
||||
|
||||
if message.Type == Enum.MessageType.MessageOutput.Value then
|
||||
color = Constants.Color.Text
|
||||
elseif message.Type == Enum.MessageType.MessageInfo.Value then
|
||||
color = Constants.Color.HighlightBlue
|
||||
image = Constants.Image.Info
|
||||
elseif message.Type == Enum.MessageType.MessageWarning.Value then
|
||||
color = Constants.Color.WarningYellow
|
||||
image = Constants.Image.Warning
|
||||
elseif message.Type == Enum.MessageType.MessageError.Value then
|
||||
color = Constants.Color.ErrorRed
|
||||
image = Constants.Image.Errors
|
||||
end
|
||||
|
||||
elements[messageCount] = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, msgDimsY),
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = messageCount,
|
||||
}, {
|
||||
image = Roact.createElement("ImageLabel", {
|
||||
Image = image,
|
||||
Size = UDim2.new(0, ICON_PADDING , 0, ICON_PADDING),
|
||||
Position = UDim2.new(0, ICON_PADDING / 4, .5, -ICON_PADDING / 2),
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
msg = Roact.createElement("TextLabel", {
|
||||
Text = fmtMessage,
|
||||
TextColor3 = color,
|
||||
TextSize = FONT_SIZE,
|
||||
Font = FONT,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
|
||||
TextWrapped = wordWrap,
|
||||
|
||||
Size = FFlagDevConsoleLogNewLineFix and
|
||||
-- flag true
|
||||
UDim2.new(1, -ARROW_OFFSET, 0, msgDimsY) or
|
||||
-- flag false
|
||||
UDim2.new(1, 0, 0, msgDimsY),
|
||||
|
||||
Position = UDim2.new(0, ARROW_OFFSET, 0, 0),
|
||||
BackgroundTransparency = 1,
|
||||
})
|
||||
})
|
||||
end
|
||||
|
||||
if paddingHeight < 0 then
|
||||
paddingHeight = scrollingFrameHeight
|
||||
else
|
||||
usedFrameSpace = usedFrameSpace + msgDimsY + LINE_PADDING
|
||||
end
|
||||
end
|
||||
|
||||
scrollingFrameHeight = scrollingFrameHeight + msgDimsY + LINE_PADDING
|
||||
|
||||
if charCount < MAX_STRING_SIZE then
|
||||
message = msgIter:next()
|
||||
else
|
||||
local maxStrMsg = string.format(MAX_STR_MSG, charCount, MAX_STRING_SIZE)
|
||||
message = {
|
||||
Message = maxStrMsg,
|
||||
CharCount = #maxStrMsg,
|
||||
Type = message.Type,
|
||||
Dims = TextService:GetTextSize(maxStrMsg, FONT_SIZE, FONT, Vector2.new())
|
||||
}
|
||||
end
|
||||
end
|
||||
elements["UIListLayout"] = Roact.createElement("UIListLayout", {
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Left,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Top,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
Padding = UDim.new(0, LINE_PADDING),
|
||||
})
|
||||
|
||||
elements["WindowingPadding"] = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, paddingHeight),
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 1,
|
||||
})
|
||||
end
|
||||
|
||||
return Roact.createElement("ScrollingFrame", {
|
||||
Size = size,
|
||||
BackgroundTransparency = 1,
|
||||
VerticalScrollBarInset = 1,
|
||||
ScrollBarThickness = 6,
|
||||
CanvasSize = UDim2.new(0, 0, 0, scrollingFrameHeight),
|
||||
CanvasPosition = canvasPos,
|
||||
LayoutOrder = layoutOrder,
|
||||
|
||||
[Roact.Ref] = self.ref,
|
||||
}, elements)
|
||||
end
|
||||
|
||||
return LogOutput
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
local Signal = require(script.Parent.Parent.Parent.Signal)
|
||||
local LogOutput = require(script.Parent.LogOutput)
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local element = Roact.createElement(LogOutput, {
|
||||
targetSignal = Signal.new()
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local RoactRodux = require(CorePackages.RoactRodux)
|
||||
|
||||
local Components = script.Parent.Parent.Parent.Components
|
||||
local ClientLog = require(Components.Log.ClientLog)
|
||||
local ServerLog = require(Components.Log.ServerLog)
|
||||
local UtilAndTab = require(Components.UtilAndTab)
|
||||
local DataConsumer = require(Components.DataConsumer)
|
||||
|
||||
local Actions = script.Parent.Parent.Parent.Actions
|
||||
local SetActiveTab = require(Actions.SetActiveTab)
|
||||
|
||||
local Constants = require(script.Parent.Parent.Parent.Constants)
|
||||
local PADDING = Constants.GeneralFormatting.MainRowPadding
|
||||
|
||||
local MsgTypeNamesOrdered = Constants.MsgTypeNamesOrdered
|
||||
|
||||
local MainViewLog = Roact.PureComponent:extend("MainViewLog")
|
||||
|
||||
function MainViewLog:init()
|
||||
self.onUtilTabHeightChanged = function(utilTabHeight)
|
||||
self:setState({
|
||||
utilTabHeight = utilTabHeight
|
||||
})
|
||||
end
|
||||
|
||||
self.onClientButton = function()
|
||||
self.props.dispatchSetActiveTab("Log", true)
|
||||
end
|
||||
|
||||
self.onServerButton = function()
|
||||
self.props.dispatchSetActiveTab("Log", false)
|
||||
end
|
||||
|
||||
self.onCheckBoxChanged = function(boxName, newState)
|
||||
if self.props.isClientView then
|
||||
self.props.ClientLogData:setFilter(boxName, newState)
|
||||
else
|
||||
self.props.ServerLogData:setFilter(boxName, newState)
|
||||
end
|
||||
end
|
||||
|
||||
self.filterUpdated = function()
|
||||
self:setState({})
|
||||
end
|
||||
|
||||
self.onSearchTermChanged = function(newSearchTerm)
|
||||
if self.props.isClientView then
|
||||
self.props.ClientLogData:setSearchTerm(newSearchTerm)
|
||||
else
|
||||
self.props.ServerLogData:setSearchTerm(newSearchTerm)
|
||||
end
|
||||
end
|
||||
|
||||
local clientFilterSignal = self.props.ClientLogData:filterUpdatedSignal()
|
||||
self.clientFilterConnection = clientFilterSignal:Connect(self.filterUpdated)
|
||||
|
||||
local serverFilterSignal = self.props.ServerLogData:filterUpdatedSignal()
|
||||
self.serverFilterConnection = serverFilterSignal:Connect(self.filterUpdated)
|
||||
|
||||
self.utilRef = Roact.createRef()
|
||||
|
||||
self.state = {
|
||||
utilTabHeight = 0
|
||||
}
|
||||
end
|
||||
|
||||
function MainViewLog:didMount()
|
||||
local utilSize = self.utilRef.current.Size
|
||||
self:setState({
|
||||
utilTabHeight = utilSize.Y.Offset
|
||||
})
|
||||
end
|
||||
|
||||
function MainViewLog:didUpdate()
|
||||
local utilSize = self.utilRef.current.Size
|
||||
if utilSize.Y.Offset ~= self.state.utilTabHeight then
|
||||
self:setState({
|
||||
utilTabHeight = utilSize.Y.Offset
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function MainViewLog:willUnmount()
|
||||
if self.clientFilterConnection then
|
||||
self.clientFilterConnection:Disconnect()
|
||||
self.clientFilterConnection = nil
|
||||
end
|
||||
if self.serverFilterConnection then
|
||||
self.serverFilterConnection:Disconnect()
|
||||
self.serverFilterConnection = nil
|
||||
end
|
||||
end
|
||||
|
||||
function MainViewLog:render()
|
||||
local size = self.props.size
|
||||
local formFactor = self.props.formFactor
|
||||
local isDeveloperView = self.props.isDeveloperView
|
||||
local tabList = self.props.tabList
|
||||
local isClientView = self.props.isClientView
|
||||
|
||||
local utilTabHeight = self.state.utilTabHeight
|
||||
|
||||
|
||||
local searchTerm
|
||||
if isClientView then
|
||||
searchTerm = self.props.ClientLogData:getSearchTerm()
|
||||
else
|
||||
searchTerm = self.props.ServerLogData:getSearchTerm()
|
||||
end
|
||||
|
||||
local elements = {}
|
||||
|
||||
elements["UIListLayout"] = Roact.createElement("UIListLayout", {
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
Padding = UDim.new(0, PADDING),
|
||||
})
|
||||
|
||||
local currCheckBoxState
|
||||
if isClientView then
|
||||
currCheckBoxState = self.props.ClientLogData:getFilters()
|
||||
else
|
||||
currCheckBoxState = self.props.ServerLogData:getFilters()
|
||||
end
|
||||
|
||||
local initCheckBoxes = {}
|
||||
for i, v in ipairs(MsgTypeNamesOrdered) do
|
||||
initCheckBoxes[i] = {
|
||||
name = v,
|
||||
state = currCheckBoxState[v],
|
||||
}
|
||||
end
|
||||
|
||||
elements ["UtilAndTab"] = Roact.createElement(UtilAndTab, {
|
||||
windowWidth = size.X.Offset,
|
||||
formFactor = formFactor,
|
||||
tabList = tabList,
|
||||
orderedCheckBoxState = initCheckBoxes,
|
||||
isClientView = isClientView,
|
||||
searchTerm = searchTerm,
|
||||
layoutOrder = 1,
|
||||
|
||||
refForParent = self.utilRef,
|
||||
|
||||
onClientButton = isDeveloperView and self.onClientButton,
|
||||
onServerButton = isDeveloperView and self.onServerButton,
|
||||
onCheckBoxChanged = self.onCheckBoxChanged,
|
||||
onSearchTermChanged = self.onSearchTermChanged,
|
||||
})
|
||||
|
||||
if utilTabHeight > 0 then
|
||||
if isClientView then
|
||||
elements["ClientLog"] = Roact.createElement(ClientLog, {
|
||||
size = UDim2.new(1, 0, 1, -utilTabHeight),
|
||||
layoutOrder = 2,
|
||||
})
|
||||
else
|
||||
elements["ServerLog"] = Roact.createElement(ServerLog, {
|
||||
size = UDim2.new(1, 0, 1, -utilTabHeight),
|
||||
layoutOrder = 2,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
return Roact.createElement("Frame", {
|
||||
Size = size,
|
||||
BackgroundColor3 = Constants.Color.BaseGray,
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 3,
|
||||
|
||||
}, elements)
|
||||
end
|
||||
|
||||
local function mapStateToProps(state, props)
|
||||
return {
|
||||
isClientView = state.MainView.isClientView
|
||||
}
|
||||
end
|
||||
|
||||
local function mapDispatchToProps(dispatch)
|
||||
return {
|
||||
dispatchSetActiveTab = function (tabListIndex, isClientView)
|
||||
dispatch(SetActiveTab(tabListIndex, isClientView))
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
return RoactRodux.UNSTABLE_connect2(mapStateToProps, mapDispatchToProps)(
|
||||
DataConsumer(MainViewLog, "ClientLogData", "ServerLogData")
|
||||
)
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local RoactRodux = require(CorePackages.RoactRodux)
|
||||
local Store = require(CorePackages.Rodux).Store
|
||||
|
||||
local DataProvider = require(script.Parent.Parent.DataProvider)
|
||||
|
||||
local MainViewLog = require(script.Parent.MainViewLog)
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local store = Store.new(function()
|
||||
return {
|
||||
MainView = {
|
||||
currTabIndex = 0,
|
||||
isDeveloperView = true,
|
||||
},
|
||||
LogData = {
|
||||
clientSearchTerm = "",
|
||||
clientTypeFilters = {},
|
||||
serverSearchTerm = "",
|
||||
serverTypeFilters = {},
|
||||
}
|
||||
}
|
||||
end)
|
||||
|
||||
local element = Roact.createElement(RoactRodux.StoreProvider, {
|
||||
store = store,
|
||||
}, {
|
||||
DataProvider = Roact.createElement(DataProvider, {}, {
|
||||
MainViewLog = Roact.createElement(MainViewLog, {
|
||||
size = UDim2.new(),
|
||||
tabList = {},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
local DataConsumer = require(script.Parent.Parent.DataConsumer)
|
||||
|
||||
local Constants = require(script.Parent.Parent.Parent.Constants)
|
||||
local COMMANDLINE_HEIGHT = Constants.LogFormatting.CommandLineHeight
|
||||
local COMMANDLINE_PADDING = 4
|
||||
|
||||
local DevConsoleCommandLine = require(script.Parent.DevConsoleCommandLine)
|
||||
local LogOutput = require(script.Parent.LogOutput)
|
||||
|
||||
local ServerLog = Roact.Component:extend("ServerLog")
|
||||
|
||||
function ServerLog:init()
|
||||
self.initServerLogData = function()
|
||||
return self.props.ServerLogData:getLogData()
|
||||
end
|
||||
end
|
||||
|
||||
function ServerLog:render()
|
||||
return Roact.createElement("Frame", {
|
||||
Size = self.props.size,
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = self.props.layoutOrder,
|
||||
}, {
|
||||
Scroll = Roact.createElement(LogOutput, {
|
||||
size = UDim2.new(1, 0 , 1, -(COMMANDLINE_HEIGHT + COMMANDLINE_PADDING)),
|
||||
targetSignal = self.props.ServerLogData:Signal(),
|
||||
initLogOutput = self.initServerLogData,
|
||||
}),
|
||||
|
||||
CommandLine = Roact.createElement(DevConsoleCommandLine, {
|
||||
pos = UDim2.new(0,0,1,-COMMANDLINE_HEIGHT),
|
||||
height = COMMANDLINE_HEIGHT
|
||||
}),
|
||||
})
|
||||
end
|
||||
|
||||
return DataConsumer(ServerLog, "ServerLogData")
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local RoactRodux = require(CorePackages.RoactRodux)
|
||||
local Store = require(CorePackages.Rodux).Store
|
||||
|
||||
local DataProvider = require(script.Parent.Parent.DataProvider)
|
||||
local ServerLog = require(script.Parent.ServerLog)
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local store = Store.new(function()
|
||||
return {
|
||||
MainView = {
|
||||
isDeveloperView = true,
|
||||
},
|
||||
}
|
||||
end)
|
||||
|
||||
local element = Roact.createElement(RoactRodux.StoreProvider, {
|
||||
store = store,
|
||||
}, {
|
||||
DataProvider = Roact.createElement(DataProvider, {}, {
|
||||
ServerLog = Roact.createElement(ServerLog)
|
||||
})
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
local Components = script.Parent.Parent.Parent.Components
|
||||
local DataConsumer = require(Components.DataConsumer)
|
||||
local MemoryView = require(Components.Memory.MemoryView)
|
||||
|
||||
local ClientMemory = Roact.Component:extend("ClientMemory")
|
||||
|
||||
function ClientMemory:render()
|
||||
return Roact.createElement(MemoryView, {
|
||||
layoutOrder = self.props.layoutOrder,
|
||||
size = self.props.size,
|
||||
searchTerm = self.props.searchTerm,
|
||||
targetMemoryData = self.props.ClientMemoryData,
|
||||
})
|
||||
end
|
||||
|
||||
return DataConsumer(ClientMemory, "ClientMemoryData")
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local RoactRodux = require(CorePackages.RoactRodux)
|
||||
local Store = require(CorePackages.Rodux).Store
|
||||
|
||||
local DataProvider = require(script.Parent.Parent.DataProvider)
|
||||
local ClientMemory = require(script.Parent.ClientMemory)
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local store = Store.new(function()
|
||||
return {
|
||||
MainView = {
|
||||
isDeveloperView = true,
|
||||
},
|
||||
}
|
||||
end)
|
||||
|
||||
local element = Roact.createElement(RoactRodux.StoreProvider, {
|
||||
store = store,
|
||||
}, {
|
||||
DataProvider = Roact.createElement(DataProvider, {}, {
|
||||
ClientMemory = Roact.createElement(ClientMemory)
|
||||
})
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+404
@@ -0,0 +1,404 @@
|
||||
local Signal = require(script.Parent.Parent.Parent.Signal)
|
||||
local SoundService = game:GetService("SoundService")
|
||||
local MeshContentProvider = game:GetService("MeshContentProvider")
|
||||
local KeyframeSequenceProvider = game:GetService("KeyframeSequenceProvider")
|
||||
|
||||
local StatsService = game:GetService("Stats")
|
||||
local StatsUtils = require(script.Parent.Parent.Parent.Parent.Stats.StatsUtils)
|
||||
|
||||
local CircularBuffer = require(script.Parent.Parent.Parent.CircularBuffer)
|
||||
local Constants = require(script.Parent.Parent.Parent.Constants)
|
||||
local HEADER_NAMES = Constants.MemoryFormatting.ChartHeaderNames
|
||||
|
||||
local MAX_DATASET_COUNT = tonumber(settings():GetFVariable("NewDevConsoleMaxGraphCount"))
|
||||
|
||||
local CLIENT_POLLING_INTERVAL = 3 -- seconds
|
||||
local BYTE_IN_MB = 1048576
|
||||
|
||||
local SORT_COMPARATOR = {
|
||||
[HEADER_NAMES[1]] = function(a, b)
|
||||
return a.name < b.name
|
||||
end,
|
||||
[HEADER_NAMES[2]] = function(a, b)
|
||||
return a.dataStats.dataSet:back().data < b.dataStats.dataSet:back().data
|
||||
end,
|
||||
}
|
||||
|
||||
local ClientMemoryData = {}
|
||||
ClientMemoryData.__index = ClientMemoryData
|
||||
|
||||
function ClientMemoryData.new()
|
||||
local self = {}
|
||||
setmetatable(self, ClientMemoryData)
|
||||
|
||||
self._pollingId = 0
|
||||
self._totalMemory = 0
|
||||
self._memoryData = {}
|
||||
self._memoryDataSorted = {}
|
||||
self._treeViewUpdatedSignal = Signal.new()
|
||||
self._totalMemoryUpdated = Signal.new()
|
||||
self._sortType = HEADER_NAMES[1]
|
||||
self._isRunning = false
|
||||
|
||||
self._doGranularMemUpdate = {}
|
||||
self._granularMemTable = {}
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
local function GetMemoryPerformanceStatsItem()
|
||||
local performanceStats = StatsService and StatsService:FindFirstChild("PerformanceStats")
|
||||
if not performanceStats then
|
||||
return nil
|
||||
end
|
||||
local memoryStats = performanceStats:FindFirstChild(
|
||||
StatsUtils.StatNames[StatsUtils.StatType_Memory])
|
||||
return memoryStats
|
||||
end
|
||||
|
||||
-- the fetch memoryData functions are used for the Granular memory data.
|
||||
local function fetchSoundMemoryData()
|
||||
local soundMemData = SoundService:GetSoundMemoryData()
|
||||
local sortedSoundMem = {}
|
||||
|
||||
for i,v in pairs(soundMemData) do
|
||||
table.insert(sortedSoundMem, {
|
||||
name = i,
|
||||
value = v,
|
||||
})
|
||||
end
|
||||
|
||||
-- entries are ordered by insert time
|
||||
-- we want it sorted by memory size
|
||||
-- we could also have it sorted in SoundService to maybe speed this up
|
||||
table.sort(sortedSoundMem, function(a, b)
|
||||
return a.value > b.value
|
||||
end)
|
||||
|
||||
return sortedSoundMem
|
||||
end
|
||||
|
||||
local function fetchGraphicsTextureMemoryData()
|
||||
local textureData = StatsService:GetPaginatedMemoryByTexture(Enum.TextureQueryType.NonHumanoid, 0, 100)
|
||||
local textureData2 = StatsService:GetPaginatedMemoryByTexture(Enum.TextureQueryType.NonHumanoidOrphaned, 0, 100)
|
||||
local sortedTextureData = {}
|
||||
|
||||
local function aggregateData(retData, data)
|
||||
for _,v in ipairs(data.Results) do
|
||||
local mem = v.MemoryInBytes / BYTE_IN_MB
|
||||
table.insert(retData, {
|
||||
name = v.TextureId,
|
||||
value = mem,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
aggregateData(sortedTextureData, textureData)
|
||||
aggregateData(sortedTextureData, textureData2)
|
||||
|
||||
table.sort(sortedTextureData, function(a, b)
|
||||
return a.value > b.value
|
||||
end)
|
||||
return sortedTextureData
|
||||
end
|
||||
|
||||
local function fetchGraphicsTextureCharacterMemoryData()
|
||||
local textureData = StatsService:GetPaginatedMemoryByTexture(Enum.TextureQueryType.Humanoid, 0, 100)
|
||||
local textureData2 = StatsService:GetPaginatedMemoryByTexture(Enum.TextureQueryType.HumanoidOrphaned, 0, 100)
|
||||
local sortedTextureData = {}
|
||||
|
||||
local function aggregateData(retData, data)
|
||||
for _,v in ipairs(data.Results) do
|
||||
local mem = v.MemoryInBytes / BYTE_IN_MB
|
||||
local compTextures = {}
|
||||
|
||||
-- we need to parse out the asset id's from this large string
|
||||
-- the first portion involves only including the Texture portions and not color
|
||||
local txtComp = v.TextureId
|
||||
local tSeries = {}
|
||||
for a,b in pairs(string.split(txtComp, " ")) do
|
||||
local firstChar = string.sub(b, 1, 2)
|
||||
if firstChar == "T[" then
|
||||
table.insert(tSeries, b)
|
||||
end
|
||||
end
|
||||
|
||||
-- next we split out the mesh from the string.
|
||||
-- we can have multiple references to same ID so
|
||||
-- so we filter them out here
|
||||
local assetString = ""
|
||||
local dedupe = {}
|
||||
for a, b in pairs(tSeries) do
|
||||
local gotoAssetStr = string.split(b, ".mesh:")[2]
|
||||
-- set to 12 to get pass the ":'" in "rbxassertid:" and "http://""
|
||||
local matchIndex = string.find(gotoAssetStr, ":", 12)
|
||||
|
||||
-- we dont want to include the ":" we found
|
||||
if matchIndex ~= nil then
|
||||
matchIndex = matchIndex - 1
|
||||
end
|
||||
assetString = string.sub(gotoAssetStr, 1, matchIndex)
|
||||
dedupe[assetString] = true
|
||||
end
|
||||
|
||||
-- using the finalized strings, we construct the list of assets used for the
|
||||
-- composite texture
|
||||
for name,_ in pairs(dedupe) do
|
||||
table.insert(compTextures, {
|
||||
name = name
|
||||
})
|
||||
end
|
||||
|
||||
table.sort(compTextures, function(a, b)
|
||||
return a.name < b.name
|
||||
end)
|
||||
|
||||
table.insert(retData, {
|
||||
name = "Composite Texture",
|
||||
value = mem,
|
||||
moreInfo = compTextures,
|
||||
})
|
||||
end
|
||||
end
|
||||
aggregateData(sortedTextureData, textureData)
|
||||
aggregateData(sortedTextureData, textureData2)
|
||||
|
||||
return sortedTextureData
|
||||
end
|
||||
|
||||
local function fetchGraphicsMeshPartsMemoryData()
|
||||
local meshData = MeshContentProvider:getContentMemoryData()
|
||||
local sortedMeshData = {}
|
||||
|
||||
for name, bytes in pairs(meshData) do
|
||||
local mem = bytes / BYTE_IN_MB
|
||||
table.insert(sortedMeshData, {
|
||||
name = name,
|
||||
value = mem,
|
||||
})
|
||||
end
|
||||
|
||||
table.sort(sortedMeshData, function(a, b)
|
||||
return a.value > b.value
|
||||
end)
|
||||
|
||||
return sortedMeshData
|
||||
end
|
||||
|
||||
local function fetchFuncAnimation()
|
||||
local memstats = KeyframeSequenceProvider:GetMemStats()
|
||||
local sortedAnimationData = {}
|
||||
|
||||
for name, bytes in pairs(memstats) do
|
||||
local mem = bytes / BYTE_IN_MB
|
||||
table.insert(sortedAnimationData, {
|
||||
name = name,
|
||||
value = mem,
|
||||
})
|
||||
end
|
||||
|
||||
table.sort(sortedAnimationData, function(a, b)
|
||||
return a.value > b.value
|
||||
end)
|
||||
|
||||
return sortedAnimationData
|
||||
end
|
||||
|
||||
function ClientMemoryData:updateCachedData(categoryName, retrieveDataCallback)
|
||||
if self._doGranularMemUpdate[categoryName] then
|
||||
self._doGranularMemUpdate[categoryName] = false
|
||||
self._granularMemTable[categoryName] = retrieveDataCallback()
|
||||
end
|
||||
end
|
||||
|
||||
function ClientMemoryData:getAdditionalMemoryFunc(name)
|
||||
local fetchFunc = nil
|
||||
if name == "Sounds" then
|
||||
fetchFunc = fetchSoundMemoryData
|
||||
|
||||
elseif name == "GraphicsTexture" then
|
||||
fetchFunc = fetchGraphicsTextureMemoryData
|
||||
|
||||
elseif name == "GraphicsTextureCharacter" then
|
||||
fetchFunc = fetchGraphicsTextureCharacterMemoryData
|
||||
|
||||
elseif name == "GraphicsMeshParts" then
|
||||
fetchFunc = fetchGraphicsMeshPartsMemoryData
|
||||
elseif name == "csgDictionary" then
|
||||
-- this case requires more work to properly reflect the desired changes
|
||||
elseif name == "Animation" then
|
||||
fetchFunc = fetchFuncAnimation
|
||||
end
|
||||
|
||||
if fetchFunc then
|
||||
return function()
|
||||
self:updateCachedData(name, fetchFunc)
|
||||
return self._granularMemTable[name]
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
function ClientMemoryData:recursiveUpdateEntry(entryList, sortedList, statsItem)
|
||||
local name = StatsUtils.GetMemoryAnalyzerStatName(statsItem.Name)
|
||||
local data = statsItem:GetValue()
|
||||
|
||||
local children = statsItem:GetChildren()
|
||||
|
||||
if not entryList[name] then
|
||||
local newBuffer = CircularBuffer.new(MAX_DATASET_COUNT)
|
||||
|
||||
newBuffer:push_back({
|
||||
data = data,
|
||||
time = self._lastUpdate
|
||||
})
|
||||
|
||||
entryList[name] = {
|
||||
min = data,
|
||||
max = data,
|
||||
dataSet = newBuffer,
|
||||
children = #children > 0 and {},
|
||||
sortedChildren = #children > 0 and {},
|
||||
}
|
||||
|
||||
-- Mem Data is aggregated from allocations and deallocations and
|
||||
-- does not know about where the allocations come from. So, that data
|
||||
-- can't be par't of the tree we get from Stats
|
||||
-- We attach a callback for the specific entries that need to
|
||||
-- show addition memory to handle this.
|
||||
local memFunc = self:getAdditionalMemoryFunc(name)
|
||||
if memFunc then
|
||||
entryList[name]["additionalInfoFunc"] = memFunc
|
||||
self._doGranularMemUpdate[name] = true
|
||||
end
|
||||
|
||||
local newEntry = {
|
||||
name = name,
|
||||
dataStats = entryList[name]
|
||||
}
|
||||
|
||||
table.insert(sortedList, newEntry)
|
||||
else
|
||||
local currMax = entryList[name].max
|
||||
local currMin = entryList[name].min
|
||||
|
||||
local update = {
|
||||
data = data,
|
||||
time = self._lastUpdate
|
||||
}
|
||||
|
||||
-- if there is any change from the previous entry, flag
|
||||
-- this category for additionaInfo Update
|
||||
if entryList[name]["additionalInfoFunc"] then
|
||||
local last = entryList[name].dataSet:back();
|
||||
if last.data ~= update.data then
|
||||
self._doGranularMemUpdate[name] = true
|
||||
end
|
||||
end
|
||||
|
||||
local overwrittenEntry = entryList[name].dataSet:push_back(update)
|
||||
|
||||
if overwrittenEntry then
|
||||
local iter = entryList[name].dataSet:iterator()
|
||||
local dat = iter:next()
|
||||
if currMax == overwrittenEntry.data then
|
||||
currMax = currMin
|
||||
while dat do
|
||||
currMax = dat.data < currMax and currMax or dat.data
|
||||
dat = iter:next()
|
||||
end
|
||||
end
|
||||
if currMin == overwrittenEntry.data then
|
||||
currMin = currMax
|
||||
while dat do
|
||||
currMin = currMin < dat.data and currMin or dat.data
|
||||
dat = iter:next()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
entryList[name].max = currMax < data and data or currMax
|
||||
entryList[name].min = currMin < data and currMin or data
|
||||
end
|
||||
|
||||
for _, childStatItem in ipairs(children) do
|
||||
self:recursiveUpdateEntry(
|
||||
entryList[name].children,
|
||||
entryList[name].sortedChildren,
|
||||
childStatItem
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
function ClientMemoryData:totalMemSignal()
|
||||
return self._totalMemoryUpdated
|
||||
end
|
||||
|
||||
function ClientMemoryData:treeUpdatedSignal()
|
||||
return self._treeViewUpdatedSignal
|
||||
end
|
||||
|
||||
function ClientMemoryData:getSortType()
|
||||
return self._sortType
|
||||
end
|
||||
|
||||
local function recursiveSort(memoryDataSort, comparator)
|
||||
table.sort(memoryDataSort, comparator)
|
||||
for _, entry in pairs(memoryDataSort) do
|
||||
if entry.dataStats.sortedChildren then
|
||||
recursiveSort(entry.dataStats.sortedChildren, comparator)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ClientMemoryData:setSortType(sortType)
|
||||
if SORT_COMPARATOR[sortType] then
|
||||
self._sortType = sortType
|
||||
-- do we need a mutex type thing here?
|
||||
recursiveSort(self._memoryDataSorted, SORT_COMPARATOR[self._sortType])
|
||||
else
|
||||
error(string.format("attempted to pass invalid sortType: %s", tostring(sortType)), 2)
|
||||
end
|
||||
end
|
||||
|
||||
function ClientMemoryData:getMemoryData()
|
||||
return self._memoryDataSorted
|
||||
end
|
||||
|
||||
function ClientMemoryData:isRunning()
|
||||
return self._isRunning
|
||||
end
|
||||
|
||||
function ClientMemoryData:start()
|
||||
spawn(function()
|
||||
self._pollingId = self._pollingId + 1
|
||||
local instanced_pollingId = self._pollingId
|
||||
self._isRunning = true
|
||||
while instanced_pollingId == self._pollingId do
|
||||
local statsItem = GetMemoryPerformanceStatsItem()
|
||||
if not statsItem then
|
||||
return
|
||||
end
|
||||
self._lastUpdate = os.time()
|
||||
self:recursiveUpdateEntry(self._memoryData, self._memoryDataSorted, statsItem)
|
||||
|
||||
if self._totalMemory ~= statsItem:getValue() then
|
||||
self._totalMemory = statsItem:getValue()
|
||||
self._totalMemoryUpdated:Fire(self._totalMemory)
|
||||
end
|
||||
|
||||
self._treeViewUpdatedSignal:Fire(self._memoryDataSorted)
|
||||
|
||||
wait(CLIENT_POLLING_INTERVAL)
|
||||
end
|
||||
self._isRunning = false
|
||||
end)
|
||||
end
|
||||
|
||||
function ClientMemoryData:stop()
|
||||
-- listeners are responsible for disconnecting themselves
|
||||
self._pollingId = self._pollingId + 1
|
||||
end
|
||||
|
||||
return ClientMemoryData
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local RoactRodux = require(CorePackages.RoactRodux)
|
||||
|
||||
local Components = script.Parent.Parent.Parent.Components
|
||||
local ClientMemory = require(Components.Memory.ClientMemory)
|
||||
local ServerMemory = require(Components.Memory.ServerMemory)
|
||||
local UtilAndTab = require(Components.UtilAndTab)
|
||||
local DataConsumer = require(Components.DataConsumer)
|
||||
|
||||
local Actions = script.Parent.Parent.Parent.Actions
|
||||
local ClientMemoryUpdateSearchFilter = require(Actions.ClientMemoryUpdateSearchFilter)
|
||||
local ServerMemoryUpdateSearchFilter = require(Actions.ServerMemoryUpdateSearchFilter)
|
||||
|
||||
local Constants = require(script.Parent.Parent.Parent.Constants)
|
||||
local MAIN_ROW_PADDING = Constants.GeneralFormatting.MainRowPadding
|
||||
|
||||
|
||||
-- may want to move LogButton into its own module
|
||||
local LogButton = Roact.Component:extend("LogButton")
|
||||
function LogButton:init()
|
||||
self.onAction = function()
|
||||
local data = self.props.isClientView and self.props.ClientMemoryData or self.props.ServerMemoryData
|
||||
local result = "\n"
|
||||
local function line(str) result = result .. str .. "\n" end
|
||||
|
||||
line("Name Min Max")
|
||||
line("---------------------------------------- --- ---")
|
||||
|
||||
local function recurseMemoryTree(name, what, indent)
|
||||
line(string.format("%s %s %s %3d %3d",
|
||||
string.rep(" ", indent),
|
||||
name,
|
||||
string.rep(" ", 40 - indent - #name),
|
||||
what.min, what.max
|
||||
))
|
||||
if what.children then
|
||||
for subname,tree in pairs(what.children) do
|
||||
recurseMemoryTree(subname, tree, indent+2)
|
||||
end
|
||||
end
|
||||
end
|
||||
recurseMemoryTree("Memory", data._memoryData.Memory, 0)
|
||||
print(result)
|
||||
end
|
||||
end
|
||||
|
||||
function LogButton:render()
|
||||
return Roact.createElement("TextButton", {
|
||||
Text = "Log",
|
||||
Visible = true,
|
||||
BorderSizePixel = 1,
|
||||
BackgroundColor3 = Constants.Color.UnselectedGray,
|
||||
TextColor3 = Constants.Color.Text,
|
||||
[Roact.Event.Activated] = self.onAction
|
||||
})
|
||||
end
|
||||
LogButton = DataConsumer(LogButton, "ClientMemoryData", "ServerMemoryData")
|
||||
|
||||
|
||||
local MainViewMemory = Roact.Component:extend("MainViewMemory")
|
||||
function MainViewMemory:init()
|
||||
self.onUtilTabHeightChanged = function(utilTabHeight)
|
||||
self:setState({
|
||||
utilTabHeight = utilTabHeight
|
||||
})
|
||||
end
|
||||
|
||||
self.onClientButton = function()
|
||||
self:setState({isClientView = true})
|
||||
end
|
||||
|
||||
self.onServerButton = function()
|
||||
self:setState({isClientView = false})
|
||||
end
|
||||
|
||||
self.onSearchTermChanged = function(newSearchTerm)
|
||||
if self.state.isClientView then
|
||||
self.props.dispatchClientMemoryUpdateSearchFilter(newSearchTerm, {})
|
||||
else
|
||||
self.props.dispatchServerMemoryUpdateSearchFilter(newSearchTerm, {})
|
||||
end
|
||||
end
|
||||
|
||||
self.utilRef = Roact.createRef()
|
||||
|
||||
self.state = {
|
||||
utilTabHeight = 0,
|
||||
isClientView = true,
|
||||
}
|
||||
end
|
||||
|
||||
function MainViewMemory:didMount()
|
||||
local utilSize = self.utilRef.current.Size
|
||||
self:setState({
|
||||
utilTabHeight = utilSize.Y.Offset,
|
||||
})
|
||||
end
|
||||
|
||||
function MainViewMemory:didUpdate()
|
||||
local utilSize = self.utilRef.current.Size
|
||||
local height = utilSize.Y.Offset
|
||||
|
||||
if height ~= self.state.utilTabHeight then
|
||||
self:setState({
|
||||
utilTabHeight = height,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function MainViewMemory:render()
|
||||
local elements = {}
|
||||
local size = self.props.size
|
||||
local isDeveloperView = self.props.isDeveloperView
|
||||
local formFactor = self.props.formFactor
|
||||
local tabList = self.props.tabList
|
||||
|
||||
local utilTabHeight = self.state.utilTabHeight
|
||||
local isClientView = self.state.isClientView
|
||||
local searchTerm = isClientView and self.props.clientSearchTerm or self.props.serverSearchTerm
|
||||
|
||||
elements["UIListLayout"] = Roact.createElement("UIListLayout", {
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
Padding = UDim.new(0, MAIN_ROW_PADDING),
|
||||
})
|
||||
|
||||
elements ["UtilAndTab"] = Roact.createElement(UtilAndTab, {
|
||||
windowWidth = size.X.Offset,
|
||||
formFactor = formFactor,
|
||||
tabList = tabList,
|
||||
isClientView = isClientView,
|
||||
searchTerm = searchTerm,
|
||||
layoutOrder = 1,
|
||||
|
||||
refForParent = self.utilRef,
|
||||
|
||||
onHeightChanged = self.onUtilTabHeightChanged,
|
||||
onClientButton = isDeveloperView and self.onClientButton,
|
||||
onServerButton = isDeveloperView and self.onServerButton,
|
||||
onSearchTermChanged = self.onSearchTermChanged,
|
||||
}, {
|
||||
LogButton and Roact.createElement(LogButton, {isClientView = isClientView}),
|
||||
})
|
||||
|
||||
|
||||
if utilTabHeight > 0 then
|
||||
if isClientView then
|
||||
elements["ClientMemory"] = Roact.createElement(ClientMemory, {
|
||||
size = UDim2.new(1, 0, 1, -utilTabHeight),
|
||||
searchTerm = searchTerm,
|
||||
layoutOrder = 2,
|
||||
})
|
||||
else
|
||||
elements["ServerMemory"] = Roact.createElement(ServerMemory, {
|
||||
size = UDim2.new(1, 0, 1, -utilTabHeight),
|
||||
searchTerm = searchTerm,
|
||||
layoutOrder = 2,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
return Roact.createElement("Frame", {
|
||||
Size = size,
|
||||
BackgroundColor3 = Constants.Color.BaseGray,
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 3,
|
||||
}, elements)
|
||||
end
|
||||
|
||||
local function mapStateToProps(state, props)
|
||||
return {
|
||||
clientSearchTerm = state.MemoryData.clientSearchTerm,
|
||||
clientTypeFilters = state.MemoryData.clientTypeFilters,
|
||||
serverSearchTerm = state.MemoryData.serverSearchTerm,
|
||||
serverTypeFilters = state.MemoryData.serverTypeFilters,
|
||||
}
|
||||
end
|
||||
|
||||
local function mapDispatchToProps(dispatch)
|
||||
return {
|
||||
dispatchClientMemoryUpdateSearchFilter = function(searchTerm, filters)
|
||||
dispatch(ClientMemoryUpdateSearchFilter(searchTerm, filters))
|
||||
end,
|
||||
|
||||
dispatchServerMemoryUpdateSearchFilter = function(searchTerm, filters)
|
||||
dispatch(ServerMemoryUpdateSearchFilter(searchTerm, filters))
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
return RoactRodux.UNSTABLE_connect2(mapStateToProps, mapDispatchToProps)(MainViewMemory)
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local RoactRodux = require(CorePackages.RoactRodux)
|
||||
local Store = require(CorePackages.Rodux).Store
|
||||
|
||||
local DataProvider = require(script.Parent.Parent.DataProvider)
|
||||
local MainViewMemory = require(script.Parent.MainViewMemory)
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local store = Store.new(function()
|
||||
return {
|
||||
MainView = {
|
||||
currTabIndex = 0,
|
||||
isDeveloperView = true,
|
||||
},
|
||||
MemoryData = {
|
||||
clientSearchTerm = "",
|
||||
clientTypeFilters = {},
|
||||
serverSearchTerm = "",
|
||||
serverTypeFilters = {},
|
||||
}
|
||||
}
|
||||
end)
|
||||
|
||||
local element = Roact.createElement(RoactRodux.StoreProvider, {
|
||||
store = store,
|
||||
}, {
|
||||
DataProvider = Roact.createElement(DataProvider, {}, {
|
||||
MainViewMemory = Roact.createElement(MainViewMemory, {
|
||||
size = UDim2.new(),
|
||||
tabList = {},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+361
@@ -0,0 +1,361 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local TweenService = game:GetService("TweenService")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
local Components = script.Parent.Parent.Parent.Components
|
||||
local HeaderButton = require(Components.HeaderButton)
|
||||
local MemoryViewEntry = require(script.Parent.MemoryViewEntry)
|
||||
|
||||
local Constants = require(script.Parent.Parent.Parent.Constants)
|
||||
local LINE_WIDTH = Constants.GeneralFormatting.LineWidth
|
||||
local LINE_COLOR = Constants.GeneralFormatting.LineColor
|
||||
|
||||
local HEADER_NAMES = Constants.MemoryFormatting.ChartHeaderNames
|
||||
local HEADER_HEIGHT = Constants.GeneralFormatting.HeaderFrameHeight
|
||||
local VALUE_CELL_WIDTH = Constants.MemoryFormatting.ValueCellWidth
|
||||
local CELL_PADDING = Constants.MemoryFormatting.CellPadding
|
||||
local VALUE_PADDING = Constants.MemoryFormatting.ValuePadding
|
||||
|
||||
local ENTRY_HEIGHT = Constants.GeneralFormatting.EntryFrameHeight
|
||||
local GRAPH_HEIGHT = Constants.GeneralFormatting.LineGraphHeight
|
||||
|
||||
local NO_RESULT_SEARCH_STR = Constants.GeneralFormatting.NoResultSearchStr
|
||||
|
||||
local tweenInfo = TweenInfo.new(
|
||||
.3, -- Time
|
||||
Enum.EasingStyle.Back,
|
||||
Enum.EasingDirection.Out,
|
||||
0,
|
||||
false,
|
||||
0
|
||||
)
|
||||
|
||||
local MemoryView = Roact.Component:extend("MemoryView")
|
||||
|
||||
local function getX(entry)
|
||||
return entry.time
|
||||
end
|
||||
|
||||
local function getY(entry)
|
||||
return entry.data
|
||||
end
|
||||
|
||||
local function formatValueStr(value)
|
||||
return string.format("%.3f", value)
|
||||
end
|
||||
|
||||
function MemoryView:init(props)
|
||||
self.getOnButtonPress = function (name, index)
|
||||
return function(rbx, input)
|
||||
if self.state.expandIndex ~= name and name then
|
||||
local scrollingRef = self.scrollingRef.current
|
||||
-- LayoutOrder is used to keep track of the order of the entires.
|
||||
-- We subtrack 1 to account for the position of the top left corner
|
||||
-- of the entry, and another 1.4 to provide a 1.4 * ENTRY_HEIGHT's
|
||||
-- worth of buffer space
|
||||
if scrollingRef then
|
||||
local newCanvasPosY = (index-2.4) * ENTRY_HEIGHT
|
||||
TweenService:Create(
|
||||
scrollingRef,
|
||||
tweenInfo,
|
||||
{CanvasPosition = Vector2.new(0, newCanvasPosY)}
|
||||
):Play()
|
||||
end
|
||||
end
|
||||
|
||||
self:setState({
|
||||
expandIndex = self.state.expandIndex ~= name and name
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
self.onSortChanged = function(sortType)
|
||||
local currSortType = props.targetMemoryData:getSortType()
|
||||
if sortType == currSortType then
|
||||
self:setState({
|
||||
reverseSort = not self.state.reverseSort
|
||||
})
|
||||
else
|
||||
props.targetMemoryData:setSortType(sortType)
|
||||
self:setState({
|
||||
reverseSort = false,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
self.onCanvasPosChanged = function()
|
||||
local canvasPos = self.scrollingRef.current.CanvasPosition
|
||||
if self.state.canvasPos ~= canvasPos then
|
||||
self:setState({
|
||||
absScrollSize = self.scrollingRef.current.AbsoluteSize,
|
||||
canvasPos = canvasPos,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
self.scrollingRef = Roact.createRef()
|
||||
|
||||
self.state = {
|
||||
memoryData = props.targetMemoryData:getMemoryData(),
|
||||
reverseSort = false,
|
||||
expandIndex = false,
|
||||
}
|
||||
end
|
||||
|
||||
function MemoryView:willUpdate()
|
||||
if self.canvasPosConnector then
|
||||
self.canvasPosConnector:Disconnect()
|
||||
end
|
||||
end
|
||||
|
||||
function MemoryView:didUpdate()
|
||||
if self.scrollingRef.current then
|
||||
local signal = self.scrollingRef.current:GetPropertyChangedSignal("CanvasPosition")
|
||||
self.canvasPosConnector = signal:Connect(self.onCanvasPosChanged)
|
||||
end
|
||||
end
|
||||
|
||||
function MemoryView:didMount()
|
||||
local treeUpdatedSignal = self.props.targetMemoryData:treeUpdatedSignal()
|
||||
self.treeViewItemConnector = treeUpdatedSignal:Connect(function(memoryData)
|
||||
self:setState({
|
||||
memoryData = memoryData
|
||||
})
|
||||
end)
|
||||
|
||||
if self.scrollingRef.current then
|
||||
local signal = self.scrollingRef.current:GetPropertyChangedSignal("CanvasPosition")
|
||||
self.canvasPosConnector = signal:Connect(self.onCanvasPosChanged)
|
||||
|
||||
self:setState({
|
||||
absScrollSize = self.scrollingRef.current.AbsoluteSize,
|
||||
canvasPos = self.scrollingRef.current.CanvasPosition,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function MemoryView:willUnmount()
|
||||
self.treeViewItemConnector:Disconnect()
|
||||
end
|
||||
|
||||
function MemoryView:appendAdditionTabInformation(elements, infoTable, parentName, depth, windowing)
|
||||
local canvasPos = self.scrollingRef.current.CanvasPosition
|
||||
local absScrollSize = self.scrollingRef.current.AbsoluteSize
|
||||
|
||||
-- this function, where applicable is in clientMemoryData
|
||||
for _,additionalEntry in ipairs(infoTable) do
|
||||
local name = additionalEntry.name
|
||||
local value = additionalEntry.value
|
||||
local new_key = parentName .. name
|
||||
windowing.layoutOrder = windowing.layoutOrder + 1
|
||||
|
||||
if windowing.scrollingFrameHeight + ENTRY_HEIGHT >= canvasPos.Y then
|
||||
if windowing.usedFrameSpace < absScrollSize.Y then
|
||||
local new_key = parentName .. name
|
||||
|
||||
elements[new_key] = Roact.createElement(MemoryViewEntry, {
|
||||
size = UDim2.new(1, 0, 0, ENTRY_HEIGHT),
|
||||
depth = depth,
|
||||
name = name,
|
||||
|
||||
showGraph = false,
|
||||
value = value,
|
||||
|
||||
formatValueStr = formatValueStr,
|
||||
layoutOrder = windowing.layoutOrder,
|
||||
})
|
||||
if windowing.paddingHeight < 0 then
|
||||
windowing.paddingHeight = windowing.scrollingFrameHeight
|
||||
else
|
||||
windowing.usedFrameSpace = windowing.usedFrameSpace + ENTRY_HEIGHT
|
||||
end
|
||||
end
|
||||
end
|
||||
windowing.scrollingFrameHeight = windowing.scrollingFrameHeight + ENTRY_HEIGHT
|
||||
|
||||
if additionalEntry.moreInfo and type(additionalEntry.moreInfo) == "table" then
|
||||
self:appendAdditionTabInformation(elements, additionalEntry.moreInfo, new_key, depth + 1, windowing)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function MemoryView:recursiveConstructEntries(elements, entry, depth, windowing)
|
||||
assert(self.scrollingRef.current, "ScrollingFrame not initialized yet")
|
||||
|
||||
local expandIndex = self.state.expandIndex
|
||||
local searchTerm = self.props.searchTerm or ""
|
||||
local reverseSort = self.state.reverseSort
|
||||
local canvasPos = self.scrollingRef.current.CanvasPosition
|
||||
local absScrollSize = self.scrollingRef.current.AbsoluteSize
|
||||
|
||||
local name = entry.name
|
||||
|
||||
local found = string.find(name:lower(), searchTerm:lower())
|
||||
|
||||
if found then
|
||||
local showGraph = expandIndex == name
|
||||
local frameHeight = showGraph and ENTRY_HEIGHT + GRAPH_HEIGHT or ENTRY_HEIGHT
|
||||
windowing.layoutOrder = windowing.layoutOrder + 1
|
||||
|
||||
if windowing.scrollingFrameHeight + frameHeight >= canvasPos.Y then
|
||||
if windowing.usedFrameSpace < absScrollSize.Y then
|
||||
elements[name] = Roact.createElement(MemoryViewEntry, {
|
||||
size = UDim2.new(1, 0, 0, frameHeight),
|
||||
depth = depth,
|
||||
name = entry.name,
|
||||
showGraph = showGraph,
|
||||
dataStats = entry.dataStats,
|
||||
|
||||
onButtonPress = self.getOnButtonPress(name, windowing.layoutOrder),
|
||||
formatValueStr = formatValueStr,
|
||||
getX = getX,
|
||||
getY = getY,
|
||||
|
||||
layoutOrder = windowing.layoutOrder,
|
||||
})
|
||||
if windowing.paddingHeight < 0 then
|
||||
windowing.paddingHeight = windowing.scrollingFrameHeight
|
||||
else
|
||||
windowing.usedFrameSpace = windowing.usedFrameSpace + frameHeight
|
||||
end
|
||||
end
|
||||
end
|
||||
windowing.scrollingFrameHeight = windowing.scrollingFrameHeight + frameHeight
|
||||
|
||||
-- callback is set in ClientMemoryData
|
||||
if showGraph then
|
||||
-- this function, where applicable is in clientMemoryData
|
||||
if entry.dataStats.additionalInfoFunc then
|
||||
local infoTable = entry.dataStats.additionalInfoFunc()
|
||||
self:appendAdditionTabInformation(elements, infoTable, entry.name, depth + 1, windowing)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local sortedChildren = entry.dataStats.sortedChildren
|
||||
if sortedChildren then
|
||||
if reverseSort then
|
||||
local totalChildren = #sortedChildren
|
||||
for i = 1, totalChildren do
|
||||
self:recursiveConstructEntries(elements, sortedChildren[totalChildren - i + 1], depth + 1, windowing)
|
||||
end
|
||||
else
|
||||
for _, entry in ipairs(sortedChildren) do
|
||||
self:recursiveConstructEntries(elements, entry, depth + 1, windowing)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function MemoryView:render()
|
||||
local elements = {}
|
||||
local layoutOrder = self.props.layoutOrder
|
||||
local size = self.props.size
|
||||
local searchTerm = self.props.searchTerm or ""
|
||||
|
||||
-- we pass this table into the recursion to keep sum up
|
||||
-- height totals for windowing
|
||||
local windowingInfo = {
|
||||
scrollingFrameHeight = 0,
|
||||
paddingHeight = -1,
|
||||
usedFrameSpace = 0,
|
||||
layoutOrder = 1
|
||||
}
|
||||
|
||||
if self.scrollingRef.current then
|
||||
for _, entry in ipairs(self.state.memoryData) do
|
||||
self:recursiveConstructEntries(elements, entry, 0, windowingInfo)
|
||||
end
|
||||
|
||||
elements["UIListLayout"] = Roact.createElement("UIListLayout", {
|
||||
FillDirection = Enum.FillDirection.Vertical,
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Left,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Top,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
})
|
||||
|
||||
elements["WindowingPadding"] = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, windowingInfo.paddingHeight),
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 1,
|
||||
})
|
||||
end
|
||||
|
||||
if windowingInfo.layoutOrder == 1 then
|
||||
if searchTerm == "" then
|
||||
elements["noDataMessage"] = Roact.createElement("TextLabel", {
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
Position = UDim2.new(0, 0, 0, 0),
|
||||
Text = "Awaiting Memory Stats",
|
||||
TextColor3 = Constants.Color.Text,
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = layoutOrder,
|
||||
})
|
||||
else
|
||||
local noResultSearchStr = string.format(NO_RESULT_SEARCH_STR, searchTerm )
|
||||
|
||||
elements["noDataMessage"] = Roact.createElement("TextLabel", {
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
Position = UDim2.new(0, 0, 0, 0),
|
||||
Text = noResultSearchStr,
|
||||
TextColor3 = Constants.Color.Text,
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = layoutOrder,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
return Roact.createElement("Frame", {
|
||||
Size = size,
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = layoutOrder,
|
||||
}, {
|
||||
Header = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, HEADER_HEIGHT),
|
||||
BackgroundTransparency = 1,
|
||||
}, {
|
||||
Name = Roact.createElement(HeaderButton, {
|
||||
text = HEADER_NAMES[1],
|
||||
size = UDim2.new(1 - VALUE_CELL_WIDTH, -VALUE_PADDING - CELL_PADDING, 0, HEADER_HEIGHT),
|
||||
pos = UDim2.new(0, CELL_PADDING, 0, 0),
|
||||
sortfunction = self.onSortChanged,
|
||||
}),
|
||||
ValueMB = Roact.createElement(HeaderButton, {
|
||||
text = HEADER_NAMES[2],
|
||||
size = UDim2.new( VALUE_CELL_WIDTH, -CELL_PADDING, 0, HEADER_HEIGHT),
|
||||
pos = UDim2.new(1 - VALUE_CELL_WIDTH, VALUE_PADDING, 0, 0),
|
||||
sortfunction = self.onSortChanged,
|
||||
}),
|
||||
TopHorizontal = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, 1),
|
||||
BackgroundColor3 = LINE_COLOR,
|
||||
BorderSizePixel = 0,
|
||||
}),
|
||||
LowerHorizontal = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, LINE_WIDTH),
|
||||
Position = UDim2.new(0, 0, 1, 0),
|
||||
BackgroundColor3 = LINE_COLOR,
|
||||
BorderSizePixel = 0,
|
||||
}),
|
||||
vertical = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(0, LINE_WIDTH, 1, 0),
|
||||
Position = UDim2.new(1 - VALUE_CELL_WIDTH, 0, 0, 0),
|
||||
BackgroundColor3 = LINE_COLOR,
|
||||
BorderSizePixel = 0,
|
||||
}),
|
||||
}),
|
||||
|
||||
Entries = Roact.createElement("ScrollingFrame", {
|
||||
Size = UDim2.new(1, 0, 1, -HEADER_HEIGHT),
|
||||
Position = UDim2.new(0, 0, 0, HEADER_HEIGHT),
|
||||
BackgroundTransparency = 1,
|
||||
VerticalScrollBarInset = 1,
|
||||
ScrollBarThickness = 5,
|
||||
CanvasSize = UDim2.new(1, 0, 0, windowingInfo.scrollingFrameHeight),
|
||||
|
||||
[Roact.Ref] = self.scrollingRef,
|
||||
}, elements),
|
||||
})
|
||||
end
|
||||
|
||||
return MemoryView
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
local Signal = require(script.Parent.Parent.Parent.Signal)
|
||||
|
||||
local MemoryView = require(script.Parent.MemoryView)
|
||||
|
||||
local dummmyMemoryData = {
|
||||
getMemoryData = function ()
|
||||
return {
|
||||
summaryTable = {},
|
||||
summaryCount = 0,
|
||||
entryList = nil,
|
||||
}
|
||||
end,
|
||||
treeUpdatedSignal = function ()
|
||||
return Signal.new()
|
||||
end,
|
||||
}
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local element = Roact.createElement(MemoryView, {
|
||||
targetMemoryData = dummmyMemoryData,
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
local Components = script.Parent.Parent.Parent.Components
|
||||
local CellLabel = require(Components.CellLabel)
|
||||
local BannerButton = require(Components.BannerButton)
|
||||
local LineGraph = require(Components.LineGraph)
|
||||
|
||||
local Constants = require(script.Parent.Parent.Parent.Constants)
|
||||
local LINE_WIDTH = Constants.GeneralFormatting.LineWidth
|
||||
local LINE_COLOR = Constants.GeneralFormatting.LineColor
|
||||
local VALUE_CELL_WIDTH = Constants.MemoryFormatting.ValueCellWidth
|
||||
local CELL_PADDING = Constants.MemoryFormatting.CellPadding
|
||||
local VALUE_PADDING = Constants.MemoryFormatting.ValuePadding
|
||||
|
||||
local ENTRY_HEIGHT = Constants.GeneralFormatting.EntryFrameHeight
|
||||
local DEPTH_INDENT = Constants.MemoryFormatting.DepthIndent
|
||||
|
||||
local convertTimeStamp = require(script.Parent.Parent.Parent.Util.convertTimeStamp)
|
||||
|
||||
return function(props)
|
||||
local size = props.size
|
||||
local depth = props.depth
|
||||
local showGraph = props.showGraph
|
||||
local dataStats = props.dataStats
|
||||
local layoutOrder = props.layoutOrder
|
||||
|
||||
local onButtonPress = props.onButtonPress
|
||||
local formatValueStr = props.formatValueStr
|
||||
local getX = props.getX
|
||||
local getY = props.getY
|
||||
|
||||
local offset = depth * DEPTH_INDENT
|
||||
|
||||
local name = props.name
|
||||
local value = props.value
|
||||
if dataStats then
|
||||
value = dataStats.dataSet:back().data
|
||||
end
|
||||
|
||||
return Roact.createElement("Frame", {
|
||||
Size = size,
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = layoutOrder,
|
||||
}, {
|
||||
button = Roact.createElement(BannerButton, {
|
||||
size = UDim2.new(1, - offset, 0, ENTRY_HEIGHT),
|
||||
pos = UDim2.new(0, offset, 0, 0),
|
||||
isExpanded = showGraph,
|
||||
|
||||
onButtonPress = onButtonPress,
|
||||
}, {
|
||||
name = Roact.createElement(CellLabel, {
|
||||
text = name,
|
||||
size = UDim2.new(1, 0, 1, 0),
|
||||
pos = UDim2.new(0, CELL_PADDING, 0, 0),
|
||||
}),
|
||||
|
||||
horizonal1 = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, -offset , 0, LINE_WIDTH),
|
||||
Position = UDim2.new(0, offset, 0, 0),
|
||||
BackgroundColor3 = LINE_COLOR,
|
||||
BorderSizePixel = 0,
|
||||
}),
|
||||
|
||||
horizonal2 = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, -offset, 0, LINE_WIDTH),
|
||||
Position = UDim2.new(0, offset, 1, 0),
|
||||
BackgroundColor3 = LINE_COLOR,
|
||||
BorderSizePixel = 0,
|
||||
}),
|
||||
}),
|
||||
|
||||
value = value and Roact.createElement(CellLabel, {
|
||||
text = formatValueStr(value),
|
||||
size = UDim2.new(VALUE_CELL_WIDTH, -VALUE_PADDING, 0, ENTRY_HEIGHT),
|
||||
pos = UDim2.new(1 - VALUE_CELL_WIDTH, VALUE_PADDING, 0, 0),
|
||||
}),
|
||||
|
||||
vertical = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(0, 1, 0, ENTRY_HEIGHT),
|
||||
Position = UDim2.new(1 - VALUE_CELL_WIDTH, 0, 0, 0),
|
||||
BackgroundColor3 = LINE_COLOR,
|
||||
BorderSizePixel = 0,
|
||||
}),
|
||||
|
||||
Graph = showGraph and Roact.createElement(LineGraph, {
|
||||
pos = UDim2.new(0, 0, 0, ENTRY_HEIGHT),
|
||||
size = UDim2.new(1, 0, 1, -ENTRY_HEIGHT),
|
||||
graphData = dataStats.dataSet,
|
||||
maxY = dataStats.max,
|
||||
minY = dataStats.min,
|
||||
|
||||
getX = getX,
|
||||
getY = getY,
|
||||
|
||||
stringFormatX = convertTimeStamp,
|
||||
stringFormatY = formatValueStr,
|
||||
|
||||
axisLabelX = "Timestamp",
|
||||
axisLabelY = name,
|
||||
}),
|
||||
})
|
||||
end
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local RoactRodux = require(CorePackages.RoactRodux)
|
||||
local Store = require(CorePackages.Rodux).Store
|
||||
|
||||
local CircularBuffer = require(script.Parent.Parent.Parent.CircularBuffer)
|
||||
|
||||
local DataProvider = require(script.Parent.Parent.DataProvider)
|
||||
local MemoryViewEntry = require(script.Parent.MemoryViewEntry)
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local store = Store.new(function()
|
||||
return {
|
||||
MainView = {
|
||||
isDeveloperView = true,
|
||||
},
|
||||
}
|
||||
end)
|
||||
|
||||
local dummyScriptEntry = {
|
||||
time = 0,
|
||||
data = {0, 0},
|
||||
}
|
||||
|
||||
local dummyDataSet = CircularBuffer.new(1)
|
||||
dummyDataSet:push_back(dummyScriptEntry)
|
||||
|
||||
local formatValueStr = function()
|
||||
return ""
|
||||
end
|
||||
|
||||
local dummyEntry = {
|
||||
name = "",
|
||||
dataStats = {
|
||||
dataSet = dummyDataSet,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
local element = Roact.createElement(RoactRodux.StoreProvider, {
|
||||
store = store,
|
||||
}, {
|
||||
DataProvider = Roact.createElement(DataProvider, {}, {
|
||||
MemoryViewEntry = Roact.createElement(MemoryViewEntry, {
|
||||
depth = 0,
|
||||
entry = dummyEntry,
|
||||
formatValueStr = formatValueStr,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
local Components = script.Parent.Parent.Parent.Components
|
||||
local DataConsumer = require(Components.DataConsumer)
|
||||
local MemoryView = require(Components.Memory.MemoryView)
|
||||
|
||||
local ServerMemory = Roact.Component:extend("ServerMemory")
|
||||
|
||||
function ServerMemory:render()
|
||||
return Roact.createElement(MemoryView, {
|
||||
layoutOrder = self.props.layoutOrder,
|
||||
size = self.props.size,
|
||||
searchTerm = self.props.searchTerm,
|
||||
targetMemoryData = self.props.ServerMemoryData,
|
||||
})
|
||||
end
|
||||
|
||||
return DataConsumer(ServerMemory, "ServerMemoryData")
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local RoactRodux = require(CorePackages.RoactRodux)
|
||||
local Store = require(CorePackages.Rodux).Store
|
||||
|
||||
local DataProvider = require(script.Parent.Parent.DataProvider)
|
||||
local ServerMemory = require(script.Parent.ServerMemory)
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local store = Store.new(function()
|
||||
return {
|
||||
MainView = {
|
||||
isDeveloperView = true,
|
||||
},
|
||||
}
|
||||
end)
|
||||
|
||||
local element = Roact.createElement(RoactRodux.StoreProvider, {
|
||||
store = store,
|
||||
}, {
|
||||
DataProvider = Roact.createElement(DataProvider, nil, {
|
||||
ServerMemory = Roact.createElement(ServerMemory)
|
||||
})
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+239
@@ -0,0 +1,239 @@
|
||||
local Signal = require(script.Parent.Parent.Parent.Signal)
|
||||
local CircularBuffer = require(script.Parent.Parent.Parent.CircularBuffer)
|
||||
local Constants = require(script.Parent.Parent.Parent.Constants)
|
||||
local HEADER_NAMES = Constants.MemoryFormatting.ChartHeaderNames
|
||||
|
||||
local MAX_DATASET_COUNT = tonumber(settings():GetFVariable("NewDevConsoleMaxGraphCount"))
|
||||
local BYTES_PER_MB = 1048576.0
|
||||
|
||||
local SORT_COMPARATOR = {
|
||||
[HEADER_NAMES[1]] = function(a, b)
|
||||
return a.name < b.name
|
||||
end,
|
||||
[HEADER_NAMES[2]] = function(a, b)
|
||||
return a.dataStats.dataSet:back().data < b.dataStats.dataSet:back().data
|
||||
end,
|
||||
}
|
||||
|
||||
local getClientReplicator = require(script.Parent.Parent.Parent.Util.getClientReplicator)
|
||||
|
||||
local ServerMemoryData = {}
|
||||
ServerMemoryData.__index = ServerMemoryData
|
||||
|
||||
function ServerMemoryData.new()
|
||||
local self = {}
|
||||
|
||||
setmetatable(self, ServerMemoryData)
|
||||
self._init = false
|
||||
self._isRunning = false
|
||||
self._totalMemory = 0
|
||||
|
||||
self._memoryData = {}
|
||||
self._memoryDataSorted = {}
|
||||
|
||||
self._coreTreeData = {}
|
||||
self._coreTreeDataSorted = {}
|
||||
|
||||
self._placeTreeData = {}
|
||||
self._placeTreeDataSorted = {}
|
||||
|
||||
self._treeViewUpdated = Signal.new()
|
||||
self._sortType = HEADER_NAMES[1]
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function ServerMemoryData:updateEntry(entryList, sortedList, name, data)
|
||||
if not entryList[name] then
|
||||
local newBuffer = CircularBuffer.new(MAX_DATASET_COUNT)
|
||||
|
||||
newBuffer:push_back({
|
||||
data = data,
|
||||
time = self._lastUpdate
|
||||
})
|
||||
|
||||
entryList[name] = {
|
||||
min = data,
|
||||
max = data,
|
||||
dataSet = newBuffer
|
||||
}
|
||||
|
||||
local newEntry = {
|
||||
name = name,
|
||||
dataStats = entryList[name]
|
||||
}
|
||||
|
||||
table.insert(sortedList, newEntry)
|
||||
else
|
||||
local currMax = entryList[name].max
|
||||
local currMin = entryList[name].min
|
||||
|
||||
local update = {
|
||||
data = data,
|
||||
time = self._lastUpdate
|
||||
}
|
||||
|
||||
local overwrittenEntry = entryList[name].dataSet:push_back(update)
|
||||
|
||||
if overwrittenEntry then
|
||||
local iter = entryList[name].dataSet:iterator()
|
||||
local dat = iter:next()
|
||||
if currMax == overwrittenEntry.data then
|
||||
currMax = currMin
|
||||
while dat do
|
||||
currMax = dat.data < currMax and currMax or dat.data
|
||||
dat = iter:next()
|
||||
end
|
||||
end
|
||||
if currMin == overwrittenEntry.data then
|
||||
currMin = currMax
|
||||
while dat do
|
||||
currMin = currMin < dat.data and currMin or dat.data
|
||||
dat = iter:next()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
entryList[name].max = currMax < data and data or currMax
|
||||
entryList[name].min = currMin < data and currMin or data
|
||||
end
|
||||
end
|
||||
|
||||
function ServerMemoryData:updateEntryList(entryList, sortedList, statsItems)
|
||||
-- All values are in bytes.
|
||||
-- Convert to MB ASAP.
|
||||
local totalMB = 0
|
||||
for label, numBytes in pairs(statsItems) do
|
||||
local value = numBytes / BYTES_PER_MB
|
||||
totalMB = totalMB + value
|
||||
|
||||
self:updateEntry(entryList, sortedList, label, value)
|
||||
end
|
||||
|
||||
return totalMB
|
||||
end
|
||||
|
||||
function ServerMemoryData:updateWithTreeStats(stats)
|
||||
local update = {
|
||||
PlaceMemory = 0,
|
||||
CoreMemory = 0,
|
||||
UntrackedMemory = 0,
|
||||
}
|
||||
|
||||
for key, value in pairs(stats) do
|
||||
if key == "totalServerMemory" then
|
||||
self._totalMemory = value / BYTES_PER_MB
|
||||
elseif key == "developerTags" then
|
||||
update.PlaceMemory = self:updateEntryList(self._placeTreeData, self._placeTreeDataSorted, value)
|
||||
elseif key == "internalCategories" then
|
||||
update.CoreMemory = self:updateEntryList(self._coreTreeData, self._coreTreeDataSorted, value)
|
||||
end
|
||||
end
|
||||
|
||||
update.UntrackedMemory = self._totalMemory - update.PlaceMemory - update.CoreMemory
|
||||
|
||||
if self._init then
|
||||
for name, value in pairs(update) do
|
||||
self:updateEntry(
|
||||
self._memoryData["Memory"].children,
|
||||
self._memoryData["Memory"].sortedChildren,
|
||||
name,
|
||||
value
|
||||
)
|
||||
end
|
||||
|
||||
self:updateEntry(self._memoryData, self._memoryDataSorted, "Memory", self._totalMemory)
|
||||
else
|
||||
local memChildren = {}
|
||||
local memChildrenSorted = {}
|
||||
|
||||
for name, value in pairs(update) do
|
||||
self:updateEntry(memChildren, memChildrenSorted, name, value)
|
||||
end
|
||||
|
||||
self:updateEntry(self._memoryData, self._memoryDataSorted, "Memory", self._totalMemory)
|
||||
|
||||
memChildren["PlaceMemory"].children = self._placeTreeData
|
||||
memChildren["PlaceMemory"].sortedChildren = self._placeTreeDataSorted
|
||||
|
||||
memChildren["CoreMemory"].children = self._coreTreeData
|
||||
memChildren["CoreMemory"].sortedChildren = self._coreTreeDataSorted
|
||||
|
||||
self._memoryData["Memory"].children = memChildren
|
||||
self._memoryData["Memory"].sortedChildren = memChildrenSorted
|
||||
|
||||
self._init = true
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function ServerMemoryData:totalMemSignal()
|
||||
return self._totalMemoryUpdated
|
||||
end
|
||||
|
||||
function ServerMemoryData:treeUpdatedSignal()
|
||||
return self._treeViewUpdated
|
||||
end
|
||||
|
||||
function ServerMemoryData:getSortType()
|
||||
return self._sortType
|
||||
end
|
||||
|
||||
local function recursiveSort(memoryDataSort, comparator)
|
||||
table.sort(memoryDataSort, comparator)
|
||||
for _, entry in pairs(memoryDataSort) do
|
||||
if entry.dataStats.sortedChildren then
|
||||
recursiveSort(entry.dataStats.sortedChildren, comparator)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ServerMemoryData:setSortType(sortType)
|
||||
if SORT_COMPARATOR[sortType] then
|
||||
self._sortType = sortType
|
||||
recursiveSort(self._memoryDataSorted, SORT_COMPARATOR[self._sortType])
|
||||
else
|
||||
error(string.format("attempted to pass invalid sortType: %s", tostring(sortType)), 2)
|
||||
end
|
||||
end
|
||||
|
||||
function ServerMemoryData:getMemoryData()
|
||||
return self._memoryDataSorted
|
||||
end
|
||||
|
||||
function ServerMemoryData:isRunning()
|
||||
return self._isRunning
|
||||
end
|
||||
|
||||
function ServerMemoryData:start()
|
||||
local clientReplicator = getClientReplicator()
|
||||
if clientReplicator and not self._statsListenerConnection then
|
||||
self._statsListenerConnection = clientReplicator.StatsReceived:connect(function(stats)
|
||||
if not stats.ServerMemoryTree then
|
||||
return
|
||||
end
|
||||
self._lastUpdate = os.time()
|
||||
|
||||
local serverMemoryTree = stats.ServerMemoryTree
|
||||
if serverMemoryTree then
|
||||
self:updateWithTreeStats(serverMemoryTree)
|
||||
self._treeViewUpdated:Fire(self._memoryDataSorted)
|
||||
end
|
||||
|
||||
end)
|
||||
clientReplicator:RequestServerStats(true)
|
||||
self._isRunning = true
|
||||
end
|
||||
end
|
||||
|
||||
function ServerMemoryData:stop()
|
||||
-- listeners are responsible for disconnecting themselves
|
||||
local clientReplicator = getClientReplicator()
|
||||
if clientReplicator then
|
||||
clientReplicator:RequestServerStats(false)
|
||||
self._statsListenerConnection:Disconnect()
|
||||
self._isRunning = false
|
||||
end
|
||||
end
|
||||
|
||||
return ServerMemoryData
|
||||
+213
@@ -0,0 +1,213 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local LogService = game:GetService("LogService")
|
||||
local AnalyticsService = game:GetService("RbxAnalyticsService")
|
||||
local Settings = UserSettings()
|
||||
local GameSettings = Settings.GameSettings
|
||||
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
local Components = script.Parent.Parent.Parent.Components
|
||||
local UtilAndTab = require(Components.UtilAndTab)
|
||||
local ServerProfilerInterface = require(script.Parent.ServerProfilerInterface)
|
||||
|
||||
local Constants = require(script.Parent.Parent.Parent.Constants)
|
||||
local PADDING = Constants.GeneralFormatting.MainRowPadding
|
||||
local BUTTON_WIDTH = Constants.MicroProfilerFormatting.ButtonWidth
|
||||
local TEXT_SIZE = Constants.MicroProfilerFormatting.ButtonTextSize
|
||||
local FONT = Constants.Font.MainWindow
|
||||
local HEADER_FONT = Constants.Font.MainWindowHeader
|
||||
local BUTTON_UNSELECTED = Constants.Color.UnselectedGray
|
||||
local BUTTON_SELECTED = Constants.Color.SelectedBlue
|
||||
|
||||
local BUTTON_COLOR = Constants.Color.UnselectedGray
|
||||
|
||||
local ROW_HEIGHT = 30
|
||||
local OFFSET = .10
|
||||
local ROW_VALUE_WIDTH = .8
|
||||
local MICROPROFILER_PRESSED_COUNTERNAME ="MicroprofilerDevConsolePressed"
|
||||
|
||||
local FFlagMicroProfilerSessionAnalytics = settings():GetFFlag("MicroProfilerSessionAnalytics")
|
||||
|
||||
local MainViewProfiler = Roact.Component:extend("MainViewProfiler")
|
||||
|
||||
function MainViewProfiler:init()
|
||||
self.onUtilTabHeightChanged = function(utilTabHeight)
|
||||
self:setState({
|
||||
utilTabHeight = utilTabHeight
|
||||
})
|
||||
end
|
||||
|
||||
self.changeProfilerState = function(screenProfilerEnabled)
|
||||
return function()
|
||||
GameSettings.OnScreenProfilerEnabled = screenProfilerEnabled
|
||||
self:setState({
|
||||
clientProfilerEnabled = screenProfilerEnabled
|
||||
})
|
||||
|
||||
if FFlagMicroProfilerSessionAnalytics then
|
||||
AnalyticsService:ReportCounter(MICROPROFILER_PRESSED_COUNTERNAME)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local microProfilerChangedSignal = GameSettings:GetPropertyChangedSignal("OnScreenProfilerEnabled")
|
||||
self.microProfilerChangedConnection = microProfilerChangedSignal:Connect(function()
|
||||
self:setState({
|
||||
clientProfilerEnabled = GameSettings.OnScreenProfilerEnabled
|
||||
})
|
||||
end)
|
||||
|
||||
|
||||
self.utilRef = Roact.createRef()
|
||||
|
||||
self.state = {
|
||||
utilTabHeight = 0,
|
||||
}
|
||||
end
|
||||
|
||||
function MainViewProfiler:didMount()
|
||||
local utilSize = self.utilRef.current.Size
|
||||
|
||||
self:setState({
|
||||
utilTabHeight = utilSize.Y.Offset
|
||||
})
|
||||
end
|
||||
|
||||
function MainViewProfiler:willUnmount()
|
||||
if self.microProfilerChangedConnection then
|
||||
self.microProfilerChangedConnection:Disconnect()
|
||||
self.microProfilerChangedConnection = nil
|
||||
end
|
||||
end
|
||||
|
||||
function MainViewProfiler:didUpdate()
|
||||
local utilSize = self.utilRef.current.Size
|
||||
if utilSize.Y.Offset ~= self.state.utilTabHeight then
|
||||
self:setState({
|
||||
utilTabHeight = utilSize.Y.Offset
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function MainViewProfiler:render()
|
||||
local size = self.props.size
|
||||
local formFactor = self.props.formFactor
|
||||
local tabList = self.props.tabList
|
||||
|
||||
local utilTabHeight = self.state.utilTabHeight
|
||||
|
||||
local frameRate = self.state.frameRate
|
||||
local timeFrame = self.state.timeFrame
|
||||
local waitingForData = self.state.waitingForData
|
||||
local clientProfilerEnabled = self.state.clientProfilerEnabled
|
||||
local outputPath = self.state.outputPath
|
||||
|
||||
local textbox_size = UDim2.new(ROW_VALUE_WIDTH, -BUTTON_WIDTH, 0, ROW_HEIGHT)
|
||||
local textbox_pos = UDim2.new(1 - ROW_VALUE_WIDTH / 2, BUTTON_WIDTH, 0, 0)
|
||||
|
||||
return Roact.createElement("Frame", {
|
||||
Size = size,
|
||||
BackgroundColor3 = Constants.Color.BaseGray,
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 3
|
||||
}, {
|
||||
UIListLayout = Roact.createElement("UIListLayout", {
|
||||
Padding = UDim.new(0, PADDING),
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
}),
|
||||
|
||||
UtilAndTab = Roact.createElement(UtilAndTab, {
|
||||
windowWidth = size.X.Offset,
|
||||
formFactor = formFactor,
|
||||
tabList = tabList,
|
||||
layoutOrder = 1,
|
||||
|
||||
refForParent = self.utilRef,
|
||||
|
||||
onHeightChanged = self.onUtilTabHeightChanged,
|
||||
}),
|
||||
|
||||
MainFrame = Roact.createElement("ScrollingFrame",{
|
||||
Size = UDim2.new(1, 0, 1, -utilTabHeight),
|
||||
CanvasSize = UDim2.new(1, 0, 1, -utilTabHeight),
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 2,
|
||||
}, {
|
||||
UIListLayout = Roact.createElement("UIListLayout", {
|
||||
Padding = UDim.new(0, PADDING * 2),
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
}),
|
||||
|
||||
MicroProfilerRow = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, ROW_HEIGHT * 3),
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 2,
|
||||
}, {
|
||||
Label = Roact.createElement("TextLabel", {
|
||||
Size = UDim2.new(0, BUTTON_WIDTH, 0, ROW_HEIGHT),
|
||||
Position = UDim2.new(OFFSET, 0, 0, 0),
|
||||
Text = "MicroProfiler",
|
||||
Font = HEADER_FONT,
|
||||
TextSize = TEXT_SIZE,
|
||||
TextColor3 = Constants.Color.Text,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextYAlignment = Enum.TextYAlignment.Center,
|
||||
BackgroundColor3 = BUTTON_COLOR,
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
|
||||
HorizontalLine = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, 1),
|
||||
Position = UDim2.new(0, 0, 0, ROW_HEIGHT),
|
||||
}),
|
||||
|
||||
ToggleButton = Roact.createElement("TextLabel", {
|
||||
Size = UDim2.new(ROW_VALUE_WIDTH, -BUTTON_WIDTH, 0, ROW_HEIGHT),
|
||||
Position = UDim2.new((1 - ROW_VALUE_WIDTH)/2, BUTTON_WIDTH, 0, ROW_HEIGHT * 1.25),
|
||||
BackgroundTransparency = 1,
|
||||
}, {
|
||||
OffButton = Roact.createElement("TextButton", {
|
||||
Size = UDim2.new(.5, 0, 1, 0),
|
||||
Text = "Off",
|
||||
Font = HEADER_FONT,
|
||||
TextSize = TEXT_SIZE,
|
||||
TextColor3 = Constants.Color.Text,
|
||||
TextXAlignment = Enum.TextXAlignment.Center,
|
||||
TextYAlignment = Enum.TextYAlignment.Center,
|
||||
BackgroundColor3 = clientProfilerEnabled and BUTTON_UNSELECTED or BUTTON_SELECTED,
|
||||
|
||||
[Roact.Event.Activated] = self.changeProfilerState(false),
|
||||
}),
|
||||
|
||||
ClientButton = Roact.createElement("TextButton", {
|
||||
Size = UDim2.new(.5, 0, 1, 0),
|
||||
Position = UDim2.new(.5, 0, 0, 0),
|
||||
Text = "Client",
|
||||
Font = HEADER_FONT,
|
||||
TextSize = TEXT_SIZE,
|
||||
TextColor3 = Constants.Color.Text,
|
||||
TextXAlignment = Enum.TextXAlignment.Center,
|
||||
TextYAlignment = Enum.TextYAlignment.Center,
|
||||
BackgroundColor3 = clientProfilerEnabled and BUTTON_SELECTED or BUTTON_UNSELECTED,
|
||||
|
||||
[Roact.Event.Activated] = self.changeProfilerState(true),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
|
||||
HorizontalLine = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1,0,0,PADDING),
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 3
|
||||
}),
|
||||
|
||||
ServerProfiler = Roact.createElement(ServerProfilerInterface, {
|
||||
Size = UDim2.new(1, 0, 0, ROW_HEIGHT * 4.5),
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 4,
|
||||
}),
|
||||
})
|
||||
})
|
||||
end
|
||||
|
||||
return MainViewProfiler
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local RoactRodux = require(CorePackages.RoactRodux)
|
||||
local Store = require(CorePackages.Rodux).Store
|
||||
|
||||
local MainViewMicroProfiler = require(script.Parent.MainViewMicroProfiler)
|
||||
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local store = Store.new(function()
|
||||
return {
|
||||
MainView = {
|
||||
currTabIndex = 0
|
||||
},
|
||||
MicroProfiler = {
|
||||
lastFileOutputLocation = ""
|
||||
},
|
||||
}
|
||||
end)
|
||||
|
||||
local element = Roact.createElement(RoactRodux.StoreProvider, {
|
||||
store = store,
|
||||
}, {
|
||||
MainViewMicroProfiler = Roact.createElement(MainViewMicroProfiler, {
|
||||
size = UDim2.new(),
|
||||
tabList = {},
|
||||
})
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local getClientReplicator = require(script.Parent.Parent.Parent.Util.getClientReplicator)
|
||||
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local RoactRodux = require(CorePackages.RoactRodux)
|
||||
|
||||
local Actions = script.Parent.Parent.Parent.Actions
|
||||
local SetRCCProfilerState = require(Actions.SetRCCProfilerState)
|
||||
|
||||
local RCCProfilerDataCompleteListener = Roact.Component:extend("RCCProfilerDataCompleteListener")
|
||||
|
||||
function RCCProfilerDataCompleteListener:didMount()
|
||||
local clientReplicator = getClientReplicator()
|
||||
if clientReplicator then
|
||||
self.completeSignal = clientReplicator.RCCProfilerDataComplete:connect(function(success, message)
|
||||
if self.props.waitingForRecording then
|
||||
if not success then
|
||||
warn(message)
|
||||
self.props.dispatchSetRCCProfilerState(false)
|
||||
else
|
||||
self.props.dispatchSetRCCProfilerState(false, message)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
function RCCProfilerDataCompleteListener:willUnmount()
|
||||
if self.completeSignal then
|
||||
self.completeSignal:Disconnect()
|
||||
self.completeSignal = nil
|
||||
end
|
||||
end
|
||||
|
||||
function RCCProfilerDataCompleteListener:render()
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
local function mapStateToProps(state, props)
|
||||
return {
|
||||
waitingForRecording = state.MicroProfiler.waitingForRecording,
|
||||
}
|
||||
end
|
||||
|
||||
local function mapDispatchToProps(dispatch)
|
||||
return {
|
||||
dispatchSetRCCProfilerState = function(waitingForRecording, fileLocation)
|
||||
dispatch(SetRCCProfilerState(waitingForRecording, fileLocation))
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
return RoactRodux.UNSTABLE_connect2(mapStateToProps, mapDispatchToProps)(RCCProfilerDataCompleteListener)
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Settings = UserSettings()
|
||||
local GameSettings = Settings.GameSettings
|
||||
|
||||
local getClientReplicator = require(script.Parent.Parent.Parent.Util.getClientReplicator)
|
||||
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local RoactRodux = require(CorePackages.RoactRodux)
|
||||
|
||||
local Components = script.Parent.Parent.Parent.Components
|
||||
|
||||
local Actions = script.Parent.Parent.Parent.Actions
|
||||
local SetRCCProfilerState = require(Actions.SetRCCProfilerState)
|
||||
|
||||
local Constants = require(script.Parent.Parent.Parent.Constants)
|
||||
local PADDING = Constants.GeneralFormatting.MainRowPadding
|
||||
local BUTTON_WIDTH = Constants.MicroProfilerFormatting.ButtonWidth
|
||||
local TEXT_SIZE = Constants.MicroProfilerFormatting.ButtonTextSize
|
||||
local FONT = Constants.Font.MainWindow
|
||||
local HEADER_FONT = Constants.Font.MainWindowHeader
|
||||
local BUTTON_UNSELECTED = Constants.Color.UnselectedGray
|
||||
local BUTTON_SELECTED = Constants.Color.SelectedBlue
|
||||
|
||||
local BUTTON_COLOR = Constants.Color.UnselectedGray
|
||||
|
||||
local ROW_HEIGHT = 30
|
||||
local OFFSET = .10
|
||||
local ROW_VALUE_WIDTH = .8
|
||||
|
||||
local ServerProfilerInteraface = Roact.Component:extend("ServerProfilerInteraface")
|
||||
|
||||
function ServerProfilerInteraface:init()
|
||||
self.onUtilTabHeightChanged = function(utilTabHeight)
|
||||
self:setState({
|
||||
utilTabHeight = utilTabHeight
|
||||
})
|
||||
end
|
||||
|
||||
self.onFocusLostFrameRate = function(rbx, enterPressed, inputThatCausedFocusLoss)
|
||||
GameSettings.RCCProfilerRecordFrameRate = rbx.Text
|
||||
-- the gamesetting value will handle value limits and valid text,
|
||||
-- therefore we need to reset the textbox Text the correct value after
|
||||
-- GameSettings has updated it.
|
||||
rbx.Text = GameSettings.RCCProfilerRecordFrameRate
|
||||
end
|
||||
|
||||
self.onFocusLostTimeFrame = function(rbx, enterPressed, inputThatCausedFocusLoss)
|
||||
GameSettings.RCCProfilerRecordTimeFrame = rbx.Text
|
||||
-- the gamesetting value will handle value limits and valid text,
|
||||
-- therefore we need to reset the textbox Text the correct value after
|
||||
-- GameSettings has updated it.
|
||||
rbx.Text = GameSettings.RCCProfilerRecordTimeFrame
|
||||
end
|
||||
|
||||
self.requestRCCProfilerData = function(rbx)
|
||||
local clientReplicator = getClientReplicator()
|
||||
if clientReplicator then
|
||||
-- a RCCProfilerDataCompleteListener is mounted in DevConsoleMaster
|
||||
-- to listen to and update the response of this call
|
||||
clientReplicator:RequestRCCProfilerData(
|
||||
GameSettings.RCCProfilerRecordFrameRate,
|
||||
GameSettings.RCCProfilerRecordTimeFrame
|
||||
)
|
||||
self.props.dispatchSetRCCProfilerState(true)
|
||||
end
|
||||
end
|
||||
|
||||
self.changeProfilerState = function(screenProfilerEnabled)
|
||||
return function()
|
||||
GameSettings.OnScreenProfilerEnabled = screenProfilerEnabled
|
||||
self:setState({
|
||||
clientProfilerEnabled = screenProfilerEnabled
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
self.state = {
|
||||
frameRate = GameSettings.RCCProfilerRecordFrameRate,
|
||||
timeFrame = GameSettings.RCCProfilerRecordTimeFrame,
|
||||
}
|
||||
end
|
||||
|
||||
function ServerProfilerInteraface:render()
|
||||
local size = self.props.size
|
||||
local formFactor = self.props.formFactor
|
||||
local tabList = self.props.tabList
|
||||
local waitingForRecording = self.props.waitingForRecording
|
||||
local lastFileOutputLocation = self.props.lastFileOutputLocation
|
||||
|
||||
local utilTabHeight = self.state.utilTabHeight
|
||||
|
||||
local frameRate = self.state.frameRate
|
||||
local timeFrame = self.state.timeFrame
|
||||
local clientProfilerEnabled = self.state.clientProfilerEnabled
|
||||
|
||||
|
||||
local displayOutputFilePath = (not waitingForRecording) and #lastFileOutputLocation > 0
|
||||
|
||||
local textbox_size = UDim2.new(ROW_VALUE_WIDTH, -BUTTON_WIDTH, 0, ROW_HEIGHT)
|
||||
local textbox_pos = UDim2.new(1 - ROW_VALUE_WIDTH / 2, BUTTON_WIDTH, 0, 0)
|
||||
|
||||
return Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, ROW_HEIGHT * 4.5),
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 4,
|
||||
}, {
|
||||
Label = Roact.createElement("TextLabel", {
|
||||
Size = UDim2.new(0, BUTTON_WIDTH, 0, ROW_HEIGHT),
|
||||
Position = UDim2.new(OFFSET, 0, 0, 0),
|
||||
Text = "ServerProfiler",
|
||||
Font = HEADER_FONT,
|
||||
TextSize = TEXT_SIZE,
|
||||
TextColor3 = Constants.Color.Text,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextYAlignment = Enum.TextYAlignment.Center,
|
||||
BackgroundColor3 = BUTTON_COLOR,
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
|
||||
HorizontalLine = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, 1),
|
||||
Position = UDim2.new(0, 0, 0, ROW_HEIGHT),
|
||||
}),
|
||||
|
||||
LabelFPS = Roact.createElement("TextLabel", {
|
||||
Size = UDim2.new(0, BUTTON_WIDTH, 0, ROW_HEIGHT),
|
||||
Position = UDim2.new(OFFSET, 0, 0, ROW_HEIGHT * 1.25),
|
||||
Text = "Frames Per Second",
|
||||
Font = FONT,
|
||||
TextSize = TEXT_SIZE,
|
||||
TextColor3 = Constants.Color.Text,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextYAlignment = Enum.TextYAlignment.Center,
|
||||
BackgroundColor3 = BUTTON_COLOR,
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
|
||||
FPSTextBox = Roact.createElement("TextBox", {
|
||||
Size = textbox_size,
|
||||
Position = UDim2.new((1 - ROW_VALUE_WIDTH)/2, BUTTON_WIDTH, 0, ROW_HEIGHT * 1.25),
|
||||
Text = frameRate,
|
||||
Font = FONT,
|
||||
TextSize = TEXT_SIZE,
|
||||
TextColor3 = Constants.Color.Text,
|
||||
TextXAlignment = Enum.TextXAlignment.Center,
|
||||
TextYAlignment = Enum.TextYAlignment.Center,
|
||||
BackgroundColor3 = BUTTON_COLOR,
|
||||
BackgroundTransparency = 0,
|
||||
|
||||
[Roact.Event.FocusLost] = self.onFocusLostFrameRate,
|
||||
}),
|
||||
|
||||
|
||||
LabelTimeFrame = Roact.createElement("TextLabel", {
|
||||
Size = UDim2.new(0, BUTTON_WIDTH, 0, ROW_HEIGHT),
|
||||
Position = UDim2.new(OFFSET, 0, 0, ROW_HEIGHT * 2.25),
|
||||
Text = "Seconds to Record",
|
||||
Font = FONT,
|
||||
TextSize = TEXT_SIZE,
|
||||
TextColor3 = Constants.Color.Text,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextYAlignment = Enum.TextYAlignment.Center,
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
|
||||
TimeFrameTextBox = Roact.createElement("TextBox", {
|
||||
Size = textbox_size,
|
||||
Position = UDim2.new((1 - ROW_VALUE_WIDTH)/2, BUTTON_WIDTH, 0, ROW_HEIGHT * 2.25),
|
||||
Text = timeFrame,
|
||||
Font = FONT,
|
||||
TextSize = TEXT_SIZE,
|
||||
TextColor3 = Constants.Color.Text,
|
||||
TextXAlignment = Enum.TextXAlignment.Center,
|
||||
TextYAlignment = Enum.TextYAlignment.Center,
|
||||
BackgroundColor3 = BUTTON_COLOR,
|
||||
BackgroundTransparency = 0,
|
||||
|
||||
[Roact.Event.FocusLost] = self.onFocusLostTimeFrame,
|
||||
},{
|
||||
-- placed here to better position this element
|
||||
GetDumpButton = Roact.createElement("TextButton", {
|
||||
Size = UDim2.new(0, BUTTON_WIDTH * .7, 0, ROW_HEIGHT),
|
||||
Position = UDim2.new(1, -BUTTON_WIDTH * .7, 1, ROW_HEIGHT),
|
||||
Text = waitingForRecording and "Recording" or "Start Recording",
|
||||
Font = FONT,
|
||||
TextSize = TEXT_SIZE,
|
||||
TextColor3 = Constants.Color.Text,
|
||||
TextXAlignment = Enum.TextXAlignment.Center,
|
||||
TextYAlignment = Enum.TextYAlignment.Center,
|
||||
BackgroundColor3 = waitingForRecording and BUTTON_UNSELECTED or BUTTON_SELECTED,
|
||||
BackgroundTransparency = waitingForRecording and .3 or 0,
|
||||
AutoButtonColor = not waitingForRecording,
|
||||
Active = not waitingForRecording,
|
||||
|
||||
[Roact.Event.Activated] = self.requestRCCProfilerData,
|
||||
})
|
||||
}),
|
||||
|
||||
OutputPath = displayOutputFilePath and Roact.createElement("TextLabel", {
|
||||
Size = UDim2.new(1, 0, 0, ROW_HEIGHT),
|
||||
Position = UDim2.new(0, 0, 0, ROW_HEIGHT * 3.25),
|
||||
Text = lastFileOutputLocation,
|
||||
Font = FONT,
|
||||
TextSize = TEXT_SIZE,
|
||||
TextColor3 = Constants.Color.Text,
|
||||
TextXAlignment = Enum.TextXAlignment.Center,
|
||||
TextYAlignment = Enum.TextYAlignment.Center,
|
||||
})
|
||||
})
|
||||
end
|
||||
|
||||
|
||||
local function mapStateToProps(state, props)
|
||||
return {
|
||||
waitingForRecording = state.MicroProfiler.waitingForRecording,
|
||||
lastFileOutputLocation = state.MicroProfiler.lastFileOutputLocation,
|
||||
}
|
||||
end
|
||||
|
||||
local function mapDispatchToProps(dispatch)
|
||||
return {
|
||||
dispatchSetRCCProfilerState = function(waitingForRecording, fileLocation)
|
||||
dispatch(SetRCCProfilerState(waitingForRecording, fileLocation))
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
return RoactRodux.UNSTABLE_connect2(mapStateToProps, mapDispatchToProps)(ServerProfilerInteraface)
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local RoactRodux = require(CorePackages.RoactRodux)
|
||||
local Store = require(CorePackages.Rodux).Store
|
||||
|
||||
local ServerProfilerInterface = require(script.Parent.ServerProfilerInterface)
|
||||
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local store = Store.new(function()
|
||||
return {
|
||||
MicroProfiler = {
|
||||
lastFileOutputLocation = ""
|
||||
},
|
||||
}
|
||||
end)
|
||||
|
||||
local element = Roact.createElement(RoactRodux.StoreProvider, {
|
||||
store = store,
|
||||
}, {
|
||||
ServerProfilerInterface = Roact.createElement(ServerProfilerInterface, {
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
local Components = script.Parent.Parent.Parent.Components
|
||||
local DataConsumer = require(Components.DataConsumer)
|
||||
local NetworkView = require(script.Parent.NetworkView)
|
||||
|
||||
local ClientNetwork = Roact.Component:extend("ClientNetwork")
|
||||
|
||||
function ClientNetwork:init(props)
|
||||
self.state = {
|
||||
targetNetworkData = self.props.ClientNetworkData
|
||||
}
|
||||
end
|
||||
|
||||
function ClientNetwork:render()
|
||||
local layoutOrder = self.props.layoutOrder
|
||||
local searchTerm = self.props.searchTerm
|
||||
local size = self.props.size
|
||||
|
||||
return Roact.createElement(NetworkView, {
|
||||
size = size,
|
||||
searchTerm = searchTerm,
|
||||
layoutOrder = layoutOrder,
|
||||
targetNetworkData = self.state.targetNetworkData
|
||||
})
|
||||
end
|
||||
|
||||
return DataConsumer(ClientNetwork, "ClientNetworkData")
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local RoactRodux = require(CorePackages.RoactRodux)
|
||||
local Store = require(CorePackages.Rodux).Store
|
||||
|
||||
local DataProvider = require(script.Parent.Parent.DataProvider)
|
||||
local ClientNetwork = require(script.Parent.ClientNetwork)
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local store = Store.new(function()
|
||||
return {
|
||||
MainView = {
|
||||
isDeveloperView = true,
|
||||
},
|
||||
}
|
||||
end)
|
||||
local element = Roact.createElement(RoactRodux.StoreProvider, {
|
||||
store = store,
|
||||
}, {
|
||||
DataProvider = Roact.createElement(DataProvider, {}, {
|
||||
ClientNetwork = Roact.createElement(ClientNetwork, {
|
||||
size = UDim2.new(),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local RoactRodux = require(CorePackages.RoactRodux)
|
||||
|
||||
local Components = script.Parent.Parent.Parent.Components
|
||||
local ClientNetwork = require(Components.Network.ClientNetwork)
|
||||
local ServerNetwork = require(Components.Network.ServerNetwork)
|
||||
local UtilAndTab = require(Components.UtilAndTab)
|
||||
|
||||
local Actions = script.Parent.Parent.Parent.Actions
|
||||
local ClientNetworkUpdateSearchFilter = require(Actions.ClientNetworkUpdateSearchFilter)
|
||||
local ServerNetworkUpdateSearchFilter = require(Actions.ServerNetworkUpdateSearchFilter)
|
||||
local SetActiveTab = require(Actions.SetActiveTab)
|
||||
|
||||
local Constants = require(script.Parent.Parent.Parent.Constants)
|
||||
local PADDING = Constants.GeneralFormatting.MainRowPadding
|
||||
|
||||
local MainViewNetwork = Roact.Component:extend("MainViewNetwork")
|
||||
|
||||
function MainViewNetwork:init()
|
||||
self.onUtilTabHeightChanged = function(utilTabHeight)
|
||||
self:setState({
|
||||
utilTabHeight = utilTabHeight
|
||||
})
|
||||
end
|
||||
|
||||
self.onClientButton = function()
|
||||
self.props.dispatchSetActiveTab("Network", true)
|
||||
end
|
||||
|
||||
self.onServerButton = function()
|
||||
self.props.dispatchSetActiveTab("Network", false)
|
||||
end
|
||||
|
||||
self.onSearchTermChanged = function(newSearchTerm)
|
||||
if self.props.isClientView then
|
||||
self.props.dispatchClientNetworkUpdateSearchFilter(newSearchTerm, {})
|
||||
else
|
||||
self.props.dispatchServerNetworkUpdateSearchFilter(newSearchTerm, {})
|
||||
end
|
||||
end
|
||||
|
||||
self.utilRef = Roact.createRef()
|
||||
|
||||
self.state = {
|
||||
utilTabHeight = 0,
|
||||
}
|
||||
end
|
||||
|
||||
function MainViewNetwork:didMount()
|
||||
local utilSize = self.utilRef.current.Size
|
||||
self:setState({
|
||||
utilTabHeight = utilSize.Y.Offset
|
||||
})
|
||||
end
|
||||
|
||||
function MainViewNetwork:didUpdate()
|
||||
local utilSize = self.utilRef.current.Size
|
||||
if utilSize.Y.Offset ~= self.state.utilTabHeight then
|
||||
self:setState({
|
||||
utilTabHeight = utilSize.Y.Offset
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function MainViewNetwork:render()
|
||||
local elements = {}
|
||||
local size = self.props.size
|
||||
local formFactor = self.props.formFactor
|
||||
local tabList = self.props.tabList
|
||||
local isClientView = self.props.isClientView
|
||||
|
||||
local utilTabHeight = self.state.utilTabHeight
|
||||
local searchTerm = isClientView and self.props.clientSearchTerm or self.props.serverSearchTerm
|
||||
|
||||
elements["UIListLayout"] = Roact.createElement("UIListLayout", {
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
Padding = UDim.new(0, PADDING),
|
||||
})
|
||||
|
||||
elements ["UtilAndTab"] = Roact.createElement(UtilAndTab, {
|
||||
windowWidth = size.X.Offset,
|
||||
formFactor = formFactor,
|
||||
tabList = tabList,
|
||||
isClientView = isClientView,
|
||||
searchTerm = searchTerm,
|
||||
layoutOrder = 1,
|
||||
|
||||
refForParent = self.utilRef,
|
||||
|
||||
onHeightChanged = self.onUtilTabHeightChanged,
|
||||
onClientButton = self.onClientButton,
|
||||
onServerButton = self.onServerButton,
|
||||
onSearchTermChanged = self.onSearchTermChanged,
|
||||
})
|
||||
|
||||
if utilTabHeight > 0 then
|
||||
if isClientView then
|
||||
elements["ClientNetwork"] = Roact.createElement(ClientNetwork, {
|
||||
size = UDim2.new(1, 0, 1, -utilTabHeight),
|
||||
searchTerm = searchTerm,
|
||||
layoutOrder = 2,
|
||||
})
|
||||
else
|
||||
elements["ServerNetwork"] = Roact.createElement(ServerNetwork, {
|
||||
size = UDim2.new(1, 0, 1, -utilTabHeight),
|
||||
searchTerm = searchTerm,
|
||||
layoutOrder = 2,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
return Roact.createElement("Frame", {
|
||||
Size = size,
|
||||
BackgroundColor3 = Constants.Color.BaseGray,
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 3,
|
||||
|
||||
}, elements)
|
||||
end
|
||||
|
||||
local function mapStateToProps(state, props)
|
||||
return {
|
||||
isClientView = state.MainView.isClientView,
|
||||
clientSearchTerm = state.NetworkData.clientSearchTerm,
|
||||
serverSearchTerm = state.NetworkData.serverSearchTerm,
|
||||
}
|
||||
end
|
||||
|
||||
local function mapDispatchToProps(dispatch)
|
||||
return {
|
||||
dispatchClientNetworkUpdateSearchFilter = function(searchTerm, filters)
|
||||
dispatch(ClientNetworkUpdateSearchFilter(searchTerm, filters))
|
||||
end,
|
||||
|
||||
dispatchServerNetworkUpdateSearchFilter = function(searchTerm, filters)
|
||||
dispatch(ServerNetworkUpdateSearchFilter(searchTerm, filters))
|
||||
end,
|
||||
dispatchSetActiveTab = function (tabListIndex, isClientView)
|
||||
dispatch(SetActiveTab(tabListIndex, isClientView))
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
return RoactRodux.UNSTABLE_connect2(mapStateToProps, mapDispatchToProps)(MainViewNetwork)
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local RoactRodux = require(CorePackages.RoactRodux)
|
||||
local Store = require(CorePackages.Rodux).Store
|
||||
|
||||
local DataProvider = require(script.Parent.Parent.DataProvider)
|
||||
local MainViewNetwork = require(script.Parent.MainViewNetwork)
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local store = Store.new(function()
|
||||
return {
|
||||
MainView = {
|
||||
currTabIndex = 0,
|
||||
isDeveloperView = true,
|
||||
},
|
||||
NetworkData = {
|
||||
clientSearchTerm = "",
|
||||
serverSearchTerm = ""
|
||||
}
|
||||
}
|
||||
end)
|
||||
|
||||
local element = Roact.createElement(RoactRodux.StoreProvider, {
|
||||
store = store,
|
||||
}, {
|
||||
DataProvider = Roact.createElement(DataProvider, {}, {
|
||||
MainViewNetwork = Roact.createElement(MainViewNetwork, {
|
||||
size = UDim2.new(),
|
||||
tabList = {},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+280
@@ -0,0 +1,280 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local TextService = game:GetService("TextService")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
local Constants = require(script.Parent.Parent.Parent.Constants)
|
||||
|
||||
local HEADER_NAMES = Constants.NetworkFormatting.ChartHeaderNames
|
||||
local CELL_WIDTHS = Constants.NetworkFormatting.ChartCellWidths
|
||||
local CELL_PADDING = Constants.NetworkFormatting.CellPadding
|
||||
local HEADER_HEIGHT = Constants.NetworkFormatting.HeaderFrameHeight
|
||||
local ENTRY_HEIGHT = Constants.NetworkFormatting.EntryFrameHeight
|
||||
local LINE_WIDTH = Constants.GeneralFormatting.LineWidth
|
||||
local LINE_COLOR = Constants.GeneralFormatting.LineColor
|
||||
|
||||
local FONT_SIZE = Constants.DefaultFontSize.MainWindow
|
||||
local MAIN_FONT = Constants.Font.MainWindow
|
||||
local RESPONSE_STR_TEXT_HEIGHT = Constants.NetworkFormatting.ResponseStrHeight
|
||||
local RESPONSE_WIDTH_RATIO = Constants.NetworkFormatting.ResponseWidthRatio
|
||||
|
||||
local Components = script.Parent.Parent.Parent.Components
|
||||
local HeaderButton = require(Components.HeaderButton)
|
||||
local NetworkChartEntry = require(script.Parent.NetworkChartEntry)
|
||||
|
||||
local totalEntryWidth = 0
|
||||
for _, cellWidth in pairs(CELL_WIDTHS) do
|
||||
totalEntryWidth = totalEntryWidth + cellWidth
|
||||
end
|
||||
|
||||
-- create table of offsets and sizes for each cell
|
||||
-- each of the first 5 cells has a fixed size
|
||||
local currOffset = 0
|
||||
local cellOffset = {}
|
||||
local headerCellSize = {}
|
||||
local entryCellSize = {}
|
||||
|
||||
for _, cellWidth in ipairs(CELL_WIDTHS) do
|
||||
table.insert(cellOffset,UDim2.new(0, currOffset + CELL_PADDING, 0, 0))
|
||||
table.insert(headerCellSize, UDim2.new(0, cellWidth - CELL_PADDING, 0, HEADER_HEIGHT))
|
||||
table.insert(entryCellSize, UDim2.new(0, cellWidth - CELL_PADDING, 0, ENTRY_HEIGHT))
|
||||
currOffset = currOffset + cellWidth
|
||||
end
|
||||
|
||||
-- cell 1-5 are defined widths,
|
||||
-- cell 6 pads out the remaining width in the row
|
||||
table.insert(cellOffset,UDim2.new(0, currOffset + CELL_PADDING, 0, 0))
|
||||
table.insert(headerCellSize, UDim2.new(1, -totalEntryWidth - CELL_PADDING, 0, HEADER_HEIGHT))
|
||||
table.insert(entryCellSize, UDim2.new(1, -totalEntryWidth - CELL_PADDING, 0, ENTRY_HEIGHT))
|
||||
|
||||
local verticalOffsets = {}
|
||||
for i, offset in ipairs(cellOffset) do
|
||||
verticalOffsets[i] = UDim2.new(offset.X.Scale, offset.X.Offset - CELL_PADDING,
|
||||
offset.Y.Scale, offset.Y.Offset)
|
||||
end
|
||||
|
||||
local NetworkChart = Roact.Component:extend("NetworkChart")
|
||||
|
||||
function NetworkChart:init()
|
||||
self.getOnExpandEntry = function (name)
|
||||
return function(rbx, input)
|
||||
self:setState({
|
||||
expandIndex = self.state.expandIndex ~= name and name
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
self.onCanvasPosChanged = function()
|
||||
local canvasPos = self.scrollingRef.current.CanvasPosition
|
||||
if self.state.canvasPos ~= canvasPos then
|
||||
self:setState({
|
||||
absScrollSize = self.scrollingRef.current.AbsoluteSize,
|
||||
canvasPos = canvasPos,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
self.ref = Roact.createRef()
|
||||
self.scrollingRef = Roact.createRef()
|
||||
|
||||
self.state = {
|
||||
expandIndex = false,
|
||||
}
|
||||
end
|
||||
|
||||
function NetworkChart:willUpdate()
|
||||
if self.canvasPosConnector then
|
||||
self.canvasPosConnector:Disconnect()
|
||||
end
|
||||
end
|
||||
|
||||
function NetworkChart:didUpdate()
|
||||
if self.scrollingRef.current then
|
||||
local signal = self.scrollingRef.current:GetPropertyChangedSignal("CanvasPosition")
|
||||
self.canvasPosConnector = signal:Connect(self.onCanvasPosChanged)
|
||||
|
||||
local absScrollSize = self.scrollingRef.current.AbsoluteSize
|
||||
if self.state.absScrollSize ~= absScrollSize then
|
||||
self:setState({
|
||||
absScrollSize = absScrollSize,
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function NetworkChart:didMount()
|
||||
if self.scrollingRef.current then
|
||||
local signal = self.scrollingRef.current:GetPropertyChangedSignal("CanvasPosition")
|
||||
self.canvasPosConnector = signal:Connect(self.onCanvasPosChanged)
|
||||
|
||||
self:setState({
|
||||
absScrollSize = self.scrollingRef.current.AbsoluteSize,
|
||||
canvasPos = self.scrollingRef.current.CanvasPosition,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function NetworkChart:render()
|
||||
local httpEntryList = self.props.httpEntryList or {}
|
||||
local chartHeight = self.props.chartHeight
|
||||
local width = self.props.width
|
||||
local searchTerm = self.props.searchTerm
|
||||
local layoutOrder = self.props.layoutOrder
|
||||
local reverseSort = self.props.reverseSort
|
||||
|
||||
local onSortChanged = self.props.onSortChanged
|
||||
|
||||
local expandIndex = self.state.expandIndex
|
||||
local absScrollSize = self.state.absScrollSize
|
||||
local canvasPos = self.state.canvasPos
|
||||
|
||||
local headerCells = {}
|
||||
for ind, name in ipairs(HEADER_NAMES) do
|
||||
headerCells[name] = Roact.createElement(HeaderButton, {
|
||||
text = name,
|
||||
size = headerCellSize[ind],
|
||||
pos = cellOffset[ind],
|
||||
|
||||
sortfunction = onSortChanged,
|
||||
})
|
||||
end
|
||||
|
||||
for i = 2, #verticalOffsets do
|
||||
local key = string.format("VerticalLine_%d",i)
|
||||
headerCells[key] = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(0, LINE_WIDTH, 0, HEADER_HEIGHT),
|
||||
Position = verticalOffsets[i],
|
||||
BackgroundColor3 = LINE_COLOR,
|
||||
BorderSizePixel = 0,
|
||||
})
|
||||
end
|
||||
|
||||
local entries = {}
|
||||
local canvasHeight = 0
|
||||
local paddingHeight = -1
|
||||
local usedFrameSpace = 0
|
||||
|
||||
local totalEntries = #httpEntryList
|
||||
local searchCount = 0
|
||||
entries["UIListLayout"] = Roact.createElement("UIListLayout", {
|
||||
FillDirection = Enum.FillDirection.Vertical,
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Left,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Top,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
})
|
||||
|
||||
if canvasPos and absScrollSize then
|
||||
for ind, entry in ipairs(httpEntryList) do
|
||||
local valid = true
|
||||
if searchTerm ~= "" then
|
||||
valid = string.find(entry.RequestType:lower(), searchTerm:lower()) ~= nil or
|
||||
string.find(entry.Url:lower(), searchTerm:lower()) ~= nil
|
||||
end
|
||||
|
||||
if not (entry.RequestType == "Default") and valid then
|
||||
searchCount = searchCount + 1
|
||||
-- insert header elements into a frame so that we can use the UIListLayout to keep everything in order
|
||||
local showResponse = expandIndex == entry.Num
|
||||
local frameHeight = ENTRY_HEIGHT
|
||||
local responseBodyHeight = 0
|
||||
|
||||
if showResponse then
|
||||
frameHeight = frameHeight + RESPONSE_STR_TEXT_HEIGHT
|
||||
if self.ref.current then
|
||||
local fSize = Vector2.new(
|
||||
self.ref.current.AbsoluteSize.X * RESPONSE_WIDTH_RATIO,
|
||||
100000000)
|
||||
local DisSize = TextService:GetTextSize(entry.Response, FONT_SIZE, MAIN_FONT, fSize)
|
||||
|
||||
responseBodyHeight = RESPONSE_STR_TEXT_HEIGHT + DisSize.Y
|
||||
frameHeight = frameHeight + responseBodyHeight
|
||||
end
|
||||
end
|
||||
|
||||
if canvasHeight + frameHeight >= canvasPos.Y then
|
||||
if usedFrameSpace < absScrollSize.Y then
|
||||
local layoutOrder = reverseSort and totalEntries - ind or ind
|
||||
|
||||
entries[ind] = Roact.createElement(NetworkChartEntry, {
|
||||
size = UDim2.new(1, 0, 0, frameHeight),
|
||||
entry = entry,
|
||||
entryCellSize = entryCellSize,
|
||||
cellOffset = cellOffset,
|
||||
verticalOffsets = verticalOffsets,
|
||||
showResponse = showResponse,
|
||||
responseBodyHeight = responseBodyHeight,
|
||||
|
||||
layoutOrder = layoutOrder + 1,
|
||||
|
||||
onButtonPress = self.getOnExpandEntry(entry.Num),
|
||||
})
|
||||
end
|
||||
if paddingHeight < 0 then
|
||||
paddingHeight = canvasHeight
|
||||
else
|
||||
usedFrameSpace = usedFrameSpace + frameHeight
|
||||
end
|
||||
end
|
||||
canvasHeight = canvasHeight + frameHeight
|
||||
end
|
||||
end
|
||||
if searchCount > 0 then
|
||||
entries["WindowingPadding"] = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, paddingHeight),
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 1,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
if totalEntries == 0 or searchCount == 0 then
|
||||
entries["NONE FOUND"] = Roact.createElement("TextLabel", {
|
||||
Size = UDim2.new(1, 0, 0, chartHeight),
|
||||
Text = "No Network Entries Found",
|
||||
TextColor3 = Constants.Color.Text,
|
||||
BackgroundTransparency = 1,
|
||||
})
|
||||
end
|
||||
|
||||
return Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, chartHeight),
|
||||
BackgroundTransparency = 1,
|
||||
ClipsDescendants = true,
|
||||
|
||||
LayoutOrder = layoutOrder,
|
||||
|
||||
[Roact.Ref] = self.ref,
|
||||
}, {
|
||||
Layout = Roact.createElement("UIListLayout", {
|
||||
FillDirection = Enum.FillDirection.Vertical,
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Left,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Top,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
}),
|
||||
|
||||
Header = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, HEADER_HEIGHT),
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 1,
|
||||
},headerCells),
|
||||
|
||||
HorizontalLine_1 = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, LINE_WIDTH),
|
||||
BackgroundColor3 = LINE_COLOR,
|
||||
BorderSizePixel = 0,
|
||||
BackgroundTransparency = 0,
|
||||
LayoutOrder = 2,
|
||||
}),
|
||||
|
||||
scrollingFrameEntries = Roact.createElement("ScrollingFrame", {
|
||||
Size = UDim2.new(1, 0, 1, -HEADER_HEIGHT),
|
||||
CanvasSize = UDim2.new(0, width, 0, canvasHeight),
|
||||
ScrollBarThickness = 6,
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 3,
|
||||
|
||||
[Roact.Ref] = self.scrollingRef,
|
||||
}, entries),
|
||||
})
|
||||
end
|
||||
|
||||
return NetworkChart
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
local NetworkChart = require(script.Parent.NetworkChart)
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local element = Roact.createElement(NetworkChart, {
|
||||
summaryHeight = 0,
|
||||
})
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
local Constants = require(script.Parent.Parent.Parent.Constants)
|
||||
local ENTRY_HEIGHT = Constants.NetworkFormatting.EntryFrameHeight
|
||||
local LINE_WIDTH = Constants.GeneralFormatting.LineWidth
|
||||
local LINE_COLOR = Constants.GeneralFormatting.LineColor
|
||||
|
||||
local RESPONSE_STR_TEXT_HEIGHT = Constants.NetworkFormatting.ResponseStrHeight
|
||||
local RESPONSE_WIDTH_RATIO = Constants.NetworkFormatting.ResponseWidthRatio
|
||||
local RESPONSE_X_OFFSET = (1 - RESPONSE_WIDTH_RATIO) / 2
|
||||
local RESPONSE_STR_X_OFFSET = (1 - RESPONSE_WIDTH_RATIO) / 4
|
||||
local RESPONSE_STR_TEXT = "Response Body:"
|
||||
|
||||
local Components = script.Parent.Parent.Parent.Components
|
||||
local CellLabel = require(Components.CellLabel)
|
||||
local HeaderButton = require(Components.HeaderButton)
|
||||
local BannerButton = require(Components.BannerButton)
|
||||
|
||||
return function(props)
|
||||
local size = props.size
|
||||
local entry = props.entry
|
||||
local entryCellSize = props.entryCellSize
|
||||
local cellOffset = props.cellOffset
|
||||
local verticalOffsets = props.verticalOffsets
|
||||
local layoutOrder = props.layoutOrder
|
||||
local showResponse = props.showResponse
|
||||
local responseBodyHeight = props.responseBodyHeight
|
||||
|
||||
|
||||
local onButtonPress = props.onButtonPress
|
||||
|
||||
local row = {}
|
||||
row["Num"] = Roact.createElement(CellLabel, {
|
||||
text = entry.Num,
|
||||
size = entryCellSize[1],
|
||||
pos = cellOffset[1],
|
||||
})
|
||||
row["Method"] = Roact.createElement(CellLabel, {
|
||||
text = entry.Method,
|
||||
size = entryCellSize[2],
|
||||
pos = cellOffset[2],
|
||||
})
|
||||
row["Status"] = Roact.createElement(CellLabel, {
|
||||
text = entry.Status,
|
||||
size = entryCellSize[3],
|
||||
pos = cellOffset[3],
|
||||
})
|
||||
row["Time"] = Roact.createElement(CellLabel, {
|
||||
text = string.format("%.3f", entry.Time),
|
||||
size = entryCellSize[4],
|
||||
pos = cellOffset[4],
|
||||
})
|
||||
row["RequestType"] = Roact.createElement(CellLabel, {
|
||||
text = entry.RequestType,
|
||||
size = entryCellSize[5],
|
||||
pos = cellOffset[5],
|
||||
})
|
||||
row["Url"] = Roact.createElement(CellLabel, {
|
||||
text = entry.Url,
|
||||
size = entryCellSize[6],
|
||||
pos = cellOffset[6],
|
||||
})
|
||||
row["LowerHorizontalLine"] = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, 1),
|
||||
BackgroundColor3 = LINE_COLOR,
|
||||
BorderSizePixel = 0,
|
||||
})
|
||||
|
||||
for i = 2, #verticalOffsets do
|
||||
local key = string.format("VerticalLine_%d",i)
|
||||
row[key] = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(0, LINE_WIDTH, 0, ENTRY_HEIGHT),
|
||||
Position = verticalOffsets[i],
|
||||
BackgroundColor3 = LINE_COLOR,
|
||||
BorderSizePixel = 0,
|
||||
})
|
||||
end
|
||||
|
||||
return Roact.createElement("Frame", {
|
||||
Size = size,
|
||||
BackgroundTransparency = 1,
|
||||
-- to make room for the windowing padding
|
||||
LayoutOrder = layoutOrder,
|
||||
}, {
|
||||
Button = Roact.createElement(BannerButton, {
|
||||
size = UDim2.new(1, 0, 0, ENTRY_HEIGHT),
|
||||
pos = UDim2.new(),
|
||||
isExpanded = showResponse,
|
||||
|
||||
onButtonPress = onButtonPress,
|
||||
}, row),
|
||||
|
||||
Response = showResponse and Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, responseBodyHeight),
|
||||
Position = UDim2.new(RESPONSE_X_OFFSET, 0, 0, ENTRY_HEIGHT),
|
||||
BackgroundTransparency = 1,
|
||||
}, {
|
||||
Text = Roact.createElement(HeaderButton, {
|
||||
text = RESPONSE_STR_TEXT,
|
||||
size = UDim2.new(1, 0, 0, RESPONSE_STR_TEXT_HEIGHT),
|
||||
pos = UDim2.new(-RESPONSE_STR_X_OFFSET, 0, 0, 0),
|
||||
}),
|
||||
ResponseBody = Roact.createElement(CellLabel, {
|
||||
text = entry.Response,
|
||||
size = UDim2.new(.8, 0, 1, -RESPONSE_STR_TEXT_HEIGHT),
|
||||
pos = UDim2.new(0, 0, 0, RESPONSE_STR_TEXT_HEIGHT),
|
||||
})
|
||||
})
|
||||
})
|
||||
end
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
local NetworkChartEntry = require(script.Parent.NetworkChartEntry)
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local dummyEntry = {
|
||||
Num = 0,
|
||||
Method = "",
|
||||
Status = "",
|
||||
Time = 0,
|
||||
RequestType = "",
|
||||
Url = "",
|
||||
Reponse = "",
|
||||
}
|
||||
local dummyCellSizes = {
|
||||
UDim2.new(),
|
||||
UDim2.new(),
|
||||
UDim2.new(),
|
||||
UDim2.new(),
|
||||
UDim2.new(),
|
||||
UDim2.new(),
|
||||
}
|
||||
|
||||
local element = Roact.createElement(NetworkChartEntry, {
|
||||
entry = dummyEntry,
|
||||
entryCellSize = dummyCellSizes,
|
||||
cellOffset = dummyCellSizes,
|
||||
verticalOffsets = dummyCellSizes,
|
||||
})
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+220
@@ -0,0 +1,220 @@
|
||||
local LogService = game:GetService("LogService")
|
||||
local CircularBuffer = require(script.Parent.Parent.Parent.CircularBuffer)
|
||||
local Signal = require(script.Parent.Parent.Parent.Signal)
|
||||
|
||||
local Constants = require(script.Parent.Parent.Parent.Constants)
|
||||
local HEADER_NAMES = Constants.NetworkFormatting.ChartHeaderNames
|
||||
|
||||
local MAX_NUM_ENTRIES = tonumber(settings():GetFVariable("NewDevConsoleMaxHttpCount"))
|
||||
|
||||
local SORT_COMPARATOR = {
|
||||
[HEADER_NAMES[1]] = function(a, b)
|
||||
return a.Num < b.Num
|
||||
end,
|
||||
[HEADER_NAMES[2]] = function(a, b)
|
||||
return a.Method < b.Method
|
||||
end,
|
||||
[HEADER_NAMES[3]] = function(a, b)
|
||||
return a.Status < b.Status
|
||||
end,
|
||||
[HEADER_NAMES[4]] = function(a, b)
|
||||
return a.Time < b.Time
|
||||
end,
|
||||
[HEADER_NAMES[5]] = function(a, b)
|
||||
return a.RequestType < b.RequestType
|
||||
end,
|
||||
[HEADER_NAMES[6]] = function(a, b)
|
||||
return a.Url < b.Url
|
||||
end,
|
||||
}
|
||||
|
||||
local function newHttpEntry(num, method, status, time, requestType, url, response)
|
||||
return {
|
||||
Num = num,
|
||||
Method = method,
|
||||
Status = status,
|
||||
Time = time,
|
||||
RequestType = requestType,
|
||||
Url = url,
|
||||
Response = response
|
||||
}
|
||||
end
|
||||
|
||||
local NetworkData = {}
|
||||
NetworkData.__index = NetworkData
|
||||
|
||||
function NetworkData.new( isClient )
|
||||
local self = {}
|
||||
setmetatable(self, NetworkData)
|
||||
|
||||
self._isRunning = false
|
||||
self._isClient = isClient
|
||||
self._httpResultSignal = isClient and LogService.HttpResultOut or LogService.ServerHttpResultOut
|
||||
|
||||
self._httpEntryAdded = Signal.new()
|
||||
self._httpSummaryTable = {}
|
||||
self._httpSummaryCount = 0
|
||||
self._httpEntryBuffer = CircularBuffer.new(MAX_NUM_ENTRIES)
|
||||
|
||||
self._httpLifeTimeEntryCount = 0
|
||||
self._sortType = HEADER_NAMES[1] -- Num
|
||||
return self
|
||||
end
|
||||
|
||||
function NetworkData:addHttpEntry(httpResult)
|
||||
-- append new entry to the back of the list
|
||||
self._httpLifeTimeEntryCount = self._httpLifeTimeEntryCount + 1
|
||||
local entry = newHttpEntry(
|
||||
self._httpLifeTimeEntryCount,
|
||||
httpResult.Method,
|
||||
httpResult.Status,
|
||||
httpResult.Time,
|
||||
httpResult.RequestType,
|
||||
httpResult.URL,
|
||||
httpResult.Response
|
||||
)
|
||||
|
||||
self._httpEntryBuffer:push_back(entry)
|
||||
|
||||
-- update Summary Data
|
||||
local requestType = httpResult.RequestType
|
||||
local summaryTable = self._httpSummaryTable[requestType]
|
||||
|
||||
if not summaryTable then
|
||||
self._httpSummaryCount = self._httpSummaryCount + 1
|
||||
|
||||
self._httpSummaryTable[requestType] = {
|
||||
RequestType = requestType,
|
||||
RequestCount = 1,
|
||||
FailedCount = 0,
|
||||
AverageTime = httpResult.Time,
|
||||
MinTime = httpResult.Time,
|
||||
MaxTime = httpResult.Time,
|
||||
}
|
||||
|
||||
if httpResult.Status >= 400 then
|
||||
self._httpSummaryTable[httpResult.RequestType].FailedCount = 1
|
||||
end
|
||||
else
|
||||
summaryTable.RequestCount = summaryTable.RequestCount + 1
|
||||
if httpResult.Status >= 400 then
|
||||
summaryTable.FailedCount = summaryTable.FailedCount + 1
|
||||
end
|
||||
|
||||
summaryTable.AverageTime = ((summaryTable.AverageTime * summaryTable.RequestCount) +
|
||||
httpResult.Time - summaryTable.AverageTime) / summaryTable.RequestCount
|
||||
|
||||
if httpResult.Time < summaryTable.MinTime then
|
||||
summaryTable.MinTime = httpResult.Time
|
||||
end
|
||||
if httpResult.Time > summaryTable.MaxTime then
|
||||
summaryTable.MaxTime = httpResult.Time
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function NetworkData:setSortType(sortType)
|
||||
if SORT_COMPARATOR[sortType] then
|
||||
self._sortType = sortType
|
||||
self._httpEntryAdded:Fire(
|
||||
self._httpSummaryTable,
|
||||
self._httpSummaryCount,
|
||||
self:getSortedEntries()
|
||||
)
|
||||
else
|
||||
error(string.format("attempted to pass invalid sortType: %s", tostring(sortType)), 2)
|
||||
end
|
||||
end
|
||||
|
||||
function NetworkData:getSortType()
|
||||
return self._sortType
|
||||
end
|
||||
|
||||
function NetworkData:resetHttpEntryList()
|
||||
self._httpSummaryTable = {}
|
||||
self._httpEntryBuffer:reset()
|
||||
self._httpLifeTimeEntryCount = 0
|
||||
if self._isClient then
|
||||
local history = LogService:GetHttpResultHistory()
|
||||
|
||||
if history then
|
||||
for _,httpResult in pairs(history) do
|
||||
self:addHttpEntry(httpResult)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function NetworkData:Signal()
|
||||
return self._httpEntryAdded
|
||||
end
|
||||
|
||||
function NetworkData:getSortedEntries()
|
||||
local iter = self._httpEntryBuffer:iterator()
|
||||
local data = iter:next()
|
||||
local entryList = {}
|
||||
local count = 1
|
||||
|
||||
while data do
|
||||
entryList[count] = data
|
||||
count = count + 1
|
||||
data = iter:next()
|
||||
end
|
||||
|
||||
table.sort(entryList, SORT_COMPARATOR[self._sortType])
|
||||
|
||||
return entryList
|
||||
end
|
||||
|
||||
function NetworkData:getCurrentData()
|
||||
return {
|
||||
summaryTable = self._httpSummaryTable,
|
||||
summaryCount = self._httpSummaryCount,
|
||||
entryList = self:getSortedEntries(),
|
||||
}
|
||||
end
|
||||
|
||||
function NetworkData:isRunning()
|
||||
return self._isRunning
|
||||
end
|
||||
|
||||
function NetworkData:start()
|
||||
if not self._httpResultConnection then
|
||||
if self._additionHttpSetup then
|
||||
self._additionHttpSetup()
|
||||
end
|
||||
|
||||
self._httpResultConnection = self._httpResultSignal:connect(function (httpResult)
|
||||
self:addHttpEntry(httpResult)
|
||||
self._httpEntryAdded:Fire(
|
||||
self._httpSummaryTable,
|
||||
self._httpSummaryCount,
|
||||
self:getSortedEntries()
|
||||
)
|
||||
end)
|
||||
|
||||
self:resetHttpEntryList()
|
||||
|
||||
if not self._isClient then
|
||||
self._onHttpResultApprovedConnection = LogService.OnHttpResultApproved:connect( function (isApproved)
|
||||
LogService:RequestHttpResultApproved()
|
||||
if isApproved then
|
||||
self._onHttpResultApprovedConnection:Disconnect()
|
||||
self._onHttpResultApprovedConnection = nil
|
||||
end
|
||||
end)
|
||||
LogService:RequestServerHttpResult()
|
||||
end
|
||||
self._isRunning = true
|
||||
end
|
||||
end
|
||||
|
||||
function NetworkData:stop()
|
||||
if self._httpResultConnection then
|
||||
self._httpResultConnection:Disconnect()
|
||||
self._httpResultConnection = nil
|
||||
end
|
||||
self._isRunning = false
|
||||
end
|
||||
|
||||
return NetworkData
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
local Constants = require(script.Parent.Parent.Parent.Constants)
|
||||
|
||||
local HEADER_NAMES = Constants.NetworkFormatting.SummaryHeaderNames
|
||||
local CELL_WIDTH = Constants.NetworkFormatting.SummaryCellWidths
|
||||
local HEADER_HEIGHT = Constants.NetworkFormatting.HeaderFrameHeight
|
||||
local ENTRY_HEIGHT = Constants.NetworkFormatting.EntryFrameHeight
|
||||
|
||||
local LINE_WIDTH = Constants.GeneralFormatting.LineWidth
|
||||
local LINE_COLOR = Constants.GeneralFormatting.LineColor
|
||||
local CELL_PADDING = Constants.NetworkFormatting.CellPadding
|
||||
local TEXT_COLOR = Constants.Color.Text
|
||||
local NON_DATA_STR = "No Summary Data Found"
|
||||
|
||||
local CellLabel = require(script.Parent.Parent.Parent.Components.CellLabel)
|
||||
local HeaderButton = require(script.Parent.Parent.Parent.Components.HeaderButton)
|
||||
|
||||
local totalSummaryWidth = 0
|
||||
for _, v in pairs(CELL_WIDTH) do
|
||||
totalSummaryWidth = totalSummaryWidth + v
|
||||
end
|
||||
|
||||
-- create table of offsets and sizes for each cell
|
||||
local currOffset = -totalSummaryWidth
|
||||
local cellOffset = {}
|
||||
local headerCellSize = {}
|
||||
local entryCellSize = {}
|
||||
|
||||
table.insert(cellOffset, UDim2.new(0, CELL_PADDING, 0, 0))
|
||||
table.insert(headerCellSize, UDim2.new(1, -totalSummaryWidth - CELL_PADDING, 0, HEADER_HEIGHT))
|
||||
table.insert(entryCellSize, UDim2.new(1, -totalSummaryWidth - CELL_PADDING, 0, ENTRY_HEIGHT))
|
||||
|
||||
for _, width in ipairs(CELL_WIDTH) do
|
||||
table.insert(cellOffset,UDim2.new(1, currOffset + CELL_PADDING, 0, 0))
|
||||
table.insert(headerCellSize, UDim2.new(0, width - CELL_PADDING, 0, HEADER_HEIGHT))
|
||||
table.insert(entryCellSize, UDim2.new(0, width - CELL_PADDING, 0, ENTRY_HEIGHT))
|
||||
currOffset = currOffset + width
|
||||
end
|
||||
|
||||
local verticalOffsets = {}
|
||||
for i, offset in ipairs(cellOffset) do
|
||||
verticalOffsets[i] = UDim2.new(
|
||||
offset.X.Scale,
|
||||
offset.X.Offset - CELL_PADDING,
|
||||
offset.Y.Scale,
|
||||
offset.Y.Offset
|
||||
)
|
||||
end
|
||||
|
||||
local NetworkSummary = Roact.Component:extend("NetworkSummary")
|
||||
|
||||
function NetworkSummary:render()
|
||||
local width = self.props.width
|
||||
local httpSummaryTable = self.props.httpSummaryTable or {}
|
||||
local layoutOrder = self.props.layoutOrder
|
||||
|
||||
local elements = {}
|
||||
elements["UIListLayout"] = Roact.createElement("UIListLayout", {
|
||||
FillDirection = Enum.FillDirection.Vertical,
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Left,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Top,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
})
|
||||
|
||||
local summaryHeader = {}
|
||||
summaryHeader["UpperHorizontalLine"] = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, LINE_WIDTH),
|
||||
BackgroundColor3 = LINE_COLOR,
|
||||
BorderSizePixel = 0,
|
||||
})
|
||||
summaryHeader["LowerHorizontalLine"] = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, LINE_WIDTH),
|
||||
Position = UDim2.new(0, 0, 1, 0),
|
||||
BackgroundColor3 = LINE_COLOR,
|
||||
BorderSizePixel = 0,
|
||||
})
|
||||
|
||||
for i = 2, #verticalOffsets do
|
||||
local key = string.format("VerticalLine_%d",i)
|
||||
summaryHeader[key] = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(0, LINE_WIDTH, 0, HEADER_HEIGHT),
|
||||
Position = verticalOffsets[i],
|
||||
BackgroundColor3 = LINE_COLOR,
|
||||
BorderSizePixel = 0,
|
||||
})
|
||||
end
|
||||
|
||||
for ind, name in ipairs(HEADER_NAMES) do
|
||||
summaryHeader[name] = Roact.createElement(HeaderButton, {
|
||||
text = name,
|
||||
size = headerCellSize[ind],
|
||||
pos = cellOffset[ind],
|
||||
})
|
||||
end
|
||||
elements["Header"] = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(0, width, 0, HEADER_HEIGHT),
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 1,
|
||||
}, summaryHeader)
|
||||
|
||||
local entryCount = 0
|
||||
for _, dataEntry in pairs(httpSummaryTable) do
|
||||
entryCount = entryCount + 1
|
||||
|
||||
local row = {}
|
||||
|
||||
row.RequestType = Roact.createElement(CellLabel, {
|
||||
text = dataEntry.RequestType,
|
||||
size = headerCellSize[1],
|
||||
pos = cellOffset[1],
|
||||
})
|
||||
|
||||
row.RequestCount = Roact.createElement(CellLabel, {
|
||||
text = dataEntry.RequestCount,
|
||||
size = headerCellSize[2],
|
||||
pos = cellOffset[2],
|
||||
})
|
||||
|
||||
row.FailedCount = Roact.createElement(CellLabel, {
|
||||
text = dataEntry.FailedCount,
|
||||
size = headerCellSize[3],
|
||||
pos = cellOffset[3],
|
||||
})
|
||||
|
||||
row.AverageTime = Roact.createElement(CellLabel, {
|
||||
text = string.format("%.3f", dataEntry.AverageTime),
|
||||
size = headerCellSize[4],
|
||||
pos = cellOffset[4],
|
||||
})
|
||||
|
||||
row.MinTime = Roact.createElement(CellLabel, {
|
||||
text = string.format("%.3f", dataEntry.MinTime),
|
||||
size = headerCellSize[5],
|
||||
pos = cellOffset[5],
|
||||
})
|
||||
|
||||
row.MaxTime = Roact.createElement(CellLabel, {
|
||||
text = string.format("%.3f", dataEntry.MaxTime),
|
||||
size = headerCellSize[6],
|
||||
pos = cellOffset[6],
|
||||
})
|
||||
|
||||
row.LowerHorizontalLine = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, LINE_WIDTH),
|
||||
Position = UDim2.new(0, 0, 1, 0),
|
||||
BackgroundColor3 = LINE_COLOR,
|
||||
BorderSizePixel = 0,
|
||||
})
|
||||
|
||||
for ind = 2, #verticalOffsets do
|
||||
local key = string.format("VerticalLine_%d",ind)
|
||||
row[key] = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(0, LINE_WIDTH, 1, 0),
|
||||
Position = verticalOffsets[ind],
|
||||
BackgroundColor3 = LINE_COLOR,
|
||||
BorderSizePixel = 0,
|
||||
})
|
||||
end
|
||||
|
||||
elements[dataEntry.RequestType] = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(0, width, 0, ENTRY_HEIGHT),
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = entryCount + 1
|
||||
},row)
|
||||
end
|
||||
|
||||
if entryCount == 0 then
|
||||
elements["Padding"] = Roact.createElement("TextLabel", {
|
||||
Size = UDim2.new(0, width, 0, ENTRY_HEIGHT),
|
||||
Text = NON_DATA_STR,
|
||||
TextColor3 = TEXT_COLOR,
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 2
|
||||
})
|
||||
entryCount = 1
|
||||
end
|
||||
|
||||
local summaryHeight = entryCount * ENTRY_HEIGHT + HEADER_HEIGHT
|
||||
|
||||
-- update offsets to remove padding so we can use for vertical line
|
||||
return Roact.createElement("ScrollingFrame", {
|
||||
Size = UDim2.new(1, 0, 0, summaryHeight),
|
||||
ScrollingEnabled = false,
|
||||
ScrollBarThickness = 0,
|
||||
BackgroundTransparency = 1,
|
||||
|
||||
LayoutOrder = layoutOrder,
|
||||
}, elements)
|
||||
end
|
||||
|
||||
return NetworkSummary
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
local NetworkSummary = require(script.Parent.NetworkSummary)
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local element = Roact.createElement(NetworkSummary)
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
local Constants = require(script.Parent.Parent.Parent.Constants)
|
||||
local HEADER_HEIGHT = Constants.NetworkFormatting.HeaderFrameHeight
|
||||
local ENTRY_HEIGHT = Constants.NetworkFormatting.EntryFrameHeight
|
||||
local SUMMARY_HEIGHT = Constants.NetworkFormatting.SummaryButtonHeight
|
||||
local MIN_WIDTH = Constants.NetworkFormatting.MinFrameWidth
|
||||
|
||||
local BANNER_FONT_SIZE = Constants.DefaultFontSize.MainWindow
|
||||
local BANNER_FONT = Constants.Font.MainWindowHeader
|
||||
|
||||
local INDENT = 30
|
||||
|
||||
local Components = script.Parent.Parent.Parent.Components
|
||||
local BannerButton = require(Components.BannerButton)
|
||||
local NetworkSummary = require(Components.Network.NetworkSummary)
|
||||
local NetworkChart = require(Components.Network.NetworkChart)
|
||||
|
||||
local NetworkView = Roact.Component:extend("NetworkView")
|
||||
|
||||
function NetworkView:init(props)
|
||||
assert(props.targetNetworkData, "Make sure the NetworkData is assigned for this NetworkView")
|
||||
|
||||
local currentData = props.targetNetworkData:getCurrentData()
|
||||
|
||||
self.onSortChanged = function(sortType)
|
||||
local currSortType = props.targetNetworkData:getSortType()
|
||||
if sortType == currSortType then
|
||||
self:setState({
|
||||
reverseSort = not self.state.reverseSort
|
||||
})
|
||||
else
|
||||
props.targetNetworkData:setSortType(sortType)
|
||||
self:setState({
|
||||
reverseSort = false,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
self.onSummaryButton = function(rbx, input)
|
||||
self:setState({
|
||||
summaryExpanded = not self.state.summaryExpanded,
|
||||
})
|
||||
end
|
||||
|
||||
self.onDetailButton = function(rbx, input)
|
||||
self:setState({
|
||||
entriesExpanded = not self.state.entriesExpanded,
|
||||
})
|
||||
end
|
||||
|
||||
self.ref = Roact.createRef()
|
||||
|
||||
self.state = {
|
||||
httpSummaryTable = currentData.summaryTable,
|
||||
httpSummaryCount = currentData.summaryCount,
|
||||
httpEntryList = currentData.entryList,
|
||||
summaryExpanded = true,
|
||||
entriesExpanded = true,
|
||||
indexOfEntryExpanded = 0,
|
||||
reverseSort = false,
|
||||
absWidth = 0,
|
||||
}
|
||||
end
|
||||
|
||||
function NetworkView:didUpdate()
|
||||
if self.ref.current then
|
||||
if self.state.absWidth ~= self.ref.current.AbsoluteSize.X then
|
||||
self:setState({
|
||||
absWidth = self.ref.current.AbsoluteSize.X,
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function NetworkView:didMount()
|
||||
local networkDataSignal = self.props.targetNetworkData:Signal()
|
||||
self.httpEntryAddedConnector = networkDataSignal:Connect(function(summaryTable, summaryCount, entryList)
|
||||
self:setState({
|
||||
httpSummaryTable = summaryTable,
|
||||
httpSummaryCount = summaryCount,
|
||||
httpEntryList = entryList,
|
||||
})
|
||||
end)
|
||||
|
||||
if self.ref.current then
|
||||
self:setState({
|
||||
absWidth = self.ref.current.AbsoluteSize.X,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function NetworkView:willUnmount()
|
||||
self.httpEntryAddedConnector:Disconnect()
|
||||
self.httpEntryAddedConnector = nil
|
||||
end
|
||||
|
||||
function NetworkView:render()
|
||||
local layoutOrder = self.props.layoutOrder
|
||||
local searchTerm = self.props.searchTerm
|
||||
local size = self.props.size
|
||||
|
||||
local summaryExpanded = self.state.summaryExpanded
|
||||
local entriesExpanded = self.state.entriesExpanded
|
||||
local reverseSort = self.state.reverseSort
|
||||
local httpSummaryTable = self.state.httpSummaryTable
|
||||
local httpEntryList = self.state.httpEntryList
|
||||
local httpSummaryCount = self.state.httpSummaryCount
|
||||
|
||||
local absWidth = math.max(MIN_WIDTH, self.state.absWidth)
|
||||
|
||||
-- for both the detail button and summary button
|
||||
local summaryHeight = SUMMARY_HEIGHT * 2
|
||||
if summaryExpanded then
|
||||
summaryHeight = summaryHeight + httpSummaryCount * ENTRY_HEIGHT + HEADER_HEIGHT
|
||||
end
|
||||
local chartHeight
|
||||
if self.ref.current then
|
||||
chartHeight = self.ref.current.AbsoluteSize.Y - summaryHeight
|
||||
else
|
||||
chartHeight = size.Y.Offset - summaryHeight
|
||||
end
|
||||
chartHeight = math.max(0, chartHeight)
|
||||
|
||||
return Roact.createElement("Frame", {
|
||||
Size = size,
|
||||
BackgroundTransparency = 1,
|
||||
ClipsDescendants = true,
|
||||
|
||||
LayoutOrder = layoutOrder,
|
||||
|
||||
[Roact.Ref] = self.ref,
|
||||
}, {
|
||||
UIListLayout = Roact.createElement("UIListLayout", {
|
||||
FillDirection = Enum.FillDirection.Vertical,
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Left,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Top,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
}),
|
||||
SummaryButton = Roact.createElement(BannerButton, {
|
||||
size = UDim2.new(1, 0, 0, SUMMARY_HEIGHT),
|
||||
pos = UDim2.new(),
|
||||
isExpanded = summaryExpanded,
|
||||
onButtonPress = self.onSummaryButton,
|
||||
layoutOrder = 1,
|
||||
}, {
|
||||
Text = Roact.createElement("TextLabel", {
|
||||
Text = "Summary",
|
||||
TextColor3 = Constants.Color.Text,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextSize = BANNER_FONT_SIZE,
|
||||
Font = BANNER_FONT,
|
||||
|
||||
Size = UDim2.new(1,-INDENT,0,SUMMARY_HEIGHT),
|
||||
Position = UDim2.new(0, INDENT, 0, 0),
|
||||
BackgroundTransparency = 1,
|
||||
})
|
||||
}),
|
||||
Summary = summaryExpanded and Roact.createElement(NetworkSummary, {
|
||||
width = absWidth,
|
||||
httpSummaryTable = httpSummaryTable,
|
||||
layoutOrder = 2,
|
||||
}),
|
||||
DetailsButton = Roact.createElement(BannerButton, {
|
||||
size = UDim2.new(1, 0, 0, SUMMARY_HEIGHT),
|
||||
pos = UDim2.new(),
|
||||
isExpanded = entriesExpanded,
|
||||
onButtonPress = self.onDetailButton,
|
||||
layoutOrder = 3,
|
||||
}, {
|
||||
Text = Roact.createElement("TextLabel", {
|
||||
Text = "Details",
|
||||
TextColor3 = Constants.Color.Text,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextSize = BANNER_FONT_SIZE,
|
||||
Font = BANNER_FONT,
|
||||
|
||||
Size = UDim2.new(1,-INDENT,0,SUMMARY_HEIGHT),
|
||||
Position = UDim2.new(0, INDENT, 0, 0),
|
||||
BackgroundTransparency = 1,
|
||||
})
|
||||
}),
|
||||
Entries = entriesExpanded and Roact.createElement(NetworkChart, {
|
||||
httpEntryList = httpEntryList,
|
||||
chartHeight = chartHeight,
|
||||
width = absWidth,
|
||||
searchTerm = searchTerm,
|
||||
reverseSort = reverseSort,
|
||||
layoutOrder = 4,
|
||||
|
||||
onSortChanged = self.onSortChanged
|
||||
})
|
||||
})
|
||||
end
|
||||
|
||||
return NetworkView
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user