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