Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User interface #2

Merged
merged 8 commits into from
Jan 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions bin/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ def main():
if args.project_path:
project_file = args.project_path / project_file

print(project_file)

command = ["rojo", "build", project_file, "-o", args.output]
if args.watch:
print("Watching for changes...")
Expand Down
44 changes: 44 additions & 0 deletions plugin/src/applyTheme.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
local Root = script:FindFirstAncestor("rbxtheme")

local getThemeColors = require(Root.getThemeColors)
local types = require(Root.types)

local function applyTheme(theme: types.ExtensionTheme, studio: Studio): string?
local colors = getThemeColors(theme)
local problems = {}

if colors and colors.found then
for name, color in colors.found do
-- Discard the alpha component of the hexcode
if #color - 1 > 6 then
local colorNoAlpha = color:sub(1, 7)

-- TODO: Blend colors with the background to get rid of the
-- alpha component. That way we don't need to warn the user
table.insert(problems, `{name} uses unsupported alpha value. Truncating {color} to {colorNoAlpha}`)

color = colorNoAlpha
end

local success, result = pcall(function()
studio[name] = Color3.fromHex(color)
end)

if not success then
table.insert(problems, `Failed to set {name}: {result}`)
end
end
end

if #problems > 0 then
local problemsStr = ""
for index, problem in problems do
problemsStr ..= `{index}. {problem}\n`
end
return `{theme.name} was applied, but not all colors were set:\n{problemsStr}`
end

return nil
end

return applyTheme
56 changes: 56 additions & 0 deletions plugin/src/components/App.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
local Root = script:FindFirstAncestor("rbxtheme")

local React = require(Root.Packages.React)
local Sift = require(Root.Packages.Sift)
local types = require(Root.types)
local Home = require(Root.Components.Home)
local ThemeDetails = require(Root.Components.ThemeDetails)

local useCallback = React.useCallback
local useState = React.useState

type PublishedExtension = types.PublishedExtension
type ExtensionTheme = types.ExtensionTheme

export type View = "Home" | "ThemeDetails"

export type Props = {
plugin: Plugin,
}

local function App(_props: Props)
local view, setView = useState("Home" :: View)
local viewParams, setViewParams = useState({})

local onBack = useCallback(function()
setViewParams({})
setView("Home")
end, {})

local onViewExtension = useCallback(function(extension: PublishedExtension, themes: { ExtensionTheme })
setViewParams({
extension = extension,
themes = themes,
})
setView("ThemeDetails")
end, {})

return React.createElement("Folder", nil, {
Home = if view == "Home"
then React.createElement(Home, {
onViewExtension = onViewExtension,
})
else nil,

ThemeDetails = if view == "ThemeDetails"
then React.createElement(
ThemeDetails,
Sift.Dictionary.join(viewParams, {
onBack = onBack,
})
)
else nil,
})
end

return App
123 changes: 123 additions & 0 deletions plugin/src/components/ExtensionsList.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
local Root = script:FindFirstAncestor("rbxtheme")

local React = require(Root.Packages.React)
local types = require(Root.types)
local getLayoutOrder = require(Root.Components.getLayoutOrder)

type PublishedExtension = types.PublishedExtension

export type Props = {
extensions: { PublishedExtension },
onView: (extension: PublishedExtension) -> (),
LayoutOrder: number?,
}

local ACTION_BUTTON_WIDTH = 120
local PADDING = UDim.new(0, 8)

local function ExtensionsList(props: Props)
local children = {
Layout = React.createElement("UIListLayout", {
Padding = PADDING,
SortOrder = Enum.SortOrder.LayoutOrder,
}),
}

for i, extension in props.extensions do
local isEven = i % 2 == 0
local latestVersion = if extension.versions then extension.versions[1] else nil

children[extension.extensionName] = React.createElement("Frame", {
LayoutOrder = i,
BackgroundTransparency = if isEven then 1 else 0.2,
BackgroundColor3 = Color3.fromRGB(100, 100, 100),
BorderSizePixel = 0,
AutomaticSize = Enum.AutomaticSize.Y,
Size = UDim2.fromScale(1, 0),
}, {
Layout = React.createElement("UIListLayout", {
SortOrder = Enum.SortOrder.LayoutOrder,
FillDirection = Enum.FillDirection.Horizontal,
VerticalAlignment = Enum.VerticalAlignment.Center,
}),

Padding = React.createElement("UIPadding", {
PaddingTop = PADDING,
PaddingRight = PADDING,
PaddingBottom = PADDING,
PaddingLeft = PADDING,
}),

Main = React.createElement("Frame", {
LayoutOrder = getLayoutOrder(),
Size = UDim2.fromScale(1, 0) - UDim2.fromOffset(ACTION_BUTTON_WIDTH, 0),
AutomaticSize = Enum.AutomaticSize.Y,
BackgroundTransparency = 1,
}, {
Layout = React.createElement("UIListLayout", {
SortOrder = Enum.SortOrder.LayoutOrder,
Padding = PADDING,
}),

Name = React.createElement("TextLabel", {
LayoutOrder = getLayoutOrder(),
AutomaticSize = Enum.AutomaticSize.XY,
BackgroundTransparency = 1,
Text = `{extension.displayName} v{latestVersion.version}`,
TextSize = 16,
Font = Enum.Font.GothamMedium,
TextXAlignment = Enum.TextXAlignment.Left,
TextYAlignment = Enum.TextYAlignment.Top,
TextColor3 = Color3.fromRGB(255, 255, 255),
TextTruncate = Enum.TextTruncate.AtEnd,
}),

Publisher = React.createElement("TextLabel", {
LayoutOrder = getLayoutOrder(),
Text = extension.publisher.publisherName,
TextSize = 14,
TextColor3 = Color3.fromRGB(200, 200, 200),
AutomaticSize = Enum.AutomaticSize.XY,
BackgroundTransparency = 1,
Font = Enum.Font.GothamMedium,
TextXAlignment = Enum.TextXAlignment.Left,
TextYAlignment = Enum.TextYAlignment.Top,
}),
}),

Action = React.createElement("TextButton", {
LayoutOrder = getLayoutOrder(),
Text = "View",
TextSize = 14,
TextColor3 = Color3.fromRGB(255, 255, 255),
BorderSizePixel = 0,
Font = Enum.Font.GothamMedium,
Size = UDim2.fromOffset(ACTION_BUTTON_WIDTH, 0),
AutomaticSize = Enum.AutomaticSize.Y,
[React.Event.Activated] = function()
props.onView(extension)
end,
}, {
Padding = React.createElement("UIPadding", {
PaddingTop = PADDING,
PaddingRight = PADDING,
PaddingBottom = PADDING,
PaddingLeft = PADDING,
}),

Corner = React.createElement("UICorner", {
CornerRadius = PADDING,
}),
}),
})
end

return React.createElement("Frame", {
LayoutOrder = props.LayoutOrder,
AutomaticSize = Enum.AutomaticSize.Y,
Size = UDim2.fromScale(1, 0),
BackgroundTransparency = 1,
}, children)
end

return ExtensionsList
141 changes: 141 additions & 0 deletions plugin/src/components/Home.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
local Root = script:FindFirstAncestor("rbxtheme")

local React = require(Root.Packages.React)
local fetchVisualStudioExtensions = require(Root.fetchVisualStudioExtensions)
local fetchExtensionThemes = require(Root.fetchExtensionThemes)
local types = require(Root.types)
local LoadingSpinner = require(Root.Components.LoadingSpinner)
local ExtensionsList = require(Root.Components.ExtensionsList)
local getLayoutOrder = require(Root.Components.getLayoutOrder)

type PublishedExtension = types.PublishedExtension
type ExtensionTheme = types.ExtensionTheme

local useCallback = React.useCallback
local useEffect = React.useEffect
local useState = React.useState

local PADDING = UDim.new(0, 8)

export type Props = {
onViewExtension: (extension: PublishedExtension, themes: { Theme }) -> (),
}

local function Home(props: Props)
local isLoading, setIsLoading = useState(true)
local extensions, setExtensions = useState({} :: { PublishedExtension })
local searchTerm, setSearchTerm = useState("")

local onView = useCallback(function(extension: PublishedExtension)
local latestVersion = extension.versions[1]

if latestVersion then
fetchExtensionThemes(extension, latestVersion.version)
:andThen(function(themes)
props.onViewExtension(extension, themes)
end)
:catch(function(err)
warn("ERR:", err)
end)
else
warn("No latest version found for extension {extension.displayName}")
end
end, {})

local onSearch = useCallback(function(rbx: TextBox, enterPressed: boolean)
if enterPressed then
setSearchTerm(rbx.Text)
end
end, {})

useEffect(function()
setIsLoading(true)
fetchVisualStudioExtensions({
-- page = page, -- TODO: Increment the page when scrolling to the bottom of the list
pageSize = 20,
searchTerm = if searchTerm ~= "" then searchTerm else "theme",
})
:andThen(function(newExtensions)
setExtensions(newExtensions)
end)
:finally(function()
setIsLoading(false)
end)
end, { searchTerm })

return React.createElement("ScrollingFrame", {
Size = UDim2.fromScale(1, 1),
BackgroundTransparency = 1,
CanvasSize = UDim2.fromScale(0, 0),
AutomaticCanvasSize = Enum.AutomaticSize.Y,
}, {
Layout = React.createElement("UIListLayout", {
SortOrder = Enum.SortOrder.LayoutOrder,
Padding = PADDING,
}),

Padding = React.createElement("UIPadding", {
PaddingTop = PADDING,
PaddingRight = PADDING,
PaddingBottom = PADDING,
PaddingLeft = PADDING,
}),

SearchForm = React.createElement("Frame", {
LayoutOrder = getLayoutOrder(),
Size = UDim2.fromScale(1, 0),
AutomaticSize = Enum.AutomaticSize.Y,
BackgroundTransparency = 1,
}, {
Input = React.createElement("TextBox", {
Text = searchTerm,
PlaceholderText = "Search themes...",
Size = UDim2.fromScale(1, 0),
TextColor3 = Color3.fromRGB(255, 255, 255),
PlaceholderColor3 = Color3.fromRGB(200, 200, 200),
TextSize = 16,
Font = Enum.Font.Gotham,
AutomaticSize = Enum.AutomaticSize.Y,
BorderSizePixel = 0,
BackgroundColor3 = Color3.fromRGB(100, 100, 100),
[React.Event.FocusLost] = onSearch,
}, {
Padding = React.createElement("UIPadding", {
PaddingTop = PADDING,
PaddingRight = PADDING,
PaddingBottom = PADDING,
PaddingLeft = PADDING,
}),
}),
}),

ExtensionsListWrapper = React.createElement(
"Frame",
{
Size = UDim2.fromScale(1, 0),
AutomaticSize = Enum.AutomaticSize.Y,
BackgroundTransparency = 1,
LayoutOrder = getLayoutOrder(),
},
if isLoading
then {
Layout = React.createElement("UIListLayout", {
HorizontalAlignment = Enum.HorizontalAlignment.Center,
VerticalAlignment = Enum.VerticalAlignment.Center,
}),

LoadingSpinner = React.createElement(LoadingSpinner),
}
else {
ExtensionList = if not isLoading
then React.createElement(ExtensionsList, {
extensions = extensions,
onView = onView,
})
else nil,
}
),
})
end

return Home
Loading
Loading