Merge branch 'master' into headless-api

This commit is contained in:
boatbomber 2025-06-20 19:56:14 -07:00 • committed by GitHub
commit 79a8a66c6b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 151 additions and 47 deletions

View file

@ -1,10 +1,12 @@
# Rojo Changelog # Rojo Changelog
## Unreleased Changes ## Unreleased
* Added headless API for Studio companion plugins. ([#639]) * Added headless API for Studio companion plugins. ([#639])
* Added an update indicator to the version header when a new version of the plugin is available. ([#1069])
[#639]: https://github.com/rojo-rbx/rojo/pull/639 [#639]: https://github.com/rojo-rbx/rojo/pull/639
[#1069]: https://github.com/rojo-rbx/rojo/pull/1069
## 7.5.1 - April 25th, 2025 ## 7.5.1 - April 25th, 2025
* Fixed output spam related to `Instance.Capabilities` in the plugin * Fixed output spam related to `Instance.Capabilities` in the plugin

View file

@ -9,8 +9,70 @@ local Assets = require(Plugin.Assets)
local Config = require(Plugin.Config) local Config = require(Plugin.Config)
local Version = require(Plugin.Version) local Version = require(Plugin.Version)
local Tooltip = require(Plugin.App.Components.Tooltip)
local SlicedImage = require(script.Parent.SlicedImage)
local e = Roact.createElement local e = Roact.createElement
local function VersionIndicator(props)
local updateMessage = Version.getUpdateMessage()
return Theme.with(function(theme)
return e("Frame", {
LayoutOrder = props.layoutOrder,
Size = UDim2.new(0, 0, 0, 25),
BackgroundTransparency = 1,
AutomaticSize = Enum.AutomaticSize.X,
}, {
Border = if updateMessage
then e(SlicedImage, {
slice = Assets.Slices.RoundedBorder,
color = theme.Button.Bordered.Enabled.BorderColor,
transparency = props.transparency,
size = UDim2.fromScale(1, 1),
zIndex = 0,
}, {
Indicator = e("ImageLabel", {
Size = UDim2.new(0, 10, 0, 10),
ScaleType = Enum.ScaleType.Fit,
Image = Assets.Images.Circles[16],
ImageColor3 = theme.Header.LogoColor,
ImageTransparency = props.transparency,
BackgroundTransparency = 1,
Position = UDim2.new(1, 0, 0, 0),
AnchorPoint = Vector2.new(0.5, 0.5),
}),
})
else nil,
Tip = if updateMessage
then e(Tooltip.Trigger, {
text = updateMessage,
delay = 0.1,
})
else nil,
VersionText = e("TextLabel", {
Text = Version.display(Config.version),
FontFace = theme.Font.Thin,
TextSize = theme.TextSize.Body,
TextColor3 = theme.Header.VersionColor,
TextXAlignment = Enum.TextXAlignment.Left,
TextTransparency = props.transparency,
BackgroundTransparency = 1,
Size = UDim2.new(0, 0, 1, 0),
AutomaticSize = Enum.AutomaticSize.X,
}, {
Padding = e("UIPadding", {
PaddingLeft = UDim.new(0, 6),
PaddingRight = UDim.new(0, 6),
}),
}),
})
end)
end
local function Header(props) local function Header(props)
return Theme.with(function(theme) return Theme.with(function(theme)
return e("Frame", { return e("Frame", {
@ -29,18 +91,9 @@ local function Header(props)
BackgroundTransparency = 1, BackgroundTransparency = 1,
}), }),
Version = e("TextLabel", { VersionIndicator = e(VersionIndicator, {
Text = Version.display(Config.version), transparency = props.transparency,
FontFace = theme.Font.Thin, layoutOrder = 2,
TextSize = theme.TextSize.Body,
TextColor3 = theme.Header.VersionColor,
TextXAlignment = Enum.TextXAlignment.Left,
TextTransparency = props.transparency,
Size = UDim2.new(1, 0, 0, theme.TextSize.Body),
LayoutOrder = 2,
BackgroundTransparency = 1,
}), }),
Layout = e("UIListLayout", { Layout = e("UIListLayout", {

View file

@ -216,7 +216,7 @@ function Trigger:managePopup()
return return
end end
self.showDelayThread = task.delay(DELAY, function() self.showDelayThread = task.delay(self.props.delay or DELAY, function()
self.props.context.addTip(self.id, { self.props.context.addTip(self.id, {
Text = self.props.text, Text = self.props.text,
Position = self:getMousePos(), Position = self:getMousePos(),

View file

@ -8,6 +8,7 @@ local Theme = require(Plugin.App.Theme)
local getTextBoundsAsync = require(Plugin.App.getTextBoundsAsync) local getTextBoundsAsync = require(Plugin.App.getTextBoundsAsync)
local TextButton = require(Plugin.App.Components.TextButton) local TextButton = require(Plugin.App.Components.TextButton)
local Header = require(Plugin.App.Components.Header)
local BorderedContainer = require(Plugin.App.Components.BorderedContainer) local BorderedContainer = require(Plugin.App.Components.BorderedContainer)
local ScrollingFrame = require(Plugin.App.Components.ScrollingFrame) local ScrollingFrame = require(Plugin.App.Components.ScrollingFrame)
local Tooltip = require(Plugin.App.Components.Tooltip) local Tooltip = require(Plugin.App.Components.Tooltip)
@ -38,6 +39,7 @@ function Error:render()
return UDim2.new(1, 0, 0, math.min(outerSize.Y, maximumSize.Y)) return UDim2.new(1, 0, 0, math.min(outerSize.Y, maximumSize.Y))
end), end),
transparency = self.props.transparency, transparency = self.props.transparency,
layoutOrder = self.props.layoutOrder,
}, { }, {
ScrollingFrame = e(ScrollingFrame, { ScrollingFrame = e(ScrollingFrame, {
size = UDim2.new(1, 0, 1, 0), size = UDim2.new(1, 0, 1, 0),
@ -108,16 +110,21 @@ function ErrorPage:render()
self.setContainerSize(object.AbsoluteSize) self.setContainerSize(object.AbsoluteSize)
end, end,
}, { }, {
Error = e(Error, { Header = e(Header, {
errorMessage = self.state.errorMessage,
containerSize = self.containerSize,
transparency = self.props.transparency, transparency = self.props.transparency,
layoutOrder = 1, layoutOrder = 1,
}), }),
Error = e(Error, {
errorMessage = self.state.errorMessage,
containerSize = self.containerSize,
transparency = self.props.transparency,
layoutOrder = 2,
}),
Buttons = e("Frame", { Buttons = e("Frame", {
Size = UDim2.new(1, 0, 0, 35), Size = UDim2.new(1, 0, 0, 35),
LayoutOrder = 2, LayoutOrder = 3,
BackgroundTransparency = 1, BackgroundTransparency = 1,
}, { }, {
Close = e(TextButton, { Close = e(TextButton, {

View file

@ -341,27 +341,10 @@ function App:closeNotification(id: number)
end end
function App:checkForUpdates() function App:checkForUpdates()
if not Settings:get("checkForUpdates") then local updateMessage = Version.getUpdateMessage()
return
end
local isLocalInstall = string.find(debug.traceback(), "\n[^\n]-user_.-$") ~= nil if updateMessage then
local latestCompatibleVersion = Version.retrieveLatestCompatible({ self:addNotification(updateMessage, 500, {
version = Config.version,
includePrereleases = isLocalInstall and Settings:get("checkForPrereleases"),
})
if not latestCompatibleVersion then
return
end
self:addNotification(
string.format(
"A newer compatible version of Rojo, %s, was published %s! Go to the Rojo releases page to learn more.",
Version.display(latestCompatibleVersion.version),
timeUtil.elapsedToText(DateTime.now().UnixTimestamp - latestCompatibleVersion.publishedUnixTimestamp)
),
500,
{
Dismiss = { Dismiss = {
text = "Dismiss", text = "Dismiss",
style = "Bordered", style = "Bordered",
@ -370,8 +353,8 @@ function App:checkForUpdates()
notification:dismiss() notification:dismiss()
end, end,
}, },
} })
) end
end end
function App:getPriorSyncInfo(): { host: string?, port: string?, projectName: string?, timestamp: number? } function App:getPriorSyncInfo(): { host: string?, port: string?, projectName: string?, timestamp: number? }

View file

@ -1,6 +1,20 @@
local Packages = script.Parent.Parent.Packages local Rojo = script:FindFirstAncestor("Rojo")
local Plugin = Rojo.Plugin
local Packages = Rojo.Packages
local Http = require(Packages.Http) local Http = require(Packages.Http)
local Promise = require(Packages.Promise) local Promise = require(Packages.Promise)
local Log = require(Packages.Log)
local Config = require(Plugin.Config)
local Settings = require(Plugin.Settings)
local timeUtil = require(Plugin.timeUtil)
type LatestReleaseInfo = {
version: { number },
prerelease: boolean,
publishedUnixTimestamp: number,
}
local function compare(a, b) local function compare(a, b)
if a > b then if a > b then
@ -88,14 +102,26 @@ function Version.display(version)
return output return output
end end
--[[
The GitHub API rate limit for unauthenticated requests is rather low,
and we don't release often enough to warrant checking it more than once a day.
--]]
Version._cachedLatestCompatible = nil :: {
value: LatestReleaseInfo?,
timestamp: number,
}?
function Version.retrieveLatestCompatible(options: { function Version.retrieveLatestCompatible(options: {
version: { number }, version: { number },
includePrereleases: boolean?, includePrereleases: boolean?,
}): { }): LatestReleaseInfo?
version: { number }, if Version._cachedLatestCompatible and os.clock() - Version._cachedLatestCompatible.timestamp < 60 * 60 * 24 then
prerelease: boolean, Log.debug("Using cached latest compatible version")
publishedUnixTimestamp: number, return Version._cachedLatestCompatible.value
}? end
Log.debug("Retrieving latest compatible version from GitHub")
local success, releases = Http.get("https://api.github.com/repos/rojo-rbx/rojo/releases?per_page=10") local success, releases = Http.get("https://api.github.com/repos/rojo-rbx/rojo/releases?per_page=10")
:andThen(function(response) :andThen(function(response)
if response.code >= 400 then if response.code >= 400 then
@ -114,7 +140,7 @@ function Version.retrieveLatestCompatible(options: {
end end
-- Iterate through releases, looking for the latest compatible version -- Iterate through releases, looking for the latest compatible version
local latestCompatible = nil local latestCompatible: LatestReleaseInfo? = nil
for _, release in releases do for _, release in releases do
-- Skip prereleases if they are not requested -- Skip prereleases if they are not requested
if (not options.includePrereleases) and release.prerelease then if (not options.includePrereleases) and release.prerelease then
@ -142,10 +168,43 @@ function Version.retrieveLatestCompatible(options: {
-- Don't return anything if the latest found is not newer than the current version -- Don't return anything if the latest found is not newer than the current version
if latestCompatible == nil or Version.compare(latestCompatible.version, options.version) <= 0 then if latestCompatible == nil or Version.compare(latestCompatible.version, options.version) <= 0 then
-- Cache as nil so we don't try again for a day
Version._cachedLatestCompatible = {
value = nil,
timestamp = os.clock(),
}
return nil return nil
end end
-- Cache the latest compatible version
Version._cachedLatestCompatible = {
value = latestCompatible,
timestamp = os.clock(),
}
return latestCompatible return latestCompatible
end end
function Version.getUpdateMessage(): string?
if not Settings:get("checkForUpdates") then
return
end
local isLocalInstall = string.find(debug.traceback(), "\n[^\n]-user_.-$") ~= nil
local latestCompatibleVersion = Version.retrieveLatestCompatible({
version = Config.version,
includePrereleases = isLocalInstall and Settings:get("checkForPrereleases"),
})
if not latestCompatibleVersion then
return
end
return string.format(
"A newer compatible version of Rojo, %s, was published %s! Go to the Rojo releases page to learn more.",
Version.display(latestCompatibleVersion.version),
timeUtil.elapsedToText(DateTime.now().UnixTimestamp - latestCompatibleVersion.publishedUnixTimestamp)
)
end
return Version return Version