* 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:
Reselim
2020-12-14 14:07:39 -05:00
committed by GitHub
parent a937fc38db
commit cae4c46669
49 changed files with 2249 additions and 1320 deletions

View 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

View 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

View 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

View 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

View 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

View 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),
}