mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-04-20 12:45:05 +00:00
Stylua formatting (#785)
Uses Stylua to format all existing Lua files, and adds a CI check in `lint` to pin this improvement. Excludes formatting dependencies, of course.
This commit is contained in:
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
|||||||
run: cargo test --locked --verbose
|
run: cargo test --locked --verbose
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
name: Rustfmt and Clippy
|
name: Rustfmt, Clippy, & Stylua
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -62,6 +62,9 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
version: 'v0.2.7'
|
version: 'v0.2.7'
|
||||||
|
|
||||||
|
- name: Stylua
|
||||||
|
run: stylua --check plugin/src
|
||||||
|
|
||||||
- name: Rustfmt
|
- name: Rustfmt
|
||||||
run: cargo fmt -- --check
|
run: cargo fmt -- --check
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
[tools]
|
[tools]
|
||||||
rojo = "rojo-rbx/rojo@7.3.0"
|
rojo = "rojo-rbx/rojo@7.3.0"
|
||||||
selene = "Kampfkarren/selene@0.20.0"
|
selene = "Kampfkarren/selene@0.25.0"
|
||||||
|
stylua = "JohnnyMorganz/stylua@0.18.2"
|
||||||
run-in-roblox = "rojo-rbx/run-in-roblox@0.3.0"
|
run-in-roblox = "rojo-rbx/run-in-roblox@0.3.0"
|
||||||
|
|||||||
@@ -24,15 +24,17 @@ end
|
|||||||
local function rejectWrongProtocolVersion(infoResponseBody)
|
local function rejectWrongProtocolVersion(infoResponseBody)
|
||||||
if infoResponseBody.protocolVersion ~= Config.protocolVersion then
|
if infoResponseBody.protocolVersion ~= Config.protocolVersion then
|
||||||
local message = (
|
local message = (
|
||||||
"Found a Rojo dev server, but it's using a different protocol version, and is incompatible." ..
|
"Found a Rojo dev server, but it's using a different protocol version, and is incompatible."
|
||||||
"\nMake sure you have matching versions of both the Rojo plugin and server!" ..
|
.. "\nMake sure you have matching versions of both the Rojo plugin and server!"
|
||||||
"\n\nYour client is version %s, with protocol version %s. It expects server version %s." ..
|
.. "\n\nYour client is version %s, with protocol version %s. It expects server version %s."
|
||||||
"\nYour server is version %s, with protocol version %s." ..
|
.. "\nYour server is version %s, with protocol version %s."
|
||||||
"\n\nGo to https://github.com/rojo-rbx/rojo for more details."
|
.. "\n\nGo to https://github.com/rojo-rbx/rojo for more details."
|
||||||
):format(
|
):format(
|
||||||
Version.display(Config.version), Config.protocolVersion,
|
Version.display(Config.version),
|
||||||
|
Config.protocolVersion,
|
||||||
Config.expectedServerVersionString,
|
Config.expectedServerVersionString,
|
||||||
infoResponseBody.serverVersion, infoResponseBody.protocolVersion
|
infoResponseBody.serverVersion,
|
||||||
|
infoResponseBody.protocolVersion
|
||||||
)
|
)
|
||||||
|
|
||||||
return Promise.reject(message)
|
return Promise.reject(message)
|
||||||
@@ -59,14 +61,11 @@ local function rejectWrongPlaceId(infoResponseBody)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local message = (
|
local message = (
|
||||||
"Found a Rojo server, but its project is set to only be used with a specific list of places." ..
|
"Found a Rojo server, but its project is set to only be used with a specific list of places."
|
||||||
"\nYour place ID is %s, but needs to be one of these:" ..
|
.. "\nYour place ID is %s, but needs to be one of these:"
|
||||||
"\n%s" ..
|
.. "\n%s"
|
||||||
"\n\nTo change this list, edit 'servePlaceIds' in your .project.json file."
|
.. "\n\nTo change this list, edit 'servePlaceIds' in your .project.json file."
|
||||||
):format(
|
):format(tostring(game.PlaceId), table.concat(idList, "\n"))
|
||||||
tostring(game.PlaceId),
|
|
||||||
table.concat(idList, "\n")
|
|
||||||
)
|
|
||||||
|
|
||||||
return Promise.reject(message)
|
return Promise.reject(message)
|
||||||
end
|
end
|
||||||
@@ -141,18 +140,15 @@ end
|
|||||||
function ApiContext:read(ids)
|
function ApiContext:read(ids)
|
||||||
local url = ("%s/api/read/%s"):format(self.__baseUrl, table.concat(ids, ","))
|
local url = ("%s/api/read/%s"):format(self.__baseUrl, table.concat(ids, ","))
|
||||||
|
|
||||||
return Http.get(url)
|
return Http.get(url):andThen(rejectFailedRequests):andThen(Http.Response.json):andThen(function(body)
|
||||||
:andThen(rejectFailedRequests)
|
if body.sessionId ~= self.__sessionId then
|
||||||
:andThen(Http.Response.json)
|
return Promise.reject("Server changed ID")
|
||||||
:andThen(function(body)
|
end
|
||||||
if body.sessionId ~= self.__sessionId then
|
|
||||||
return Promise.reject("Server changed ID")
|
|
||||||
end
|
|
||||||
|
|
||||||
assert(validateApiRead(body))
|
assert(validateApiRead(body))
|
||||||
|
|
||||||
return body
|
return body
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
function ApiContext:write(patch)
|
function ApiContext:write(patch)
|
||||||
@@ -189,28 +185,24 @@ function ApiContext:write(patch)
|
|||||||
|
|
||||||
body = Http.jsonEncode(body)
|
body = Http.jsonEncode(body)
|
||||||
|
|
||||||
return Http.post(url, body)
|
return Http.post(url, body):andThen(rejectFailedRequests):andThen(Http.Response.json):andThen(function(body)
|
||||||
:andThen(rejectFailedRequests)
|
Log.info("Write response: {:?}", body)
|
||||||
:andThen(Http.Response.json)
|
|
||||||
:andThen(function(body)
|
|
||||||
Log.info("Write response: {:?}", body)
|
|
||||||
|
|
||||||
return body
|
return body
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
function ApiContext:retrieveMessages()
|
function ApiContext:retrieveMessages()
|
||||||
local url = ("%s/api/subscribe/%s"):format(self.__baseUrl, self.__messageCursor)
|
local url = ("%s/api/subscribe/%s"):format(self.__baseUrl, self.__messageCursor)
|
||||||
|
|
||||||
local function sendRequest()
|
local function sendRequest()
|
||||||
local request = Http.get(url)
|
local request = Http.get(url):catch(function(err)
|
||||||
:catch(function(err)
|
if err.type == Http.Error.Kind.Timeout and self.__connected then
|
||||||
if err.type == Http.Error.Kind.Timeout and self.__connected then
|
return sendRequest()
|
||||||
return sendRequest()
|
end
|
||||||
end
|
|
||||||
|
|
||||||
return Promise.reject(err)
|
return Promise.reject(err)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
Log.trace("Tracking request {}", request)
|
Log.trace("Tracking request {}", request)
|
||||||
self.__activeRequests[request] = true
|
self.__activeRequests[request] = true
|
||||||
@@ -222,35 +214,29 @@ function ApiContext:retrieveMessages()
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
return sendRequest()
|
return sendRequest():andThen(rejectFailedRequests):andThen(Http.Response.json):andThen(function(body)
|
||||||
:andThen(rejectFailedRequests)
|
if body.sessionId ~= self.__sessionId then
|
||||||
:andThen(Http.Response.json)
|
return Promise.reject("Server changed ID")
|
||||||
:andThen(function(body)
|
end
|
||||||
if body.sessionId ~= self.__sessionId then
|
|
||||||
return Promise.reject("Server changed ID")
|
|
||||||
end
|
|
||||||
|
|
||||||
assert(validateApiSubscribe(body))
|
assert(validateApiSubscribe(body))
|
||||||
|
|
||||||
self:setMessageCursor(body.messageCursor)
|
self:setMessageCursor(body.messageCursor)
|
||||||
|
|
||||||
return body.messages
|
return body.messages
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
function ApiContext:open(id)
|
function ApiContext:open(id)
|
||||||
local url = ("%s/api/open/%s"):format(self.__baseUrl, id)
|
local url = ("%s/api/open/%s"):format(self.__baseUrl, id)
|
||||||
|
|
||||||
return Http.post(url, "")
|
return Http.post(url, ""):andThen(rejectFailedRequests):andThen(Http.Response.json):andThen(function(body)
|
||||||
:andThen(rejectFailedRequests)
|
if body.sessionId ~= self.__sessionId then
|
||||||
:andThen(Http.Response.json)
|
return Promise.reject("Server changed ID")
|
||||||
:andThen(function(body)
|
end
|
||||||
if body.sessionId ~= self.__sessionId then
|
|
||||||
return Promise.reject("Server changed ID")
|
|
||||||
end
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
return ApiContext
|
return ApiContext
|
||||||
|
|||||||
@@ -23,12 +23,10 @@ end
|
|||||||
|
|
||||||
function Checkbox:didUpdate(lastProps)
|
function Checkbox:didUpdate(lastProps)
|
||||||
if lastProps.active ~= self.props.active then
|
if lastProps.active ~= self.props.active then
|
||||||
self.motor:setGoal(
|
self.motor:setGoal(Flipper.Spring.new(self.props.active and 1 or 0, {
|
||||||
Flipper.Spring.new(self.props.active and 1 or 0, {
|
frequency = 6,
|
||||||
frequency = 6,
|
dampingRatio = 1.1,
|
||||||
dampingRatio = 1.1,
|
}))
|
||||||
})
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -52,13 +50,14 @@ function Checkbox:render()
|
|||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
|
|
||||||
[Roact.Event.Activated] = function()
|
[Roact.Event.Activated] = function()
|
||||||
if self.props.locked then return end
|
if self.props.locked then
|
||||||
|
return
|
||||||
|
end
|
||||||
self.props.onClick()
|
self.props.onClick()
|
||||||
end,
|
end,
|
||||||
}, {
|
}, {
|
||||||
StateTip = e(Tooltip.Trigger, {
|
StateTip = e(Tooltip.Trigger, {
|
||||||
text =
|
text = (if self.props.locked then "[LOCKED] " else "")
|
||||||
(if self.props.locked then "[LOCKED] " else "")
|
|
||||||
.. (if self.props.active then "Enabled" else "Disabled"),
|
.. (if self.props.active then "Enabled" else "Disabled"),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -89,7 +88,9 @@ function Checkbox:render()
|
|||||||
size = UDim2.new(1, 0, 1, 0),
|
size = UDim2.new(1, 0, 1, 0),
|
||||||
}, {
|
}, {
|
||||||
Icon = e("ImageLabel", {
|
Icon = e("ImageLabel", {
|
||||||
Image = if self.props.locked then Assets.Images.Checkbox.Locked else Assets.Images.Checkbox.Inactive,
|
Image = if self.props.locked
|
||||||
|
then Assets.Images.Checkbox.Locked
|
||||||
|
else Assets.Images.Checkbox.Inactive,
|
||||||
ImageColor3 = theme.Inactive.IconColor,
|
ImageColor3 = theme.Inactive.IconColor,
|
||||||
ImageTransparency = self.props.transparency,
|
ImageTransparency = self.props.transparency,
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ function CodeLabel:didMount()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function CodeLabel:didUpdate()
|
function CodeLabel:didUpdate()
|
||||||
self:updateHighlights()
|
self:updateHighlights()
|
||||||
end
|
end
|
||||||
|
|
||||||
function CodeLabel:updateHighlights()
|
function CodeLabel:updateHighlights()
|
||||||
|
|||||||
@@ -36,12 +36,10 @@ function Dropdown:didUpdate(prevProps)
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
self.openMotor:setGoal(
|
self.openMotor:setGoal(Flipper.Spring.new(self.state.open and 1 or 0, {
|
||||||
Flipper.Spring.new(self.state.open and 1 or 0, {
|
frequency = 6,
|
||||||
frequency = 6,
|
dampingRatio = 1.1,
|
||||||
dampingRatio = 1.1,
|
}))
|
||||||
})
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function Dropdown:render()
|
function Dropdown:render()
|
||||||
@@ -52,10 +50,7 @@ function Dropdown:render()
|
|||||||
local width = -1
|
local width = -1
|
||||||
for i, option in self.props.options do
|
for i, option in self.props.options do
|
||||||
local text = tostring(option or "")
|
local text = tostring(option or "")
|
||||||
local textSize = TextService:GetTextSize(
|
local textSize = TextService:GetTextSize(text, 15, Enum.Font.GothamMedium, Vector2.new(math.huge, 20))
|
||||||
text, 15, Enum.Font.GothamMedium,
|
|
||||||
Vector2.new(math.huge, 20)
|
|
||||||
)
|
|
||||||
if textSize.X > width then
|
if textSize.X > width then
|
||||||
width = textSize.X
|
width = textSize.X
|
||||||
end
|
end
|
||||||
@@ -74,7 +69,9 @@ function Dropdown:render()
|
|||||||
Font = Enum.Font.GothamMedium,
|
Font = Enum.Font.GothamMedium,
|
||||||
|
|
||||||
[Roact.Event.Activated] = function()
|
[Roact.Event.Activated] = function()
|
||||||
if self.props.locked then return end
|
if self.props.locked then
|
||||||
|
return
|
||||||
|
end
|
||||||
self:setState({
|
self:setState({
|
||||||
open = false,
|
open = false,
|
||||||
})
|
})
|
||||||
@@ -88,7 +85,7 @@ function Dropdown:render()
|
|||||||
end
|
end
|
||||||
|
|
||||||
return e("ImageButton", {
|
return e("ImageButton", {
|
||||||
Size = UDim2.new(0, width+50, 0, 28),
|
Size = UDim2.new(0, width + 50, 0, 28),
|
||||||
Position = self.props.position,
|
Position = self.props.position,
|
||||||
AnchorPoint = self.props.anchorPoint,
|
AnchorPoint = self.props.anchorPoint,
|
||||||
LayoutOrder = self.props.layoutOrder,
|
LayoutOrder = self.props.layoutOrder,
|
||||||
@@ -96,7 +93,9 @@ function Dropdown:render()
|
|||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
|
|
||||||
[Roact.Event.Activated] = function()
|
[Roact.Event.Activated] = function()
|
||||||
if self.props.locked then return end
|
if self.props.locked then
|
||||||
|
return
|
||||||
|
end
|
||||||
self:setState({
|
self:setState({
|
||||||
open = not self.state.open,
|
open = not self.state.open,
|
||||||
})
|
})
|
||||||
@@ -136,40 +135,42 @@ function Dropdown:render()
|
|||||||
TextTransparency = self.props.transparency,
|
TextTransparency = self.props.transparency,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
Options = if self.state.open then e(SlicedImage, {
|
Options = if self.state.open
|
||||||
slice = Assets.Slices.RoundedBackground,
|
then e(SlicedImage, {
|
||||||
color = theme.BackgroundColor,
|
slice = Assets.Slices.RoundedBackground,
|
||||||
position = UDim2.new(1, 0, 1, 3),
|
color = theme.BackgroundColor,
|
||||||
size = self.openBinding:map(function(a)
|
position = UDim2.new(1, 0, 1, 3),
|
||||||
return UDim2.new(1, 0, a*math.min(3, #self.props.options), 0)
|
size = self.openBinding:map(function(a)
|
||||||
end),
|
return UDim2.new(1, 0, a * math.min(3, #self.props.options), 0)
|
||||||
anchorPoint = Vector2.new(1, 0),
|
end),
|
||||||
}, {
|
anchorPoint = Vector2.new(1, 0),
|
||||||
Border = e(SlicedImage, {
|
|
||||||
slice = Assets.Slices.RoundedBorder,
|
|
||||||
color = theme.BorderColor,
|
|
||||||
transparency = self.props.transparency,
|
|
||||||
size = UDim2.new(1, 0, 1, 0),
|
|
||||||
}),
|
|
||||||
ScrollingFrame = e(ScrollingFrame, {
|
|
||||||
size = UDim2.new(1, -4, 1, -4),
|
|
||||||
position = UDim2.new(0, 2, 0, 2),
|
|
||||||
transparency = self.props.transparency,
|
|
||||||
contentSize = self.contentSize,
|
|
||||||
}, {
|
}, {
|
||||||
Layout = e("UIListLayout", {
|
Border = e(SlicedImage, {
|
||||||
VerticalAlignment = Enum.VerticalAlignment.Top,
|
slice = Assets.Slices.RoundedBorder,
|
||||||
FillDirection = Enum.FillDirection.Vertical,
|
color = theme.BorderColor,
|
||||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
transparency = self.props.transparency,
|
||||||
Padding = UDim.new(0, 0),
|
size = UDim2.new(1, 0, 1, 0),
|
||||||
|
|
||||||
[Roact.Change.AbsoluteContentSize] = function(object)
|
|
||||||
self.setContentSize(object.AbsoluteContentSize)
|
|
||||||
end,
|
|
||||||
}),
|
}),
|
||||||
Roact.createFragment(optionButtons),
|
ScrollingFrame = e(ScrollingFrame, {
|
||||||
}),
|
size = UDim2.new(1, -4, 1, -4),
|
||||||
}) else nil,
|
position = UDim2.new(0, 2, 0, 2),
|
||||||
|
transparency = self.props.transparency,
|
||||||
|
contentSize = self.contentSize,
|
||||||
|
}, {
|
||||||
|
Layout = e("UIListLayout", {
|
||||||
|
VerticalAlignment = Enum.VerticalAlignment.Top,
|
||||||
|
FillDirection = Enum.FillDirection.Vertical,
|
||||||
|
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||||
|
Padding = UDim.new(0, 0),
|
||||||
|
|
||||||
|
[Roact.Change.AbsoluteContentSize] = function(object)
|
||||||
|
self.setContentSize(object.AbsoluteContentSize)
|
||||||
|
end,
|
||||||
|
}),
|
||||||
|
Roact.createFragment(optionButtons),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
else nil,
|
||||||
})
|
})
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -38,15 +38,11 @@ function IconButton:render()
|
|||||||
[Roact.Event.Activated] = self.props.onClick,
|
[Roact.Event.Activated] = self.props.onClick,
|
||||||
|
|
||||||
[Roact.Event.MouseEnter] = function()
|
[Roact.Event.MouseEnter] = function()
|
||||||
self.motor:setGoal(
|
self.motor:setGoal(Flipper.Spring.new(1, HOVER_SPRING_PROPS))
|
||||||
Flipper.Spring.new(1, HOVER_SPRING_PROPS)
|
|
||||||
)
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
[Roact.Event.MouseLeave] = function()
|
[Roact.Event.MouseLeave] = function()
|
||||||
self.motor:setGoal(
|
self.motor:setGoal(Flipper.Spring.new(0, HOVER_SPRING_PROPS))
|
||||||
Flipper.Spring.new(0, HOVER_SPRING_PROPS)
|
|
||||||
)
|
|
||||||
end,
|
end,
|
||||||
}, {
|
}, {
|
||||||
Icon = e("ImageLabel", {
|
Icon = e("ImageLabel", {
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ local function DisplayValue(props)
|
|||||||
Position = UDim2.new(0, 25, 0, 0),
|
Position = UDim2.new(0, 25, 0, 0),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
elseif t == "table" then
|
elseif t == "table" then
|
||||||
-- Showing a memory address for tables is useless, so we want to show the best we can
|
-- Showing a memory address for tables is useless, so we want to show the best we can
|
||||||
local textRepresentation = nil
|
local textRepresentation = nil
|
||||||
@@ -62,10 +61,10 @@ local function DisplayValue(props)
|
|||||||
|
|
||||||
-- Wrap strings in quotes
|
-- Wrap strings in quotes
|
||||||
if type(k) == "string" then
|
if type(k) == "string" then
|
||||||
k = "\"" .. k .. "\""
|
k = '"' .. k .. '"'
|
||||||
end
|
end
|
||||||
if type(v) == "string" then
|
if type(v) == "string" then
|
||||||
v = "\"" .. v .. "\""
|
v = '"' .. v .. '"'
|
||||||
end
|
end
|
||||||
|
|
||||||
out[i] = string.format("[%s] = %s", tostring(k), tostring(v))
|
out[i] = string.format("[%s] = %s", tostring(k), tostring(v))
|
||||||
|
|||||||
@@ -43,9 +43,14 @@ end
|
|||||||
function PatchVisualizer:render()
|
function PatchVisualizer:render()
|
||||||
local patchTree = self.props.patchTree
|
local patchTree = self.props.patchTree
|
||||||
if patchTree == nil and self.props.patch ~= nil then
|
if patchTree == nil and self.props.patch ~= nil then
|
||||||
patchTree = PatchTree.build(self.props.patch, self.props.instanceMap, self.props.changeListHeaders or { "Property", "Current", "Incoming" })
|
patchTree = PatchTree.build(
|
||||||
|
self.props.patch,
|
||||||
|
self.props.instanceMap,
|
||||||
|
self.props.changeListHeaders or { "Property", "Current", "Incoming" }
|
||||||
|
)
|
||||||
if self.props.unappliedPatch then
|
if self.props.unappliedPatch then
|
||||||
patchTree = PatchTree.updateMetadata(patchTree, self.props.patch, self.props.instanceMap, self.props.unappliedPatch)
|
patchTree =
|
||||||
|
PatchTree.updateMetadata(patchTree, self.props.patch, self.props.instanceMap, self.props.unappliedPatch)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -10,11 +10,15 @@ local StudioPluginContext = require(script.Parent.StudioPluginContext)
|
|||||||
|
|
||||||
local e = Roact.createElement
|
local e = Roact.createElement
|
||||||
|
|
||||||
local StudioPluginAction = Roact.Component:extend("StudioPluginAction")
|
local StudioPluginAction = Roact.Component:extend("StudioPluginAction")
|
||||||
|
|
||||||
function StudioPluginAction:init()
|
function StudioPluginAction:init()
|
||||||
self.pluginAction = self.props.plugin:CreatePluginAction(
|
self.pluginAction = self.props.plugin:CreatePluginAction(
|
||||||
self.props.name, self.props.title, self.props.description, self.props.icon, self.props.bindable
|
self.props.name,
|
||||||
|
self.props.title,
|
||||||
|
self.props.description,
|
||||||
|
self.props.icon,
|
||||||
|
self.props.bindable
|
||||||
)
|
)
|
||||||
|
|
||||||
self.pluginAction.Triggered:Connect(self.props.onTriggered)
|
self.pluginAction.Triggered:Connect(self.props.onTriggered)
|
||||||
@@ -31,9 +35,12 @@ end
|
|||||||
local function StudioPluginActionWrapper(props)
|
local function StudioPluginActionWrapper(props)
|
||||||
return e(StudioPluginContext.Consumer, {
|
return e(StudioPluginContext.Consumer, {
|
||||||
render = function(plugin)
|
render = function(plugin)
|
||||||
return e(StudioPluginAction, Dictionary.merge(props, {
|
return e(
|
||||||
plugin = plugin,
|
StudioPluginAction,
|
||||||
}))
|
Dictionary.merge(props, {
|
||||||
|
plugin = plugin,
|
||||||
|
})
|
||||||
|
)
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -38,7 +38,10 @@ function StudioPluginGui:init()
|
|||||||
minimumSize.Y
|
minimumSize.Y
|
||||||
)
|
)
|
||||||
|
|
||||||
local pluginGui = self.props.plugin:CreateDockWidgetPluginGui(if self.props.isEphemeral then HttpService:GenerateGUID(false) else self.props.id, dockWidgetPluginGuiInfo)
|
local pluginGui = self.props.plugin:CreateDockWidgetPluginGui(
|
||||||
|
if self.props.isEphemeral then HttpService:GenerateGUID(false) else self.props.id,
|
||||||
|
dockWidgetPluginGuiInfo
|
||||||
|
)
|
||||||
|
|
||||||
pluginGui.Name = self.props.id
|
pluginGui.Name = self.props.id
|
||||||
pluginGui.Title = self.props.title
|
pluginGui.Title = self.props.title
|
||||||
|
|||||||
@@ -18,12 +18,8 @@ StudioToggleButton.defaultProps = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function StudioToggleButton:init()
|
function StudioToggleButton:init()
|
||||||
local button = self.props.toolbar:CreateButton(
|
local button =
|
||||||
self.props.name,
|
self.props.toolbar:CreateButton(self.props.name, self.props.tooltip, self.props.icon, self.props.text)
|
||||||
self.props.tooltip,
|
|
||||||
self.props.icon,
|
|
||||||
self.props.text
|
|
||||||
)
|
|
||||||
|
|
||||||
button.Click:Connect(function()
|
button.Click:Connect(function()
|
||||||
if self.props.onClick then
|
if self.props.onClick then
|
||||||
@@ -61,9 +57,12 @@ end
|
|||||||
local function StudioToggleButtonWrapper(props)
|
local function StudioToggleButtonWrapper(props)
|
||||||
return e(StudioToolbarContext.Consumer, {
|
return e(StudioToolbarContext.Consumer, {
|
||||||
render = function(toolbar)
|
render = function(toolbar)
|
||||||
return e(StudioToggleButton, Dictionary.merge(props, {
|
return e(
|
||||||
toolbar = toolbar,
|
StudioToggleButton,
|
||||||
}))
|
Dictionary.merge(props, {
|
||||||
|
toolbar = toolbar,
|
||||||
|
})
|
||||||
|
)
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -36,9 +36,12 @@ end
|
|||||||
local function StudioToolbarWrapper(props)
|
local function StudioToolbarWrapper(props)
|
||||||
return e(StudioPluginContext.Consumer, {
|
return e(StudioPluginContext.Consumer, {
|
||||||
render = function(plugin)
|
render = function(plugin)
|
||||||
return e(StudioToolbar, Dictionary.merge(props, {
|
return e(
|
||||||
plugin = plugin,
|
StudioToolbar,
|
||||||
}))
|
Dictionary.merge(props, {
|
||||||
|
plugin = plugin,
|
||||||
|
})
|
||||||
|
)
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -41,10 +41,8 @@ end
|
|||||||
|
|
||||||
function TextButton:render()
|
function TextButton:render()
|
||||||
return Theme.with(function(theme)
|
return Theme.with(function(theme)
|
||||||
local textSize = TextService:GetTextSize(
|
local textSize =
|
||||||
self.props.text, 18, Enum.Font.GothamSemibold,
|
TextService:GetTextSize(self.props.text, 18, Enum.Font.GothamSemibold, Vector2.new(math.huge, math.huge))
|
||||||
Vector2.new(math.huge, math.huge)
|
|
||||||
)
|
|
||||||
|
|
||||||
local style = self.props.style
|
local style = self.props.style
|
||||||
|
|
||||||
@@ -124,7 +122,11 @@ function TextButton:render()
|
|||||||
|
|
||||||
Background = style == "Solid" and e(SlicedImage, {
|
Background = style == "Solid" and e(SlicedImage, {
|
||||||
slice = Assets.Slices.RoundedBackground,
|
slice = Assets.Slices.RoundedBackground,
|
||||||
color = bindingUtil.mapLerp(bindingEnabled, theme.Enabled.BackgroundColor, theme.Disabled.BackgroundColor),
|
color = bindingUtil.mapLerp(
|
||||||
|
bindingEnabled,
|
||||||
|
theme.Enabled.BackgroundColor,
|
||||||
|
theme.Disabled.BackgroundColor
|
||||||
|
),
|
||||||
transparency = self.props.transparency,
|
transparency = self.props.transparency,
|
||||||
|
|
||||||
size = UDim2.new(1, 0, 1, 0),
|
size = UDim2.new(1, 0, 1, 0),
|
||||||
|
|||||||
@@ -38,22 +38,22 @@ end
|
|||||||
|
|
||||||
function TextInput:render()
|
function TextInput:render()
|
||||||
return Theme.with(function(theme)
|
return Theme.with(function(theme)
|
||||||
theme = theme.TextInput
|
theme = theme.TextInput
|
||||||
|
|
||||||
local bindingHover = bindingUtil.deriveProperty(self.binding, "hover")
|
local bindingHover = bindingUtil.deriveProperty(self.binding, "hover")
|
||||||
local bindingEnabled = bindingUtil.deriveProperty(self.binding, "enabled")
|
local bindingEnabled = bindingUtil.deriveProperty(self.binding, "enabled")
|
||||||
|
|
||||||
return e(SlicedImage, {
|
return e(SlicedImage, {
|
||||||
slice = Assets.Slices.RoundedBorder,
|
slice = Assets.Slices.RoundedBorder,
|
||||||
color = bindingUtil.mapLerp(bindingEnabled, theme.Enabled.BorderColor, theme.Disabled.BorderColor),
|
color = bindingUtil.mapLerp(bindingEnabled, theme.Enabled.BorderColor, theme.Disabled.BorderColor),
|
||||||
transparency = self.props.transparency,
|
transparency = self.props.transparency,
|
||||||
|
|
||||||
size = self.props.size or UDim2.new(1, 0, 1, 0),
|
size = self.props.size or UDim2.new(1, 0, 1, 0),
|
||||||
position = self.props.position,
|
position = self.props.position,
|
||||||
layoutOrder = self.props.layoutOrder,
|
layoutOrder = self.props.layoutOrder,
|
||||||
anchorPoint = self.props.anchorPoint,
|
anchorPoint = self.props.anchorPoint,
|
||||||
}, {
|
}, {
|
||||||
HoverOverlay = e(SlicedImage, {
|
HoverOverlay = e(SlicedImage, {
|
||||||
slice = Assets.Slices.RoundedBackground,
|
slice = Assets.Slices.RoundedBackground,
|
||||||
color = theme.ActionFillColor,
|
color = theme.ActionFillColor,
|
||||||
transparency = Roact.joinBindings({
|
transparency = Roact.joinBindings({
|
||||||
@@ -67,36 +67,40 @@ function TextInput:render()
|
|||||||
size = UDim2.new(1, 0, 1, 0),
|
size = UDim2.new(1, 0, 1, 0),
|
||||||
zIndex = -1,
|
zIndex = -1,
|
||||||
}),
|
}),
|
||||||
Input = e("TextBox", {
|
Input = e("TextBox", {
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
Size = UDim2.fromScale(1, 1),
|
Size = UDim2.fromScale(1, 1),
|
||||||
Text = self.props.text,
|
Text = self.props.text,
|
||||||
PlaceholderText = self.props.placeholder,
|
PlaceholderText = self.props.placeholder,
|
||||||
Font = Enum.Font.GothamMedium,
|
Font = Enum.Font.GothamMedium,
|
||||||
TextColor3 = bindingUtil.mapLerp(bindingEnabled, theme.Disabled.TextColor, theme.Enabled.TextColor),
|
TextColor3 = bindingUtil.mapLerp(bindingEnabled, theme.Disabled.TextColor, theme.Enabled.TextColor),
|
||||||
PlaceholderColor3 = bindingUtil.mapLerp(bindingEnabled, theme.Disabled.PlaceholderColor, theme.Enabled.PlaceholderColor),
|
PlaceholderColor3 = bindingUtil.mapLerp(
|
||||||
TextSize = 18,
|
bindingEnabled,
|
||||||
TextEditable = self.props.enabled,
|
theme.Disabled.PlaceholderColor,
|
||||||
ClearTextOnFocus = self.props.clearTextOnFocus,
|
theme.Enabled.PlaceholderColor
|
||||||
|
),
|
||||||
|
TextSize = 18,
|
||||||
|
TextEditable = self.props.enabled,
|
||||||
|
ClearTextOnFocus = self.props.clearTextOnFocus,
|
||||||
|
|
||||||
[Roact.Event.MouseEnter] = function()
|
[Roact.Event.MouseEnter] = function()
|
||||||
self.motor:setGoal({
|
self.motor:setGoal({
|
||||||
hover = Flipper.Spring.new(1, SPRING_PROPS),
|
hover = Flipper.Spring.new(1, SPRING_PROPS),
|
||||||
})
|
})
|
||||||
end,
|
end,
|
||||||
|
|
||||||
[Roact.Event.MouseLeave] = function()
|
[Roact.Event.MouseLeave] = function()
|
||||||
self.motor:setGoal({
|
self.motor:setGoal({
|
||||||
hover = Flipper.Spring.new(0, SPRING_PROPS),
|
hover = Flipper.Spring.new(0, SPRING_PROPS),
|
||||||
})
|
})
|
||||||
end,
|
end,
|
||||||
|
|
||||||
[Roact.Event.FocusLost] = function(rbx)
|
[Roact.Event.FocusLost] = function(rbx)
|
||||||
self.props.onEntered(rbx.Text)
|
self.props.onEntered(rbx.Text)
|
||||||
end,
|
end,
|
||||||
}),
|
}),
|
||||||
Children = Roact.createFragment(self.props[Roact.Children]),
|
Children = Roact.createFragment(self.props[Roact.Children]),
|
||||||
})
|
})
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -22,12 +22,16 @@ local TooltipContext = Roact.createContext({})
|
|||||||
|
|
||||||
local function Popup(props)
|
local function Popup(props)
|
||||||
local textSize = TextService:GetTextSize(
|
local textSize = TextService:GetTextSize(
|
||||||
props.Text, 16, Enum.Font.GothamMedium, Vector2.new(math.min(props.parentSize.X, 160), math.huge)
|
props.Text,
|
||||||
|
16,
|
||||||
|
Enum.Font.GothamMedium,
|
||||||
|
Vector2.new(math.min(props.parentSize.X, 160), math.huge)
|
||||||
) + TEXT_PADDING + (Vector2.one * 2)
|
) + TEXT_PADDING + (Vector2.one * 2)
|
||||||
|
|
||||||
local trigger = props.Trigger:getValue()
|
local trigger = props.Trigger:getValue()
|
||||||
|
|
||||||
local spaceBelow = props.parentSize.Y - (trigger.AbsolutePosition.Y + trigger.AbsoluteSize.Y - Y_OVERLAP + TAIL_SIZE)
|
local spaceBelow = props.parentSize.Y
|
||||||
|
- (trigger.AbsolutePosition.Y + trigger.AbsoluteSize.Y - Y_OVERLAP + TAIL_SIZE)
|
||||||
local spaceAbove = trigger.AbsolutePosition.Y + Y_OVERLAP - TAIL_SIZE
|
local spaceAbove = trigger.AbsolutePosition.Y + Y_OVERLAP - TAIL_SIZE
|
||||||
|
|
||||||
-- If there's not enough space below, and there's more space above, then show the tooltip above the trigger
|
-- If there's not enough space below, and there's more space above, then show the tooltip above the trigger
|
||||||
@@ -39,7 +43,10 @@ local function Popup(props)
|
|||||||
if displayAbove then
|
if displayAbove then
|
||||||
Y = math.max(trigger.AbsolutePosition.Y - TAIL_SIZE - textSize.Y + Y_OVERLAP, 0)
|
Y = math.max(trigger.AbsolutePosition.Y - TAIL_SIZE - textSize.Y + Y_OVERLAP, 0)
|
||||||
else
|
else
|
||||||
Y = math.min(trigger.AbsolutePosition.Y + trigger.AbsoluteSize.Y + TAIL_SIZE - Y_OVERLAP, props.parentSize.Y - textSize.Y)
|
Y = math.min(
|
||||||
|
trigger.AbsolutePosition.Y + trigger.AbsoluteSize.Y + TAIL_SIZE - Y_OVERLAP,
|
||||||
|
props.parentSize.Y - textSize.Y
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
return Theme.with(function(theme)
|
return Theme.with(function(theme)
|
||||||
@@ -64,17 +71,9 @@ local function Popup(props)
|
|||||||
|
|
||||||
Tail = e("ImageLabel", {
|
Tail = e("ImageLabel", {
|
||||||
ZIndex = 100,
|
ZIndex = 100,
|
||||||
Position =
|
Position = if displayAbove
|
||||||
if displayAbove then
|
then UDim2.new(0, math.clamp(props.Position.X - X, 6, textSize.X - 6), 1, -1)
|
||||||
UDim2.new(
|
else UDim2.new(0, math.clamp(props.Position.X - X, 6, textSize.X - 6), 0, -TAIL_SIZE + 1),
|
||||||
0, math.clamp(props.Position.X - X, 6, textSize.X-6),
|
|
||||||
1, -1
|
|
||||||
)
|
|
||||||
else
|
|
||||||
UDim2.new(
|
|
||||||
0, math.clamp(props.Position.X - X, 6, textSize.X-6),
|
|
||||||
0, -TAIL_SIZE+1
|
|
||||||
),
|
|
||||||
Size = UDim2.fromOffset(TAIL_SIZE, TAIL_SIZE),
|
Size = UDim2.fromOffset(TAIL_SIZE, TAIL_SIZE),
|
||||||
AnchorPoint = Vector2.new(0.5, 0),
|
AnchorPoint = Vector2.new(0.5, 0),
|
||||||
Rotation = if displayAbove then 180 else 0,
|
Rotation = if displayAbove then 180 else 0,
|
||||||
@@ -90,7 +89,7 @@ local function Popup(props)
|
|||||||
ImageColor3 = theme.BorderedContainer.BorderColor,
|
ImageColor3 = theme.BorderedContainer.BorderColor,
|
||||||
ImageTransparency = props.transparency,
|
ImageTransparency = props.transparency,
|
||||||
}),
|
}),
|
||||||
})
|
}),
|
||||||
})
|
})
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
@@ -200,9 +199,10 @@ function Trigger:isHovering()
|
|||||||
local size = rbx.AbsoluteSize
|
local size = rbx.AbsoluteSize
|
||||||
local mousePos = self.mousePos
|
local mousePos = self.mousePos
|
||||||
|
|
||||||
return
|
return mousePos.X >= pos.X
|
||||||
mousePos.X >= pos.X and mousePos.X <= pos.X + size.X
|
and mousePos.X <= pos.X + size.X
|
||||||
and mousePos.Y >= pos.Y and mousePos.Y <= pos.Y + size.Y
|
and mousePos.Y >= pos.Y
|
||||||
|
and mousePos.Y <= pos.Y + size.Y
|
||||||
end
|
end
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
@@ -236,7 +236,9 @@ end
|
|||||||
function Trigger:render()
|
function Trigger:render()
|
||||||
local function recalculate(rbx)
|
local function recalculate(rbx)
|
||||||
local widget = rbx:FindFirstAncestorOfClass("DockWidgetPluginGui")
|
local widget = rbx:FindFirstAncestorOfClass("DockWidgetPluginGui")
|
||||||
if not widget then return end
|
if not widget then
|
||||||
|
return
|
||||||
|
end
|
||||||
self.mousePos = widget:GetRelativeMousePosition()
|
self.mousePos = widget:GetRelativeMousePosition()
|
||||||
|
|
||||||
self:managePopup()
|
self:managePopup()
|
||||||
|
|||||||
@@ -24,9 +24,7 @@ function TouchRipple:init()
|
|||||||
})
|
})
|
||||||
self.binding = bindingUtil.fromMotor(self.motor)
|
self.binding = bindingUtil.fromMotor(self.motor)
|
||||||
|
|
||||||
self.position, self.setPosition = Roact.createBinding(
|
self.position, self.setPosition = Roact.createBinding(Vector2.new(0, 0))
|
||||||
Vector2.new(0, 0)
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function TouchRipple:reset()
|
function TouchRipple:reset()
|
||||||
@@ -43,10 +41,7 @@ function TouchRipple:calculateRadius(position)
|
|||||||
local container = self.ref.current
|
local container = self.ref.current
|
||||||
|
|
||||||
if container then
|
if container then
|
||||||
local corner = Vector2.new(
|
local corner = Vector2.new(math.floor((1 - position.X) + 0.5), math.floor((1 - position.Y) + 0.5))
|
||||||
math.floor((1 - position.X) + 0.5),
|
|
||||||
math.floor((1 - position.Y) + 0.5)
|
|
||||||
)
|
|
||||||
|
|
||||||
local size = container.AbsoluteSize
|
local size = container.AbsoluteSize
|
||||||
local ratio = size / math.min(size.X, size.Y)
|
local ratio = size / math.min(size.X, size.Y)
|
||||||
@@ -93,10 +88,7 @@ function TouchRipple:render()
|
|||||||
input:GetPropertyChangedSignal("UserInputState"):Connect(function()
|
input:GetPropertyChangedSignal("UserInputState"):Connect(function()
|
||||||
local userInputState = input.UserInputState
|
local userInputState = input.UserInputState
|
||||||
|
|
||||||
if
|
if userInputState == Enum.UserInputState.Cancel or userInputState == Enum.UserInputState.End then
|
||||||
userInputState == Enum.UserInputState.Cancel
|
|
||||||
or userInputState == Enum.UserInputState.End
|
|
||||||
then
|
|
||||||
self.motor:setGoal({
|
self.motor:setGoal({
|
||||||
opacity = Flipper.Spring.new(0, {
|
opacity = Flipper.Spring.new(0, {
|
||||||
frequency = 5,
|
frequency = 5,
|
||||||
@@ -127,8 +119,10 @@ function TouchRipple:render()
|
|||||||
local containerAspect = containerSize.X / containerSize.Y
|
local containerAspect = containerSize.X / containerSize.Y
|
||||||
|
|
||||||
return UDim2.new(
|
return UDim2.new(
|
||||||
currentSize / math.max(containerAspect, 1), 0,
|
currentSize / math.max(containerAspect, 1),
|
||||||
currentSize * math.min(containerAspect, 1), 0
|
0,
|
||||||
|
currentSize * math.min(containerAspect, 1),
|
||||||
|
0
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end),
|
end),
|
||||||
|
|||||||
@@ -37,28 +37,24 @@ function Notification:init()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Notification:dismiss()
|
function Notification:dismiss()
|
||||||
self.motor:setGoal(
|
self.motor:setGoal(Flipper.Spring.new(0, {
|
||||||
Flipper.Spring.new(0, {
|
frequency = 5,
|
||||||
frequency = 5,
|
dampingRatio = 1,
|
||||||
dampingRatio = 1,
|
}))
|
||||||
})
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function Notification:didMount()
|
function Notification:didMount()
|
||||||
self.motor:setGoal(
|
self.motor:setGoal(Flipper.Spring.new(1, {
|
||||||
Flipper.Spring.new(1, {
|
frequency = 3,
|
||||||
frequency = 3,
|
dampingRatio = 1,
|
||||||
dampingRatio = 1,
|
}))
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
self.props.soundPlayer:play(Assets.Sounds.Notification)
|
self.props.soundPlayer:play(Assets.Sounds.Notification)
|
||||||
|
|
||||||
self.timeout = task.spawn(function()
|
self.timeout = task.spawn(function()
|
||||||
local clock = os.clock()
|
local clock = os.clock()
|
||||||
local seen = false
|
local seen = false
|
||||||
while task.wait(1/10) do
|
while task.wait(1 / 10) do
|
||||||
local now = os.clock()
|
local now = os.clock()
|
||||||
local dt = now - clock
|
local dt = now - clock
|
||||||
clock = now
|
clock = now
|
||||||
@@ -90,12 +86,7 @@ function Notification:render()
|
|||||||
return 1 - value
|
return 1 - value
|
||||||
end)
|
end)
|
||||||
|
|
||||||
local textBounds = TextService:GetTextSize(
|
local textBounds = TextService:GetTextSize(self.props.text, 15, Enum.Font.GothamMedium, Vector2.new(350, 700))
|
||||||
self.props.text,
|
|
||||||
15,
|
|
||||||
Enum.Font.GothamMedium,
|
|
||||||
Vector2.new(350, 700)
|
|
||||||
)
|
|
||||||
|
|
||||||
local actionButtons = {}
|
local actionButtons = {}
|
||||||
local buttonsX = 0
|
local buttonsX = 0
|
||||||
@@ -116,7 +107,9 @@ function Notification:render()
|
|||||||
})
|
})
|
||||||
|
|
||||||
buttonsX += TextService:GetTextSize(
|
buttonsX += TextService:GetTextSize(
|
||||||
action.text, 18, Enum.Font.GothamMedium,
|
action.text,
|
||||||
|
18,
|
||||||
|
Enum.Font.GothamMedium,
|
||||||
Vector2.new(math.huge, math.huge)
|
Vector2.new(math.huge, math.huge)
|
||||||
).X + 30
|
).X + 30
|
||||||
|
|
||||||
@@ -156,7 +149,7 @@ function Notification:render()
|
|||||||
Contents = e("Frame", {
|
Contents = e("Frame", {
|
||||||
Size = UDim2.new(0, 35 + contentX, 1, -paddingY),
|
Size = UDim2.new(0, 35 + contentX, 1, -paddingY),
|
||||||
Position = UDim2.new(0, 0, 0, paddingY / 2),
|
Position = UDim2.new(0, 0, 0, paddingY / 2),
|
||||||
BackgroundTransparency = 1
|
BackgroundTransparency = 1,
|
||||||
}, {
|
}, {
|
||||||
Logo = e("ImageLabel", {
|
Logo = e("ImageLabel", {
|
||||||
ImageTransparency = transparency,
|
ImageTransparency = transparency,
|
||||||
@@ -181,28 +174,30 @@ function Notification:render()
|
|||||||
LayoutOrder = 1,
|
LayoutOrder = 1,
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
}),
|
}),
|
||||||
Actions = if self.props.actions then e("Frame", {
|
Actions = if self.props.actions
|
||||||
Size = UDim2.new(1, -40, 0, 35),
|
then e("Frame", {
|
||||||
Position = UDim2.new(1, 0, 1, 0),
|
Size = UDim2.new(1, -40, 0, 35),
|
||||||
AnchorPoint = Vector2.new(1, 1),
|
Position = UDim2.new(1, 0, 1, 0),
|
||||||
BackgroundTransparency = 1,
|
AnchorPoint = Vector2.new(1, 1),
|
||||||
}, {
|
BackgroundTransparency = 1,
|
||||||
Layout = e("UIListLayout", {
|
}, {
|
||||||
FillDirection = Enum.FillDirection.Horizontal,
|
Layout = e("UIListLayout", {
|
||||||
HorizontalAlignment = Enum.HorizontalAlignment.Right,
|
FillDirection = Enum.FillDirection.Horizontal,
|
||||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
HorizontalAlignment = Enum.HorizontalAlignment.Right,
|
||||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||||
Padding = UDim.new(0, 5),
|
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||||
}),
|
Padding = UDim.new(0, 5),
|
||||||
Buttons = Roact.createFragment(actionButtons),
|
}),
|
||||||
}) else nil,
|
Buttons = Roact.createFragment(actionButtons),
|
||||||
|
})
|
||||||
|
else nil,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
Padding = e("UIPadding", {
|
Padding = e("UIPadding", {
|
||||||
PaddingLeft = UDim.new(0, 17),
|
PaddingLeft = UDim.new(0, 17),
|
||||||
PaddingRight = UDim.new(0, 15),
|
PaddingRight = UDim.new(0, 15),
|
||||||
}),
|
}),
|
||||||
})
|
}),
|
||||||
})
|
})
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ local Page = Roact.Component:extend("Page")
|
|||||||
|
|
||||||
function Page:init()
|
function Page:init()
|
||||||
self:setState({
|
self:setState({
|
||||||
rendered = self.props.active
|
rendered = self.props.active,
|
||||||
})
|
})
|
||||||
|
|
||||||
self.motor = Flipper.SingleMotor.new(self.props.active and 1 or 0)
|
self.motor = Flipper.SingleMotor.new(self.props.active and 1 or 0)
|
||||||
@@ -51,20 +51,21 @@ function Page:render()
|
|||||||
Size = UDim2.new(1, 0, 1, 0),
|
Size = UDim2.new(1, 0, 1, 0),
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
}, {
|
}, {
|
||||||
Component = e(self.props.component, Dictionary.merge(self.props, {
|
Component = e(
|
||||||
transparency = transparency,
|
self.props.component,
|
||||||
}))
|
Dictionary.merge(self.props, {
|
||||||
|
transparency = transparency,
|
||||||
|
})
|
||||||
|
),
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
function Page:didUpdate(lastProps)
|
function Page:didUpdate(lastProps)
|
||||||
if self.props.active ~= lastProps.active then
|
if self.props.active ~= lastProps.active then
|
||||||
self.motor:setGoal(
|
self.motor:setGoal(Flipper.Spring.new(self.props.active and 1 or 0, {
|
||||||
Flipper.Spring.new(self.props.active and 1 or 0, {
|
frequency = 6,
|
||||||
frequency = 6,
|
dampingRatio = 1,
|
||||||
dampingRatio = 1,
|
}))
|
||||||
})
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ function ConfirmingPage:render()
|
|||||||
onClick = self.props.onAbort,
|
onClick = self.props.onAbort,
|
||||||
}, {
|
}, {
|
||||||
Tip = e(Tooltip.Trigger, {
|
Tip = e(Tooltip.Trigger, {
|
||||||
text = "Stop the connection process"
|
text = "Stop the connection process",
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -98,7 +98,7 @@ function ConfirmingPage:render()
|
|||||||
onClick = self.props.onReject,
|
onClick = self.props.onReject,
|
||||||
}, {
|
}, {
|
||||||
Tip = e(Tooltip.Trigger, {
|
Tip = e(Tooltip.Trigger, {
|
||||||
text = "Push Studio changes to the Rojo server"
|
text = "Push Studio changes to the Rojo server",
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
else nil,
|
else nil,
|
||||||
@@ -111,7 +111,7 @@ function ConfirmingPage:render()
|
|||||||
onClick = self.props.onAccept,
|
onClick = self.props.onAccept,
|
||||||
}, {
|
}, {
|
||||||
Tip = e(Tooltip.Trigger, {
|
Tip = e(Tooltip.Trigger, {
|
||||||
text = "Pull Rojo server changes to Studio"
|
text = "Pull Rojo server changes to Studio",
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -169,7 +169,7 @@ function ConfirmingPage:render()
|
|||||||
|
|
||||||
oldText = self.state.oldSource,
|
oldText = self.state.oldSource,
|
||||||
newText = self.state.newSource,
|
newText = self.state.newSource,
|
||||||
})
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -21,7 +21,17 @@ local StringDiffVisualizer = require(Plugin.App.Components.StringDiffVisualizer)
|
|||||||
|
|
||||||
local e = Roact.createElement
|
local e = Roact.createElement
|
||||||
|
|
||||||
local AGE_UNITS = { {31556909, "year"}, {2629743, "month"}, {604800, "week"}, {86400, "day"}, {3600, "hour"}, {60, "minute"}, }
|
local AGE_UNITS = {
|
||||||
|
{ 31556909, "year" },
|
||||||
|
{ 2629743, "month" },
|
||||||
|
{ 604800, "week" },
|
||||||
|
{ 86400, "day" },
|
||||||
|
{ 3600, "hour" },
|
||||||
|
{
|
||||||
|
60,
|
||||||
|
"minute",
|
||||||
|
},
|
||||||
|
}
|
||||||
function timeSinceText(elapsed: number): string
|
function timeSinceText(elapsed: number): string
|
||||||
if elapsed < 3 then
|
if elapsed < 3 then
|
||||||
return "just now"
|
return "just now"
|
||||||
@@ -159,16 +169,15 @@ function ConnectedPage:getChangeInfoText()
|
|||||||
local elapsed = os.time() - patchData.timestamp
|
local elapsed = os.time() - patchData.timestamp
|
||||||
local unapplied = PatchSet.countChanges(patchData.unapplied)
|
local unapplied = PatchSet.countChanges(patchData.unapplied)
|
||||||
|
|
||||||
return
|
return "<i>Synced "
|
||||||
"<i>Synced "
|
|
||||||
.. timeSinceText(elapsed)
|
.. timeSinceText(elapsed)
|
||||||
.. (if unapplied > 0 then
|
.. (if unapplied > 0
|
||||||
string.format(
|
then string.format(
|
||||||
", <font color=\"#FF8E3C\">but %d change%s failed to apply</font>",
|
', <font color="#FF8E3C">but %d change%s failed to apply</font>',
|
||||||
unapplied,
|
unapplied,
|
||||||
unapplied == 1 and "" or "s"
|
unapplied == 1 and "" or "s"
|
||||||
)
|
)
|
||||||
else "")
|
else "")
|
||||||
.. "</i>"
|
.. "</i>"
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -297,7 +306,7 @@ function ConnectedPage:render()
|
|||||||
onClick = self.props.onNavigateSettings,
|
onClick = self.props.onNavigateSettings,
|
||||||
}, {
|
}, {
|
||||||
Tip = e(Tooltip.Trigger, {
|
Tip = e(Tooltip.Trigger, {
|
||||||
text = "View and modify plugin settings"
|
text = "View and modify plugin settings",
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -309,7 +318,7 @@ function ConnectedPage:render()
|
|||||||
onClick = self.props.onDisconnect,
|
onClick = self.props.onDisconnect,
|
||||||
}, {
|
}, {
|
||||||
Tip = e(Tooltip.Trigger, {
|
Tip = e(Tooltip.Trigger, {
|
||||||
text = "Disconnect from the Rojo sync server"
|
text = "Disconnect from the Rojo sync server",
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -427,7 +436,7 @@ function ConnectedPage:render()
|
|||||||
|
|
||||||
oldText = self.state.oldSource,
|
oldText = self.state.oldSource,
|
||||||
newText = self.state.newSource,
|
newText = self.state.newSource,
|
||||||
})
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -50,7 +50,9 @@ function Error:render()
|
|||||||
local containerSize = object.AbsoluteSize - ERROR_PADDING * 2
|
local containerSize = object.AbsoluteSize - ERROR_PADDING * 2
|
||||||
|
|
||||||
local textBounds = TextService:GetTextSize(
|
local textBounds = TextService:GetTextSize(
|
||||||
self.props.errorMessage, 16, Enum.Font.Code,
|
self.props.errorMessage,
|
||||||
|
16,
|
||||||
|
Enum.Font.Code,
|
||||||
Vector2.new(containerSize.X, math.huge)
|
Vector2.new(containerSize.X, math.huge)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -60,12 +62,13 @@ function Error:render()
|
|||||||
ErrorMessage = Theme.with(function(theme)
|
ErrorMessage = Theme.with(function(theme)
|
||||||
return e("TextBox", {
|
return e("TextBox", {
|
||||||
[Roact.Event.InputBegan] = function(rbx, input)
|
[Roact.Event.InputBegan] = function(rbx, input)
|
||||||
if input.UserInputType ~= Enum.UserInputType.MouseButton1 then return end
|
if input.UserInputType ~= Enum.UserInputType.MouseButton1 then
|
||||||
|
return
|
||||||
|
end
|
||||||
rbx.SelectionStart = 0
|
rbx.SelectionStart = 0
|
||||||
rbx.CursorPosition = #rbx.Text+1
|
rbx.CursorPosition = #rbx.Text + 1
|
||||||
end,
|
end,
|
||||||
|
|
||||||
|
|
||||||
Text = self.props.errorMessage,
|
Text = self.props.errorMessage,
|
||||||
TextEditable = false,
|
TextEditable = false,
|
||||||
Font = Enum.Font.Code,
|
Font = Enum.Font.Code,
|
||||||
@@ -126,7 +129,7 @@ function ErrorPage:render()
|
|||||||
onClick = self.props.onClose,
|
onClick = self.props.onClose,
|
||||||
}, {
|
}, {
|
||||||
Tip = e(Tooltip.Trigger, {
|
Tip = e(Tooltip.Trigger, {
|
||||||
text = "Dismiss message"
|
text = "Dismiss message",
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ local function AddressEntry(props)
|
|||||||
if props.onHostChange ~= nil then
|
if props.onHostChange ~= nil then
|
||||||
props.onHostChange(object.Text)
|
props.onHostChange(object.Text)
|
||||||
end
|
end
|
||||||
end
|
end,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
Port = e("TextBox", {
|
Port = e("TextBox", {
|
||||||
@@ -120,7 +120,7 @@ function NotConnectedPage:render()
|
|||||||
onClick = self.props.onNavigateSettings,
|
onClick = self.props.onNavigateSettings,
|
||||||
}, {
|
}, {
|
||||||
Tip = e(Tooltip.Trigger, {
|
Tip = e(Tooltip.Trigger, {
|
||||||
text = "View and modify plugin settings"
|
text = "View and modify plugin settings",
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -132,7 +132,7 @@ function NotConnectedPage:render()
|
|||||||
onClick = self.props.onConnect,
|
onClick = self.props.onConnect,
|
||||||
}, {
|
}, {
|
||||||
Tip = e(Tooltip.Trigger, {
|
Tip = e(Tooltip.Trigger, {
|
||||||
text = "Connect to a Rojo sync server"
|
text = "Connect to a Rojo sync server",
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|||||||
@@ -81,40 +81,39 @@ function Setting:render()
|
|||||||
end,
|
end,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
Input =
|
Input = if self.props.input ~= nil
|
||||||
if self.props.input ~= nil then
|
then self.props.input
|
||||||
self.props.input
|
elseif self.props.options ~= nil then e(Dropdown, {
|
||||||
elseif self.props.options ~= nil then
|
locked = self.props.locked,
|
||||||
e(Dropdown, {
|
options = self.props.options,
|
||||||
locked = self.props.locked,
|
active = self.state.setting,
|
||||||
options = self.props.options,
|
transparency = self.props.transparency,
|
||||||
active = self.state.setting,
|
onClick = function(option)
|
||||||
transparency = self.props.transparency,
|
Settings:set(self.props.id, option)
|
||||||
onClick = function(option)
|
end,
|
||||||
Settings:set(self.props.id, option)
|
})
|
||||||
end,
|
else e(Checkbox, {
|
||||||
})
|
locked = self.props.locked,
|
||||||
else
|
active = self.state.setting,
|
||||||
e(Checkbox, {
|
transparency = self.props.transparency,
|
||||||
locked = self.props.locked,
|
onClick = function()
|
||||||
active = self.state.setting,
|
local currentValue = Settings:get(self.props.id)
|
||||||
transparency = self.props.transparency,
|
Settings:set(self.props.id, not currentValue)
|
||||||
onClick = function()
|
end,
|
||||||
local currentValue = Settings:get(self.props.id)
|
}),
|
||||||
Settings:set(self.props.id, not currentValue)
|
|
||||||
end,
|
|
||||||
}),
|
|
||||||
|
|
||||||
Reset = if self.props.onReset then e(IconButton, {
|
Reset = if self.props.onReset
|
||||||
icon = Assets.Images.Icons.Reset,
|
then e(IconButton, {
|
||||||
iconSize = 24,
|
icon = Assets.Images.Icons.Reset,
|
||||||
color = theme.BackButtonColor,
|
iconSize = 24,
|
||||||
transparency = self.props.transparency,
|
color = theme.BackButtonColor,
|
||||||
visible = self.props.showReset,
|
transparency = self.props.transparency,
|
||||||
layoutOrder = -1,
|
visible = self.props.showReset,
|
||||||
|
layoutOrder = -1,
|
||||||
|
|
||||||
onClick = self.props.onReset,
|
onClick = self.props.onReset,
|
||||||
}) else nil,
|
})
|
||||||
|
else nil,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
Text = e("Frame", {
|
Text = e("Frame", {
|
||||||
@@ -122,7 +121,8 @@ function Setting:render()
|
|||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
}, {
|
}, {
|
||||||
Name = e("TextLabel", {
|
Name = e("TextLabel", {
|
||||||
Text = (if self.props.experimental then "<font color=\"#FF8E3C\">⚠ </font>" else "") .. self.props.name,
|
Text = (if self.props.experimental then '<font color="#FF8E3C">⚠ </font>' else "")
|
||||||
|
.. self.props.name,
|
||||||
Font = Enum.Font.GothamBold,
|
Font = Enum.Font.GothamBold,
|
||||||
TextSize = 17,
|
TextSize = 17,
|
||||||
TextColor3 = theme.Setting.NameColor,
|
TextColor3 = theme.Setting.NameColor,
|
||||||
@@ -137,7 +137,8 @@ function Setting:render()
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
Description = e("TextLabel", {
|
Description = e("TextLabel", {
|
||||||
Text = (if self.props.experimental then "<font color=\"#FF8E3C\">[Experimental] </font>" else "") .. self.props.description,
|
Text = (if self.props.experimental then '<font color="#FF8E3C">[Experimental] </font>' else "")
|
||||||
|
.. self.props.description,
|
||||||
Font = Enum.Font.Gotham,
|
Font = Enum.Font.Gotham,
|
||||||
LineHeight = 1.2,
|
LineHeight = 1.2,
|
||||||
TextSize = 14,
|
TextSize = 14,
|
||||||
@@ -151,10 +152,14 @@ function Setting:render()
|
|||||||
containerSize = self.containerSize,
|
containerSize = self.containerSize,
|
||||||
inputSize = self.inputSize,
|
inputSize = self.inputSize,
|
||||||
}):map(function(values)
|
}):map(function(values)
|
||||||
local desc = (if self.props.experimental then "[Experimental] " else "") .. self.props.description
|
local desc = (if self.props.experimental then "[Experimental] " else "")
|
||||||
|
.. self.props.description
|
||||||
local offset = values.inputSize.X + 5
|
local offset = values.inputSize.X + 5
|
||||||
local textBounds = getTextBounds(
|
local textBounds = getTextBounds(
|
||||||
desc, 14, Enum.Font.Gotham, 1.2,
|
desc,
|
||||||
|
14,
|
||||||
|
Enum.Font.Gotham,
|
||||||
|
1.2,
|
||||||
Vector2.new(values.containerSize.X - offset, math.huge)
|
Vector2.new(values.containerSize.X - offset, math.huge)
|
||||||
)
|
)
|
||||||
return UDim2.new(1, -offset, 0, textBounds.Y)
|
return UDim2.new(1, -offset, 0, textBounds.Y)
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ local function Navbar(props)
|
|||||||
onClick = props.onBack,
|
onClick = props.onBack,
|
||||||
}, {
|
}, {
|
||||||
Tip = e(Tooltip.Trigger, {
|
Tip = e(Tooltip.Trigger, {
|
||||||
text = "Back"
|
text = "Back",
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ local function Navbar(props)
|
|||||||
Size = UDim2.new(1, 0, 1, 0),
|
Size = UDim2.new(1, 0, 1, 0),
|
||||||
|
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
})
|
}),
|
||||||
})
|
})
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
@@ -138,7 +138,10 @@ function SettingsPage:render()
|
|||||||
Settings:set("largeChangesConfirmationThreshold", math.clamp(number, 1, 999))
|
Settings:set("largeChangesConfirmationThreshold", math.clamp(number, 1, 999))
|
||||||
else
|
else
|
||||||
-- Force text back to last valid value
|
-- Force text back to last valid value
|
||||||
Settings:set("largeChangesConfirmationThreshold", Settings:get("largeChangesConfirmationThreshold"))
|
Settings:set(
|
||||||
|
"largeChangesConfirmationThreshold",
|
||||||
|
Settings:get("largeChangesConfirmationThreshold")
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ local lightTheme = strict("LightTheme", {
|
|||||||
},
|
},
|
||||||
AddressEntry = {
|
AddressEntry = {
|
||||||
TextColor = Color3.fromHex("000000"),
|
TextColor = Color3.fromHex("000000"),
|
||||||
PlaceholderColor = Color3.fromHex("8C8C8C")
|
PlaceholderColor = Color3.fromHex("8C8C8C"),
|
||||||
},
|
},
|
||||||
BorderedContainer = {
|
BorderedContainer = {
|
||||||
BorderColor = Color3.fromHex("CBCBCB"),
|
BorderColor = Color3.fromHex("CBCBCB"),
|
||||||
@@ -200,7 +200,7 @@ local darkTheme = strict("DarkTheme", {
|
|||||||
},
|
},
|
||||||
AddressEntry = {
|
AddressEntry = {
|
||||||
TextColor = Color3.fromHex("FFFFFF"),
|
TextColor = Color3.fromHex("FFFFFF"),
|
||||||
PlaceholderColor = Color3.fromHex("8B8B8B")
|
PlaceholderColor = Color3.fromHex("8B8B8B"),
|
||||||
},
|
},
|
||||||
BorderedContainer = {
|
BorderedContainer = {
|
||||||
BorderColor = Color3.fromHex("535353"),
|
BorderColor = Color3.fromHex("535353"),
|
||||||
@@ -235,7 +235,7 @@ local darkTheme = strict("DarkTheme", {
|
|||||||
},
|
},
|
||||||
Header = {
|
Header = {
|
||||||
LogoColor = BRAND_COLOR,
|
LogoColor = BRAND_COLOR,
|
||||||
VersionColor = Color3.fromHex("D3D3D3")
|
VersionColor = Color3.fromHex("D3D3D3"),
|
||||||
},
|
},
|
||||||
Notification = {
|
Notification = {
|
||||||
InfoColor = Color3.fromHex("FFFFFF"),
|
InfoColor = Color3.fromHex("FFFFFF"),
|
||||||
|
|||||||
@@ -469,13 +469,17 @@ function App:startSession()
|
|||||||
if confirmationBehavior == "Initial" then
|
if confirmationBehavior == "Initial" then
|
||||||
-- Only confirm if we haven't synced this project yet this session
|
-- Only confirm if we haven't synced this project yet this session
|
||||||
if self.knownProjects[serverInfo.projectName] then
|
if self.knownProjects[serverInfo.projectName] then
|
||||||
Log.trace("Accepting patch without confirmation because project has already been connected and behavior is set to Initial")
|
Log.trace(
|
||||||
|
"Accepting patch without confirmation because project has already been connected and behavior is set to Initial"
|
||||||
|
)
|
||||||
return "Accept"
|
return "Accept"
|
||||||
end
|
end
|
||||||
elseif confirmationBehavior == "Large Changes" then
|
elseif confirmationBehavior == "Large Changes" then
|
||||||
-- Only confirm if the patch impacts many instances
|
-- Only confirm if the patch impacts many instances
|
||||||
if PatchSet.countInstances(patch) < Settings:get("largeChangesConfirmationThreshold") then
|
if PatchSet.countInstances(patch) < Settings:get("largeChangesConfirmationThreshold") then
|
||||||
Log.trace("Accepting patch without confirmation because patch is small and behavior is set to Large Changes")
|
Log.trace(
|
||||||
|
"Accepting patch without confirmation because patch is small and behavior is set to Large Changes"
|
||||||
|
)
|
||||||
return "Accept"
|
return "Accept"
|
||||||
end
|
end
|
||||||
elseif confirmationBehavior == "Unlisted PlaceId" then
|
elseif confirmationBehavior == "Unlisted PlaceId" then
|
||||||
@@ -483,7 +487,9 @@ function App:startSession()
|
|||||||
if serverInfo.expectedPlaceIds then
|
if serverInfo.expectedPlaceIds then
|
||||||
local isListed = table.find(serverInfo.expectedPlaceIds, game.PlaceId) ~= nil
|
local isListed = table.find(serverInfo.expectedPlaceIds, game.PlaceId) ~= nil
|
||||||
if isListed then
|
if isListed then
|
||||||
Log.trace("Accepting patch without confirmation because placeId is listed and behavior is set to Unlisted PlaceId")
|
Log.trace(
|
||||||
|
"Accepting patch without confirmation because placeId is listed and behavior is set to Unlisted PlaceId"
|
||||||
|
)
|
||||||
return "Accept"
|
return "Accept"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -657,7 +663,8 @@ function App:render()
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
Settings = createPageElement(AppStatus.Settings, {
|
Settings = createPageElement(AppStatus.Settings, {
|
||||||
syncActive = self.serveSession ~= nil and self.serveSession:getStatus() == ServeSession.Status.Connected,
|
syncActive = self.serveSession ~= nil
|
||||||
|
and self.serveSession:getStatus() == ServeSession.Status.Connected,
|
||||||
|
|
||||||
onBack = function()
|
onBack = function()
|
||||||
self:setState({
|
self:setState({
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ local Assets = {
|
|||||||
[32] = "rbxassetid://3088713341",
|
[32] = "rbxassetid://3088713341",
|
||||||
[64] = "rbxassetid://4918677124",
|
[64] = "rbxassetid://4918677124",
|
||||||
[128] = "rbxassetid://2600845734",
|
[128] = "rbxassetid://2600845734",
|
||||||
[500] = "rbxassetid://2609138523"
|
[500] = "rbxassetid://2609138523",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Sounds = {
|
Sounds = {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ local isDevBuild = script.Parent.Parent:FindFirstChild("ROJO_DEV_BUILD") ~= nil
|
|||||||
return strict("Config", {
|
return strict("Config", {
|
||||||
isDevBuild = isDevBuild,
|
isDevBuild = isDevBuild,
|
||||||
codename = "Epiphany",
|
codename = "Epiphany",
|
||||||
version = {7, 3, 0},
|
version = { 7, 3, 0 },
|
||||||
expectedServerVersionString = "7.2 or newer",
|
expectedServerVersionString = "7.2 or newer",
|
||||||
protocolVersion = 4,
|
protocolVersion = 4,
|
||||||
defaultHost = "localhost",
|
defaultHost = "localhost",
|
||||||
|
|||||||
@@ -30,4 +30,4 @@ end
|
|||||||
return {
|
return {
|
||||||
None = None,
|
None = None,
|
||||||
merge = merge,
|
merge = merge,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ function InstanceMap:__fmtDebug(output)
|
|||||||
for id, instance in pairs(self.fromIds) do
|
for id, instance in pairs(self.fromIds) do
|
||||||
local label = string.format("%s (%s)", instance:GetFullName(), instance.ClassName)
|
local label = string.format("%s (%s)", instance:GetFullName(), instance.ClassName)
|
||||||
|
|
||||||
table.insert(entries, {id, label})
|
table.insert(entries, { id, label })
|
||||||
end
|
end
|
||||||
|
|
||||||
table.sort(entries, function(a, b)
|
table.sort(entries, function(a, b)
|
||||||
|
|||||||
@@ -26,7 +26,9 @@ local function deepEqual(a: any, b: any): boolean
|
|||||||
end
|
end
|
||||||
|
|
||||||
for key, value in b do
|
for key, value in b do
|
||||||
if checkedKeys[key] then continue end
|
if checkedKeys[key] then
|
||||||
|
continue
|
||||||
|
end
|
||||||
if deepEqual(value, a[key]) == false then
|
if deepEqual(value, a[key]) == false then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
@@ -65,9 +67,7 @@ end
|
|||||||
Tells whether the given PatchSet is empty.
|
Tells whether the given PatchSet is empty.
|
||||||
]]
|
]]
|
||||||
function PatchSet.isEmpty(patchSet)
|
function PatchSet.isEmpty(patchSet)
|
||||||
return next(patchSet.removed) == nil and
|
return next(patchSet.removed) == nil and next(patchSet.added) == nil and next(patchSet.updated) == nil
|
||||||
next(patchSet.added) == nil and
|
|
||||||
next(patchSet.updated) == nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
@@ -342,9 +342,15 @@ function PatchSet.humanSummary(instanceMap, patchSet)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
table.insert(statements, string.format(
|
table.insert(
|
||||||
"- Add instance %q (ClassName %q) to %s",
|
statements,
|
||||||
virtualInstance.Name, virtualInstance.ClassName, parentDisplayName))
|
string.format(
|
||||||
|
"- Add instance %q (ClassName %q) to %s",
|
||||||
|
virtualInstance.Name,
|
||||||
|
virtualInstance.ClassName,
|
||||||
|
parentDisplayName
|
||||||
|
)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
for _, update in ipairs(patchSet.updated) do
|
for _, update in ipairs(patchSet.updated) do
|
||||||
@@ -374,9 +380,10 @@ function PatchSet.humanSummary(instanceMap, patchSet)
|
|||||||
displayName = "[unknown instance]"
|
displayName = "[unknown instance]"
|
||||||
end
|
end
|
||||||
|
|
||||||
table.insert(statements, string.format(
|
table.insert(
|
||||||
"- Update properties on %s: %s",
|
statements,
|
||||||
displayName, table.concat(updatedProperties, ",")))
|
string.format("- Update properties on %s: %s", displayName, table.concat(updatedProperties, ","))
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
return table.concat(statements, "\n")
|
return table.concat(statements, "\n")
|
||||||
|
|||||||
@@ -401,7 +401,9 @@ end
|
|||||||
|
|
||||||
-- Creates a deep copy of a tree for immutability purposes in Roact
|
-- Creates a deep copy of a tree for immutability purposes in Roact
|
||||||
function PatchTree.clone(tree)
|
function PatchTree.clone(tree)
|
||||||
if not tree then return end
|
if not tree then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
local newTree = Tree.new()
|
local newTree = Tree.new()
|
||||||
tree:forEach(function(node)
|
tree:forEach(function(node)
|
||||||
|
|||||||
@@ -83,11 +83,11 @@ return function()
|
|||||||
ClassName = "Model",
|
ClassName = "Model",
|
||||||
Name = "Child",
|
Name = "Child",
|
||||||
Parent = "ROOT",
|
Parent = "ROOT",
|
||||||
Children = {"GRANDCHILD"},
|
Children = { "GRANDCHILD" },
|
||||||
Properties = {},
|
Properties = {},
|
||||||
}
|
}
|
||||||
|
|
||||||
patch.added["GRANDCHILD"] = {
|
patch.added["GRANDCHILD"] = {
|
||||||
Id = "GRANDCHILD",
|
Id = "GRANDCHILD",
|
||||||
ClassName = "Part",
|
ClassName = "Part",
|
||||||
Name = "Grandchild",
|
Name = "Grandchild",
|
||||||
@@ -193,4 +193,4 @@ return function()
|
|||||||
assert(newChild ~= nil, "expected child to be present")
|
assert(newChild ~= nil, "expected child to be present")
|
||||||
assert(newChild == child, "expected child to be preserved")
|
assert(newChild == child, "expected child to be preserved")
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -30,10 +30,11 @@ local function decodeValue(encodedValue, instanceMap)
|
|||||||
local ok, decodedValue = RbxDom.EncodedValue.decode(encodedValue)
|
local ok, decodedValue = RbxDom.EncodedValue.decode(encodedValue)
|
||||||
|
|
||||||
if not ok then
|
if not ok then
|
||||||
return false, Error.new(Error.CannotDecodeValue, {
|
return false,
|
||||||
encodedValue = encodedValue,
|
Error.new(Error.CannotDecodeValue, {
|
||||||
innerError = decodedValue,
|
encodedValue = encodedValue,
|
||||||
})
|
innerError = decodedValue,
|
||||||
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
return true, decodedValue
|
return true, decodedValue
|
||||||
|
|||||||
@@ -37,7 +37,9 @@ local function trueEquals(a, b): boolean
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
for key, value in pairs(b) do
|
for key, value in pairs(b) do
|
||||||
if checkedKeys[key] then continue end
|
if checkedKeys[key] then
|
||||||
|
continue
|
||||||
|
end
|
||||||
if not trueEquals(value, a[key]) then
|
if not trueEquals(value, a[key]) then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
@@ -62,7 +64,7 @@ local function trueEquals(a, b): boolean
|
|||||||
|
|
||||||
-- For CFrames, compare to components with epsilon of 0.0001 to avoid floating point inequality
|
-- For CFrames, compare to components with epsilon of 0.0001 to avoid floating point inequality
|
||||||
elseif typeA == "CFrame" and typeB == "CFrame" then
|
elseif typeA == "CFrame" and typeB == "CFrame" then
|
||||||
local aComponents, bComponents = {a:GetComponents()}, {b:GetComponents()}
|
local aComponents, bComponents = { a:GetComponents() }, { b:GetComponents() }
|
||||||
for i, aComponent in aComponents do
|
for i, aComponent in aComponents do
|
||||||
if not fuzzyEq(aComponent, bComponents[i], 0.0001) then
|
if not fuzzyEq(aComponent, bComponents[i], 0.0001) then
|
||||||
return false
|
return false
|
||||||
@@ -72,7 +74,7 @@ local function trueEquals(a, b): boolean
|
|||||||
|
|
||||||
-- For Vector3s, compare to components with epsilon of 0.0001 to avoid floating point inequality
|
-- For Vector3s, compare to components with epsilon of 0.0001 to avoid floating point inequality
|
||||||
elseif typeA == "Vector3" and typeB == "Vector3" then
|
elseif typeA == "Vector3" and typeB == "Vector3" then
|
||||||
local aComponents, bComponents = {a.X, a.Y, a.Z}, {b.X, b.Y, b.Z}
|
local aComponents, bComponents = { a.X, a.Y, a.Z }, { b.X, b.Y, b.Z }
|
||||||
for i, aComponent in aComponents do
|
for i, aComponent in aComponents do
|
||||||
if not fuzzyEq(aComponent, bComponents[i], 0.0001) then
|
if not fuzzyEq(aComponent, bComponents[i], 0.0001) then
|
||||||
return false
|
return false
|
||||||
@@ -82,14 +84,13 @@ local function trueEquals(a, b): boolean
|
|||||||
|
|
||||||
-- For Vector2s, compare to components with epsilon of 0.0001 to avoid floating point inequality
|
-- For Vector2s, compare to components with epsilon of 0.0001 to avoid floating point inequality
|
||||||
elseif typeA == "Vector2" and typeB == "Vector2" then
|
elseif typeA == "Vector2" and typeB == "Vector2" then
|
||||||
local aComponents, bComponents = {a.X, a.Y}, {b.X, b.Y}
|
local aComponents, bComponents = { a.X, a.Y }, { b.X, b.Y }
|
||||||
for i, aComponent in aComponents do
|
for i, aComponent in aComponents do
|
||||||
if not fuzzyEq(aComponent, bComponents[i], 0.0001) then
|
if not fuzzyEq(aComponent, bComponents[i], 0.0001) then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return true
|
return true
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return false
|
return false
|
||||||
@@ -154,7 +155,13 @@ local function diff(instanceMap, virtualInstances, rootId)
|
|||||||
|
|
||||||
if ok then
|
if ok then
|
||||||
if not trueEquals(existingValue, decodedValue) then
|
if not trueEquals(existingValue, decodedValue) then
|
||||||
Log.debug("{}.{} changed from '{}' to '{}'", instance:GetFullName(), propertyName, existingValue, decodedValue)
|
Log.debug(
|
||||||
|
"{}.{} changed from '{}' to '{}'",
|
||||||
|
instance:GetFullName(),
|
||||||
|
propertyName,
|
||||||
|
existingValue,
|
||||||
|
decodedValue
|
||||||
|
)
|
||||||
changedProperties[propertyName] = virtualValue
|
changedProperties[propertyName] = virtualValue
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -264,7 +264,7 @@ return function()
|
|||||||
ClassName = "Folder",
|
ClassName = "Folder",
|
||||||
Name = "Folder",
|
Name = "Folder",
|
||||||
Properties = {},
|
Properties = {},
|
||||||
Children = {"CHILD"},
|
Children = { "CHILD" },
|
||||||
},
|
},
|
||||||
|
|
||||||
CHILD = {
|
CHILD = {
|
||||||
|
|||||||
@@ -14,17 +14,19 @@ local function getProperty(instance, propertyName)
|
|||||||
-- A good example of a property like this is `Model.ModelInPrimary`, which
|
-- A good example of a property like this is `Model.ModelInPrimary`, which
|
||||||
-- is serialized but not reflected to Lua.
|
-- is serialized but not reflected to Lua.
|
||||||
if descriptor == nil then
|
if descriptor == nil then
|
||||||
return false, Error.new(Error.UnknownProperty, {
|
return false,
|
||||||
className = instance.ClassName,
|
Error.new(Error.UnknownProperty, {
|
||||||
propertyName = propertyName,
|
className = instance.ClassName,
|
||||||
})
|
propertyName = propertyName,
|
||||||
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
if descriptor.scriptability == "None" or descriptor.scriptability == "Write" then
|
if descriptor.scriptability == "None" or descriptor.scriptability == "Write" then
|
||||||
return false, Error.new(Error.UnreadableProperty, {
|
return false,
|
||||||
className = instance.ClassName,
|
Error.new(Error.UnreadableProperty, {
|
||||||
propertyName = propertyName,
|
className = instance.ClassName,
|
||||||
})
|
propertyName = propertyName,
|
||||||
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
local success, valueOrErr = descriptor:read(instance)
|
local success, valueOrErr = descriptor:read(instance)
|
||||||
@@ -35,23 +37,26 @@ local function getProperty(instance, propertyName)
|
|||||||
-- If we don't have permission to read a property, we can chalk that up
|
-- If we don't have permission to read a property, we can chalk that up
|
||||||
-- to our database being out of date and the engine being right.
|
-- to our database being out of date and the engine being right.
|
||||||
if err.kind == RbxDom.Error.Kind.Roblox and err.extra:find("lacking permission") then
|
if err.kind == RbxDom.Error.Kind.Roblox and err.extra:find("lacking permission") then
|
||||||
return false, Error.new(Error.LackingPropertyPermissions, {
|
return false,
|
||||||
className = instance.ClassName,
|
Error.new(Error.LackingPropertyPermissions, {
|
||||||
propertyName = propertyName,
|
className = instance.ClassName,
|
||||||
})
|
propertyName = propertyName,
|
||||||
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
if err.kind == RbxDom.Error.Kind.Roblox and err.extra:find("is not a valid member of") then
|
if err.kind == RbxDom.Error.Kind.Roblox and err.extra:find("is not a valid member of") then
|
||||||
return false, Error.new(Error.UnknownProperty, {
|
return false,
|
||||||
|
Error.new(Error.UnknownProperty, {
|
||||||
|
className = instance.ClassName,
|
||||||
|
propertyName = propertyName,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
return false,
|
||||||
|
Error.new(Error.OtherPropertyError, {
|
||||||
className = instance.ClassName,
|
className = instance.ClassName,
|
||||||
propertyName = propertyName,
|
propertyName = propertyName,
|
||||||
})
|
})
|
||||||
end
|
|
||||||
|
|
||||||
return false, Error.new(Error.OtherPropertyError, {
|
|
||||||
className = instance.ClassName,
|
|
||||||
propertyName = propertyName,
|
|
||||||
})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return true, valueOrErr
|
return true, valueOrErr
|
||||||
|
|||||||
@@ -47,4 +47,4 @@ local function hydrate(instanceMap, virtualInstances, rootId, rootInstance)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return hydrate
|
return hydrate
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ return function()
|
|||||||
ClassName = "Folder",
|
ClassName = "Folder",
|
||||||
Name = "Root",
|
Name = "Root",
|
||||||
Properties = {},
|
Properties = {},
|
||||||
Children = {"CHILD1", "CHILD2"},
|
Children = { "CHILD1", "CHILD2" },
|
||||||
},
|
},
|
||||||
|
|
||||||
CHILD1 = {
|
CHILD1 = {
|
||||||
@@ -126,4 +126,4 @@ return function()
|
|||||||
expect(knownInstances.fromIds["CHILD1"]).to.equal(child1)
|
expect(knownInstances.fromIds["CHILD1"]).to.equal(child1)
|
||||||
expect(knownInstances.fromIds["CHILD2"]).to.equal(child2)
|
expect(knownInstances.fromIds["CHILD2"]).to.equal(child2)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ return function()
|
|||||||
ClassName = "Folder",
|
ClassName = "Folder",
|
||||||
Name = "Parent",
|
Name = "Parent",
|
||||||
Properties = {},
|
Properties = {},
|
||||||
Children = {"CHILD"},
|
Children = { "CHILD" },
|
||||||
},
|
},
|
||||||
|
|
||||||
CHILD = {
|
CHILD = {
|
||||||
@@ -112,7 +112,7 @@ return function()
|
|||||||
ClassName = "Folder",
|
ClassName = "Folder",
|
||||||
Name = "Parent",
|
Name = "Parent",
|
||||||
Properties = {},
|
Properties = {},
|
||||||
Children = {"CHILD"},
|
Children = { "CHILD" },
|
||||||
},
|
},
|
||||||
|
|
||||||
CHILD = {
|
CHILD = {
|
||||||
@@ -147,7 +147,7 @@ return function()
|
|||||||
Properties = {
|
Properties = {
|
||||||
Value = {
|
Value = {
|
||||||
Type = "Vector3",
|
Type = "Vector3",
|
||||||
Value = {1, 2, 3},
|
Value = { 1, 2, 3 },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Children = {},
|
Children = {},
|
||||||
@@ -182,7 +182,7 @@ return function()
|
|||||||
ClassName = "Folder",
|
ClassName = "Folder",
|
||||||
Name = "Root",
|
Name = "Root",
|
||||||
Properties = {},
|
Properties = {},
|
||||||
Children = {"CHILD"},
|
Children = { "CHILD" },
|
||||||
},
|
},
|
||||||
|
|
||||||
CHILD = {
|
CHILD = {
|
||||||
@@ -247,7 +247,7 @@ return function()
|
|||||||
ClassName = "Folder",
|
ClassName = "Folder",
|
||||||
Name = "Root",
|
Name = "Root",
|
||||||
Properties = {},
|
Properties = {},
|
||||||
Children = {"CHILD_A", "CHILD_B"},
|
Children = { "CHILD_A", "CHILD_B" },
|
||||||
},
|
},
|
||||||
|
|
||||||
CHILD_A = {
|
CHILD_A = {
|
||||||
@@ -297,7 +297,7 @@ return function()
|
|||||||
Ref = "CHILD",
|
Ref = "CHILD",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Children = {"CHILD"},
|
Children = { "CHILD" },
|
||||||
},
|
},
|
||||||
|
|
||||||
CHILD = {
|
CHILD = {
|
||||||
|
|||||||
@@ -21,26 +21,29 @@ local function setProperty(instance, propertyName, value)
|
|||||||
end
|
end
|
||||||
|
|
||||||
if descriptor.scriptability == "None" or descriptor.scriptability == "Read" then
|
if descriptor.scriptability == "None" or descriptor.scriptability == "Read" then
|
||||||
return false, Error.new(Error.UnwritableProperty, {
|
return false,
|
||||||
className = instance.ClassName,
|
Error.new(Error.UnwritableProperty, {
|
||||||
propertyName = propertyName,
|
className = instance.ClassName,
|
||||||
})
|
propertyName = propertyName,
|
||||||
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
local ok, err = descriptor:write(instance, value)
|
local ok, err = descriptor:write(instance, value)
|
||||||
|
|
||||||
if not ok then
|
if not ok then
|
||||||
if err.kind == RbxDom.Error.Kind.Roblox and err.extra:find("lacking permission") then
|
if err.kind == RbxDom.Error.Kind.Roblox and err.extra:find("lacking permission") then
|
||||||
return false, Error.new(Error.LackingPropertyPermissions, {
|
return false,
|
||||||
|
Error.new(Error.LackingPropertyPermissions, {
|
||||||
|
className = instance.ClassName,
|
||||||
|
propertyName = propertyName,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
return false,
|
||||||
|
Error.new(Error.OtherPropertyError, {
|
||||||
className = instance.ClassName,
|
className = instance.ClassName,
|
||||||
propertyName = propertyName,
|
propertyName = propertyName,
|
||||||
})
|
})
|
||||||
end
|
|
||||||
|
|
||||||
return false, Error.new(Error.OtherPropertyError, {
|
|
||||||
className = instance.ClassName,
|
|
||||||
propertyName = propertyName,
|
|
||||||
})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -77,15 +77,13 @@ function ServeSession.new(options)
|
|||||||
|
|
||||||
local connections = {}
|
local connections = {}
|
||||||
|
|
||||||
local connection = StudioService
|
local connection = StudioService:GetPropertyChangedSignal("ActiveScript"):Connect(function()
|
||||||
:GetPropertyChangedSignal("ActiveScript")
|
local activeScript = StudioService.ActiveScript
|
||||||
:Connect(function()
|
|
||||||
local activeScript = StudioService.ActiveScript
|
|
||||||
|
|
||||||
if activeScript ~= nil then
|
if activeScript ~= nil then
|
||||||
self:__onActiveScriptChanged(activeScript)
|
self:__onActiveScriptChanged(activeScript)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
table.insert(connections, connection)
|
table.insert(connections, connection)
|
||||||
|
|
||||||
self = {
|
self = {
|
||||||
@@ -139,16 +137,16 @@ end
|
|||||||
function ServeSession:start()
|
function ServeSession:start()
|
||||||
self:__setStatus(Status.Connecting)
|
self:__setStatus(Status.Connecting)
|
||||||
|
|
||||||
self.__apiContext:connect()
|
self.__apiContext
|
||||||
|
:connect()
|
||||||
:andThen(function(serverInfo)
|
:andThen(function(serverInfo)
|
||||||
self:__applyGameAndPlaceId(serverInfo)
|
self:__applyGameAndPlaceId(serverInfo)
|
||||||
|
|
||||||
return self:__initialSync(serverInfo)
|
return self:__initialSync(serverInfo):andThen(function()
|
||||||
:andThen(function()
|
self:__setStatus(Status.Connected, serverInfo.projectName)
|
||||||
self:__setStatus(Status.Connected, serverInfo.projectName)
|
|
||||||
|
|
||||||
return self:__mainSyncLoop()
|
return self:__mainSyncLoop()
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
:catch(function(err)
|
:catch(function(err)
|
||||||
if self.__status ~= Status.Disconnected then
|
if self.__status ~= Status.Disconnected then
|
||||||
@@ -211,97 +209,93 @@ function ServeSession:__onActiveScriptChanged(activeScript)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function ServeSession:__initialSync(serverInfo)
|
function ServeSession:__initialSync(serverInfo)
|
||||||
return self.__apiContext:read({ serverInfo.rootInstanceId })
|
return self.__apiContext:read({ serverInfo.rootInstanceId }):andThen(function(readResponseBody)
|
||||||
:andThen(function(readResponseBody)
|
-- Tell the API Context that we're up-to-date with the version of
|
||||||
-- Tell the API Context that we're up-to-date with the version of
|
-- the tree defined in this response.
|
||||||
-- the tree defined in this response.
|
self.__apiContext:setMessageCursor(readResponseBody.messageCursor)
|
||||||
self.__apiContext:setMessageCursor(readResponseBody.messageCursor)
|
|
||||||
|
|
||||||
-- For any instances that line up with the Rojo server's view, start
|
-- For any instances that line up with the Rojo server's view, start
|
||||||
-- tracking them in the reconciler.
|
-- tracking them in the reconciler.
|
||||||
Log.trace("Matching existing Roblox instances to Rojo IDs")
|
Log.trace("Matching existing Roblox instances to Rojo IDs")
|
||||||
self.__reconciler:hydrate(readResponseBody.instances, serverInfo.rootInstanceId, game)
|
self.__reconciler:hydrate(readResponseBody.instances, serverInfo.rootInstanceId, game)
|
||||||
|
|
||||||
-- Calculate the initial patch to apply to the DataModel to catch us
|
-- Calculate the initial patch to apply to the DataModel to catch us
|
||||||
-- up to what Rojo thinks the place should look like.
|
-- up to what Rojo thinks the place should look like.
|
||||||
Log.trace("Computing changes that plugin needs to make to catch up to server...")
|
Log.trace("Computing changes that plugin needs to make to catch up to server...")
|
||||||
local success, catchUpPatch = self.__reconciler:diff(
|
local success, catchUpPatch =
|
||||||
readResponseBody.instances,
|
self.__reconciler:diff(readResponseBody.instances, serverInfo.rootInstanceId, game)
|
||||||
serverInfo.rootInstanceId,
|
|
||||||
game
|
|
||||||
)
|
|
||||||
|
|
||||||
if not success then
|
if not success then
|
||||||
Log.error("Could not compute a diff to catch up to the Rojo server: {:#?}", catchUpPatch)
|
Log.error("Could not compute a diff to catch up to the Rojo server: {:#?}", catchUpPatch)
|
||||||
end
|
end
|
||||||
|
|
||||||
for _, update in catchUpPatch.updated do
|
for _, update in catchUpPatch.updated do
|
||||||
if
|
if update.id == self.__instanceMap.fromInstances[game] and update.changedClassName ~= nil then
|
||||||
update.id == self.__instanceMap.fromInstances[game]
|
-- Non-place projects will try to update the classname of game from DataModel to
|
||||||
and update.changedClassName ~= nil
|
-- something like Folder, ModuleScript, etc. This would fail, so we exit with a clear
|
||||||
then
|
-- message instead of crashing.
|
||||||
-- Non-place projects will try to update the classname of game from DataModel to
|
return Promise.reject(
|
||||||
-- something like Folder, ModuleScript, etc. This would fail, so we exit with a clear
|
"Cannot sync a model as a place."
|
||||||
-- message instead of crashing.
|
|
||||||
return Promise.reject(
|
|
||||||
"Cannot sync a model as a place."
|
|
||||||
.. "\nEnsure Rojo is serving a project file that has a DataModel at the root of its tree and try again."
|
.. "\nEnsure Rojo is serving a project file that has a DataModel at the root of its tree and try again."
|
||||||
.. "\nSee project file docs: https://rojo.space/docs/v7/project-format/"
|
.. "\nSee project file docs: https://rojo.space/docs/v7/project-format/"
|
||||||
)
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Log.trace("Computed hydration patch: {:#?}", debugPatch(catchUpPatch))
|
||||||
|
|
||||||
|
local userDecision = "Accept"
|
||||||
|
if self.__userConfirmCallback ~= nil then
|
||||||
|
userDecision = self.__userConfirmCallback(self.__instanceMap, catchUpPatch, serverInfo)
|
||||||
|
end
|
||||||
|
|
||||||
|
if userDecision == "Abort" then
|
||||||
|
return Promise.reject("Aborted Rojo sync operation")
|
||||||
|
elseif userDecision == "Reject" and self.__twoWaySync then
|
||||||
|
-- The user wants their studio DOM to write back to their Rojo DOM
|
||||||
|
-- so we will reverse the patch and send it back
|
||||||
|
|
||||||
|
local inversePatch = PatchSet.newEmpty()
|
||||||
|
|
||||||
|
-- Send back the current properties
|
||||||
|
for _, change in catchUpPatch.updated do
|
||||||
|
local instance = self.__instanceMap.fromIds[change.id]
|
||||||
|
if not instance then
|
||||||
|
continue
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local update = encodePatchUpdate(instance, change.id, change.changedProperties)
|
||||||
|
table.insert(inversePatch.updated, update)
|
||||||
|
end
|
||||||
|
-- Add the removed instances back to Rojo
|
||||||
|
-- selene:allow(empty_if, unused_variable)
|
||||||
|
for _, instance in catchUpPatch.removed do
|
||||||
|
-- TODO: Generate ID for our instance and add it to inversePatch.added
|
||||||
|
end
|
||||||
|
-- Remove the additions we've rejected
|
||||||
|
for id, _change in catchUpPatch.added do
|
||||||
|
table.insert(inversePatch.removed, id)
|
||||||
end
|
end
|
||||||
|
|
||||||
Log.trace("Computed hydration patch: {:#?}", debugPatch(catchUpPatch))
|
self.__apiContext:write(inversePatch)
|
||||||
|
elseif userDecision == "Accept" then
|
||||||
|
local unappliedPatch = self.__reconciler:applyPatch(catchUpPatch)
|
||||||
|
|
||||||
local userDecision = "Accept"
|
if not PatchSet.isEmpty(unappliedPatch) then
|
||||||
if self.__userConfirmCallback ~= nil then
|
Log.warn(
|
||||||
userDecision = self.__userConfirmCallback(self.__instanceMap, catchUpPatch, serverInfo)
|
"Could not apply all changes requested by the Rojo server:\n{}",
|
||||||
|
PatchSet.humanSummary(self.__instanceMap, unappliedPatch)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
if userDecision == "Abort" then
|
end)
|
||||||
return Promise.reject("Aborted Rojo sync operation")
|
|
||||||
|
|
||||||
elseif userDecision == "Reject" and self.__twoWaySync then
|
|
||||||
-- The user wants their studio DOM to write back to their Rojo DOM
|
|
||||||
-- so we will reverse the patch and send it back
|
|
||||||
|
|
||||||
local inversePatch = PatchSet.newEmpty()
|
|
||||||
|
|
||||||
-- Send back the current properties
|
|
||||||
for _, change in catchUpPatch.updated do
|
|
||||||
local instance = self.__instanceMap.fromIds[change.id]
|
|
||||||
if not instance then continue end
|
|
||||||
|
|
||||||
local update = encodePatchUpdate(instance, change.id, change.changedProperties)
|
|
||||||
table.insert(inversePatch.updated, update)
|
|
||||||
end
|
|
||||||
-- Add the removed instances back to Rojo
|
|
||||||
-- selene:allow(empty_if, unused_variable)
|
|
||||||
for _, instance in catchUpPatch.removed do
|
|
||||||
-- TODO: Generate ID for our instance and add it to inversePatch.added
|
|
||||||
end
|
|
||||||
-- Remove the additions we've rejected
|
|
||||||
for id, _change in catchUpPatch.added do
|
|
||||||
table.insert(inversePatch.removed, id)
|
|
||||||
end
|
|
||||||
|
|
||||||
self.__apiContext:write(inversePatch)
|
|
||||||
|
|
||||||
elseif userDecision == "Accept" then
|
|
||||||
local unappliedPatch = self.__reconciler:applyPatch(catchUpPatch)
|
|
||||||
|
|
||||||
if not PatchSet.isEmpty(unappliedPatch) then
|
|
||||||
Log.warn("Could not apply all changes requested by the Rojo server:\n{}",
|
|
||||||
PatchSet.humanSummary(self.__instanceMap, unappliedPatch))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function ServeSession:__mainSyncLoop()
|
function ServeSession:__mainSyncLoop()
|
||||||
return Promise.new(function(resolve, reject)
|
return Promise.new(function(resolve, reject)
|
||||||
while self.__status == Status.Connected do
|
while self.__status == Status.Connected do
|
||||||
local success, result = self.__apiContext:retrieveMessages()
|
local success, result = self.__apiContext
|
||||||
|
:retrieveMessages()
|
||||||
:andThen(function(messages)
|
:andThen(function(messages)
|
||||||
if self.__status == Status.Disconnected then
|
if self.__status == Status.Disconnected then
|
||||||
-- In the time it took to retrieve messages, we disconnected
|
-- In the time it took to retrieve messages, we disconnected
|
||||||
@@ -315,11 +309,14 @@ function ServeSession:__mainSyncLoop()
|
|||||||
local unappliedPatch = self.__reconciler:applyPatch(message)
|
local unappliedPatch = self.__reconciler:applyPatch(message)
|
||||||
|
|
||||||
if not PatchSet.isEmpty(unappliedPatch) then
|
if not PatchSet.isEmpty(unappliedPatch) then
|
||||||
Log.warn("Could not apply all changes requested by the Rojo server:\n{}",
|
Log.warn(
|
||||||
PatchSet.humanSummary(self.__instanceMap, unappliedPatch))
|
"Could not apply all changes requested by the Rojo server:\n{}",
|
||||||
|
PatchSet.humanSummary(self.__instanceMap, unappliedPatch)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end):await()
|
end)
|
||||||
|
:await()
|
||||||
|
|
||||||
if self.__status == Status.Disconnected then
|
if self.__status == Status.Disconnected then
|
||||||
-- If we are no longer connected after applying, we stop silently
|
-- If we are no longer connected after applying, we stop silently
|
||||||
|
|||||||
@@ -56,11 +56,7 @@ local ApiSubscribeResponse = t.interface({
|
|||||||
})
|
})
|
||||||
|
|
||||||
local ApiError = t.interface({
|
local ApiError = t.interface({
|
||||||
kind = t.union(
|
kind = t.union(t.literal("NotFound"), t.literal("BadRequest"), t.literal("InternalError")),
|
||||||
t.literal("NotFound"),
|
|
||||||
t.literal("BadRequest"),
|
|
||||||
t.literal("InternalError")
|
|
||||||
),
|
|
||||||
details = t.string,
|
details = t.string,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -43,4 +43,4 @@ function Version.display(version)
|
|||||||
return output
|
return output
|
||||||
end
|
end
|
||||||
|
|
||||||
return Version
|
return Version
|
||||||
|
|||||||
@@ -2,27 +2,27 @@ return function()
|
|||||||
local Version = require(script.Parent.Version)
|
local Version = require(script.Parent.Version)
|
||||||
|
|
||||||
it("should compare equal versions", function()
|
it("should compare equal versions", function()
|
||||||
expect(Version.compare({1, 2, 3}, {1, 2, 3})).to.equal(0)
|
expect(Version.compare({ 1, 2, 3 }, { 1, 2, 3 })).to.equal(0)
|
||||||
expect(Version.compare({0, 4, 0}, {0, 4})).to.equal(0)
|
expect(Version.compare({ 0, 4, 0 }, { 0, 4 })).to.equal(0)
|
||||||
expect(Version.compare({0, 0, 123}, {0, 0, 123})).to.equal(0)
|
expect(Version.compare({ 0, 0, 123 }, { 0, 0, 123 })).to.equal(0)
|
||||||
expect(Version.compare({26}, {26})).to.equal(0)
|
expect(Version.compare({ 26 }, { 26 })).to.equal(0)
|
||||||
expect(Version.compare({26, 42}, {26, 42})).to.equal(0)
|
expect(Version.compare({ 26, 42 }, { 26, 42 })).to.equal(0)
|
||||||
expect(Version.compare({1, 0, 0}, {1})).to.equal(0)
|
expect(Version.compare({ 1, 0, 0 }, { 1 })).to.equal(0)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("should compare newer, older versions", function()
|
it("should compare newer, older versions", function()
|
||||||
expect(Version.compare({1}, {0})).to.equal(1)
|
expect(Version.compare({ 1 }, { 0 })).to.equal(1)
|
||||||
expect(Version.compare({1, 1}, {1, 0})).to.equal(1)
|
expect(Version.compare({ 1, 1 }, { 1, 0 })).to.equal(1)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("should compare different major versions", function()
|
it("should compare different major versions", function()
|
||||||
expect(Version.compare({1, 3, 2}, {2, 2, 1})).to.equal(-1)
|
expect(Version.compare({ 1, 3, 2 }, { 2, 2, 1 })).to.equal(-1)
|
||||||
expect(Version.compare({1, 2}, {2, 1})).to.equal(-1)
|
expect(Version.compare({ 1, 2 }, { 2, 1 })).to.equal(-1)
|
||||||
expect(Version.compare({1}, {2})).to.equal(-1)
|
expect(Version.compare({ 1 }, { 2 })).to.equal(-1)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("should compare different minor versions", function()
|
it("should compare different minor versions", function()
|
||||||
expect(Version.compare({1, 2, 3}, {1, 3, 2})).to.equal(-1)
|
expect(Version.compare({ 1, 2, 3 }, { 1, 3, 2 })).to.equal(-1)
|
||||||
expect(Version.compare({50, 1}, {50, 2})).to.equal(-1)
|
expect(Version.compare({ 50, 1 }, { 50, 2 })).to.equal(-1)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -50,4 +50,4 @@ local function createSignal()
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
return createSignal
|
return createSignal
|
||||||
|
|||||||
@@ -5,23 +5,23 @@
|
|||||||
--]]
|
--]]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
["0"] = true, -- Local file
|
["0"] = true, -- Local file
|
||||||
["95206881"] = true, -- Baseplate
|
["95206881"] = true, -- Baseplate
|
||||||
["6560363541"] = true, -- Classic Baseplate
|
["6560363541"] = true, -- Classic Baseplate
|
||||||
["95206192"] = true, -- Flat Terrain
|
["95206192"] = true, -- Flat Terrain
|
||||||
["13165709401"] = true, -- Modern City
|
["13165709401"] = true, -- Modern City
|
||||||
["520390648"] = true, -- Village
|
["520390648"] = true, -- Village
|
||||||
["203810088"] = true, -- Castle
|
["203810088"] = true, -- Castle
|
||||||
["366130569"] = true, -- Suburban
|
["366130569"] = true, -- Suburban
|
||||||
["215383192"] = true, -- Racing
|
["215383192"] = true, -- Racing
|
||||||
["264719325"] = true, -- Pirate Island
|
["264719325"] = true, -- Pirate Island
|
||||||
["203812057"] = true, -- Obby
|
["203812057"] = true, -- Obby
|
||||||
["379736082"] = true, -- Starting Place
|
["379736082"] = true, -- Starting Place
|
||||||
["301530843"] = true, -- Line Runner
|
["301530843"] = true, -- Line Runner
|
||||||
["92721754"] = true, -- Capture The Flag
|
["92721754"] = true, -- Capture The Flag
|
||||||
["301529772"] = true, -- Team/FFA Arena
|
["301529772"] = true, -- Team/FFA Arena
|
||||||
["203885589"] = true, -- Combat
|
["203885589"] = true, -- Combat
|
||||||
["10275826693"] = true, -- Concert
|
["10275826693"] = true, -- Concert
|
||||||
["5353920686"] = true, -- Move It Simulator
|
["5353920686"] = true, -- Move It Simulator
|
||||||
["6936227200"] = true, -- Mansion Of Wonder
|
["6936227200"] = true, -- Mansion Of Wonder
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ Log.setLogLevelThunk(function()
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
local app = Roact.createElement(App, {
|
local app = Roact.createElement(App, {
|
||||||
plugin = plugin
|
plugin = plugin,
|
||||||
})
|
})
|
||||||
local tree = Roact.mount(app, game:GetService("CoreGui"), "Rojo UI")
|
local tree = Roact.mount(app, game:GetService("CoreGui"), "Rojo UI")
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
local Packages = script.Parent.Parent.Packages
|
local Packages = script.Parent.Parent.Packages
|
||||||
local Fmt = require(Packages.Fmt)
|
local Fmt = require(Packages.Fmt)
|
||||||
|
|
||||||
@@ -17,10 +16,10 @@ else
|
|||||||
message = Fmt.fmt(message, ...)
|
message = Fmt.fmt(message, ...)
|
||||||
|
|
||||||
local fullMessage = string.format(
|
local fullMessage = string.format(
|
||||||
"Rojo detected an invariant violation within itself:\n" ..
|
"Rojo detected an invariant violation within itself:\n"
|
||||||
"%s\n\n" ..
|
.. "%s\n\n"
|
||||||
"This is a bug in Rojo. Please file an issue:\n" ..
|
.. "This is a bug in Rojo. Please file an issue:\n"
|
||||||
"https://github.com/rojo-rbx/rojo/issues",
|
.. "https://github.com/rojo-rbx/rojo/issues",
|
||||||
message
|
message
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,10 @@
|
|||||||
local plugin = plugin or script:FindFirstAncestorWhichIsA("Plugin")
|
local plugin = plugin or script:FindFirstAncestorWhichIsA("Plugin")
|
||||||
local widget = nil
|
local widget = nil
|
||||||
if plugin then
|
if plugin then
|
||||||
widget = plugin:CreateDockWidgetPluginGui("Rojo_soundPlayer", DockWidgetPluginGuiInfo.new(
|
widget = plugin:CreateDockWidgetPluginGui(
|
||||||
Enum.InitialDockState.Float,
|
"Rojo_soundPlayer",
|
||||||
false, true,
|
DockWidgetPluginGuiInfo.new(Enum.InitialDockState.Float, false, true, 10, 10, 10, 10)
|
||||||
10, 10,
|
)
|
||||||
10, 10
|
|
||||||
))
|
|
||||||
widget.Name = "Rojo_soundPlayer"
|
widget.Name = "Rojo_soundPlayer"
|
||||||
widget.Title = "Rojo Sound Player"
|
widget.Title = "Rojo Sound Player"
|
||||||
end
|
end
|
||||||
@@ -22,7 +20,9 @@ function SoundPlayer.new(settings)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function SoundPlayer:play(soundId)
|
function SoundPlayer:play(soundId)
|
||||||
if self.settings and self.settings:get("playSounds") == false then return end
|
if self.settings and self.settings:get("playSounds") == false then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
local sound = Instance.new("Sound")
|
local sound = Instance.new("Sound")
|
||||||
sound.SoundId = soundId
|
sound.SoundId = soundId
|
||||||
|
|||||||
@@ -21,4 +21,4 @@ return function(nameOrTarget, target)
|
|||||||
else
|
else
|
||||||
return strictInner("<unnamed table>", target)
|
return strictInner("<unnamed table>", target)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user