mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-04-20 12:45:05 +00:00
Check for compatible updates in plugin (#832)
This commit is contained in:
@@ -24,6 +24,7 @@
|
||||
* Added popout diff visualizer for table properties like Attributes and Tags ([#834])
|
||||
* Updated Theme to use Studio colors ([#838])
|
||||
* Improved patch visualizer UX ([#883])
|
||||
* Added update notifications for newer compatible versions in the Studio plugin. ([#832])
|
||||
* Added experimental setting for Auto Connect in playtests ([#840])
|
||||
* Improved settings UI ([#886])
|
||||
* `Open Scripts Externally` option can now be changed while syncing ([#911])
|
||||
@@ -75,6 +76,7 @@
|
||||
**All** sync rules are reset between project files, so they must be specified in each one when nesting them. This is to ensure that nothing can break other projects by changing how files are synced!
|
||||
|
||||
[#813]: https://github.com/rojo-rbx/rojo/pull/813
|
||||
[#832]: https://github.com/rojo-rbx/rojo/pull/832
|
||||
[#834]: https://github.com/rojo-rbx/rojo/pull/834
|
||||
[#838]: https://github.com/rojo-rbx/rojo/pull/838
|
||||
[#840]: https://github.com/rojo-rbx/rojo/pull/840
|
||||
|
||||
@@ -4,6 +4,7 @@ local Packages = Rojo.Packages
|
||||
|
||||
local Roact = require(Packages.Roact)
|
||||
|
||||
local timeUtil = require(Plugin.timeUtil)
|
||||
local Theme = require(Plugin.App.Theme)
|
||||
local Assets = require(Plugin.Assets)
|
||||
local PatchSet = require(Plugin.PatchSet)
|
||||
@@ -20,28 +21,6 @@ local TableDiffVisualizer = require(Plugin.App.Components.TableDiffVisualizer)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local AGE_UNITS = {
|
||||
{ 31556909, "y" },
|
||||
{ 2629743, "mon" },
|
||||
{ 604800, "w" },
|
||||
{ 86400, "d" },
|
||||
{ 3600, "h" },
|
||||
{ 60, "m" },
|
||||
}
|
||||
function timeSinceText(elapsed: number): string
|
||||
local ageText = string.format("%ds", elapsed)
|
||||
|
||||
for _, UnitData in ipairs(AGE_UNITS) do
|
||||
local UnitSeconds, UnitName = UnitData[1], UnitData[2]
|
||||
if elapsed > UnitSeconds then
|
||||
ageText = elapsed // UnitSeconds .. UnitName
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
return ageText
|
||||
end
|
||||
|
||||
local ChangesViewer = Roact.Component:extend("ChangesViewer")
|
||||
|
||||
function ChangesViewer:init()
|
||||
@@ -287,7 +266,7 @@ function ConnectedPage:getChangeInfoText()
|
||||
if patchData == nil then
|
||||
return ""
|
||||
end
|
||||
return timeSinceText(DateTime.now().UnixTimestamp - patchData.timestamp)
|
||||
return timeUtil.elapsedToText(DateTime.now().UnixTimestamp - patchData.timestamp)
|
||||
end
|
||||
|
||||
function ConnectedPage:startChangeInfoTextUpdater()
|
||||
@@ -303,7 +282,7 @@ function ConnectedPage:startChangeInfoTextUpdater()
|
||||
local updateInterval = 1
|
||||
|
||||
-- Update timestamp text as frequently as currently needed
|
||||
for _, UnitData in ipairs(AGE_UNITS) do
|
||||
for _, UnitData in ipairs(timeUtil.AGE_UNITS) do
|
||||
local UnitSeconds = UnitData[1]
|
||||
if elapsed > UnitSeconds then
|
||||
updateInterval = UnitSeconds
|
||||
|
||||
@@ -162,6 +162,25 @@ function SettingsPage:render()
|
||||
layoutOrder = layoutIncrement(),
|
||||
}),
|
||||
|
||||
CheckForUpdates = e(Setting, {
|
||||
id = "checkForUpdates",
|
||||
name = "Check For Updates",
|
||||
description = "Notify about newer compatible Rojo releases",
|
||||
transparency = self.props.transparency,
|
||||
layoutOrder = layoutIncrement(),
|
||||
}),
|
||||
|
||||
CheckForPreleases = e(Setting, {
|
||||
id = "checkForPrereleases",
|
||||
name = "Include Prerelease Updates",
|
||||
description = "Include prereleases when checking for updates",
|
||||
transparency = self.props.transparency,
|
||||
layoutOrder = layoutIncrement(),
|
||||
visible = if string.find(debug.traceback(), "\n[^\n]-user_.-$") == nil
|
||||
then false -- Must be a local install to allow prerelease checks
|
||||
else Settings:getBinding("checkForUpdates"),
|
||||
}),
|
||||
|
||||
AutoConnectPlaytestServer = e(Setting, {
|
||||
id = "autoConnectPlaytestServer",
|
||||
name = "Auto Connect Playtest Server",
|
||||
|
||||
@@ -23,6 +23,7 @@ local PatchTree = require(Plugin.PatchTree)
|
||||
local preloadAssets = require(Plugin.preloadAssets)
|
||||
local soundPlayer = require(Plugin.soundPlayer)
|
||||
local ignorePlaceIds = require(Plugin.ignorePlaceIds)
|
||||
local timeUtil = require(Plugin.timeUtil)
|
||||
local Theme = require(script.Theme)
|
||||
|
||||
local Page = require(script.Page)
|
||||
@@ -118,6 +119,13 @@ function App:init()
|
||||
end)
|
||||
end)
|
||||
|
||||
self.disconnectUpdatesCheckChanged = Settings:onChanged("checkForUpdates", function()
|
||||
self:checkForUpdates()
|
||||
end)
|
||||
self.disconnectPrereleasesCheckChanged = Settings:onChanged("checkForPrereleases", function()
|
||||
self:checkForUpdates()
|
||||
end)
|
||||
|
||||
self:setState({
|
||||
appStatus = AppStatus.NotConnected,
|
||||
guiEnabled = false,
|
||||
@@ -131,32 +139,35 @@ function App:init()
|
||||
toolbarIcon = Assets.Images.PluginButton,
|
||||
})
|
||||
|
||||
if
|
||||
RunService:IsEdit()
|
||||
and self.serveSession == nil
|
||||
and Settings:get("syncReminder")
|
||||
and self:getLastSyncTimestamp()
|
||||
and (self:isSyncLockAvailable())
|
||||
then
|
||||
self:addNotification("You've previously synced this place. Would you like to reconnect?", 300, {
|
||||
Connect = {
|
||||
text = "Connect",
|
||||
style = "Solid",
|
||||
layoutOrder = 1,
|
||||
onClick = function(notification)
|
||||
notification:dismiss()
|
||||
self:startSession()
|
||||
end,
|
||||
},
|
||||
Dismiss = {
|
||||
text = "Dismiss",
|
||||
style = "Bordered",
|
||||
layoutOrder = 2,
|
||||
onClick = function(notification)
|
||||
notification:dismiss()
|
||||
end,
|
||||
},
|
||||
})
|
||||
if RunService:IsEdit() then
|
||||
self:checkForUpdates()
|
||||
|
||||
if
|
||||
Settings:get("syncReminder")
|
||||
and self.serveSession == nil
|
||||
and self:getLastSyncTimestamp()
|
||||
and (self:isSyncLockAvailable())
|
||||
then
|
||||
self:addNotification("You've previously synced this place. Would you like to reconnect?", 300, {
|
||||
Connect = {
|
||||
text = "Connect",
|
||||
style = "Solid",
|
||||
layoutOrder = 1,
|
||||
onClick = function(notification)
|
||||
notification:dismiss()
|
||||
self:startSession()
|
||||
end,
|
||||
},
|
||||
Dismiss = {
|
||||
text = "Dismiss",
|
||||
style = "Bordered",
|
||||
layoutOrder = 2,
|
||||
onClick = function(notification)
|
||||
notification:dismiss()
|
||||
end,
|
||||
},
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
if self:isAutoConnectPlaytestServerAvailable() then
|
||||
@@ -179,6 +190,10 @@ end
|
||||
function App:willUnmount()
|
||||
self.waypointConnection:Disconnect()
|
||||
self.confirmationBindable:Destroy()
|
||||
|
||||
self.disconnectUpdatesCheckChanged()
|
||||
self.disconnectPrereleasesCheckChanged()
|
||||
|
||||
self.autoConnectPlaytestServerListener()
|
||||
self:clearRunningConnectionInfo()
|
||||
end
|
||||
@@ -225,6 +240,40 @@ function App:closeNotification(id: number)
|
||||
})
|
||||
end
|
||||
|
||||
function App:checkForUpdates()
|
||||
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
|
||||
|
||||
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 = {
|
||||
text = "Dismiss",
|
||||
style = "Bordered",
|
||||
layoutOrder = 2,
|
||||
onClick = function(notification)
|
||||
notification:dismiss()
|
||||
end,
|
||||
},
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
function App:getPriorEndpoint()
|
||||
local priorEndpoints = Settings:get("priorEndpoints")
|
||||
if not priorEndpoints then
|
||||
|
||||
@@ -14,6 +14,8 @@ local defaultSettings = {
|
||||
twoWaySync = false,
|
||||
showNotifications = true,
|
||||
syncReminder = true,
|
||||
checkForUpdates = true,
|
||||
checkForPrereleases = false,
|
||||
autoConnectPlaytestServer = false,
|
||||
confirmationBehavior = "Initial",
|
||||
largeChangesConfirmationThreshold = 5,
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
local Packages = script.Parent.Parent.Packages
|
||||
local Http = require(Packages.Http)
|
||||
local Promise = require(Packages.Promise)
|
||||
|
||||
local function compare(a, b)
|
||||
if a > b then
|
||||
return 1
|
||||
@@ -30,7 +34,48 @@ function Version.compare(a, b)
|
||||
return minor
|
||||
end
|
||||
|
||||
return revision
|
||||
if revision ~= 0 then
|
||||
return revision
|
||||
end
|
||||
|
||||
local aPrerelease = if a[4] == "" then nil else a[4]
|
||||
local bPrerelease = if b[4] == "" then nil else b[4]
|
||||
|
||||
-- If neither are prerelease, they are the same
|
||||
if aPrerelease == nil and bPrerelease == nil then
|
||||
return 0
|
||||
end
|
||||
|
||||
-- If one is prerelease it is older
|
||||
if aPrerelease ~= nil and bPrerelease == nil then
|
||||
return -1
|
||||
end
|
||||
if aPrerelease == nil and bPrerelease ~= nil then
|
||||
return 1
|
||||
end
|
||||
|
||||
-- If they are both prereleases, compare those based on number
|
||||
local aPrereleaseNumeric = string.match(aPrerelease, "(%d+).*$")
|
||||
local bPrereleaseNumeric = string.match(bPrerelease, "(%d+).*$")
|
||||
|
||||
if aPrereleaseNumeric == nil or bPrereleaseNumeric == nil then
|
||||
-- If one or both lack a number, comparing isn't meaningful
|
||||
return 0
|
||||
end
|
||||
return compare(tonumber(aPrereleaseNumeric) or 0, tonumber(bPrereleaseNumeric) or 0)
|
||||
end
|
||||
|
||||
function Version.parse(versionString: string)
|
||||
local version = { string.match(versionString, "^v?(%d+)%.(%d+)%.(%d+)(.*)$") }
|
||||
for i, v in version do
|
||||
version[i] = tonumber(v) or v
|
||||
end
|
||||
|
||||
if version[4] == "" then
|
||||
version[4] = nil
|
||||
end
|
||||
|
||||
return version
|
||||
end
|
||||
|
||||
function Version.display(version)
|
||||
@@ -43,4 +88,64 @@ function Version.display(version)
|
||||
return output
|
||||
end
|
||||
|
||||
function Version.retrieveLatestCompatible(options: {
|
||||
version: { number },
|
||||
includePrereleases: boolean?,
|
||||
}): {
|
||||
version: { number },
|
||||
prerelease: boolean,
|
||||
publishedUnixTimestamp: number,
|
||||
}?
|
||||
local success, releases = Http.get("https://api.github.com/repos/rojo-rbx/rojo/releases?per_page=10")
|
||||
:andThen(function(response)
|
||||
if response.code >= 400 then
|
||||
local message = string.format("HTTP %s:\n%s", tostring(response.code), response.body)
|
||||
|
||||
return Promise.reject(message)
|
||||
end
|
||||
|
||||
return response
|
||||
end)
|
||||
:andThen(Http.Response.json)
|
||||
:await()
|
||||
|
||||
if success == false or type(releases) ~= "table" or next(releases) ~= 1 then
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Iterate through releases, looking for the latest compatible version
|
||||
local latestCompatible = nil
|
||||
for _, release in releases do
|
||||
-- Skip prereleases if they are not requested
|
||||
if (not options.includePrereleases) and release.prerelease then
|
||||
continue
|
||||
end
|
||||
|
||||
local releaseVersion = Version.parse(release.tag_name)
|
||||
|
||||
-- Skip releases that are potentially incompatible
|
||||
if releaseVersion[1] > options.version[1] then
|
||||
continue
|
||||
end
|
||||
|
||||
-- Skip releases that are older than the latest compatible version
|
||||
if latestCompatible ~= nil and Version.compare(releaseVersion, latestCompatible.version) <= 0 then
|
||||
continue
|
||||
end
|
||||
|
||||
latestCompatible = {
|
||||
version = releaseVersion,
|
||||
prerelease = release.prerelease,
|
||||
publishedUnixTimestamp = DateTime.fromIsoDate(release.published_at).UnixTimestamp,
|
||||
}
|
||||
end
|
||||
|
||||
-- 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
|
||||
return nil
|
||||
end
|
||||
|
||||
return latestCompatible
|
||||
end
|
||||
|
||||
return Version
|
||||
|
||||
@@ -3,6 +3,7 @@ return function()
|
||||
|
||||
it("should compare equal versions", function()
|
||||
expect(Version.compare({ 1, 2, 3 }, { 1, 2, 3 })).to.equal(0)
|
||||
expect(Version.compare({ 1, 2, 3, "rc1" }, { 1, 2, 3, "rc1" })).to.equal(0)
|
||||
expect(Version.compare({ 0, 4, 0 }, { 0, 4 })).to.equal(0)
|
||||
expect(Version.compare({ 0, 0, 123 }, { 0, 0, 123 })).to.equal(0)
|
||||
expect(Version.compare({ 26 }, { 26 })).to.equal(0)
|
||||
@@ -13,6 +14,7 @@ return function()
|
||||
it("should compare newer, older versions", function()
|
||||
expect(Version.compare({ 1 }, { 0 })).to.equal(1)
|
||||
expect(Version.compare({ 1, 1 }, { 1, 0 })).to.equal(1)
|
||||
expect(Version.compare({ 1, 2, 3 }, { 1, 2, 0 })).to.equal(1)
|
||||
end)
|
||||
|
||||
it("should compare different major versions", function()
|
||||
@@ -25,4 +27,37 @@ return function()
|
||||
expect(Version.compare({ 1, 2, 3 }, { 1, 3, 2 })).to.equal(-1)
|
||||
expect(Version.compare({ 50, 1 }, { 50, 2 })).to.equal(-1)
|
||||
end)
|
||||
|
||||
it("should compare different patch versions", function()
|
||||
expect(Version.compare({ 1, 1, 3 }, { 1, 1, 2 })).to.equal(1)
|
||||
expect(Version.compare({ 1, 1, 2 }, { 1, 1, 3 })).to.equal(-1)
|
||||
expect(Version.compare({ 1, 1, 3, "-rc1" }, { 1, 1, 2, "-rc2" })).to.equal(1)
|
||||
expect(Version.compare({ 1, 1, 2, "-rc5" }, { 1, 1, 3, "-alpha" })).to.equal(-1)
|
||||
end)
|
||||
|
||||
it("should compare prerelease tags", function()
|
||||
expect(Version.compare({ 1, 0, 0, "-alpha" }, { 1, 0, 0 })).to.equal(-1)
|
||||
expect(Version.compare({ 1, 0, 0 }, { 1, 0, 0, "-alpha" })).to.equal(1)
|
||||
expect(Version.compare({ 1, 0, 0, "-rc1" }, { 1, 0, 0, "-rc2" })).to.equal(-1)
|
||||
expect(Version.compare({ 1, 0, 0, "-rc2" }, { 1, 0, 0, "-rc1" })).to.equal(1)
|
||||
|
||||
-- Non number prereleases are not compared since that isn't meaningful
|
||||
expect(Version.compare({ 1, 0, 0, "-alpha" }, { 1, 0, 0, "-beta" })).to.equal(0)
|
||||
end)
|
||||
|
||||
it("should parse version from strings", function()
|
||||
local a = Version.parse("v1.0.0")
|
||||
expect(a).to.be.ok()
|
||||
expect(a[1]).to.equal(1)
|
||||
expect(a[2]).to.equal(0)
|
||||
expect(a[3]).to.equal(0)
|
||||
expect(a[4]).to.equal(nil)
|
||||
|
||||
local b = Version.parse("7.3.1-rc1")
|
||||
expect(b).to.be.ok()
|
||||
expect(b[1]).to.equal(7)
|
||||
expect(b[2]).to.equal(3)
|
||||
expect(b[3]).to.equal(1)
|
||||
expect(b[4]).to.equal("-rc1")
|
||||
end)
|
||||
end
|
||||
|
||||
31
plugin/src/timeUtil.lua
Normal file
31
plugin/src/timeUtil.lua
Normal file
@@ -0,0 +1,31 @@
|
||||
local timeUtil = {}
|
||||
|
||||
timeUtil.AGE_UNITS = table.freeze({
|
||||
{ 31556909, "year" },
|
||||
{ 2629743, "month" },
|
||||
{ 604800, "week" },
|
||||
{ 86400, "day" },
|
||||
{ 3600, "hour" },
|
||||
{ 60, "minute" },
|
||||
})
|
||||
|
||||
function timeUtil.elapsedToText(elapsed: number): string
|
||||
if elapsed < 3 then
|
||||
return "just now"
|
||||
end
|
||||
|
||||
local ageText = string.format("%d seconds ago", elapsed)
|
||||
|
||||
for _, UnitData in timeUtil.AGE_UNITS do
|
||||
local UnitSeconds, UnitName = UnitData[1], UnitData[2]
|
||||
if elapsed > UnitSeconds then
|
||||
local c = math.floor(elapsed / UnitSeconds)
|
||||
ageText = string.format("%d %s%s ago", c, UnitName, c > 1 and "s" or "")
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
return ageText
|
||||
end
|
||||
|
||||
return timeUtil
|
||||
Reference in New Issue
Block a user