forked from rojo-rbx/rojo
New UI (#367)
* Add Flipper * Remove old UI * Add boilerplate UI * Change plugin version * Merge upstream * Bunch of new UI changes Too lazy to list them all in individual commits * Touch ripple for buttons and a few other things * Make the close button on the PluginGui work * Set button state to guiEnabled * Implement Connecting, NotConnected; add Header; don't update plugin button on render * Replace mapLerpColor with mapLerp * Update blendAlpha to be 0 without any values * Add ActionFillTransparency to Theme.Button * Suffix all Theme entries * Update Flipper * Add disconnect button * Remove cancel button * Add settings page * Add scrollbar and dark theme support to settings * Include settings in startSession * Set context default value to nil I always thought this was the name, lol... * Add Error page * Fix preloadAssets * Fix preloadAssets import * Update checkbox colors a little * Add setting descriptions * Fix scrolling frame in settings panel * Remove .vscode * Rename Throbber to Spinner * Update merge * Move Spinner images to assets * Change casing of directories * Remove old directories * Add comments to getDerivedStateFromProps * Account for offset in host TextBox size * Turn width variables into constants * Attempt to fix the comments * Add a missing comma in Settings * Remove a double space * Remove Dummy object * Move most of the Studio logic out of render * Don't truncate port input * Replace merge with Dictionary.merge * Replace "Got it!" with "Okay" * Add projectName to setStatus call * Add Flipper to build.rs
This commit is contained in:
41
plugin/src/App/Components/BorderedContainer.lua
Normal file
41
plugin/src/App/Components/BorderedContainer.lua
Normal file
@@ -0,0 +1,41 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
|
||||
local Theme = require(Plugin.App.Theme)
|
||||
local Assets = require(Plugin.Assets)
|
||||
|
||||
local SlicedImage = require(script.Parent.SlicedImage)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local function BorderedContainer(props)
|
||||
return Theme.with(function(theme)
|
||||
return e(SlicedImage, {
|
||||
slice = Assets.Slices.RoundedBackground,
|
||||
color = theme.BorderedContainer.BackgroundColor,
|
||||
transparency = props.transparency,
|
||||
|
||||
size = props.size,
|
||||
position = props.position,
|
||||
anchorPoint = props.anchorPoint,
|
||||
layoutOrder = props.layoutOrder,
|
||||
}, {
|
||||
Content = e("Frame", {
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
BackgroundTransparency = 1,
|
||||
}, props[Roact.Children]),
|
||||
|
||||
Border = e(SlicedImage, {
|
||||
slice = Assets.Slices.RoundedBorder,
|
||||
color = theme.BorderedContainer.BorderColor,
|
||||
transparency = props.transparency,
|
||||
|
||||
size = UDim2.new(1, 0, 1, 0),
|
||||
}),
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
return BorderedContainer
|
||||
96
plugin/src/App/Components/Checkbox.lua
Normal file
96
plugin/src/App/Components/Checkbox.lua
Normal file
@@ -0,0 +1,96 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
local Flipper = require(Rojo.Flipper)
|
||||
|
||||
local Assets = require(Plugin.Assets)
|
||||
local Theme = require(Plugin.App.Theme)
|
||||
local bindingUtil = require(Plugin.App.bindingUtil)
|
||||
|
||||
local SlicedImage = require(script.Parent.SlicedImage)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local Checkbox = Roact.Component:extend("Checkbox")
|
||||
|
||||
function Checkbox:init()
|
||||
self.motor = Flipper.SingleMotor.new(self.props.active and 1 or 0)
|
||||
self.binding = bindingUtil.fromMotor(self.motor)
|
||||
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,
|
||||
})
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
function Checkbox:render()
|
||||
return Theme.with(function(theme)
|
||||
theme = theme.Checkbox
|
||||
|
||||
local activeTransparency = Roact.joinBindings({
|
||||
self.binding:map(function(value)
|
||||
return 1 - value
|
||||
end),
|
||||
self.props.transparency,
|
||||
}):map(bindingUtil.blendAlpha)
|
||||
|
||||
return e("ImageButton", {
|
||||
Size = UDim2.new(0, 28, 0, 28),
|
||||
Position = self.props.position,
|
||||
AnchorPoint = self.props.anchorPoint,
|
||||
LayoutOrder = self.props.layoutOrder,
|
||||
ZIndex = self.props.zIndex,
|
||||
BackgroundTransparency = 1,
|
||||
|
||||
[Roact.Event.Activated] = self.props.onClick,
|
||||
}, {
|
||||
Active = e(SlicedImage, {
|
||||
slice = Assets.Slices.RoundedBackground,
|
||||
color = theme.Active.BackgroundColor,
|
||||
transparency = activeTransparency,
|
||||
size = UDim2.new(1, 0, 1, 0),
|
||||
zIndex = 2,
|
||||
}, {
|
||||
Icon = e("ImageLabel", {
|
||||
Image = Assets.Images.Checkbox.Active,
|
||||
ImageColor3 = theme.Active.IconColor,
|
||||
ImageTransparency = activeTransparency,
|
||||
|
||||
Size = UDim2.new(0, 16, 0, 16),
|
||||
Position = UDim2.new(0.5, 0, 0.5, 0),
|
||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
||||
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
}),
|
||||
|
||||
Inactive = e(SlicedImage, {
|
||||
slice = Assets.Slices.RoundedBorder,
|
||||
color = theme.Inactive.BorderColor,
|
||||
transparency = self.props.transparency,
|
||||
size = UDim2.new(1, 0, 1, 0),
|
||||
}, {
|
||||
Icon = e("ImageLabel", {
|
||||
Image = Assets.Images.Checkbox.Inactive,
|
||||
ImageColor3 = theme.Inactive.IconColor,
|
||||
ImageTransparency = self.props.transparency,
|
||||
|
||||
Size = UDim2.new(0, 16, 0, 16),
|
||||
Position = UDim2.new(0.5, 0, 0.5, 0),
|
||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
||||
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
}),
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
return Checkbox
|
||||
55
plugin/src/App/Components/Header.lua
Normal file
55
plugin/src/App/Components/Header.lua
Normal file
@@ -0,0 +1,55 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
|
||||
local Theme = require(Plugin.App.Theme)
|
||||
local Assets = require(Plugin.Assets)
|
||||
local Config = require(Plugin.Config)
|
||||
local Version = require(Plugin.Version)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local function Header(props)
|
||||
return Theme.with(function(theme)
|
||||
return e("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, 32),
|
||||
LayoutOrder = props.layoutOrder,
|
||||
BackgroundTransparency = 1,
|
||||
}, {
|
||||
Logo = e("ImageLabel", {
|
||||
Image = Assets.Images.Logo,
|
||||
ImageColor3 = theme.Header.LogoColor,
|
||||
ImageTransparency = props.transparency,
|
||||
|
||||
Size = UDim2.new(0, 60, 0, 27),
|
||||
|
||||
LayoutOrder = 1,
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
|
||||
Version = e("TextLabel", {
|
||||
Text = Version.display(Config.version),
|
||||
Font = Enum.Font.Gotham,
|
||||
TextSize = 14,
|
||||
TextColor3 = theme.Header.VersionColor,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextTransparency = props.transparency,
|
||||
|
||||
Size = UDim2.new(1, 0, 0, 14),
|
||||
|
||||
LayoutOrder = 2,
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
|
||||
Layout = e("UIListLayout", {
|
||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||
FillDirection = Enum.FillDirection.Horizontal,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
Padding = UDim.new(0, 15),
|
||||
}),
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
return Header
|
||||
79
plugin/src/App/Components/IconButton.lua
Normal file
79
plugin/src/App/Components/IconButton.lua
Normal file
@@ -0,0 +1,79 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
local Flipper = require(Rojo.Flipper)
|
||||
|
||||
local Assets = require(Plugin.Assets)
|
||||
local bindingUtil = require(Plugin.App.bindingUtil)
|
||||
|
||||
local HOVER_SPRING_PROPS = {
|
||||
frequency = 5,
|
||||
dampingRatio = 1.1,
|
||||
}
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local IconButton = Roact.Component:extend("IconButton")
|
||||
|
||||
function IconButton:init()
|
||||
self.motor = Flipper.SingleMotor.new(0)
|
||||
self.binding = bindingUtil.fromMotor(self.motor)
|
||||
end
|
||||
|
||||
function IconButton:render()
|
||||
local iconSize = self.props.iconSize
|
||||
|
||||
return e("ImageButton", {
|
||||
Size = UDim2.new(0, iconSize * 1.5, 0, iconSize * 1.5),
|
||||
Position = self.props.position,
|
||||
AnchorPoint = self.props.anchorPoint,
|
||||
|
||||
LayoutOrder = self.props.layoutOrder,
|
||||
ZIndex = self.props.zIndex,
|
||||
BackgroundTransparency = 1,
|
||||
|
||||
[Roact.Event.Activated] = self.props.onClick,
|
||||
|
||||
[Roact.Event.MouseEnter] = function()
|
||||
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)
|
||||
)
|
||||
end,
|
||||
}, {
|
||||
Icon = e("ImageLabel", {
|
||||
Image = self.props.icon,
|
||||
ImageColor3 = self.props.color,
|
||||
ImageTransparency = self.props.transparency,
|
||||
|
||||
Size = UDim2.new(0, iconSize, 0, iconSize),
|
||||
Position = UDim2.new(0.5, 0, 0.5, 0),
|
||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
||||
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
|
||||
HoverCircle = e("ImageLabel", {
|
||||
Image = Assets.Images.Circles[128],
|
||||
ImageColor3 = self.props.color,
|
||||
ImageTransparency = Roact.joinBindings({
|
||||
hover = self.binding,
|
||||
transparency = self.props.transparency,
|
||||
}):map(function(values)
|
||||
return bindingUtil.blendAlpha({ 0.85, 1 - values.hover, values.transparency })
|
||||
end),
|
||||
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
})
|
||||
end
|
||||
|
||||
return IconButton
|
||||
42
plugin/src/App/Components/ScrollingFrame.lua
Normal file
42
plugin/src/App/Components/ScrollingFrame.lua
Normal file
@@ -0,0 +1,42 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
|
||||
local Assets = require(Plugin.Assets)
|
||||
local Theme = require(Plugin.App.Theme)
|
||||
local bindingUtil = require(Plugin.App.bindingUtil)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local function ScrollingFrame(props)
|
||||
return Theme.with(function(theme)
|
||||
return e("ScrollingFrame", {
|
||||
ScrollBarThickness = 9,
|
||||
ScrollBarImageColor3 = theme.ScrollBarColor,
|
||||
ScrollBarImageTransparency = props.transparency:map(function(value)
|
||||
return bindingUtil.blendAlpha({ 0.65, value })
|
||||
end),
|
||||
TopImage = Assets.Images.ScrollBar.Top,
|
||||
MidImage = Assets.Images.ScrollBar.Middle,
|
||||
BottomImage = Assets.Images.ScrollBar.Bottom,
|
||||
|
||||
ElasticBehavior = Enum.ElasticBehavior.Always,
|
||||
ScrollingDirection = Enum.ScrollingDirection.Y,
|
||||
|
||||
Size = props.size,
|
||||
Position = props.position,
|
||||
AnchorPoint = props.anchorPoint,
|
||||
CanvasSize = props.contentSize:map(function(value)
|
||||
return UDim2.new(0, 0, 0, value.Y)
|
||||
end),
|
||||
|
||||
BorderSizePixel = 0,
|
||||
BackgroundTransparency = 1,
|
||||
|
||||
[Roact.Change.AbsoluteSize] = props[Roact.Change.AbsoluteSize]
|
||||
}, props[Roact.Children])
|
||||
end)
|
||||
end
|
||||
|
||||
return ScrollingFrame
|
||||
29
plugin/src/App/Components/SlicedImage.lua
Normal file
29
plugin/src/App/Components/SlicedImage.lua
Normal file
@@ -0,0 +1,29 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local function SlicedImage(props)
|
||||
local slice = props.slice
|
||||
|
||||
return e("ImageLabel", {
|
||||
Image = slice.Image,
|
||||
ImageColor3 = props.color,
|
||||
ImageTransparency = props.transparency,
|
||||
|
||||
ScaleType = Enum.ScaleType.Slice,
|
||||
SliceCenter = slice.Center,
|
||||
SliceScale = slice.Scale,
|
||||
|
||||
Size = props.size,
|
||||
Position = props.position,
|
||||
AnchorPoint = props.anchorPoint,
|
||||
|
||||
ZIndex = props.zIndex,
|
||||
LayoutOrder = props.layoutOrder,
|
||||
BackgroundTransparency = 1,
|
||||
}, props[Roact.Children])
|
||||
end
|
||||
|
||||
return SlicedImage
|
||||
66
plugin/src/App/Components/Spinner.lua
Normal file
66
plugin/src/App/Components/Spinner.lua
Normal file
@@ -0,0 +1,66 @@
|
||||
local RunService = game:GetService("RunService")
|
||||
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
|
||||
local Theme = require(Plugin.App.Theme)
|
||||
local Assets = require(Plugin.Assets)
|
||||
|
||||
local ROTATIONS_PER_SECOND = 1.75
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local Spinner = Roact.PureComponent:extend("Spinner")
|
||||
|
||||
function Spinner:init()
|
||||
self.rotation, self.setRotation = Roact.createBinding(0)
|
||||
end
|
||||
|
||||
function Spinner:render()
|
||||
return Theme.with(function(theme)
|
||||
return e("ImageLabel", {
|
||||
Image = Assets.Images.Spinner.Background,
|
||||
ImageColor3 = theme.Spinner.BackgroundColor,
|
||||
ImageTransparency = self.props.transparency,
|
||||
|
||||
Size = UDim2.new(0, 24, 0, 24),
|
||||
Position = self.props.position,
|
||||
AnchorPoint = self.props.anchorPoint,
|
||||
|
||||
LayoutOrder = self.props.layoutOrder,
|
||||
BackgroundTransparency = 1,
|
||||
}, {
|
||||
Foreground = e("ImageLabel", {
|
||||
Image = Assets.Images.Spinner.Foreground,
|
||||
ImageColor3 = theme.Spinner.ForegroundColor,
|
||||
ImageTransparency = self.props.transparency,
|
||||
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
Rotation = self.rotation:map(function(value)
|
||||
return value * 360
|
||||
end),
|
||||
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
function Spinner:didMount()
|
||||
self.stepper = RunService.RenderStepped:Connect(function(deltaTime)
|
||||
local rotation = self.rotation:getValue()
|
||||
|
||||
rotation = rotation + deltaTime * ROTATIONS_PER_SECOND
|
||||
rotation = rotation % 1
|
||||
|
||||
self.setRotation(rotation)
|
||||
end)
|
||||
end
|
||||
|
||||
function Spinner:willUnmount()
|
||||
self.stepper:Disconnect()
|
||||
end
|
||||
|
||||
return Spinner
|
||||
7
plugin/src/App/Components/Studio/StudioPluginContext.lua
Normal file
7
plugin/src/App/Components/Studio/StudioPluginContext.lua
Normal file
@@ -0,0 +1,7 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
|
||||
local StudioPluginContext = Roact.createContext(nil)
|
||||
|
||||
return StudioPluginContext
|
||||
84
plugin/src/App/Components/Studio/StudioPluginGui.lua
Normal file
84
plugin/src/App/Components/Studio/StudioPluginGui.lua
Normal file
@@ -0,0 +1,84 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
|
||||
local Dictionary = require(Plugin.Dictionary)
|
||||
|
||||
local StudioPluginContext = require(script.Parent.StudioPluginContext)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local StudioPluginGui = Roact.PureComponent:extend("StudioPluginGui")
|
||||
|
||||
StudioPluginGui.defaultProps = {
|
||||
initDockState = Enum.InitialDockState.Right,
|
||||
active = false,
|
||||
overridePreviousState = false,
|
||||
floatingSize = Vector2.new(0, 0),
|
||||
minimumSize = Vector2.new(0, 0),
|
||||
zIndexBehavior = Enum.ZIndexBehavior.Sibling,
|
||||
}
|
||||
|
||||
function StudioPluginGui:init()
|
||||
local floatingSize = self.props.floatingSize
|
||||
local minimumSize = self.props.minimumSize
|
||||
|
||||
local dockWidgetPluginGuiInfo = DockWidgetPluginGuiInfo.new(
|
||||
self.props.initDockState,
|
||||
self.props.active,
|
||||
self.props.overridePreviousState,
|
||||
floatingSize.X, floatingSize.Y,
|
||||
minimumSize.X, minimumSize.Y
|
||||
)
|
||||
|
||||
local pluginGui = self.props.plugin:CreateDockWidgetPluginGui(self.props.id, dockWidgetPluginGuiInfo)
|
||||
|
||||
pluginGui.Name = self.props.id
|
||||
pluginGui.Title = self.props.title
|
||||
pluginGui.ZIndexBehavior = self.props.zIndexBehavior
|
||||
|
||||
if self.props.onInitialState then
|
||||
self.props.onInitialState(pluginGui.Enabled)
|
||||
end
|
||||
|
||||
pluginGui:BindToClose(function()
|
||||
if self.props.onClose then
|
||||
self.props.onClose()
|
||||
else
|
||||
pluginGui.Enabled = false
|
||||
end
|
||||
end)
|
||||
|
||||
self.pluginGui = pluginGui
|
||||
end
|
||||
|
||||
function StudioPluginGui:render()
|
||||
return e(Roact.Portal, {
|
||||
target = self.pluginGui,
|
||||
}, self.props[Roact.Children])
|
||||
end
|
||||
|
||||
function StudioPluginGui:didUpdate(lastProps)
|
||||
if self.props.active ~= lastProps.active then
|
||||
-- This is intentionally in didUpdate to make sure the initial active state
|
||||
-- (if the PluginGui is open initially) is preserved.
|
||||
self.pluginGui.Enabled = self.props.active
|
||||
end
|
||||
end
|
||||
|
||||
function StudioPluginGui:willUnmount()
|
||||
self.pluginGui:Destroy()
|
||||
end
|
||||
|
||||
local function StudioPluginGuiWrapper(props)
|
||||
return e(StudioPluginContext.Consumer, {
|
||||
render = function(plugin)
|
||||
return e(StudioPluginGui, Dictionary.merge(props, {
|
||||
plugin = plugin,
|
||||
}))
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
return StudioPluginGuiWrapper
|
||||
66
plugin/src/App/Components/Studio/StudioToggleButton.lua
Normal file
66
plugin/src/App/Components/Studio/StudioToggleButton.lua
Normal file
@@ -0,0 +1,66 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
|
||||
local Dictionary = require(Plugin.Dictionary)
|
||||
|
||||
local StudioToolbarContext = require(script.Parent.StudioToolbarContext)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local StudioToggleButton = Roact.Component:extend("StudioToggleButton")
|
||||
|
||||
StudioToggleButton.defaultProps = {
|
||||
enabled = true,
|
||||
active = false,
|
||||
}
|
||||
|
||||
function StudioToggleButton:init()
|
||||
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
|
||||
self.props.onClick()
|
||||
end
|
||||
end)
|
||||
|
||||
button.ClickableWhenViewportHidden = true
|
||||
|
||||
self.button = button
|
||||
end
|
||||
|
||||
function StudioToggleButton:render()
|
||||
return nil
|
||||
end
|
||||
|
||||
function StudioToggleButton:didUpdate(lastProps)
|
||||
if self.props.enabled ~= lastProps.enabled then
|
||||
self.button.Enabled = self.props.enabled
|
||||
end
|
||||
|
||||
if self.props.active ~= lastProps.active then
|
||||
self.button:SetActive(self.props.active)
|
||||
end
|
||||
end
|
||||
|
||||
function StudioToggleButton:willUnmount()
|
||||
self.button:Destroy()
|
||||
end
|
||||
|
||||
local function StudioToggleButtonWrapper(props)
|
||||
return e(StudioToolbarContext.Consumer, {
|
||||
render = function(toolbar)
|
||||
return e(StudioToggleButton, Dictionary.merge(props, {
|
||||
toolbar = toolbar,
|
||||
}))
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
return StudioToggleButtonWrapper
|
||||
45
plugin/src/App/Components/Studio/StudioToolbar.lua
Normal file
45
plugin/src/App/Components/Studio/StudioToolbar.lua
Normal file
@@ -0,0 +1,45 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
|
||||
local Dictionary = require(Plugin.Dictionary)
|
||||
|
||||
local StudioToolbarContext = require(script.Parent.StudioToolbarContext)
|
||||
local StudioPluginContext = require(script.Parent.StudioPluginContext)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local StudioToolbar = Roact.Component:extend("StudioToolbar")
|
||||
|
||||
function StudioToolbar:init()
|
||||
self.toolbar = self.props.plugin:CreateToolbar(self.props.name)
|
||||
end
|
||||
|
||||
function StudioToolbar:render()
|
||||
return e(StudioToolbarContext.Provider, {
|
||||
value = self.toolbar,
|
||||
}, self.props[Roact.Children])
|
||||
end
|
||||
|
||||
function StudioToolbar:didUpdate(lastProps)
|
||||
if self.props.name ~= lastProps.name then
|
||||
self.toolbar.Name = self.props.name
|
||||
end
|
||||
end
|
||||
|
||||
function StudioToolbar:willUnmount()
|
||||
self.toolbar:Destroy()
|
||||
end
|
||||
|
||||
local function StudioToolbarWrapper(props)
|
||||
return e(StudioPluginContext.Consumer, {
|
||||
render = function(plugin)
|
||||
return e(StudioToolbar, Dictionary.merge(props, {
|
||||
plugin = plugin,
|
||||
}))
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
return StudioToolbarWrapper
|
||||
@@ -0,0 +1,7 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
|
||||
local StudioToolbarContext = Roact.createContext(nil)
|
||||
|
||||
return StudioToolbarContext
|
||||
137
plugin/src/App/Components/TextButton.lua
Normal file
137
plugin/src/App/Components/TextButton.lua
Normal file
@@ -0,0 +1,137 @@
|
||||
local TextService = game:GetService("TextService")
|
||||
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
local Flipper = require(Rojo.Flipper)
|
||||
|
||||
local Theme = require(Plugin.App.Theme)
|
||||
local Assets = require(Plugin.Assets)
|
||||
local bindingUtil = require(Plugin.App.bindingUtil)
|
||||
|
||||
local SlicedImage = require(script.Parent.SlicedImage)
|
||||
local TouchRipple = require(script.Parent.TouchRipple)
|
||||
|
||||
local SPRING_PROPS = {
|
||||
frequency = 5,
|
||||
dampingRatio = 1,
|
||||
}
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local TextButton = Roact.Component:extend("TextButton")
|
||||
|
||||
function TextButton:init()
|
||||
self.motor = Flipper.GroupMotor.new({
|
||||
hover = 0,
|
||||
enabled = self.props.enabled and 1 or 0,
|
||||
})
|
||||
self.binding = bindingUtil.fromMotor(self.motor)
|
||||
end
|
||||
|
||||
function TextButton:didUpdate(lastProps)
|
||||
if lastProps.enabled ~= self.props.enabled then
|
||||
self.motor:setGoal({
|
||||
enabled = Flipper.Spring.new(self.props.enabled and 1 or 0),
|
||||
})
|
||||
end
|
||||
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 style = self.props.style
|
||||
|
||||
theme = theme.Button[style]
|
||||
|
||||
local bindingHover = bindingUtil.deriveProperty(self.binding, "hover")
|
||||
local bindingEnabled = bindingUtil.deriveProperty(self.binding, "enabled")
|
||||
|
||||
return e("ImageButton", {
|
||||
Size = UDim2.new(0, 15 + textSize.X + 15, 0, 34),
|
||||
Position = self.props.position,
|
||||
AnchorPoint = self.props.anchorPoint,
|
||||
|
||||
LayoutOrder = self.props.layoutOrder,
|
||||
BackgroundTransparency = 1,
|
||||
|
||||
[Roact.Event.Activated] = self.props.onClick,
|
||||
|
||||
[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,
|
||||
}, {
|
||||
TouchRipple = e(TouchRipple, {
|
||||
color = theme.ActionFillColor,
|
||||
transparency = self.props.transparency:map(function(value)
|
||||
return bindingUtil.blendAlpha({ theme.ActionFillTransparency, value })
|
||||
end),
|
||||
zIndex = 2,
|
||||
}),
|
||||
|
||||
Text = e("TextLabel", {
|
||||
Text = self.props.text,
|
||||
Font = Enum.Font.GothamSemibold,
|
||||
TextSize = 18,
|
||||
TextColor3 = bindingUtil.mapLerp(bindingEnabled, theme.Enabled.TextColor, theme.Disabled.TextColor),
|
||||
TextTransparency = self.props.transparency,
|
||||
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
|
||||
Border = style == "Bordered" and e(SlicedImage, {
|
||||
slice = Assets.Slices.RoundedBorder,
|
||||
color = bindingUtil.mapLerp(bindingEnabled, theme.Enabled.BorderColor, theme.Disabled.BorderColor),
|
||||
transparency = self.props.transparency,
|
||||
|
||||
size = UDim2.new(1, 0, 1, 0),
|
||||
|
||||
zIndex = 0,
|
||||
}),
|
||||
|
||||
HoverOverlay = e(SlicedImage, {
|
||||
slice = Assets.Slices.RoundedBackground,
|
||||
color = theme.ActionFillColor,
|
||||
transparency = Roact.joinBindings({
|
||||
hover = bindingHover:map(function(value)
|
||||
return 1 - value
|
||||
end),
|
||||
transparency = self.props.transparency,
|
||||
}):map(function(values)
|
||||
return bindingUtil.blendAlpha({ theme.ActionFillTransparency, values.hover, values.transparency })
|
||||
end),
|
||||
|
||||
size = UDim2.new(1, 0, 1, 0),
|
||||
|
||||
zIndex = -1,
|
||||
}),
|
||||
|
||||
Background = style == "Solid" and e(SlicedImage, {
|
||||
slice = Assets.Slices.RoundedBackground,
|
||||
color = bindingUtil.mapLerp(bindingEnabled, theme.Enabled.BackgroundColor, theme.Disabled.BackgroundColor),
|
||||
transparency = self.props.transparency,
|
||||
|
||||
size = UDim2.new(1, 0, 1, 0),
|
||||
|
||||
zIndex = -2,
|
||||
}),
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
return TextButton
|
||||
145
plugin/src/App/Components/TouchRipple.lua
Normal file
145
plugin/src/App/Components/TouchRipple.lua
Normal file
@@ -0,0 +1,145 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
local Flipper = require(Rojo.Flipper)
|
||||
|
||||
local Assets = require(Plugin.Assets)
|
||||
local bindingUtil = require(Plugin.App.bindingUtil)
|
||||
|
||||
local EXPAND_SPRING = {
|
||||
frequency = 7,
|
||||
dampingRatio = 2,
|
||||
}
|
||||
|
||||
local TouchRipple = Roact.Component:extend("TouchRipple")
|
||||
|
||||
function TouchRipple:init()
|
||||
self.ref = Roact.createRef()
|
||||
|
||||
self.motor = Flipper.GroupMotor.new({
|
||||
scale = 0,
|
||||
opacity = 0,
|
||||
})
|
||||
self.binding = bindingUtil.fromMotor(self.motor)
|
||||
|
||||
self.position, self.setPosition = Roact.createBinding(
|
||||
Vector2.new(0, 0)
|
||||
)
|
||||
end
|
||||
|
||||
function TouchRipple:reset()
|
||||
self.motor:setGoal({
|
||||
scale = Flipper.Instant.new(0),
|
||||
opacity = Flipper.Instant.new(0),
|
||||
})
|
||||
|
||||
-- Forces motor to update
|
||||
self.motor:step(0)
|
||||
end
|
||||
|
||||
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 size = container.AbsoluteSize
|
||||
local ratio = size / math.min(size.X, size.Y)
|
||||
|
||||
return ((corner * ratio) - (position * ratio)).Magnitude
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
function TouchRipple:render()
|
||||
local scale = bindingUtil.deriveProperty(self.binding, "scale")
|
||||
local transparency = bindingUtil.deriveProperty(self.binding, "opacity"):map(function(value)
|
||||
return 1 - value
|
||||
end)
|
||||
|
||||
transparency = Roact.joinBindings({
|
||||
transparency,
|
||||
self.props.transparency,
|
||||
}):map(bindingUtil.blendAlpha)
|
||||
|
||||
return Roact.createElement("Frame", {
|
||||
ClipsDescendants = true,
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
ZIndex = self.props.zIndex,
|
||||
BackgroundTransparency = 1,
|
||||
|
||||
[Roact.Ref] = self.ref,
|
||||
|
||||
[Roact.Event.InputBegan] = function(object, input)
|
||||
if input.UserInputType == Enum.UserInputType.MouseButton1 then
|
||||
self:reset()
|
||||
|
||||
local position = Vector2.new(input.Position.X, input.Position.Y)
|
||||
local relativePosition = (position - object.AbsolutePosition) / object.AbsoluteSize
|
||||
|
||||
self.setPosition(relativePosition)
|
||||
|
||||
self.motor:setGoal({
|
||||
scale = Flipper.Spring.new(1, EXPAND_SPRING),
|
||||
opacity = Flipper.Spring.new(1, EXPAND_SPRING),
|
||||
})
|
||||
|
||||
input:GetPropertyChangedSignal("UserInputState"):Connect(function()
|
||||
local userInputState = input.UserInputState
|
||||
|
||||
if
|
||||
userInputState == Enum.UserInputState.Cancel
|
||||
or userInputState == Enum.UserInputState.End
|
||||
then
|
||||
self.motor:setGoal({
|
||||
opacity = Flipper.Spring.new(0, {
|
||||
frequency = 5,
|
||||
dampingRatio = 1,
|
||||
}),
|
||||
})
|
||||
end
|
||||
end)
|
||||
end
|
||||
end,
|
||||
}, {
|
||||
Circle = Roact.createElement("ImageLabel", {
|
||||
Image = Assets.Images.Circles[500],
|
||||
ImageColor3 = self.props.color,
|
||||
ImageTransparency = transparency,
|
||||
|
||||
Size = Roact.joinBindings({
|
||||
scale = scale,
|
||||
position = self.position,
|
||||
}):map(function(values)
|
||||
local targetSize = self:calculateRadius(values.position) * 2
|
||||
local currentSize = targetSize * values.scale
|
||||
|
||||
local container = self.ref.current
|
||||
|
||||
if container then
|
||||
local containerSize = container.AbsoluteSize
|
||||
local containerAspect = containerSize.X / containerSize.Y
|
||||
|
||||
return UDim2.new(
|
||||
currentSize / math.max(containerAspect, 1), 0,
|
||||
currentSize * math.min(containerAspect, 1), 0
|
||||
)
|
||||
end
|
||||
end),
|
||||
|
||||
Position = self.position:map(function(value)
|
||||
return UDim2.new(value.X, 0, value.Y, 0)
|
||||
end),
|
||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
||||
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
})
|
||||
end
|
||||
|
||||
return TouchRipple
|
||||
70
plugin/src/App/Page.lua
Normal file
70
plugin/src/App/Page.lua
Normal file
@@ -0,0 +1,70 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
local Flipper = require(Rojo.Flipper)
|
||||
|
||||
local Dictionary = require(Plugin.Dictionary)
|
||||
|
||||
local bindingUtil = require(script.Parent.bindingUtil)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local Page = Roact.Component:extend("Page")
|
||||
|
||||
function Page:init()
|
||||
self:setState({
|
||||
rendered = self.props.active
|
||||
})
|
||||
|
||||
self.motor = Flipper.SingleMotor.new(self.props.active and 1 or 0)
|
||||
self.binding = bindingUtil.fromMotor(self.motor)
|
||||
|
||||
self.motor:onStep(function(value)
|
||||
local rendered = value > 0
|
||||
|
||||
self:setState(function(state)
|
||||
if state.rendered ~= rendered then
|
||||
return {
|
||||
rendered = rendered,
|
||||
}
|
||||
end
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
function Page:render()
|
||||
if not self.state.rendered then
|
||||
return nil
|
||||
end
|
||||
|
||||
local transparency = self.binding:map(function(value)
|
||||
return 1 - value
|
||||
end)
|
||||
|
||||
return e("Frame", {
|
||||
Position = transparency:map(function(value)
|
||||
value = self.props.active and value or -value
|
||||
return UDim2.new(0, value * 30, 0, 0)
|
||||
end),
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
BackgroundTransparency = 1,
|
||||
}, {
|
||||
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,
|
||||
})
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
return Page
|
||||
125
plugin/src/App/StatusPages/Connected.lua
Normal file
125
plugin/src/App/StatusPages/Connected.lua
Normal file
@@ -0,0 +1,125 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
|
||||
local Theme = require(Plugin.App.Theme)
|
||||
local Assets = require(Plugin.Assets)
|
||||
|
||||
local Header = require(Plugin.App.Components.Header)
|
||||
local IconButton = require(Plugin.App.Components.IconButton)
|
||||
local BorderedContainer = require(Plugin.App.Components.BorderedContainer)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local function ConnectionDetails(props)
|
||||
return Theme.with(function(theme)
|
||||
return e(BorderedContainer, {
|
||||
transparency = props.transparency,
|
||||
size = UDim2.new(1, 0, 0, 70),
|
||||
layoutOrder = props.layoutOrder,
|
||||
}, {
|
||||
TextContainer = e("Frame", {
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
BackgroundTransparency = 1
|
||||
}, {
|
||||
ProjectName = e("TextLabel", {
|
||||
Text = props.projectName,
|
||||
Font = Enum.Font.GothamBold,
|
||||
TextSize = 20,
|
||||
TextColor3 = theme.ConnectionDetails.ProjectNameColor,
|
||||
TextTransparency = props.transparency,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
|
||||
Size = UDim2.new(1, 0, 0, 20),
|
||||
|
||||
LayoutOrder = 1,
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
|
||||
Address = e("TextLabel", {
|
||||
Text = props.address,
|
||||
Font = Enum.Font.Code,
|
||||
TextSize = 15,
|
||||
TextColor3 = theme.ConnectionDetails.AddressColor,
|
||||
TextTransparency = props.transparency,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
|
||||
Size = UDim2.new(1, 0, 0, 15),
|
||||
|
||||
LayoutOrder = 2,
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
|
||||
Layout = e("UIListLayout", {
|
||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||
FillDirection = Enum.FillDirection.Vertical,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
Padding = UDim.new(0, 6),
|
||||
}),
|
||||
}),
|
||||
|
||||
Disconnect = e(IconButton, {
|
||||
icon = Assets.Images.Icons.Close,
|
||||
iconSize = 24,
|
||||
color = theme.ConnectionDetails.DisconnectColor,
|
||||
transparency = props.transparency,
|
||||
|
||||
position = UDim2.new(1, 0, 0.5, 0),
|
||||
anchorPoint = Vector2.new(1, 0.5),
|
||||
|
||||
onClick = props.onDisconnect,
|
||||
}),
|
||||
|
||||
Padding = e("UIPadding", {
|
||||
PaddingLeft = UDim.new(0, 17),
|
||||
PaddingRight = UDim.new(0, 15),
|
||||
}),
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
local ConnectedPage = Roact.Component:extend("ConnectedPage")
|
||||
|
||||
function ConnectedPage:render()
|
||||
return Roact.createFragment({
|
||||
Header = e(Header, {
|
||||
transparency = self.props.transparency,
|
||||
layoutOrder = 1,
|
||||
}),
|
||||
|
||||
ConnectionDetails = e(ConnectionDetails, {
|
||||
projectName = self.state.projectName,
|
||||
address = self.state.address,
|
||||
transparency = self.props.transparency,
|
||||
layoutOrder = 2,
|
||||
|
||||
onDisconnect = self.props.onDisconnect,
|
||||
}),
|
||||
|
||||
Layout = e("UIListLayout", {
|
||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||
FillDirection = Enum.FillDirection.Vertical,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
Padding = UDim.new(0, 10),
|
||||
}),
|
||||
|
||||
Padding = e("UIPadding", {
|
||||
PaddingLeft = UDim.new(0, 20),
|
||||
PaddingRight = UDim.new(0, 20),
|
||||
}),
|
||||
})
|
||||
end
|
||||
|
||||
function ConnectedPage.getDerivedStateFromProps(props)
|
||||
-- If projectName or address ever get removed from props, make sure we still have
|
||||
-- the properties! The component still needs to have its data for it to be properly
|
||||
-- animated out without the labels changing.
|
||||
|
||||
return {
|
||||
projectName = props.projectName,
|
||||
address = props.address,
|
||||
}
|
||||
end
|
||||
|
||||
return ConnectedPage
|
||||
20
plugin/src/App/StatusPages/Connecting.lua
Normal file
20
plugin/src/App/StatusPages/Connecting.lua
Normal file
@@ -0,0 +1,20 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
|
||||
local Spinner = require(Plugin.App.Components.Spinner)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local ConnectingPage = Roact.Component:extend("ConnectingPage")
|
||||
|
||||
function ConnectingPage:render()
|
||||
return e(Spinner, {
|
||||
position = UDim2.new(0.5, 0, 0.5, 0),
|
||||
anchorPoint = Vector2.new(0.5, 0.5),
|
||||
transparency = self.props.transparency,
|
||||
})
|
||||
end
|
||||
|
||||
return ConnectingPage
|
||||
153
plugin/src/App/StatusPages/Error.lua
Normal file
153
plugin/src/App/StatusPages/Error.lua
Normal file
@@ -0,0 +1,153 @@
|
||||
local TextService = game:GetService("TextService")
|
||||
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
|
||||
local Theme = require(Plugin.App.Theme)
|
||||
|
||||
local TextButton = require(Plugin.App.Components.TextButton)
|
||||
local BorderedContainer = require(Plugin.App.Components.BorderedContainer)
|
||||
local ScrollingFrame = require(Plugin.App.Components.ScrollingFrame)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local ERROR_PADDING = Vector2.new(13, 10)
|
||||
|
||||
local Error = Roact.Component:extend("Error")
|
||||
|
||||
function Error:init()
|
||||
self.contentSize, self.setContentSize = Roact.createBinding(Vector2.new(0, 0))
|
||||
end
|
||||
|
||||
function Error:render()
|
||||
return e(BorderedContainer, {
|
||||
size = Roact.joinBindings({
|
||||
containerSize = self.props.containerSize,
|
||||
contentSize = self.contentSize,
|
||||
}):map(function(values)
|
||||
local maximumSize = values.containerSize
|
||||
maximumSize -= Vector2.new(14, 14) * 2 -- Page padding
|
||||
maximumSize -= Vector2.new(0, 34 + 10) -- Buttons and spacing
|
||||
|
||||
local outerSize = values.contentSize + ERROR_PADDING * 2
|
||||
|
||||
return UDim2.new(1, 0, 0, math.min(outerSize.Y, maximumSize.Y))
|
||||
end),
|
||||
transparency = self.props.transparency,
|
||||
}, {
|
||||
ScrollingFrame = e(ScrollingFrame, {
|
||||
size = UDim2.new(1, 0, 1, 0),
|
||||
contentSize = self.contentSize:map(function(value)
|
||||
return value + ERROR_PADDING * 2
|
||||
end),
|
||||
transparency = self.props.transparency,
|
||||
|
||||
[Roact.Change.AbsoluteSize] = function(object)
|
||||
local containerSize = object.AbsoluteSize - ERROR_PADDING * 2
|
||||
|
||||
local textBounds = TextService:GetTextSize(
|
||||
self.props.errorMessage, 16, Enum.Font.Code,
|
||||
Vector2.new(containerSize.X, math.huge)
|
||||
)
|
||||
|
||||
self.setContentSize(Vector2.new(containerSize.X, textBounds.Y))
|
||||
end,
|
||||
}, {
|
||||
ErrorMessage = Theme.with(function(theme)
|
||||
return e("TextLabel", {
|
||||
Text = self.props.errorMessage,
|
||||
Font = Enum.Font.Code,
|
||||
TextSize = 16,
|
||||
TextColor3 = theme.ErrorColor,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextYAlignment = Enum.TextYAlignment.Top,
|
||||
TextTransparency = self.props.transparency,
|
||||
TextWrapped = true,
|
||||
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
|
||||
BackgroundTransparency = 1,
|
||||
})
|
||||
end),
|
||||
|
||||
Padding = e("UIPadding", {
|
||||
PaddingLeft = UDim.new(0, ERROR_PADDING.X),
|
||||
PaddingRight = UDim.new(0, ERROR_PADDING.X),
|
||||
PaddingTop = UDim.new(0, ERROR_PADDING.Y),
|
||||
PaddingBottom = UDim.new(0, ERROR_PADDING.Y),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
end
|
||||
|
||||
local ErrorPage = Roact.Component:extend("ErrorPage")
|
||||
|
||||
function ErrorPage:init()
|
||||
self.containerSize, self.setContainerSize = Roact.createBinding(Vector2.new(0, 0))
|
||||
end
|
||||
|
||||
function ErrorPage:render()
|
||||
return Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
BackgroundTransparency = 1,
|
||||
|
||||
[Roact.Change.AbsoluteSize] = function(object)
|
||||
self.setContainerSize(object.AbsoluteSize)
|
||||
end,
|
||||
}, {
|
||||
Error = e(Error, {
|
||||
errorMessage = self.state.errorMessage,
|
||||
containerSize = self.containerSize,
|
||||
transparency = self.props.transparency,
|
||||
layoutOrder = 1,
|
||||
}),
|
||||
|
||||
Buttons = e("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, 35),
|
||||
LayoutOrder = 2,
|
||||
BackgroundTransparency = 1,
|
||||
}, {
|
||||
Close = e(TextButton, {
|
||||
text = "Okay",
|
||||
style = "Bordered",
|
||||
transparency = self.props.transparency,
|
||||
layoutOrder = 1,
|
||||
onClick = self.props.onClose,
|
||||
}),
|
||||
|
||||
Layout = e("UIListLayout", {
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Right,
|
||||
FillDirection = Enum.FillDirection.Horizontal,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
}),
|
||||
}),
|
||||
|
||||
Layout = e("UIListLayout", {
|
||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||
FillDirection = Enum.FillDirection.Vertical,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
Padding = UDim.new(0, 10),
|
||||
}),
|
||||
|
||||
Padding = e("UIPadding", {
|
||||
PaddingLeft = UDim.new(0, 14),
|
||||
PaddingRight = UDim.new(0, 14),
|
||||
PaddingTop = UDim.new(0, 14),
|
||||
PaddingBottom = UDim.new(0, 14),
|
||||
}),
|
||||
})
|
||||
end
|
||||
|
||||
function ErrorPage.getDerivedStateFromProps(props)
|
||||
-- If errorMessage ever gets removed from props, make sure we still have the
|
||||
-- property! The component still needs to have its data for it to be properly
|
||||
-- animated out without the labels changing.
|
||||
|
||||
return {
|
||||
errorMessage = props.errorMessage,
|
||||
}
|
||||
end
|
||||
|
||||
return ErrorPage
|
||||
154
plugin/src/App/StatusPages/NotConnected.lua
Normal file
154
plugin/src/App/StatusPages/NotConnected.lua
Normal file
@@ -0,0 +1,154 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
|
||||
local Config = require(Plugin.Config)
|
||||
|
||||
local Theme = require(Plugin.App.Theme)
|
||||
local BorderedContainer = require(Plugin.App.Components.BorderedContainer)
|
||||
local TextButton = require(Plugin.App.Components.TextButton)
|
||||
local Header = require(Plugin.App.Components.Header)
|
||||
|
||||
local PORT_WIDTH = 74
|
||||
local DIVIDER_WIDTH = 1
|
||||
local HOST_OFFSET = 12
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local function AddressEntry(props)
|
||||
return Theme.with(function(theme)
|
||||
return e(BorderedContainer, {
|
||||
transparency = props.transparency,
|
||||
size = UDim2.new(1, 0, 0, 36),
|
||||
layoutOrder = props.layoutOrder,
|
||||
}, {
|
||||
Host = e("TextBox", {
|
||||
Text = "",
|
||||
Font = Enum.Font.Code,
|
||||
TextSize = 18,
|
||||
TextColor3 = theme.AddressEntry.TextColor,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextTransparency = props.transparency,
|
||||
PlaceholderText = Config.defaultHost,
|
||||
PlaceholderColor3 = theme.AddressEntry.PlaceholderColor,
|
||||
|
||||
Size = UDim2.new(1, -(HOST_OFFSET + DIVIDER_WIDTH + PORT_WIDTH), 1, 0),
|
||||
Position = UDim2.new(0, HOST_OFFSET, 0, 0),
|
||||
|
||||
ClipsDescendants = true,
|
||||
BackgroundTransparency = 1,
|
||||
|
||||
[Roact.Ref] = props.hostRef,
|
||||
}),
|
||||
|
||||
Port = e("TextBox", {
|
||||
Text = "",
|
||||
Font = Enum.Font.Code,
|
||||
TextSize = 18,
|
||||
TextColor3 = theme.AddressEntry.TextColor,
|
||||
TextTransparency = props.transparency,
|
||||
PlaceholderText = Config.defaultPort,
|
||||
PlaceholderColor3 = theme.AddressEntry.PlaceholderColor,
|
||||
|
||||
Size = UDim2.new(0, PORT_WIDTH, 1, 0),
|
||||
Position = UDim2.new(1, 0, 0, 0),
|
||||
AnchorPoint = Vector2.new(1, 0),
|
||||
|
||||
ClipsDescendants = true,
|
||||
BackgroundTransparency = 1,
|
||||
|
||||
[Roact.Ref] = props.portRef,
|
||||
|
||||
[Roact.Change.Text] = function(object)
|
||||
local text = object.Text
|
||||
text = text:gsub("%D", "")
|
||||
object.Text = text
|
||||
end,
|
||||
}, {
|
||||
Divider = e("Frame", {
|
||||
BackgroundColor3 = theme.BorderedContainer.BorderColor,
|
||||
BackgroundTransparency = props.transparency,
|
||||
Size = UDim2.new(0, DIVIDER_WIDTH, 1, 0),
|
||||
AnchorPoint = Vector2.new(1, 0),
|
||||
BorderSizePixel = 0,
|
||||
}),
|
||||
}),
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
local NotConnectedPage = Roact.Component:extend("NotConnectedPage")
|
||||
|
||||
function NotConnectedPage:init()
|
||||
self.hostRef = Roact.createRef()
|
||||
self.portRef = Roact.createRef()
|
||||
end
|
||||
|
||||
function NotConnectedPage:render()
|
||||
return Roact.createFragment({
|
||||
Header = e(Header, {
|
||||
transparency = self.props.transparency,
|
||||
layoutOrder = 1,
|
||||
}),
|
||||
|
||||
AddressEntry = e(AddressEntry, {
|
||||
hostRef = self.hostRef,
|
||||
portRef = self.portRef,
|
||||
transparency = self.props.transparency,
|
||||
layoutOrder = 2,
|
||||
}),
|
||||
|
||||
Buttons = e("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, 34),
|
||||
LayoutOrder = 3,
|
||||
BackgroundTransparency = 1,
|
||||
}, {
|
||||
Settings = e(TextButton, {
|
||||
text = "Settings",
|
||||
style = "Bordered",
|
||||
transparency = self.props.transparency,
|
||||
layoutOrder = 1,
|
||||
onClick = self.props.onNavigateSettings,
|
||||
}),
|
||||
|
||||
Connect = e(TextButton, {
|
||||
text = "Connect",
|
||||
style = "Solid",
|
||||
transparency = self.props.transparency,
|
||||
layoutOrder = 2,
|
||||
onClick = function()
|
||||
local hostText = self.hostRef.current.Text
|
||||
local portText = self.portRef.current.Text
|
||||
|
||||
self.props.onConnect(
|
||||
#hostText > 0 and hostText or Config.defaultHost,
|
||||
#portText > 0 and portText or Config.defaultPort
|
||||
)
|
||||
end,
|
||||
}),
|
||||
|
||||
Layout = e("UIListLayout", {
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Right,
|
||||
FillDirection = Enum.FillDirection.Horizontal,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
Padding = UDim.new(0, 10),
|
||||
}),
|
||||
}),
|
||||
|
||||
Layout = e("UIListLayout", {
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Center,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||
FillDirection = Enum.FillDirection.Vertical,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
Padding = UDim.new(0, 10),
|
||||
}),
|
||||
|
||||
Padding = e("UIPadding", {
|
||||
PaddingLeft = UDim.new(0, 20),
|
||||
PaddingRight = UDim.new(0, 20),
|
||||
}),
|
||||
})
|
||||
end
|
||||
|
||||
return NotConnectedPage
|
||||
230
plugin/src/App/StatusPages/Settings.lua
Normal file
230
plugin/src/App/StatusPages/Settings.lua
Normal file
@@ -0,0 +1,230 @@
|
||||
local TextService = game:GetService("TextService")
|
||||
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
|
||||
local Assets = require(Plugin.Assets)
|
||||
local Theme = require(Plugin.App.Theme)
|
||||
local PluginSettings = require(Plugin.App.PluginSettings)
|
||||
|
||||
local Checkbox = require(Plugin.App.Components.Checkbox)
|
||||
local IconButton = require(Plugin.App.Components.IconButton)
|
||||
local ScrollingFrame = require(Plugin.App.Components.ScrollingFrame)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local DIVIDER_FADE_SIZE = 0.1
|
||||
|
||||
local function getTextBounds(text, textSize, font, lineHeight, bounds)
|
||||
local textBounds = TextService:GetTextSize(text, textSize, font, bounds)
|
||||
|
||||
local lineCount = textBounds.Y / textSize
|
||||
local lineHeightAbsolute = textSize * lineHeight
|
||||
|
||||
return Vector2.new(textBounds.X, lineHeightAbsolute * lineCount - (lineHeightAbsolute - textSize))
|
||||
end
|
||||
|
||||
local function Navbar(props)
|
||||
return Theme.with(function(theme)
|
||||
theme = theme.Settings.Navbar
|
||||
|
||||
return e("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, 46),
|
||||
LayoutOrder = props.layoutOrder,
|
||||
BackgroundTransparency = 1,
|
||||
}, {
|
||||
Back = e(IconButton, {
|
||||
icon = Assets.Images.Icons.Back,
|
||||
iconSize = 24,
|
||||
color = theme.BackButtonColor,
|
||||
transparency = props.transparency,
|
||||
|
||||
position = UDim2.new(0, 0, 0.5, 0),
|
||||
anchorPoint = Vector2.new(0, 0.5),
|
||||
|
||||
onClick = props.onBack,
|
||||
}),
|
||||
|
||||
Text = e("TextLabel", {
|
||||
Text = "Settings",
|
||||
Font = Enum.Font.Gotham,
|
||||
TextSize = 18,
|
||||
TextColor3 = theme.TextColor,
|
||||
TextTransparency = props.transparency,
|
||||
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
|
||||
BackgroundTransparency = 1,
|
||||
})
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
local Setting = Roact.Component:extend("Setting")
|
||||
|
||||
function Setting:init()
|
||||
self.contentSize, self.setContentSize = Roact.createBinding(Vector2.new(0, 0))
|
||||
self.containerSize, self.setContainerSize = Roact.createBinding(Vector2.new(0, 0))
|
||||
end
|
||||
|
||||
function Setting:render()
|
||||
return Theme.with(function(theme)
|
||||
theme = theme.Settings
|
||||
|
||||
return PluginSettings.with(function(settings)
|
||||
return e("Frame", {
|
||||
Size = self.contentSize:map(function(value)
|
||||
return UDim2.new(1, 0, 0, 20 + value.Y + 20)
|
||||
end),
|
||||
LayoutOrder = self.props.layoutOrder,
|
||||
BackgroundTransparency = 1,
|
||||
|
||||
[Roact.Change.AbsoluteSize] = function(object)
|
||||
self.setContainerSize(object.AbsoluteSize)
|
||||
end,
|
||||
}, {
|
||||
Checkbox = e(Checkbox, {
|
||||
active = settings:get(self.props.id),
|
||||
transparency = self.props.transparency,
|
||||
position = UDim2.new(1, 0, 0.5, 0),
|
||||
anchorPoint = Vector2.new(1, 0.5),
|
||||
onClick = function()
|
||||
local currentValue = settings:get(self.props.id)
|
||||
settings:set(self.props.id, not currentValue)
|
||||
end,
|
||||
}),
|
||||
|
||||
Text = e("Frame", {
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
BackgroundTransparency = 1,
|
||||
}, {
|
||||
Name = e("TextLabel", {
|
||||
Text = self.props.name,
|
||||
Font = Enum.Font.GothamBold,
|
||||
TextSize = 17,
|
||||
TextColor3 = theme.Setting.NameColor,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextTransparency = self.props.transparency,
|
||||
|
||||
Size = UDim2.new(1, 0, 0, 17),
|
||||
|
||||
LayoutOrder = 1,
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
|
||||
Description = e("TextLabel", {
|
||||
Text = self.props.description,
|
||||
Font = Enum.Font.Gotham,
|
||||
LineHeight = 1.2,
|
||||
TextSize = 14,
|
||||
TextColor3 = theme.Setting.DescriptionColor,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextTransparency = self.props.transparency,
|
||||
TextWrapped = true,
|
||||
|
||||
Size = self.containerSize:map(function(value)
|
||||
local textBounds = getTextBounds(
|
||||
self.props.description, 14, Enum.Font.Gotham, 1.2,
|
||||
Vector2.new(value.X - 50, math.huge)
|
||||
)
|
||||
return UDim2.new(1, -50, 0, textBounds.Y)
|
||||
end),
|
||||
|
||||
LayoutOrder = 2,
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
|
||||
Layout = e("UIListLayout", {
|
||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||
FillDirection = Enum.FillDirection.Vertical,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
Padding = UDim.new(0, 6),
|
||||
|
||||
[Roact.Change.AbsoluteContentSize] = function(object)
|
||||
self.setContentSize(object.AbsoluteContentSize)
|
||||
end,
|
||||
}),
|
||||
|
||||
Padding = e("UIPadding", {
|
||||
PaddingTop = UDim.new(0, 20),
|
||||
PaddingBottom = UDim.new(0, 20),
|
||||
}),
|
||||
}),
|
||||
|
||||
Divider = e("Frame", {
|
||||
BackgroundColor3 = theme.DividerColor,
|
||||
BackgroundTransparency = self.props.transparency,
|
||||
Size = UDim2.new(1, 0, 0, 1),
|
||||
BorderSizePixel = 0,
|
||||
}, {
|
||||
Gradient = e("UIGradient", {
|
||||
Transparency = NumberSequence.new({
|
||||
NumberSequenceKeypoint.new(0, 1),
|
||||
NumberSequenceKeypoint.new(DIVIDER_FADE_SIZE, 0),
|
||||
NumberSequenceKeypoint.new(1 - DIVIDER_FADE_SIZE, 0),
|
||||
NumberSequenceKeypoint.new(1, 1),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
local SettingsPage = Roact.Component:extend("SettingsPage")
|
||||
|
||||
function SettingsPage:init()
|
||||
self.contentSize, self.setContentSize = Roact.createBinding(Vector2.new(0, 0))
|
||||
end
|
||||
|
||||
function SettingsPage:render()
|
||||
return Theme.with(function(theme)
|
||||
theme = theme.Settings
|
||||
|
||||
return e(ScrollingFrame, {
|
||||
size = UDim2.new(1, 0, 1, 0),
|
||||
contentSize = self.contentSize,
|
||||
transparency = self.props.transparency,
|
||||
}, {
|
||||
Navbar = e(Navbar, {
|
||||
onBack = self.props.onBack,
|
||||
transparency = self.props.transparency,
|
||||
layoutOrder = 0,
|
||||
}),
|
||||
|
||||
OpenScriptsExternally = e(Setting, {
|
||||
id = "openScriptsExternally",
|
||||
name = "Open Scripts Externally",
|
||||
description = "Attempt to open scripts in an external editor",
|
||||
transparency = self.props.transparency,
|
||||
layoutOrder = 1,
|
||||
}),
|
||||
|
||||
TwoWaySync = e(Setting, {
|
||||
id = "twoWaySync",
|
||||
name = "Two-Way Sync",
|
||||
description = "Editing files in Studio will sync them into the filesystem",
|
||||
transparency = self.props.transparency,
|
||||
layoutOrder = 2,
|
||||
}),
|
||||
|
||||
Layout = e("UIListLayout", {
|
||||
FillDirection = Enum.FillDirection.Vertical,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
|
||||
[Roact.Change.AbsoluteContentSize] = function(object)
|
||||
self.setContentSize(object.AbsoluteContentSize)
|
||||
end,
|
||||
}),
|
||||
|
||||
Padding = e("UIPadding", {
|
||||
PaddingLeft = UDim.new(0, 20),
|
||||
PaddingRight = UDim.new(0, 20),
|
||||
}),
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
return SettingsPage
|
||||
7
plugin/src/App/StatusPages/init.lua
Normal file
7
plugin/src/App/StatusPages/init.lua
Normal file
@@ -0,0 +1,7 @@
|
||||
return {
|
||||
NotConnected = require(script.NotConnected),
|
||||
Settings = require(script.Settings),
|
||||
Connecting = require(script.Connecting),
|
||||
Connected = require(script.Connected),
|
||||
Error = require(script.Error),
|
||||
}
|
||||
239
plugin/src/App/Theme.lua
Normal file
239
plugin/src/App/Theme.lua
Normal file
@@ -0,0 +1,239 @@
|
||||
--[[
|
||||
Theming system taking advantage of Roact's new context API.
|
||||
Doesn't use colors provided by Studio and instead just branches on theme
|
||||
name. This isn't exactly best practice.
|
||||
]]
|
||||
|
||||
-- Studio does not exist outside Roblox Studio, so we'll lazily initialize it
|
||||
-- when possible.
|
||||
local _Studio
|
||||
local function getStudio()
|
||||
if _Studio == nil then
|
||||
_Studio = settings():GetService("Studio")
|
||||
end
|
||||
|
||||
return _Studio
|
||||
end
|
||||
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
local Log = require(Rojo.Log)
|
||||
|
||||
local strict = require(script.Parent.Parent.strict)
|
||||
|
||||
-- Copying hex colors back and forth from design programs is faster
|
||||
local function hexColor(decimal)
|
||||
local red = bit32.band(bit32.rshift(decimal, 16), 2^8 - 1)
|
||||
local green = bit32.band(bit32.rshift(decimal, 8), 2^8 - 1)
|
||||
local blue = bit32.band(decimal, 2^8 - 1)
|
||||
|
||||
return Color3.fromRGB(red, green, blue)
|
||||
end
|
||||
|
||||
local BRAND_COLOR = hexColor(0xE13835)
|
||||
|
||||
local lightTheme = strict("LightTheme", {
|
||||
BackgroundColor = hexColor(0xF0F0F0),
|
||||
Button = {
|
||||
Solid = {
|
||||
ActionFillColor = hexColor(0xFFFFFF),
|
||||
ActionFillTransparency = 0.8,
|
||||
Enabled = {
|
||||
TextColor = hexColor(0xFFFFFF),
|
||||
BackgroundColor = BRAND_COLOR,
|
||||
},
|
||||
Disabled = {
|
||||
TextColor = hexColor(0xFFFFFF),
|
||||
BackgroundColor = BRAND_COLOR,
|
||||
},
|
||||
},
|
||||
Bordered = {
|
||||
ActionFillColor = hexColor(0x000000),
|
||||
ActionFillTransparency = 0.9,
|
||||
Enabled = {
|
||||
TextColor = hexColor(0x393939),
|
||||
BorderColor = hexColor(0xACACAC),
|
||||
},
|
||||
Disabled = {
|
||||
TextColor = hexColor(0x393939),
|
||||
BorderColor = hexColor(0xACACAC),
|
||||
},
|
||||
},
|
||||
},
|
||||
Checkbox = {
|
||||
Active = {
|
||||
IconColor = hexColor(0xFFFFFF),
|
||||
BackgroundColor = BRAND_COLOR,
|
||||
},
|
||||
Inactive = {
|
||||
IconColor = hexColor(0xCACACA),
|
||||
BorderColor = hexColor(0xAFAFAF),
|
||||
},
|
||||
},
|
||||
AddressEntry = {
|
||||
TextColor = hexColor(0x000000),
|
||||
PlaceholderColor = hexColor(0x8C8C8C)
|
||||
},
|
||||
BorderedContainer = {
|
||||
BorderColor = hexColor(0xCBCBCB),
|
||||
BackgroundColor = hexColor(0xE0E0E0),
|
||||
},
|
||||
Spinner = {
|
||||
ForegroundColor = BRAND_COLOR,
|
||||
BackgroundColor = hexColor(0xE0E0E0),
|
||||
},
|
||||
ConnectionDetails = {
|
||||
ProjectNameColor = hexColor(0x00000),
|
||||
AddressColor = hexColor(0x00000),
|
||||
DisconnectColor = BRAND_COLOR,
|
||||
},
|
||||
Settings = {
|
||||
DividerColor = hexColor(0xCBCBCB),
|
||||
Navbar = {
|
||||
BackButtonColor = hexColor(0x000000),
|
||||
TextColor = hexColor(0x000000),
|
||||
},
|
||||
Setting = {
|
||||
NameColor = hexColor(0x000000),
|
||||
DescriptionColor = hexColor(0x5F5F5F),
|
||||
},
|
||||
},
|
||||
Header = {
|
||||
LogoColor = BRAND_COLOR,
|
||||
VersionColor = hexColor(0x727272),
|
||||
},
|
||||
ErrorColor = hexColor(0x000000),
|
||||
ScrollBarColor = hexColor(0x000000),
|
||||
})
|
||||
|
||||
local darkTheme = strict("DarkTheme", {
|
||||
BackgroundColor = hexColor(0x272727),
|
||||
Button = {
|
||||
Solid = {
|
||||
ActionFillColor = hexColor(0xFFFFFF),
|
||||
ActionFillTransparency = 0.8,
|
||||
Enabled = {
|
||||
TextColor = hexColor(0xFFFFFF),
|
||||
BackgroundColor = BRAND_COLOR,
|
||||
},
|
||||
Disabled = {
|
||||
TextColor = hexColor(0xFFFFFF),
|
||||
BackgroundColor = BRAND_COLOR,
|
||||
},
|
||||
},
|
||||
Bordered = {
|
||||
ActionFillColor = hexColor(0xFFFFFF),
|
||||
ActionFillTransparency = 0.9,
|
||||
Enabled = {
|
||||
TextColor = hexColor(0xDBDBDB),
|
||||
BorderColor = hexColor(0x535353),
|
||||
},
|
||||
Disabled = {
|
||||
TextColor = hexColor(0xDBDBDB),
|
||||
BorderColor = hexColor(0x535353),
|
||||
},
|
||||
},
|
||||
},
|
||||
Checkbox = {
|
||||
Active = {
|
||||
IconColor = hexColor(0xFFFFFF),
|
||||
BackgroundColor = BRAND_COLOR,
|
||||
},
|
||||
Inactive = {
|
||||
IconColor = hexColor(0x484848),
|
||||
BorderColor = hexColor(0x5A5A5A),
|
||||
},
|
||||
},
|
||||
AddressEntry = {
|
||||
TextColor = hexColor(0xFFFFFF),
|
||||
PlaceholderColor = hexColor(0x717171)
|
||||
},
|
||||
BorderedContainer = {
|
||||
BorderColor = hexColor(0x535353),
|
||||
BackgroundColor = hexColor(0x323232),
|
||||
},
|
||||
Spinner = {
|
||||
ForegroundColor = BRAND_COLOR,
|
||||
BackgroundColor = hexColor(0x323232),
|
||||
},
|
||||
ConnectionDetails = {
|
||||
ProjectNameColor = hexColor(0xFFFFFF),
|
||||
AddressColor = hexColor(0xFFFFFF),
|
||||
DisconnectColor = hexColor(0xFFFFFF),
|
||||
},
|
||||
Settings = {
|
||||
DividerColor = hexColor(0x535353),
|
||||
Navbar = {
|
||||
BackButtonColor = hexColor(0xFFFFFF),
|
||||
TextColor = hexColor(0xFFFFFF),
|
||||
},
|
||||
Setting = {
|
||||
NameColor = hexColor(0xFFFFFF),
|
||||
DescriptionColor = hexColor(0xD3D3D3),
|
||||
},
|
||||
},
|
||||
Header = {
|
||||
LogoColor = hexColor(0xFFFFFF),
|
||||
VersionColor = hexColor(0xD3D3D3)
|
||||
},
|
||||
ErrorColor = hexColor(0xFFFFFF),
|
||||
ScrollBarColor = hexColor(0xFFFFFF),
|
||||
})
|
||||
|
||||
local Context = Roact.createContext(lightTheme)
|
||||
|
||||
local StudioProvider = Roact.Component:extend("StudioProvider")
|
||||
|
||||
-- Pull the current theme from Roblox Studio and update state with it.
|
||||
function StudioProvider:updateTheme()
|
||||
local studioTheme = getStudio().Theme
|
||||
|
||||
if studioTheme.Name == "Light" then
|
||||
self:setState({
|
||||
theme = lightTheme,
|
||||
})
|
||||
elseif studioTheme.Name == "Dark" then
|
||||
self:setState({
|
||||
theme = darkTheme,
|
||||
})
|
||||
else
|
||||
Log.warn("Unexpected theme '{}'' -- falling back to light theme!", studioTheme.Name)
|
||||
|
||||
self:setState({
|
||||
theme = lightTheme,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function StudioProvider:init()
|
||||
self:updateTheme()
|
||||
end
|
||||
|
||||
function StudioProvider:render()
|
||||
return Roact.createElement(Context.Provider, {
|
||||
value = self.state.theme,
|
||||
}, self.props[Roact.Children])
|
||||
end
|
||||
|
||||
function StudioProvider:didMount()
|
||||
self.connection = getStudio().ThemeChanged:Connect(function()
|
||||
self:updateTheme()
|
||||
end)
|
||||
end
|
||||
|
||||
function StudioProvider:willUnmount()
|
||||
self.connection:Disconnect()
|
||||
end
|
||||
|
||||
local function with(callback)
|
||||
return Roact.createElement(Context.Consumer, {
|
||||
render = callback,
|
||||
})
|
||||
end
|
||||
|
||||
return {
|
||||
StudioProvider = StudioProvider,
|
||||
Consumer = Context.Consumer,
|
||||
with = with,
|
||||
}
|
||||
58
plugin/src/App/bindingUtil.lua
Normal file
58
plugin/src/App/bindingUtil.lua
Normal file
@@ -0,0 +1,58 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
local Log = require(Rojo.Log)
|
||||
|
||||
local LERP_DATA_TYPES = {
|
||||
Color3 = true,
|
||||
UDim = true,
|
||||
UDim2 = true,
|
||||
Vector2 = true,
|
||||
Vector3 = true,
|
||||
}
|
||||
|
||||
local function fromMotor(motor)
|
||||
local motorBinding, setMotorBinding = Roact.createBinding(motor:getValue())
|
||||
motor:onStep(setMotorBinding)
|
||||
return motorBinding
|
||||
end
|
||||
|
||||
local function mapLerp(binding, value1, value2)
|
||||
local valueType = typeof(value1)
|
||||
if valueType ~= typeof(value2) then
|
||||
Log.error("Type mismatch between values ({}, {}})", valueType, typeof(value2))
|
||||
end
|
||||
|
||||
return binding:map(function(position)
|
||||
if valueType == "number" then
|
||||
return value1 - (value2 - value1) * position
|
||||
elseif LERP_DATA_TYPES[valueType] then
|
||||
return value1:lerp(value2, position)
|
||||
else
|
||||
Log.error("Unable to interpolate type {}", valueType)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
local function deriveProperty(binding, propertyName)
|
||||
return binding:map(function(values)
|
||||
return values[propertyName]
|
||||
end)
|
||||
end
|
||||
|
||||
local function blendAlpha(alphaValues)
|
||||
local alpha = 0
|
||||
|
||||
for _, value in pairs(alphaValues) do
|
||||
alpha = alpha + (1 - alpha) * value
|
||||
end
|
||||
|
||||
return alpha
|
||||
end
|
||||
|
||||
return {
|
||||
fromMotor = fromMotor,
|
||||
mapLerp = mapLerp,
|
||||
deriveProperty = deriveProperty,
|
||||
blendAlpha = blendAlpha,
|
||||
}
|
||||
226
plugin/src/App/init.lua
Normal file
226
plugin/src/App/init.lua
Normal file
@@ -0,0 +1,226 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
local Log = require(Rojo.Log)
|
||||
|
||||
local Assets = require(Plugin.Assets)
|
||||
local Version = require(Plugin.Version)
|
||||
local Config = require(Plugin.Config)
|
||||
local strict = require(Plugin.strict)
|
||||
local Dictionary = require(Plugin.Dictionary)
|
||||
local ServeSession = require(Plugin.ServeSession)
|
||||
local ApiContext = require(Plugin.ApiContext)
|
||||
local preloadAssets = require(Plugin.preloadAssets)
|
||||
local Theme = require(script.Theme)
|
||||
local PluginSettings = require(script.PluginSettings)
|
||||
|
||||
local Page = require(script.Page)
|
||||
local StudioToolbar = require(script.Components.Studio.StudioToolbar)
|
||||
local StudioToggleButton = require(script.Components.Studio.StudioToggleButton)
|
||||
local StudioPluginGui = require(script.Components.Studio.StudioPluginGui)
|
||||
local StudioPluginContext = require(script.Components.Studio.StudioPluginContext)
|
||||
local StatusPages = require(script.StatusPages)
|
||||
|
||||
local AppStatus = strict("AppStatus", {
|
||||
NotConnected = "NotConnected",
|
||||
Settings = "Settings",
|
||||
Connecting = "Connecting",
|
||||
Connected = "Connected",
|
||||
Error = "Error",
|
||||
})
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local App = Roact.Component:extend("App")
|
||||
|
||||
function App:init()
|
||||
preloadAssets()
|
||||
|
||||
self:setState({
|
||||
appStatus = AppStatus.NotConnected,
|
||||
guiEnabled = false,
|
||||
})
|
||||
end
|
||||
|
||||
function App:startSession(host, port, sessionOptions)
|
||||
local baseUrl = ("http://%s:%s"):format(host, port)
|
||||
local apiContext = ApiContext.new(baseUrl)
|
||||
|
||||
local serveSession = ServeSession.new({
|
||||
apiContext = apiContext,
|
||||
openScriptsExternally = sessionOptions.openScriptsExternally,
|
||||
twoWaySync = sessionOptions.twoWaySync,
|
||||
})
|
||||
|
||||
serveSession:onStatusChanged(function(status, details)
|
||||
if status == ServeSession.Status.Connecting then
|
||||
self:setState({
|
||||
appStatus = AppStatus.Connecting,
|
||||
})
|
||||
elseif status == ServeSession.Status.Connected then
|
||||
local address = ("%s:%s"):format(host, port)
|
||||
self:setState({
|
||||
appStatus = AppStatus.Connected,
|
||||
projectName = details,
|
||||
address = address,
|
||||
})
|
||||
elseif status == ServeSession.Status.Disconnected then
|
||||
self.serveSession = nil
|
||||
|
||||
-- Details being present indicates that this
|
||||
-- disconnection was from an error.
|
||||
if details ~= nil then
|
||||
Log.warn("Disconnected from an error: {}", details)
|
||||
|
||||
self:setState({
|
||||
appStatus = AppStatus.Error,
|
||||
errorMessage = tostring(details),
|
||||
})
|
||||
else
|
||||
self:setState({
|
||||
appStatus = AppStatus.NotConnected,
|
||||
})
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
serveSession:start()
|
||||
|
||||
self.serveSession = serveSession
|
||||
end
|
||||
|
||||
function App:render()
|
||||
local pluginName = "Rojo " .. Version.display(Config.version)
|
||||
|
||||
local function createPageElement(appStatus, additionalProps)
|
||||
additionalProps = additionalProps or {}
|
||||
|
||||
local props = Dictionary.merge(additionalProps, {
|
||||
component = StatusPages[appStatus],
|
||||
active = self.state.appStatus == appStatus,
|
||||
})
|
||||
|
||||
return e(Page, props)
|
||||
end
|
||||
|
||||
return e(StudioPluginContext.Provider, {
|
||||
value = self.props.plugin,
|
||||
}, {
|
||||
e(Theme.StudioProvider, nil, {
|
||||
e(PluginSettings.StudioProvider, {
|
||||
plugin = self.props.plugin,
|
||||
}, {
|
||||
gui = e(StudioPluginGui, {
|
||||
id = pluginName,
|
||||
title = pluginName,
|
||||
active = self.state.guiEnabled,
|
||||
|
||||
initDockState = Enum.InitialDockState.Right,
|
||||
initEnabled = false,
|
||||
overridePreviousState = false,
|
||||
floatingSize = Vector2.new(300, 200),
|
||||
minimumSize = Vector2.new(300, 200),
|
||||
|
||||
zIndexBehavior = Enum.ZIndexBehavior.Sibling,
|
||||
|
||||
onInitialState = function(initialState)
|
||||
self:setState({
|
||||
guiEnabled = initialState,
|
||||
})
|
||||
end,
|
||||
|
||||
onClose = function()
|
||||
self:setState({
|
||||
guiEnabled = false,
|
||||
})
|
||||
end,
|
||||
}, {
|
||||
NotConnectedPage = PluginSettings.with(function(settings)
|
||||
return createPageElement(AppStatus.NotConnected, {
|
||||
onConnect = function(host, port)
|
||||
self:startSession(host, port, {
|
||||
openScriptsExternally = settings:get("openScriptsExternally"),
|
||||
twoWaySync = settings:get("twoWaySync"),
|
||||
})
|
||||
end,
|
||||
|
||||
onNavigateSettings = function()
|
||||
self:setState({
|
||||
appStatus = AppStatus.Settings,
|
||||
})
|
||||
end,
|
||||
})
|
||||
end),
|
||||
|
||||
Connecting = createPageElement(AppStatus.Connecting),
|
||||
|
||||
Connected = createPageElement(AppStatus.Connected, {
|
||||
projectName = self.state.projectName,
|
||||
address = self.state.address,
|
||||
|
||||
onDisconnect = function()
|
||||
Log.trace("Disconnecting session")
|
||||
|
||||
self.serveSession:stop()
|
||||
self.serveSession = nil
|
||||
self:setState({
|
||||
appStatus = AppStatus.NotConnected,
|
||||
})
|
||||
|
||||
Log.trace("Session terminated by user")
|
||||
end,
|
||||
}),
|
||||
|
||||
Settings = createPageElement(AppStatus.Settings, {
|
||||
onBack = function()
|
||||
self:setState({
|
||||
appStatus = AppStatus.NotConnected,
|
||||
})
|
||||
end,
|
||||
}),
|
||||
|
||||
Error = createPageElement(AppStatus.Error, {
|
||||
errorMessage = self.state.errorMessage,
|
||||
|
||||
onClose = function()
|
||||
self:setState({
|
||||
appStatus = AppStatus.NotConnected,
|
||||
})
|
||||
end,
|
||||
}),
|
||||
|
||||
Background = Theme.with(function(theme)
|
||||
return e("Frame", {
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
BackgroundColor3 = theme.BackgroundColor,
|
||||
ZIndex = 0,
|
||||
BorderSizePixel = 0,
|
||||
})
|
||||
end),
|
||||
}),
|
||||
|
||||
toolbar = e(StudioToolbar, {
|
||||
name = pluginName,
|
||||
}, {
|
||||
button = e(StudioToggleButton, {
|
||||
name = "Rojo",
|
||||
tooltip = "Show or hide the Rojo panel",
|
||||
icon = Assets.Images.PluginButton,
|
||||
active = self.state.guiEnabled,
|
||||
enabled = true,
|
||||
onClick = function()
|
||||
self:setState(function(state)
|
||||
return {
|
||||
guiEnabled = not state.guiEnabled,
|
||||
}
|
||||
end)
|
||||
end,
|
||||
})
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
end
|
||||
|
||||
return App
|
||||
@@ -3,16 +3,45 @@ local strict = require(script.Parent.strict)
|
||||
local Assets = {
|
||||
Sprites = {},
|
||||
Slices = {
|
||||
RoundBox = {
|
||||
asset = "rbxassetid://2773204550",
|
||||
offset = Vector2.new(0, 0),
|
||||
size = Vector2.new(32, 32),
|
||||
center = Rect.new(4, 4, 4, 4),
|
||||
RoundedBackground = {
|
||||
Image = "rbxassetid://5981360418",
|
||||
Center = Rect.new(10, 10, 10, 10),
|
||||
Scale = 0.5,
|
||||
},
|
||||
|
||||
RoundedBorder = {
|
||||
Image = "rbxassetid://5981360137",
|
||||
Center = Rect.new(10, 10, 10, 10),
|
||||
Scale = 0.5,
|
||||
},
|
||||
},
|
||||
Images = {
|
||||
Logo = "rbxassetid://3405346157",
|
||||
Icon = "rbxassetid://3405341609",
|
||||
Logo = "rbxassetid://5990772764",
|
||||
PluginButton = "rbxassetid://3405341609",
|
||||
Icons = {
|
||||
Close = "rbxassetid://6012985953",
|
||||
Back = "rbxassetid://6017213752",
|
||||
},
|
||||
Checkbox = {
|
||||
Active = "rbxassetid://6016251644",
|
||||
Inactive = "rbxassetid://6016251963",
|
||||
},
|
||||
Spinner = {
|
||||
Foreground = "rbxassetid://3222731032",
|
||||
Background = "rbxassetid://3222730627",
|
||||
},
|
||||
ScrollBar = {
|
||||
Top = "rbxassetid://6017290134",
|
||||
Middle = "rbxassetid://6017289904",
|
||||
Bottom = "rbxassetid://6017289712",
|
||||
},
|
||||
Circles = {
|
||||
[16] = "rbxassetid://3056541177",
|
||||
[32] = "rbxassetid://3088713341",
|
||||
[64] = "rbxassetid://4918677124",
|
||||
[128] = "rbxassetid://2600845734",
|
||||
[500] = "rbxassetid://2609138523"
|
||||
},
|
||||
},
|
||||
StartSession = "",
|
||||
SessionActive = "",
|
||||
|
||||
@@ -1,241 +0,0 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
local Log = require(Rojo.Log)
|
||||
|
||||
local ApiContext = require(Plugin.ApiContext)
|
||||
local Assets = require(Plugin.Assets)
|
||||
local Config = require(Plugin.Config)
|
||||
local DevSettings = require(Plugin.DevSettings)
|
||||
local ServeSession = require(Plugin.ServeSession)
|
||||
local Version = require(Plugin.Version)
|
||||
local preloadAssets = require(Plugin.preloadAssets)
|
||||
local strict = require(Plugin.strict)
|
||||
|
||||
local ConnectPanel = require(Plugin.Components.ConnectPanel)
|
||||
local ConnectingPanel = require(Plugin.Components.ConnectingPanel)
|
||||
local ConnectionActivePanel = require(Plugin.Components.ConnectionActivePanel)
|
||||
local ErrorPanel = require(Plugin.Components.ErrorPanel)
|
||||
local SettingsPanel = require(Plugin.Components.SettingsPanel)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local function showUpgradeMessage(lastVersion)
|
||||
local message = (
|
||||
"Rojo detected an upgrade from version %s to version %s." ..
|
||||
"\nMake sure you have also upgraded your server!" ..
|
||||
"\n\nRojo plugin version %s is intended for use with server version %s."
|
||||
):format(
|
||||
Version.display(lastVersion), Version.display(Config.version),
|
||||
Version.display(Config.version), Config.expectedServerVersionString
|
||||
)
|
||||
|
||||
Log.info(message)
|
||||
end
|
||||
|
||||
--[[
|
||||
Check if the user is using a newer version of Rojo than last time. If they
|
||||
are, show them a reminder to make sure they check their server version.
|
||||
]]
|
||||
local function checkUpgrade(plugin)
|
||||
-- When developing Rojo, there's no use in doing version checks
|
||||
if DevSettings:isEnabled() then
|
||||
return
|
||||
end
|
||||
|
||||
local lastVersion = plugin:GetSetting("LastRojoVersion")
|
||||
|
||||
if lastVersion then
|
||||
local wasUpgraded = Version.compare(Config.version, lastVersion) == 1
|
||||
|
||||
if wasUpgraded then
|
||||
showUpgradeMessage(lastVersion)
|
||||
end
|
||||
end
|
||||
|
||||
plugin:SetSetting("LastRojoVersion", Config.version)
|
||||
end
|
||||
|
||||
local AppStatus = strict("AppStatus", {
|
||||
NotStarted = "NotStarted",
|
||||
Connecting = "Connecting",
|
||||
Connected = "Connected",
|
||||
Error = "Error",
|
||||
Settings = "Settings",
|
||||
})
|
||||
|
||||
local App = Roact.Component:extend("App")
|
||||
|
||||
function App:init()
|
||||
self:setState({
|
||||
appStatus = AppStatus.NotStarted,
|
||||
errorMessage = nil,
|
||||
})
|
||||
|
||||
self.signals = {}
|
||||
self.serveSession = nil
|
||||
self.displayedVersion = Version.display(Config.version)
|
||||
|
||||
local toolbar = self.props.plugin:CreateToolbar("Rojo " .. self.displayedVersion)
|
||||
|
||||
self.toggleButton = toolbar:CreateButton(
|
||||
"Rojo",
|
||||
"Show or hide the Rojo panel",
|
||||
Assets.Images.Icon)
|
||||
self.toggleButton.ClickableWhenViewportHidden = true
|
||||
self.toggleButton.Click:Connect(function()
|
||||
self.dockWidget.Enabled = not self.dockWidget.Enabled
|
||||
end)
|
||||
|
||||
local widgetInfo = DockWidgetPluginGuiInfo.new(
|
||||
Enum.InitialDockState.Right,
|
||||
false, -- Initially enabled state
|
||||
false, -- Whether to override the widget's previous state
|
||||
360, 190, -- Floating size
|
||||
360, 190 -- Minimum size
|
||||
)
|
||||
|
||||
self.dockWidget = self.props.plugin:CreateDockWidgetPluginGui("Rojo-" .. self.displayedVersion, widgetInfo)
|
||||
self.dockWidget.Name = "Rojo " .. self.displayedVersion
|
||||
self.dockWidget.Title = "Rojo " .. self.displayedVersion
|
||||
self.dockWidget.AutoLocalize = false
|
||||
self.dockWidget.ZIndexBehavior = Enum.ZIndexBehavior.Sibling
|
||||
|
||||
self.signals.dockWidgetEnabled = self.dockWidget:GetPropertyChangedSignal("Enabled"):Connect(function()
|
||||
self.toggleButton:SetActive(self.dockWidget.Enabled)
|
||||
end)
|
||||
end
|
||||
|
||||
function App:startSession(address, port, sessionOptions)
|
||||
Log.trace("Starting new session")
|
||||
|
||||
local baseUrl = ("http://%s:%s"):format(address, port)
|
||||
self.serveSession = ServeSession.new({
|
||||
apiContext = ApiContext.new(baseUrl),
|
||||
openScriptsExternally = sessionOptions.openScriptsExternally,
|
||||
twoWaySync = sessionOptions.twoWaySync,
|
||||
})
|
||||
|
||||
self.serveSession:onStatusChanged(function(status, details)
|
||||
if status == ServeSession.Status.Connecting then
|
||||
self:setState({
|
||||
appStatus = AppStatus.Connecting,
|
||||
})
|
||||
elseif status == ServeSession.Status.Connected then
|
||||
self:setState({
|
||||
appStatus = AppStatus.Connected,
|
||||
})
|
||||
elseif status == ServeSession.Status.Disconnected then
|
||||
self.serveSession = nil
|
||||
|
||||
-- Details being present indicates that this
|
||||
-- disconnection was from an error.
|
||||
if details ~= nil then
|
||||
Log.warn("Disconnected from an error: {}", details)
|
||||
|
||||
self:setState({
|
||||
appStatus = AppStatus.Error,
|
||||
errorMessage = tostring(details),
|
||||
})
|
||||
else
|
||||
self:setState({
|
||||
appStatus = AppStatus.NotStarted,
|
||||
})
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
self.serveSession:start()
|
||||
end
|
||||
|
||||
function App:render()
|
||||
local children
|
||||
|
||||
if self.state.appStatus == AppStatus.NotStarted then
|
||||
children = {
|
||||
ConnectPanel = e(ConnectPanel, {
|
||||
startSession = function(address, port, settings)
|
||||
self:startSession(address, port, settings)
|
||||
end,
|
||||
openSettings = function()
|
||||
self:setState({
|
||||
appStatus = AppStatus.Settings,
|
||||
})
|
||||
end,
|
||||
cancel = function()
|
||||
Log.trace("Canceling session configuration")
|
||||
|
||||
self:setState({
|
||||
appStatus = AppStatus.NotStarted,
|
||||
})
|
||||
end,
|
||||
}),
|
||||
}
|
||||
elseif self.state.appStatus == AppStatus.Connecting then
|
||||
children = {
|
||||
ConnectingPanel = e(ConnectingPanel),
|
||||
}
|
||||
elseif self.state.appStatus == AppStatus.Connected then
|
||||
children = {
|
||||
ConnectionActivePanel = e(ConnectionActivePanel, {
|
||||
stopSession = function()
|
||||
Log.trace("Disconnecting session")
|
||||
|
||||
self.serveSession:stop()
|
||||
self.serveSession = nil
|
||||
self:setState({
|
||||
appStatus = AppStatus.NotStarted,
|
||||
})
|
||||
|
||||
Log.trace("Session terminated by user")
|
||||
end,
|
||||
}),
|
||||
}
|
||||
elseif self.state.appStatus == AppStatus.Settings then
|
||||
children = {
|
||||
e(SettingsPanel, {
|
||||
back = function()
|
||||
self:setState({
|
||||
appStatus = AppStatus.NotStarted,
|
||||
})
|
||||
end,
|
||||
}),
|
||||
}
|
||||
elseif self.state.appStatus == AppStatus.Error then
|
||||
children = {
|
||||
ErrorPanel = e(ErrorPanel, {
|
||||
errorMessage = self.state.errorMessage,
|
||||
onDismiss = function()
|
||||
self:setState({
|
||||
appStatus = AppStatus.NotStarted,
|
||||
})
|
||||
end,
|
||||
}),
|
||||
}
|
||||
end
|
||||
|
||||
return e(Roact.Portal, {
|
||||
target = self.dockWidget,
|
||||
}, children)
|
||||
end
|
||||
|
||||
function App:didMount()
|
||||
Log.trace("Rojo {} initializing", self.displayedVersion)
|
||||
|
||||
checkUpgrade(self.props.plugin)
|
||||
preloadAssets()
|
||||
end
|
||||
|
||||
function App:willUnmount()
|
||||
if self.serveSession ~= nil then
|
||||
self.serveSession:stop()
|
||||
self.serveSession = nil
|
||||
end
|
||||
|
||||
for _, signal in pairs(self.signals) do
|
||||
signal:Disconnect()
|
||||
end
|
||||
end
|
||||
|
||||
return App
|
||||
@@ -1,39 +0,0 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
|
||||
local Theme = require(Plugin.Components.Theme)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local function Checkbox(props)
|
||||
local checked = props.checked
|
||||
local layoutOrder = props.layoutOrder
|
||||
local onChange = props.onChange
|
||||
|
||||
return Theme.with(function(theme)
|
||||
return e("ImageButton", {
|
||||
LayoutOrder = layoutOrder,
|
||||
Size = UDim2.new(0, 20, 0, 20),
|
||||
BorderSizePixel = 2,
|
||||
BorderColor3 = theme.Text2,
|
||||
BackgroundColor3 = theme.Background2,
|
||||
|
||||
[Roact.Event.Activated] = function()
|
||||
onChange(not checked)
|
||||
end,
|
||||
}, {
|
||||
Indicator = e("Frame", {
|
||||
Size = UDim2.new(0, 18, 0, 18),
|
||||
Position = UDim2.new(0.5, 0, 0.5, 0),
|
||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
||||
BorderSizePixel = 0,
|
||||
BackgroundColor3 = theme.Brand1,
|
||||
BackgroundTransparency = checked and 0 or 1,
|
||||
})
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
return Checkbox
|
||||
@@ -1,183 +0,0 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
|
||||
local Config = require(Plugin.Config)
|
||||
|
||||
local Theme = require(Plugin.Components.Theme)
|
||||
local Panel = require(Plugin.Components.Panel)
|
||||
local FitList = require(Plugin.Components.FitList)
|
||||
local FitText = require(Plugin.Components.FitText)
|
||||
local FormButton = require(Plugin.Components.FormButton)
|
||||
local FormTextInput = require(Plugin.Components.FormTextInput)
|
||||
local PluginSettings = require(Plugin.Components.PluginSettings)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local ConnectPanel = Roact.Component:extend("ConnectPanel")
|
||||
|
||||
function ConnectPanel:init()
|
||||
self:setState({
|
||||
address = "",
|
||||
port = "",
|
||||
})
|
||||
end
|
||||
|
||||
function ConnectPanel:render()
|
||||
local startSession = self.props.startSession
|
||||
local openSettings = self.props.openSettings
|
||||
|
||||
return Theme.with(function(theme)
|
||||
return PluginSettings.with(function(settings)
|
||||
return e(Panel, nil, {
|
||||
Layout = e("UIListLayout", {
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Center,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||
}),
|
||||
|
||||
Inputs = e(FitList, {
|
||||
containerProps = {
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 1,
|
||||
},
|
||||
layoutProps = {
|
||||
FillDirection = Enum.FillDirection.Horizontal,
|
||||
Padding = UDim.new(0, 8),
|
||||
},
|
||||
paddingProps = {
|
||||
PaddingTop = UDim.new(0, 20),
|
||||
PaddingBottom = UDim.new(0, 10),
|
||||
PaddingLeft = UDim.new(0, 24),
|
||||
PaddingRight = UDim.new(0, 24),
|
||||
},
|
||||
}, {
|
||||
Address = e(FitList, {
|
||||
containerProps = {
|
||||
LayoutOrder = 1,
|
||||
BackgroundTransparency = 1,
|
||||
},
|
||||
layoutProps = {
|
||||
Padding = UDim.new(0, 4),
|
||||
},
|
||||
}, {
|
||||
Label = e(FitText, {
|
||||
Kind = "TextLabel",
|
||||
LayoutOrder = 1,
|
||||
BackgroundTransparency = 1,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
Font = theme.TitleFont,
|
||||
TextSize = 20,
|
||||
Text = "Address",
|
||||
TextColor3 = theme.Text1,
|
||||
}),
|
||||
|
||||
Input = e(FormTextInput, {
|
||||
layoutOrder = 2,
|
||||
width = UDim.new(0, 220),
|
||||
value = self.state.address,
|
||||
placeholderValue = Config.defaultHost,
|
||||
onValueChange = function(newValue)
|
||||
self:setState({
|
||||
address = newValue,
|
||||
})
|
||||
end,
|
||||
}),
|
||||
}),
|
||||
|
||||
Port = e(FitList, {
|
||||
containerProps = {
|
||||
LayoutOrder = 2,
|
||||
BackgroundTransparency = 1,
|
||||
},
|
||||
layoutProps = {
|
||||
Padding = UDim.new(0, 4),
|
||||
},
|
||||
}, {
|
||||
Label = e(FitText, {
|
||||
Kind = "TextLabel",
|
||||
LayoutOrder = 1,
|
||||
BackgroundTransparency = 1,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
Font = theme.TitleFont,
|
||||
TextSize = 20,
|
||||
Text = "Port",
|
||||
TextColor3 = theme.Text1,
|
||||
}),
|
||||
|
||||
Input = e(FormTextInput, {
|
||||
layoutOrder = 2,
|
||||
width = UDim.new(0, 80),
|
||||
value = self.state.port,
|
||||
placeholderValue = Config.defaultPort,
|
||||
onValueChange = function(newValue)
|
||||
self:setState({
|
||||
port = newValue,
|
||||
})
|
||||
end,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
|
||||
Buttons = e(FitList, {
|
||||
fitAxes = "Y",
|
||||
containerProps = {
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 2,
|
||||
Size = UDim2.new(1, 0, 0, 0),
|
||||
},
|
||||
layoutProps = {
|
||||
FillDirection = Enum.FillDirection.Horizontal,
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Right,
|
||||
Padding = UDim.new(0, 8),
|
||||
},
|
||||
paddingProps = {
|
||||
PaddingTop = UDim.new(0, 0),
|
||||
PaddingBottom = UDim.new(0, 20),
|
||||
PaddingLeft = UDim.new(0, 24),
|
||||
PaddingRight = UDim.new(0, 24),
|
||||
},
|
||||
}, {
|
||||
e(FormButton, {
|
||||
layoutOrder = 1,
|
||||
text = "Settings",
|
||||
secondary = true,
|
||||
onClick = function()
|
||||
if openSettings ~= nil then
|
||||
openSettings()
|
||||
end
|
||||
end,
|
||||
}),
|
||||
|
||||
e(FormButton, {
|
||||
layoutOrder = 2,
|
||||
text = "Connect",
|
||||
onClick = function()
|
||||
if startSession ~= nil then
|
||||
local address = self.state.address
|
||||
if address:len() == 0 then
|
||||
address = Config.defaultHost
|
||||
end
|
||||
|
||||
local port = self.state.port
|
||||
if port:len() == 0 then
|
||||
port = Config.defaultPort
|
||||
end
|
||||
|
||||
local sessionOptions = {
|
||||
openScriptsExternally = settings:get("openScriptsExternally"),
|
||||
twoWaySync = settings:get("twoWaySync"),
|
||||
}
|
||||
|
||||
startSession(address, port, sessionOptions)
|
||||
end
|
||||
end,
|
||||
}),
|
||||
}),
|
||||
})
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
return ConnectPanel
|
||||
@@ -1,35 +0,0 @@
|
||||
local Roact = require(script:FindFirstAncestor("Rojo").Roact)
|
||||
|
||||
local Plugin = script:FindFirstAncestor("Plugin")
|
||||
|
||||
local Theme = require(Plugin.Components.Theme)
|
||||
local Panel = require(Plugin.Components.Panel)
|
||||
local FitText = require(Plugin.Components.FitText)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local ConnectingPanel = Roact.Component:extend("ConnectingPanel")
|
||||
|
||||
function ConnectingPanel:render()
|
||||
return Theme.with(function(theme)
|
||||
return e(Panel, nil, {
|
||||
Layout = Roact.createElement("UIListLayout", {
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Center,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
Padding = UDim.new(0, 8),
|
||||
}),
|
||||
|
||||
Text = e(FitText, {
|
||||
Padding = Vector2.new(12, 6),
|
||||
Font = theme.ButtonFont,
|
||||
TextSize = 18,
|
||||
Text = "Connecting...",
|
||||
TextColor3 = theme.Text1,
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
return ConnectingPanel
|
||||
@@ -1,47 +0,0 @@
|
||||
local Roact = require(script:FindFirstAncestor("Rojo").Roact)
|
||||
|
||||
local Plugin = script:FindFirstAncestor("Plugin")
|
||||
|
||||
local Theme = require(Plugin.Components.Theme)
|
||||
local Panel = require(Plugin.Components.Panel)
|
||||
local FitText = require(Plugin.Components.FitText)
|
||||
local FormButton = require(Plugin.Components.FormButton)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local ConnectionActivePanel = Roact.Component:extend("ConnectionActivePanel")
|
||||
|
||||
function ConnectionActivePanel:render()
|
||||
local stopSession = self.props.stopSession
|
||||
|
||||
return Theme.with(function(theme)
|
||||
return e(Panel, nil, {
|
||||
Layout = Roact.createElement("UIListLayout", {
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Center,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
Padding = UDim.new(0, 8),
|
||||
}),
|
||||
|
||||
Text = e(FitText, {
|
||||
Padding = Vector2.new(12, 6),
|
||||
Font = theme.ButtonFont,
|
||||
TextSize = 18,
|
||||
Text = "Connected to Live-Sync Server",
|
||||
TextColor3 = theme.Text1,
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
|
||||
DisconnectButton = e(FormButton, {
|
||||
layoutOrder = 2,
|
||||
text = "Disconnect",
|
||||
secondary = true,
|
||||
onClick = function()
|
||||
stopSession()
|
||||
end,
|
||||
}),
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
return ConnectionActivePanel
|
||||
@@ -1,70 +0,0 @@
|
||||
local Roact = require(script:FindFirstAncestor("Rojo").Roact)
|
||||
|
||||
local Plugin = script:FindFirstAncestor("Plugin")
|
||||
|
||||
local Theme = require(Plugin.Components.Theme)
|
||||
local Panel = require(Plugin.Components.Panel)
|
||||
local FitText = require(Plugin.Components.FitText)
|
||||
local FitScrollingFrame = require(Plugin.Components.FitScrollingFrame)
|
||||
local FormButton = require(Plugin.Components.FormButton)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local BUTTON_HEIGHT = 60
|
||||
local HOR_PADDING = 8
|
||||
|
||||
local ErrorPanel = Roact.Component:extend("ErrorPanel")
|
||||
|
||||
function ErrorPanel:render()
|
||||
local errorMessage = self.props.errorMessage
|
||||
local onDismiss = self.props.onDismiss
|
||||
|
||||
return Theme.with(function(theme)
|
||||
return e(Panel, nil, {
|
||||
Layout = Roact.createElement("UIListLayout", {
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Center,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
Padding = UDim.new(0, 8),
|
||||
}),
|
||||
|
||||
ErrorContainer = e(FitScrollingFrame, {
|
||||
containerProps = {
|
||||
BackgroundTransparency = 1,
|
||||
BorderSizePixel = 0,
|
||||
Size = UDim2.new(1, -HOR_PADDING * 2, 1, -BUTTON_HEIGHT),
|
||||
Position = UDim2.new(0, HOR_PADDING, 0, 0),
|
||||
ScrollBarImageColor3 = theme.Text1,
|
||||
VerticalScrollBarInset = Enum.ScrollBarInset.ScrollBar,
|
||||
ScrollingDirection = Enum.ScrollingDirection.Y,
|
||||
},
|
||||
}, {
|
||||
Text = e(FitText, {
|
||||
Size = UDim2.new(1, 0, 0, 0),
|
||||
|
||||
LayoutOrder = 1,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextYAlignment = Enum.TextYAlignment.Top,
|
||||
FitAxis = "Y",
|
||||
Font = theme.ButtonFont,
|
||||
TextSize = 18,
|
||||
Text = errorMessage,
|
||||
TextWrap = true,
|
||||
TextColor3 = theme.Text1,
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
}),
|
||||
|
||||
DismissButton = e(FormButton, {
|
||||
layoutOrder = 2,
|
||||
text = "Dismiss",
|
||||
secondary = true,
|
||||
onClick = function()
|
||||
onDismiss()
|
||||
end,
|
||||
}),
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
return ErrorPanel
|
||||
@@ -1,63 +0,0 @@
|
||||
local Roact = require(script:FindFirstAncestor("Rojo").Roact)
|
||||
|
||||
local Dictionary = require(script.Parent.Parent.Dictionary)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local FitList = Roact.Component:extend("FitList")
|
||||
|
||||
function FitList:init()
|
||||
self.sizeBinding, self.setSize = Roact.createBinding(UDim2.new())
|
||||
end
|
||||
|
||||
function FitList:render()
|
||||
local containerKind = self.props.containerKind or "Frame"
|
||||
local fitAxes = self.props.fitAxes or "XY"
|
||||
local containerProps = self.props.containerProps
|
||||
local layoutProps = self.props.layoutProps
|
||||
local paddingProps = self.props.paddingProps
|
||||
|
||||
local padding
|
||||
if paddingProps ~= nil then
|
||||
padding = e("UIPadding", paddingProps)
|
||||
end
|
||||
|
||||
local children = Dictionary.merge(self.props[Roact.Children], {
|
||||
["$Layout"] = e("UIListLayout", Dictionary.merge({
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
[Roact.Change.AbsoluteContentSize] = function(instance)
|
||||
local contentSize = instance.AbsoluteContentSize
|
||||
|
||||
if paddingProps ~= nil then
|
||||
contentSize = contentSize + Vector2.new(
|
||||
paddingProps.PaddingLeft.Offset + paddingProps.PaddingRight.Offset,
|
||||
paddingProps.PaddingTop.Offset + paddingProps.PaddingBottom.Offset)
|
||||
end
|
||||
|
||||
local combinedSize
|
||||
|
||||
if fitAxes == "X" then
|
||||
combinedSize = UDim2.new(0, contentSize.X, containerProps.Size.Y.Scale, containerProps.Size.Y.Offset)
|
||||
elseif fitAxes == "Y" then
|
||||
combinedSize = UDim2.new(containerProps.Size.X.Scale, containerProps.Size.X.Offset, 0, contentSize.Y)
|
||||
elseif fitAxes == "XY" then
|
||||
combinedSize = UDim2.new(0, contentSize.X, 0, contentSize.Y)
|
||||
else
|
||||
error("Invalid fitAxes value")
|
||||
end
|
||||
|
||||
self.setSize(combinedSize)
|
||||
end,
|
||||
}, layoutProps)),
|
||||
|
||||
["$Padding"] = padding,
|
||||
})
|
||||
|
||||
local fullContainerProps = Dictionary.merge(containerProps, {
|
||||
Size = self.sizeBinding,
|
||||
})
|
||||
|
||||
return e(containerKind, fullContainerProps, children)
|
||||
end
|
||||
|
||||
return FitList
|
||||
@@ -1,33 +0,0 @@
|
||||
local Roact = require(script:FindFirstAncestor("Rojo").Roact)
|
||||
|
||||
local Dictionary = require(script.Parent.Parent.Dictionary)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local FitScrollingFrame = Roact.Component:extend("FitScrollingFrame")
|
||||
|
||||
function FitScrollingFrame:init()
|
||||
self.sizeBinding, self.setSize = Roact.createBinding(UDim2.new())
|
||||
end
|
||||
|
||||
function FitScrollingFrame:render()
|
||||
local containerProps = self.props.containerProps
|
||||
local layoutProps = self.props.layoutProps
|
||||
|
||||
local children = Dictionary.merge(self.props[Roact.Children], {
|
||||
["$Layout"] = e("UIListLayout", Dictionary.merge({
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
[Roact.Change.AbsoluteContentSize] = function(instance)
|
||||
self.setSize(UDim2.new(0, 0, 0, instance.AbsoluteContentSize.Y))
|
||||
end,
|
||||
}, layoutProps)),
|
||||
})
|
||||
|
||||
local fullContainerProps = Dictionary.merge(containerProps, {
|
||||
CanvasSize = self.sizeBinding,
|
||||
})
|
||||
|
||||
return e("ScrollingFrame", fullContainerProps, children)
|
||||
end
|
||||
|
||||
return FitScrollingFrame
|
||||
@@ -1,88 +0,0 @@
|
||||
local TextService = game:GetService("TextService")
|
||||
|
||||
local Roact = require(script:FindFirstAncestor("Rojo").Roact)
|
||||
|
||||
local Dictionary = require(script.Parent.Parent.Dictionary)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local FitText = Roact.Component:extend("FitText")
|
||||
|
||||
function FitText:init()
|
||||
self.ref = Roact.createRef()
|
||||
self.sizeBinding, self.setSize = Roact.createBinding(UDim2.new())
|
||||
end
|
||||
|
||||
function FitText:render()
|
||||
local kind = self.props.Kind or "TextLabel"
|
||||
|
||||
local containerProps = Dictionary.merge(self.props, {
|
||||
FitAxis = Dictionary.None,
|
||||
Kind = Dictionary.None,
|
||||
Padding = Dictionary.None,
|
||||
MinSize = Dictionary.None,
|
||||
Size = self.sizeBinding,
|
||||
[Roact.Ref] = self.ref,
|
||||
[Roact.Change.AbsoluteSize] = function()
|
||||
self:updateTextMeasurements()
|
||||
end
|
||||
})
|
||||
|
||||
return e(kind, containerProps)
|
||||
end
|
||||
|
||||
function FitText:didMount()
|
||||
self:updateTextMeasurements()
|
||||
end
|
||||
|
||||
function FitText:didUpdate()
|
||||
self:updateTextMeasurements()
|
||||
end
|
||||
|
||||
function FitText:updateTextMeasurements()
|
||||
local minSize = self.props.MinSize or Vector2.new(0, 0)
|
||||
local padding = self.props.Padding or Vector2.new(0, 0)
|
||||
local fitAxis = self.props.FitAxis or "XY"
|
||||
local baseSize = self.props.Size
|
||||
|
||||
local text = self.props.Text or ""
|
||||
local font = self.props.Font or Enum.Font.Legacy
|
||||
local textSize = self.props.TextSize or 12
|
||||
|
||||
local containerSize = self.ref.current.AbsoluteSize
|
||||
|
||||
local textBounds
|
||||
|
||||
if fitAxis == "XY" then
|
||||
textBounds = Vector2.new(9e6, 9e6)
|
||||
elseif fitAxis == "X" then
|
||||
textBounds = Vector2.new(9e6, containerSize.Y - padding.Y * 2)
|
||||
elseif fitAxis == "Y" then
|
||||
textBounds = Vector2.new(containerSize.X - padding.X * 2, 9e6)
|
||||
end
|
||||
|
||||
local measuredText = TextService:GetTextSize(text, textSize, font, textBounds)
|
||||
|
||||
local computedX = math.max(minSize.X, padding.X * 2 + measuredText.X)
|
||||
local computedY = math.max(minSize.Y, padding.Y * 2 + measuredText.Y)
|
||||
|
||||
local totalSize
|
||||
|
||||
if fitAxis == "XY" then
|
||||
totalSize = UDim2.new(
|
||||
0, computedX,
|
||||
0, computedY)
|
||||
elseif fitAxis == "X" then
|
||||
totalSize = UDim2.new(
|
||||
0, computedX,
|
||||
baseSize.Y.Scale, baseSize.Y.Offset)
|
||||
elseif fitAxis == "Y" then
|
||||
totalSize = UDim2.new(
|
||||
baseSize.X.Scale, baseSize.X.Offset,
|
||||
0, computedY)
|
||||
end
|
||||
|
||||
self.setSize(totalSize)
|
||||
end
|
||||
|
||||
return FitText
|
||||
@@ -1,64 +0,0 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
|
||||
local Assets = require(Plugin.Assets)
|
||||
local Theme = require(Plugin.Components.Theme)
|
||||
local FitList = require(Plugin.Components.FitList)
|
||||
local FitText = require(Plugin.Components.FitText)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local RoundBox = Assets.Slices.RoundBox
|
||||
|
||||
local function FormButton(props)
|
||||
local text = props.text
|
||||
local layoutOrder = props.layoutOrder
|
||||
local onClick = props.onClick
|
||||
|
||||
local textColor
|
||||
local backgroundColor
|
||||
|
||||
return Theme.with(function(theme)
|
||||
if props.secondary then
|
||||
textColor = theme.Brand1
|
||||
backgroundColor = theme.Background2
|
||||
else
|
||||
textColor = theme.TextOnAccent
|
||||
backgroundColor = theme.Brand1
|
||||
end
|
||||
|
||||
return e(FitList, {
|
||||
containerKind = "ImageButton",
|
||||
containerProps = {
|
||||
LayoutOrder = layoutOrder,
|
||||
BackgroundTransparency = 1,
|
||||
Image = RoundBox.asset,
|
||||
ImageRectOffset = RoundBox.offset,
|
||||
ImageRectSize = RoundBox.size,
|
||||
SliceCenter = RoundBox.center,
|
||||
ScaleType = Enum.ScaleType.Slice,
|
||||
ImageColor3 = backgroundColor,
|
||||
|
||||
[Roact.Event.Activated] = function()
|
||||
if onClick ~= nil then
|
||||
onClick()
|
||||
end
|
||||
end,
|
||||
},
|
||||
}, {
|
||||
Text = e(FitText, {
|
||||
Kind = "TextLabel",
|
||||
Text = text,
|
||||
TextSize = 18,
|
||||
TextColor3 = textColor,
|
||||
Font = theme.ButtonFont,
|
||||
Padding = Vector2.new(16, 8),
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
return FormButton
|
||||
@@ -1,82 +0,0 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
|
||||
local Assets = require(Plugin.Assets)
|
||||
local Theme = require(Plugin.Components.Theme)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local RoundBox = Assets.Slices.RoundBox
|
||||
|
||||
local TEXT_SIZE = 22
|
||||
local PADDING = 8
|
||||
|
||||
local FormTextInput = Roact.Component:extend("FormTextInput")
|
||||
|
||||
function FormTextInput:init()
|
||||
self:setState({
|
||||
focused = false,
|
||||
})
|
||||
end
|
||||
|
||||
function FormTextInput:render()
|
||||
local value = self.props.value
|
||||
local placeholderValue = self.props.placeholderValue
|
||||
local onValueChange = self.props.onValueChange
|
||||
local layoutOrder = self.props.layoutOrder
|
||||
local width = self.props.width
|
||||
|
||||
local shownPlaceholder
|
||||
if self.state.focused then
|
||||
shownPlaceholder = ""
|
||||
else
|
||||
shownPlaceholder = placeholderValue
|
||||
end
|
||||
|
||||
return Theme.with(function(theme)
|
||||
return e("ImageLabel", {
|
||||
LayoutOrder = layoutOrder,
|
||||
Image = RoundBox.asset,
|
||||
ImageRectOffset = RoundBox.offset,
|
||||
ImageRectSize = RoundBox.size,
|
||||
ScaleType = Enum.ScaleType.Slice,
|
||||
SliceCenter = RoundBox.center,
|
||||
ImageColor3 = theme.Background2,
|
||||
Size = UDim2.new(width.Scale, width.Offset, 0, TEXT_SIZE + PADDING * 2),
|
||||
BackgroundTransparency = 1,
|
||||
}, {
|
||||
InputInner = e("TextBox", {
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.new(1, -PADDING * 2, 1, -PADDING * 2),
|
||||
Position = UDim2.new(0.5, 0, 0.5, 0),
|
||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
||||
Font = theme.InputFont,
|
||||
ClearTextOnFocus = false,
|
||||
TextXAlignment = Enum.TextXAlignment.Center,
|
||||
TextSize = TEXT_SIZE,
|
||||
Text = value,
|
||||
PlaceholderText = shownPlaceholder,
|
||||
PlaceholderColor3 = theme.Text2,
|
||||
TextColor3 = theme.Text1,
|
||||
|
||||
[Roact.Change.Text] = function(rbx)
|
||||
onValueChange(rbx.Text)
|
||||
end,
|
||||
[Roact.Event.Focused] = function()
|
||||
self:setState({
|
||||
focused = true,
|
||||
})
|
||||
end,
|
||||
[Roact.Event.FocusLost] = function()
|
||||
self:setState({
|
||||
focused = false,
|
||||
})
|
||||
end,
|
||||
}),
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
return FormTextInput
|
||||
@@ -1,38 +0,0 @@
|
||||
local Roact = require(script:FindFirstAncestor("Rojo").Roact)
|
||||
|
||||
local Plugin = script:FindFirstAncestor("Plugin")
|
||||
|
||||
local RojoFooter = require(Plugin.Components.RojoFooter)
|
||||
local Theme = require(Plugin.Components.Theme)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local Panel = Roact.Component:extend("Panel")
|
||||
|
||||
function Panel:init()
|
||||
self.footerSize, self.setFooterSize = Roact.createBinding(Vector2.new())
|
||||
end
|
||||
|
||||
function Panel:render()
|
||||
return Theme.with(function(theme)
|
||||
return e("Frame", {
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
BackgroundColor3 = theme.Background1,
|
||||
BorderSizePixel = 1,
|
||||
}, {
|
||||
Layout = Roact.createElement("UIListLayout", {
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Center,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
}),
|
||||
|
||||
Body = e("Frame", {
|
||||
Size = UDim2.new(0, 360, 1, -32),
|
||||
BackgroundTransparency = 1,
|
||||
}, self.props[Roact.Children]),
|
||||
|
||||
Footer = e(RojoFooter),
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
return Panel
|
||||
@@ -1,70 +0,0 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
|
||||
local Config = require(Plugin.Config)
|
||||
local Version = require(Plugin.Version)
|
||||
local Assets = require(Plugin.Assets)
|
||||
local Theme = require(Plugin.Components.Theme)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local RojoFooter = Roact.Component:extend("RojoFooter")
|
||||
|
||||
function RojoFooter:init()
|
||||
self.footerSize, self.setFooterSize = Roact.createBinding(Vector2.new())
|
||||
self.footerVersionSize, self.setFooterVersionSize = Roact.createBinding(Vector2.new())
|
||||
end
|
||||
|
||||
function RojoFooter:render()
|
||||
return Theme.with(function(theme)
|
||||
return e("Frame", {
|
||||
LayoutOrder = 3,
|
||||
Size = UDim2.new(1, 0, 0, 32),
|
||||
BackgroundColor3 = theme.Background2,
|
||||
BorderSizePixel = 0,
|
||||
ZIndex = 2,
|
||||
}, {
|
||||
Padding = e("UIPadding", {
|
||||
PaddingTop = UDim.new(0, 4),
|
||||
PaddingBottom = UDim.new(0, 4),
|
||||
PaddingLeft = UDim.new(0, 8),
|
||||
PaddingRight = UDim.new(0, 8),
|
||||
}),
|
||||
|
||||
LogoContainer = e("Frame", {
|
||||
BackgroundTransparency = 1,
|
||||
|
||||
Size = UDim2.new(0, 0, 0, 32),
|
||||
}, {
|
||||
Logo = e("ImageLabel", {
|
||||
Image = Assets.Images.Logo,
|
||||
Size = UDim2.new(0, 80, 0, 40),
|
||||
ScaleType = Enum.ScaleType.Fit,
|
||||
BackgroundTransparency = 1,
|
||||
Position = UDim2.new(0, 0, 1, -10),
|
||||
AnchorPoint = Vector2.new(0, 1),
|
||||
}),
|
||||
}),
|
||||
|
||||
Version = e("TextLabel", {
|
||||
Position = UDim2.new(1, 0, 0, 0),
|
||||
Size = UDim2.new(0, 0, 1, 0),
|
||||
AnchorPoint = Vector2.new(1, 0),
|
||||
Font = theme.TitleFont,
|
||||
TextSize = 18,
|
||||
Text = Version.display(Config.version),
|
||||
TextXAlignment = Enum.TextXAlignment.Right,
|
||||
TextColor3 = theme.Text2,
|
||||
BackgroundTransparency = 1,
|
||||
|
||||
[Roact.Change.AbsoluteSize] = function(rbx)
|
||||
self.setFooterVersionSize(rbx.AbsoluteSize)
|
||||
end,
|
||||
}),
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
return RojoFooter
|
||||
@@ -1,119 +0,0 @@
|
||||
local Roact = require(script:FindFirstAncestor("Rojo").Roact)
|
||||
|
||||
local Plugin = script:FindFirstAncestor("Plugin")
|
||||
|
||||
local Checkbox = require(Plugin.Components.Checkbox)
|
||||
local FitList = require(Plugin.Components.FitList)
|
||||
local FitText = require(Plugin.Components.FitText)
|
||||
local FormButton = require(Plugin.Components.FormButton)
|
||||
local Panel = require(Plugin.Components.Panel)
|
||||
local PluginSettings = require(Plugin.Components.PluginSettings)
|
||||
local Theme = require(Plugin.Components.Theme)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local SettingsPanel = Roact.Component:extend("SettingsPanel")
|
||||
|
||||
function SettingsPanel:render()
|
||||
local back = self.props.back
|
||||
|
||||
return Theme.with(function(theme)
|
||||
return PluginSettings.with(function(settings)
|
||||
return e(Panel, nil, {
|
||||
Layout = Roact.createElement("UIListLayout", {
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Center,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
Padding = UDim.new(0, 16),
|
||||
}),
|
||||
|
||||
OpenScriptsExternally = e(FitList, {
|
||||
containerProps = {
|
||||
LayoutOrder = 1,
|
||||
BackgroundTransparency = 1,
|
||||
},
|
||||
layoutProps = {
|
||||
Padding = UDim.new(0, 4),
|
||||
FillDirection = Enum.FillDirection.Horizontal,
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Left,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||
},
|
||||
}, {
|
||||
Label = e(FitText, {
|
||||
Kind = "TextLabel",
|
||||
LayoutOrder = 1,
|
||||
BackgroundTransparency = 1,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
Font = theme.MainFont,
|
||||
TextSize = 16,
|
||||
Text = "Open Scripts Externally",
|
||||
TextColor3 = theme.Text1,
|
||||
}),
|
||||
|
||||
Padding = e("Frame", {
|
||||
Size = UDim2.new(0, 8, 0, 0),
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 2,
|
||||
}),
|
||||
|
||||
Input = e(Checkbox, {
|
||||
layoutOrder = 3,
|
||||
checked = settings:get("openScriptsExternally"),
|
||||
onChange = function(newValue)
|
||||
settings:set("openScriptsExternally", not settings:get("openScriptsExternally"))
|
||||
end,
|
||||
}),
|
||||
}),
|
||||
|
||||
TwoWaySync = e(FitList, {
|
||||
containerProps = {
|
||||
LayoutOrder = 2,
|
||||
BackgroundTransparency = 1,
|
||||
},
|
||||
layoutProps = {
|
||||
Padding = UDim.new(0, 4),
|
||||
FillDirection = Enum.FillDirection.Horizontal,
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Left,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||
},
|
||||
}, {
|
||||
Label = e(FitText, {
|
||||
Kind = "TextLabel",
|
||||
LayoutOrder = 1,
|
||||
BackgroundTransparency = 1,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
Font = theme.MainFont,
|
||||
TextSize = 16,
|
||||
Text = "Two-Way Sync (Experimental!)",
|
||||
TextColor3 = theme.Text1,
|
||||
}),
|
||||
|
||||
Padding = e("Frame", {
|
||||
Size = UDim2.new(0, 8, 0, 0),
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 2,
|
||||
}),
|
||||
|
||||
Input = e(Checkbox, {
|
||||
layoutOrder = 3,
|
||||
checked = settings:get("twoWaySync"),
|
||||
onChange = function(newValue)
|
||||
settings:set("twoWaySync", not settings:get("twoWaySync"))
|
||||
end,
|
||||
}),
|
||||
}),
|
||||
|
||||
BackButton = e(FormButton, {
|
||||
layoutOrder = 4,
|
||||
text = "Okay",
|
||||
secondary = true,
|
||||
onClick = function()
|
||||
back()
|
||||
end,
|
||||
}),
|
||||
})
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
return SettingsPanel
|
||||
@@ -1,113 +0,0 @@
|
||||
--[[
|
||||
Theming system taking advantage of Roact's new context API.
|
||||
|
||||
Doesn't use colors provided by Studio and instead just branches on theme
|
||||
name. This isn't exactly best practice.
|
||||
]]
|
||||
|
||||
-- Studio does not exist outside Roblox Studio, so we'll lazily initialize it
|
||||
-- when possible.
|
||||
local _Studio
|
||||
local function getStudio()
|
||||
if _Studio == nil then
|
||||
_Studio = settings():GetService("Studio")
|
||||
end
|
||||
|
||||
return _Studio
|
||||
end
|
||||
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
local Log = require(Rojo.Log)
|
||||
|
||||
local strict = require(script.Parent.Parent.strict)
|
||||
|
||||
local lightTheme = strict("Theme", {
|
||||
ButtonFont = Enum.Font.GothamSemibold,
|
||||
InputFont = Enum.Font.Code,
|
||||
TitleFont = Enum.Font.GothamBold,
|
||||
MainFont = Enum.Font.Gotham,
|
||||
|
||||
Brand1 = Color3.fromRGB(225, 56, 53),
|
||||
|
||||
Text1 = Color3.fromRGB(64, 64, 64),
|
||||
Text2 = Color3.fromRGB(160, 160, 160),
|
||||
TextOnAccent = Color3.fromRGB(235, 235, 235),
|
||||
|
||||
Background1 = Color3.fromRGB(255, 255, 255),
|
||||
Background2 = Color3.fromRGB(235, 235, 235),
|
||||
})
|
||||
|
||||
local darkTheme = strict("Theme", {
|
||||
ButtonFont = Enum.Font.GothamSemibold,
|
||||
InputFont = Enum.Font.Code,
|
||||
TitleFont = Enum.Font.GothamBold,
|
||||
MainFont = Enum.Font.Gotham,
|
||||
|
||||
Brand1 = Color3.fromRGB(225, 56, 53),
|
||||
|
||||
Text1 = Color3.fromRGB(235, 235, 235),
|
||||
Text2 = Color3.fromRGB(200, 200, 200),
|
||||
TextOnAccent = Color3.fromRGB(235, 235, 235),
|
||||
|
||||
Background1 = Color3.fromRGB(48, 48, 48),
|
||||
Background2 = Color3.fromRGB(64, 64, 64),
|
||||
})
|
||||
|
||||
local Context = Roact.createContext(lightTheme)
|
||||
|
||||
local StudioProvider = Roact.Component:extend("StudioProvider")
|
||||
|
||||
-- Pull the current theme from Roblox Studio and update state with it.
|
||||
function StudioProvider:updateTheme()
|
||||
local studioTheme = getStudio().Theme
|
||||
|
||||
if studioTheme.Name == "Light" then
|
||||
self:setState({
|
||||
theme = lightTheme,
|
||||
})
|
||||
elseif studioTheme.Name == "Dark" then
|
||||
self:setState({
|
||||
theme = darkTheme,
|
||||
})
|
||||
else
|
||||
Log.warn("Unexpected theme '{}'' -- falling back to light theme!", studioTheme.Name)
|
||||
|
||||
self:setState({
|
||||
theme = lightTheme,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function StudioProvider:init()
|
||||
self:updateTheme()
|
||||
end
|
||||
|
||||
function StudioProvider:render()
|
||||
return Roact.createElement(Context.Provider, {
|
||||
value = self.state.theme,
|
||||
}, self.props[Roact.Children])
|
||||
end
|
||||
|
||||
function StudioProvider:didMount()
|
||||
self.connection = getStudio().ThemeChanged:Connect(function()
|
||||
self:updateTheme()
|
||||
end)
|
||||
end
|
||||
|
||||
function StudioProvider:willUnmount()
|
||||
self.connection:Disconnect()
|
||||
end
|
||||
|
||||
local function with(callback)
|
||||
return Roact.createElement(Context.Consumer, {
|
||||
render = callback,
|
||||
})
|
||||
end
|
||||
|
||||
return {
|
||||
StudioProvider = StudioProvider,
|
||||
Consumer = Context.Consumer,
|
||||
with = with,
|
||||
}
|
||||
@@ -110,7 +110,7 @@ function ServeSession:start()
|
||||
|
||||
self.__apiContext:connect()
|
||||
:andThen(function(serverInfo)
|
||||
self:__setStatus(Status.Connected)
|
||||
self:__setStatus(Status.Connected, serverInfo.projectName)
|
||||
|
||||
local rootInstanceId = serverInfo.rootInstanceId
|
||||
|
||||
|
||||
@@ -13,20 +13,11 @@ end)
|
||||
local Roact = require(script.Parent.Roact)
|
||||
|
||||
local Config = require(script.Config)
|
||||
local App = require(script.Components.App)
|
||||
local Theme = require(script.Components.Theme)
|
||||
local PluginSettings = require(script.Components.PluginSettings)
|
||||
local App = require(script.App)
|
||||
|
||||
local app = Roact.createElement(Theme.StudioProvider, nil, {
|
||||
Roact.createElement(PluginSettings.StudioProvider, {
|
||||
plugin = plugin,
|
||||
}, {
|
||||
RojoUI = Roact.createElement(App, {
|
||||
plugin = plugin,
|
||||
}),
|
||||
})
|
||||
local app = Roact.createElement(App, {
|
||||
plugin = plugin
|
||||
})
|
||||
|
||||
local tree = Roact.mount(app, nil, "Rojo UI")
|
||||
|
||||
plugin.Unloading:Connect(function()
|
||||
|
||||
@@ -4,20 +4,23 @@ local Log = require(script.Parent.Parent.Log)
|
||||
|
||||
local Assets = require(script.Parent.Assets)
|
||||
|
||||
local gatherAssetUrlsRecursive
|
||||
function gatherAssetUrlsRecursive(currentTable, currentUrls)
|
||||
currentUrls = currentUrls or {}
|
||||
|
||||
for _, value in pairs(currentTable) do
|
||||
if typeof(value) == "string" then
|
||||
table.insert(currentUrls, value)
|
||||
elseif typeof(value) == "table" then
|
||||
gatherAssetUrlsRecursive(value)
|
||||
end
|
||||
end
|
||||
|
||||
return currentUrls
|
||||
end
|
||||
|
||||
local function preloadAssets()
|
||||
local contentUrls = {}
|
||||
|
||||
for _, sprite in pairs(Assets.Sprites) do
|
||||
table.insert(contentUrls, sprite.asset)
|
||||
end
|
||||
|
||||
for _, slice in pairs(Assets.Slices) do
|
||||
table.insert(contentUrls, slice.asset)
|
||||
end
|
||||
|
||||
for _, url in pairs(Assets.Images) do
|
||||
table.insert(contentUrls, url)
|
||||
end
|
||||
local contentUrls = gatherAssetUrlsRecursive(Assets)
|
||||
|
||||
Log.trace("Preloading assets: {:?}", contentUrls)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user