mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-23 05:51:39 +00:00
Add headless API
This commit is contained in:
parent
003abe86bb
commit
4832035a18
6 changed files with 149 additions and 25 deletions
BIN
assets/thirdParty.png
Normal file
BIN
assets/thirdParty.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.8 KiB |
|
|
@ -42,7 +42,7 @@ end
|
||||||
function TextButton:render()
|
function TextButton:render()
|
||||||
return Theme.with(function(theme)
|
return Theme.with(function(theme)
|
||||||
local textSize = TextService:GetTextSize(
|
local textSize = TextService:GetTextSize(
|
||||||
self.props.text, 18, Enum.Font.GothamSemibold,
|
self.props.text, 18, Enum.Font.GothamMedium,
|
||||||
Vector2.new(math.huge, math.huge)
|
Vector2.new(math.huge, math.huge)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -85,7 +85,7 @@ function TextButton:render()
|
||||||
|
|
||||||
Text = e("TextLabel", {
|
Text = e("TextLabel", {
|
||||||
Text = self.props.text,
|
Text = self.props.text,
|
||||||
Font = Enum.Font.GothamSemibold,
|
Font = Enum.Font.GothamMedium,
|
||||||
TextSize = 18,
|
TextSize = 18,
|
||||||
TextColor3 = bindingUtil.mapLerp(bindingEnabled, theme.Enabled.TextColor, theme.Disabled.TextColor),
|
TextColor3 = bindingUtil.mapLerp(bindingEnabled, theme.Enabled.TextColor, theme.Disabled.TextColor),
|
||||||
TextTransparency = self.props.transparency,
|
TextTransparency = self.props.transparency,
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ function Notification:render()
|
||||||
local textBounds = TextService:GetTextSize(
|
local textBounds = TextService:GetTextSize(
|
||||||
self.props.text,
|
self.props.text,
|
||||||
15,
|
15,
|
||||||
Enum.Font.GothamSemibold,
|
Enum.Font.GothamMedium,
|
||||||
Vector2.new(350, 700)
|
Vector2.new(350, 700)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -102,7 +102,7 @@ function Notification:render()
|
||||||
local size = self.binding:map(function(value)
|
local size = self.binding:map(function(value)
|
||||||
return UDim2.fromOffset(
|
return UDim2.fromOffset(
|
||||||
(35+40+textBounds.X)*value,
|
(35+40+textBounds.X)*value,
|
||||||
math.max(14+20+textBounds.Y, 32+20)
|
math.max(16+20+textBounds.Y, 54)
|
||||||
)
|
)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
@ -129,15 +129,15 @@ function Notification:render()
|
||||||
}, {
|
}, {
|
||||||
Logo = e("ImageLabel", {
|
Logo = e("ImageLabel", {
|
||||||
ImageTransparency = transparency,
|
ImageTransparency = transparency,
|
||||||
Image = Assets.Images.PluginButton,
|
Image = if self.props.thirdParty then Assets.Images.ThirdPartyPlugin else Assets.Images.PluginButton,
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
Size = UDim2.new(0, 32, 0, 32),
|
Size = UDim2.new(0, 32, 0, 32),
|
||||||
Position = UDim2.new(0, 0, 0.5, 0),
|
Position = UDim2.new(0, 0, 0.5, 0),
|
||||||
AnchorPoint = Vector2.new(0, 0.5),
|
AnchorPoint = Vector2.new(0, 0.5),
|
||||||
}),
|
}),
|
||||||
Info = e("TextLabel", {
|
Message = e("TextLabel", {
|
||||||
Text = self.props.text,
|
Text = self.props.text,
|
||||||
Font = Enum.Font.GothamSemibold,
|
Font = Enum.Font.GothamMedium,
|
||||||
TextSize = 15,
|
TextSize = 15,
|
||||||
TextColor3 = theme.Notification.InfoColor,
|
TextColor3 = theme.Notification.InfoColor,
|
||||||
TextTransparency = transparency,
|
TextTransparency = transparency,
|
||||||
|
|
@ -145,21 +145,21 @@ function Notification:render()
|
||||||
TextWrapped = true,
|
TextWrapped = true,
|
||||||
|
|
||||||
Size = UDim2.new(0, textBounds.X, 0, textBounds.Y),
|
Size = UDim2.new(0, textBounds.X, 0, textBounds.Y),
|
||||||
Position = UDim2.fromOffset(35, 0),
|
Position = UDim2.fromOffset(38, 0),
|
||||||
|
|
||||||
LayoutOrder = 1,
|
LayoutOrder = 1,
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
}),
|
}),
|
||||||
Time = e("TextLabel", {
|
Info = e("TextLabel", {
|
||||||
Text = time:FormatLocalTime("LTS", "en-us"),
|
Text = if self.props.source then "From: " .. self.props.source else time:FormatLocalTime("LTS", "en-us"),
|
||||||
Font = Enum.Font.Code,
|
Font = Enum.Font.Gotham,
|
||||||
TextSize = 12,
|
TextSize = 12,
|
||||||
TextColor3 = theme.Notification.InfoColor,
|
TextColor3 = theme.Notification.InfoColor,
|
||||||
TextTransparency = transparency,
|
TextTransparency = transparency,
|
||||||
TextXAlignment = Enum.TextXAlignment.Left,
|
TextXAlignment = Enum.TextXAlignment.Left,
|
||||||
|
|
||||||
Size = UDim2.new(1, -35, 0, 14),
|
Size = UDim2.new(1, -38, 0, 14),
|
||||||
Position = UDim2.new(0, 35, 1, -14),
|
Position = UDim2.new(0, 38, 1, -14),
|
||||||
|
|
||||||
LayoutOrder = 1,
|
LayoutOrder = 1,
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
|
|
@ -167,8 +167,8 @@ function Notification:render()
|
||||||
}),
|
}),
|
||||||
|
|
||||||
Padding = e("UIPadding", {
|
Padding = e("UIPadding", {
|
||||||
PaddingLeft = UDim.new(0, 17),
|
PaddingLeft = UDim.new(0, 12),
|
||||||
PaddingRight = UDim.new(0, 15),
|
PaddingRight = UDim.new(0, 12),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
@ -181,16 +181,18 @@ function Notifications:render()
|
||||||
local notifs = {}
|
local notifs = {}
|
||||||
|
|
||||||
for index, notif in ipairs(self.props.notifications) do
|
for index, notif in ipairs(self.props.notifications) do
|
||||||
notifs[notif] = e(Notification, {
|
local props = {
|
||||||
soundPlayer = self.props.soundPlayer,
|
soundPlayer = self.props.soundPlayer,
|
||||||
text = notif.text,
|
|
||||||
timestamp = notif.timestamp,
|
|
||||||
timeout = notif.timeout,
|
|
||||||
layoutOrder = (notif.timestamp - baseClock),
|
layoutOrder = (notif.timestamp - baseClock),
|
||||||
onClose = function()
|
onClose = function()
|
||||||
self.props.onClose(index)
|
self.props.onClose(index)
|
||||||
end,
|
end,
|
||||||
})
|
}
|
||||||
|
for key, value in notif do
|
||||||
|
props[key] = value
|
||||||
|
end
|
||||||
|
|
||||||
|
notifs[notif] = e(Notification, props)
|
||||||
end
|
end
|
||||||
|
|
||||||
return Roact.createFragment(notifs)
|
return Roact.createFragment(notifs)
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ local strict = require(Plugin.strict)
|
||||||
local Dictionary = require(Plugin.Dictionary)
|
local Dictionary = require(Plugin.Dictionary)
|
||||||
local ServeSession = require(Plugin.ServeSession)
|
local ServeSession = require(Plugin.ServeSession)
|
||||||
local ApiContext = require(Plugin.ApiContext)
|
local ApiContext = require(Plugin.ApiContext)
|
||||||
|
local HeadlessAPI = require(Plugin.HeadlessAPI)
|
||||||
local preloadAssets = require(Plugin.preloadAssets)
|
local preloadAssets = require(Plugin.preloadAssets)
|
||||||
local soundPlayer = require(Plugin.soundPlayer)
|
local soundPlayer = require(Plugin.soundPlayer)
|
||||||
local Theme = require(script.Theme)
|
local Theme = require(script.Theme)
|
||||||
|
|
@ -48,6 +49,11 @@ function App:init()
|
||||||
self.host, self.setHost = Roact.createBinding(priorHost or "")
|
self.host, self.setHost = Roact.createBinding(priorHost or "")
|
||||||
self.port, self.setPort = Roact.createBinding(priorPort or "")
|
self.port, self.setPort = Roact.createBinding(priorPort or "")
|
||||||
|
|
||||||
|
self.headlessAPI, self.readOnlyHeadlessAPI = HeadlessAPI.new(self, Config, Settings)
|
||||||
|
|
||||||
|
-- selene: allow(global_usage)
|
||||||
|
_G.Rojo = self.readOnlyHeadlessAPI -- Expose headless to other plugins and command bar
|
||||||
|
|
||||||
self.patchInfo, self.setPatchInfo = Roact.createBinding({
|
self.patchInfo, self.setPatchInfo = Roact.createBinding({
|
||||||
changes = 0,
|
changes = 0,
|
||||||
timestamp = os.time(),
|
timestamp = os.time(),
|
||||||
|
|
@ -78,6 +84,25 @@ function App:addNotification(text: string, timeout: number?)
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function App:addThirdPartyNotification(source: string, text: string, timeout: number?)
|
||||||
|
if not Settings:get("showNotifications") then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local notifications = table.clone(self.state.notifications)
|
||||||
|
table.insert(notifications, {
|
||||||
|
text = text,
|
||||||
|
timestamp = DateTime.now().UnixTimestampMillis,
|
||||||
|
timeout = timeout or 3,
|
||||||
|
thirdParty = true,
|
||||||
|
source = source,
|
||||||
|
})
|
||||||
|
|
||||||
|
self:setState({
|
||||||
|
notifications = notifications,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
function App:closeNotification(index: number)
|
function App:closeNotification(index: number)
|
||||||
local notifications = table.clone(self.state.notifications)
|
local notifications = table.clone(self.state.notifications)
|
||||||
table.remove(notifications, index)
|
table.remove(notifications, index)
|
||||||
|
|
@ -126,7 +151,7 @@ function App:setPriorEndpoint(host: string, port: string)
|
||||||
Settings:set("priorEndpoints", priorEndpoints)
|
Settings:set("priorEndpoints", priorEndpoints)
|
||||||
end
|
end
|
||||||
|
|
||||||
function App:getHostAndPort()
|
function App:getHostAndPort(): (string, string)
|
||||||
local host = self.host:getValue()
|
local host = self.host:getValue()
|
||||||
local port = self.port:getValue()
|
local port = self.port:getValue()
|
||||||
|
|
||||||
|
|
@ -179,7 +204,7 @@ function App:releaseSyncLock()
|
||||||
Log.trace("Could not relase sync lock because it is owned by {}", lock.Value)
|
Log.trace("Could not relase sync lock because it is owned by {}", lock.Value)
|
||||||
end
|
end
|
||||||
|
|
||||||
function App:startSession()
|
function App:startSession(host: string?, port: string?)
|
||||||
local claimedLock, priorOwner = self:claimSyncLock()
|
local claimedLock, priorOwner = self:claimSyncLock()
|
||||||
if not claimedLock then
|
if not claimedLock then
|
||||||
local msg = string.format("Could not sync because user '%s' is already syncing", tostring(priorOwner))
|
local msg = string.format("Could not sync because user '%s' is already syncing", tostring(priorOwner))
|
||||||
|
|
@ -195,14 +220,16 @@ function App:startSession()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local host, port = self:getHostAndPort()
|
if host == nil or port == nil then
|
||||||
|
host, port = self:getHostAndPort()
|
||||||
|
end
|
||||||
|
|
||||||
local sessionOptions = {
|
local sessionOptions = {
|
||||||
openScriptsExternally = Settings:get("openScriptsExternally"),
|
openScriptsExternally = Settings:get("openScriptsExternally"),
|
||||||
twoWaySync = Settings:get("twoWaySync"),
|
twoWaySync = Settings:get("twoWaySync"),
|
||||||
}
|
}
|
||||||
|
|
||||||
local baseUrl = ("http://%s:%s"):format(host, port)
|
local baseUrl = string.format("http://%s:%s", host :: string, port :: string)
|
||||||
local apiContext = ApiContext.new(baseUrl)
|
local apiContext = ApiContext.new(baseUrl)
|
||||||
|
|
||||||
local serveSession = ServeSession.new({
|
local serveSession = ServeSession.new({
|
||||||
|
|
@ -240,6 +267,8 @@ function App:startSession()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
serveSession:onStatusChanged(function(status, details)
|
serveSession:onStatusChanged(function(status, details)
|
||||||
|
self.headlessAPI.Connected = status == ServeSession.Status.Connected
|
||||||
|
|
||||||
if status == ServeSession.Status.Connecting then
|
if status == ServeSession.Status.Connecting then
|
||||||
self:setPriorEndpoint(host, port)
|
self:setPriorEndpoint(host, port)
|
||||||
|
|
||||||
|
|
@ -249,7 +278,7 @@ function App:startSession()
|
||||||
})
|
})
|
||||||
self:addNotification("Connecting to session...")
|
self:addNotification("Connecting to session...")
|
||||||
elseif status == ServeSession.Status.Connected then
|
elseif status == ServeSession.Status.Connected then
|
||||||
local address = ("%s:%s"):format(host, port)
|
local address = string.format("%s:%s", host :: string, port :: string)
|
||||||
self:setState({
|
self:setState({
|
||||||
appStatus = AppStatus.Connected,
|
appStatus = AppStatus.Connected,
|
||||||
projectName = details,
|
projectName = details,
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ local Assets = {
|
||||||
PluginButton = "rbxassetid://3405341609",
|
PluginButton = "rbxassetid://3405341609",
|
||||||
PluginButtonConnected = "rbxassetid://9529783993",
|
PluginButtonConnected = "rbxassetid://9529783993",
|
||||||
PluginButtonWarning = "rbxassetid://9529784530",
|
PluginButtonWarning = "rbxassetid://9529784530",
|
||||||
|
ThirdPartyPlugin = "rbxassetid://11064843298",
|
||||||
Icons = {
|
Icons = {
|
||||||
Close = "rbxassetid://6012985953",
|
Close = "rbxassetid://6012985953",
|
||||||
Back = "rbxassetid://6017213752",
|
Back = "rbxassetid://6017213752",
|
||||||
|
|
|
||||||
92
plugin/src/HeadlessAPI.lua
Normal file
92
plugin/src/HeadlessAPI.lua
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
local API = {}
|
||||||
|
|
||||||
|
function API.new(app, config, settings)
|
||||||
|
local Rojo = {}
|
||||||
|
|
||||||
|
Rojo.Connected = if app.serveSession then app.serveSession:getStatus() == "Connected" else false
|
||||||
|
Rojo.Version = table.clone(config.version)
|
||||||
|
Rojo.ProtocolVersion = config.protocolVersion
|
||||||
|
|
||||||
|
Rojo._notifRateLimit = {}
|
||||||
|
|
||||||
|
function Rojo:Test(...)
|
||||||
|
print("Rojo:Test called by", Rojo:_getCaller(), "with args", ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Rojo:_getCaller()
|
||||||
|
local traceback = string.split(debug.traceback(), "\n")
|
||||||
|
local topLevel = traceback[#traceback - 1]
|
||||||
|
|
||||||
|
local debugPlugin = string.match(topLevel, "^PluginDebugService%.user_(.-)%.")
|
||||||
|
if debugPlugin then
|
||||||
|
return debugPlugin
|
||||||
|
end
|
||||||
|
|
||||||
|
local localPlugin = string.match(topLevel, "^user_(.-)%.")
|
||||||
|
if localPlugin then
|
||||||
|
return localPlugin
|
||||||
|
end
|
||||||
|
|
||||||
|
local cloudPlugin = string.match(topLevel, "cloud_%d-%.(.-)%.")
|
||||||
|
if cloudPlugin then
|
||||||
|
return cloudPlugin
|
||||||
|
end
|
||||||
|
|
||||||
|
return "Command Bar"
|
||||||
|
end
|
||||||
|
|
||||||
|
function Rojo:ConnectAsync(host: string?, port: number?)
|
||||||
|
app:startSession(host, port)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Rojo:DisconnectAsync()
|
||||||
|
app:endSession()
|
||||||
|
end
|
||||||
|
|
||||||
|
function Rojo:GetSetting(setting: string): any
|
||||||
|
return settings:get(setting)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Rojo:SetSetting(setting: string, value: any)
|
||||||
|
return settings:set(setting, value)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Rojo:Notify(msg: string, timeout: number?)
|
||||||
|
local source = Rojo:_getCaller()
|
||||||
|
|
||||||
|
if Rojo._notifRateLimit[source] == nil then
|
||||||
|
Rojo._notifRateLimit[source] = 0
|
||||||
|
elseif Rojo._notifRateLimit[source] > 45 then
|
||||||
|
return -- Rate limited
|
||||||
|
end
|
||||||
|
|
||||||
|
Rojo._notifRateLimit[source] += 1
|
||||||
|
task.delay(30, function()
|
||||||
|
Rojo._notifRateLimit[source] -= 1
|
||||||
|
end)
|
||||||
|
|
||||||
|
app:addThirdPartyNotification(source, msg, timeout)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
function Rojo:GetHostAndPort(): (string, string)
|
||||||
|
return app:getHostAndPort()
|
||||||
|
end
|
||||||
|
|
||||||
|
local ReadOnly = setmetatable({}, {
|
||||||
|
__index = function(_, key)
|
||||||
|
if string.find(key, "^_") then
|
||||||
|
return nil -- Don't expose private members
|
||||||
|
end
|
||||||
|
return Rojo[key]
|
||||||
|
end,
|
||||||
|
__newindex = function(_, key, value)
|
||||||
|
error(string.format("Attempted to set Rojo.%s to %q but it's a read-only value", key, value), 2)
|
||||||
|
return
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
return Rojo, ReadOnly
|
||||||
|
end
|
||||||
|
|
||||||
|
return API
|
||||||
Loading…
Add table
Add a link
Reference in a new issue