mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-21 12:01:38 +00:00
Improve UX/DX for requesting permissions
This commit is contained in:
parent
0de0610bda
commit
9444c84d73
6 changed files with 387 additions and 281 deletions
|
|
@ -1,137 +0,0 @@
|
|||
local TextService = game:GetService("TextService")
|
||||
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
local Packages = Rojo.Packages
|
||||
|
||||
local Roact = require(Packages.Roact)
|
||||
|
||||
local Theme = require(Plugin.App.Theme)
|
||||
|
||||
local Checkbox = require(Plugin.App.Components.Checkbox)
|
||||
|
||||
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 Toggle = Roact.Component:extend("Toggle")
|
||||
|
||||
function Toggle:init()
|
||||
self.contentSize, self.setContentSize = Roact.createBinding(Vector2.new(0, 0))
|
||||
self.containerSize, self.setContainerSize = Roact.createBinding(Vector2.new(0, 0))
|
||||
end
|
||||
|
||||
function Toggle:render()
|
||||
return Theme.with(function(theme)
|
||||
theme = theme.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,
|
||||
ZIndex = -self.props.layoutOrder,
|
||||
BackgroundTransparency = 1,
|
||||
|
||||
[Roact.Change.AbsoluteSize] = function(object)
|
||||
self.setContainerSize(object.AbsoluteSize)
|
||||
end,
|
||||
}, {
|
||||
Input = e(Checkbox, {
|
||||
active = self.props.active,
|
||||
transparency = self.props.transparency,
|
||||
position = UDim2.new(1, 0, 0.5, 0),
|
||||
anchorPoint = Vector2.new(1, 0.5),
|
||||
onClick = self.props.onClick,
|
||||
}),
|
||||
|
||||
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 offset = self.props.options ~= nil and 120 or 40
|
||||
local textBounds = getTextBounds(
|
||||
self.props.description,
|
||||
14,
|
||||
Enum.Font.Gotham,
|
||||
1.2,
|
||||
Vector2.new(value.X - offset, math.huge)
|
||||
)
|
||||
return UDim2.new(1, -offset, 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
|
||||
|
||||
return Toggle
|
||||
|
|
@ -4,51 +4,139 @@ local Packages = Rojo.Packages
|
|||
|
||||
local Roact = require(Packages.Roact)
|
||||
|
||||
local Assets = require(Plugin.Assets)
|
||||
local Theme = require(Plugin.App.Theme)
|
||||
|
||||
local Toggle = require(Plugin.App.Components.Toggle)
|
||||
local ScrollingFrame = require(Plugin.App.Components.ScrollingFrame)
|
||||
local TextButton = require(Plugin.App.Components.TextButton)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local DIVIDER_FADE_SIZE = 0.1
|
||||
|
||||
local PermissionPopup = Roact.Component:extend("PermissionPopup")
|
||||
|
||||
function PermissionPopup:init()
|
||||
self.contentSize, self.setContentSize = Roact.createBinding(Vector2.new(0, 0))
|
||||
self.infoSize, self.setInfoSize = Roact.createBinding(Vector2.new(0, 0))
|
||||
|
||||
local response = {}
|
||||
for _, api in self.props.apis do
|
||||
response[api] = if self.props.initialState[api] == nil then true else self.props.initialState[api]
|
||||
end
|
||||
|
||||
self:setState({
|
||||
response = response,
|
||||
})
|
||||
end
|
||||
|
||||
function PermissionPopup:render()
|
||||
return Theme.with(function(theme)
|
||||
theme = theme.Settings
|
||||
|
||||
local apiToggles = {}
|
||||
local thumbnail = Assets.Images.ThirdPartyPlugin
|
||||
local thumbnailId = string.match(self.props.source, "cloud_(%d+)")
|
||||
if thumbnailId then
|
||||
thumbnail = string.format("rbxthumb://type=Asset&id=%s&w=150&h=150", thumbnailId)
|
||||
end
|
||||
|
||||
local apiRequests = {
|
||||
Event = {},
|
||||
Property = {},
|
||||
Method = {},
|
||||
}
|
||||
for index, api in self.props.apis do
|
||||
apiToggles[api] = e(Toggle, {
|
||||
active = self.state.response[api],
|
||||
name = api,
|
||||
description = self.props.apiDescriptions[api],
|
||||
transparency = self.props.transparency,
|
||||
layoutOrder = index,
|
||||
onClick = function()
|
||||
self:setState(function(state)
|
||||
state.response[api] = not state.response[api]
|
||||
return state
|
||||
end)
|
||||
end,
|
||||
local apiDesc = self.props.apiDescriptions[api]
|
||||
|
||||
apiRequests[apiDesc.Type][api] = e("Frame", {
|
||||
LayoutOrder = index,
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.new(1, 0, 0, 17),
|
||||
AutomaticSize = Enum.AutomaticSize.Y,
|
||||
}, {
|
||||
Divider = e("Frame", {
|
||||
BackgroundColor3 = theme.DividerColor,
|
||||
BackgroundTransparency = self.props.transparency,
|
||||
Size = UDim2.new(1, 0, 0, 1),
|
||||
Position = UDim2.new(0, 0, 0, -2),
|
||||
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),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
Name = e("TextLabel", {
|
||||
BackgroundTransparency = 1,
|
||||
Position = UDim2.new(0, 0, 0, 0),
|
||||
Size = UDim2.new(0, 140, 0, 17),
|
||||
TextWrapped = true,
|
||||
AutomaticSize = Enum.AutomaticSize.Y,
|
||||
Text = api,
|
||||
Font = Enum.Font.Gotham,
|
||||
TextSize = 17,
|
||||
TextColor3 = theme.Setting.NameColor,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextTransparency = self.props.transparency,
|
||||
}),
|
||||
Desc = e("TextLabel", {
|
||||
BackgroundTransparency = 1,
|
||||
Position = UDim2.new(0, 145, 0, 0),
|
||||
Size = UDim2.new(1, -145, 0, 17),
|
||||
TextWrapped = true,
|
||||
AutomaticSize = Enum.AutomaticSize.Y,
|
||||
Text = apiDesc.Description,
|
||||
Font = Enum.Font.Gotham,
|
||||
TextSize = 15,
|
||||
TextColor3 = theme.Setting.DescriptionColor,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextTransparency = self.props.transparency,
|
||||
}),
|
||||
})
|
||||
end
|
||||
|
||||
-- Add labels to explain the api types
|
||||
if next(apiRequests.Event) then
|
||||
apiRequests.Event["_apiTypeInfo"] = e("TextLabel", {
|
||||
LayoutOrder = -1,
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.new(1, 0, 0, 18),
|
||||
Text = string.format("%s will be able to listen to these events:", self.props.name),
|
||||
TextWrapped = true,
|
||||
AutomaticSize = Enum.AutomaticSize.Y,
|
||||
Font = Enum.Font.GothamMedium,
|
||||
TextSize = 17,
|
||||
TextColor3 = theme.Setting.NameColor,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextTransparency = self.props.transparency,
|
||||
}, e("UIPadding", { PaddingBottom = UDim.new(0, 8) }))
|
||||
end
|
||||
if next(apiRequests.Property) then
|
||||
apiRequests.Property["_apiTypeInfo"] = e("TextLabel", {
|
||||
LayoutOrder = -1,
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.new(1, 0, 0, 18),
|
||||
Text = string.format("%s will be able to read these properties:", self.props.name),
|
||||
TextWrapped = true,
|
||||
AutomaticSize = Enum.AutomaticSize.Y,
|
||||
Font = Enum.Font.GothamMedium,
|
||||
TextSize = 17,
|
||||
TextColor3 = theme.Setting.NameColor,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextTransparency = self.props.transparency,
|
||||
}, e("UIPadding", { PaddingBottom = UDim.new(0, 8) }))
|
||||
end
|
||||
if next(apiRequests.Method) then
|
||||
apiRequests.Method["_apiTypeInfo"] = e("TextLabel", {
|
||||
LayoutOrder = -1,
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.new(1, 0, 0, 18),
|
||||
Text = string.format("%s will be able to call these methods:", self.props.name),
|
||||
TextWrapped = true,
|
||||
AutomaticSize = Enum.AutomaticSize.Y,
|
||||
Font = Enum.Font.GothamMedium,
|
||||
TextSize = 17,
|
||||
TextColor3 = theme.Setting.NameColor,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextTransparency = self.props.transparency,
|
||||
}, e("UIPadding", { PaddingBottom = UDim.new(0, 8) }))
|
||||
end
|
||||
|
||||
return e("Frame", {
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
|
|
@ -56,8 +144,8 @@ function PermissionPopup:render()
|
|||
Layout = e("UIListLayout", {
|
||||
FillDirection = Enum.FillDirection.Vertical,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
Padding = UDim.new(0, 5),
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Right,
|
||||
Padding = UDim.new(0, 15),
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Center,
|
||||
}),
|
||||
|
||||
Padding = e("UIPadding", {
|
||||
|
|
@ -67,59 +155,171 @@ function PermissionPopup:render()
|
|||
PaddingBottom = UDim.new(0, 15),
|
||||
}),
|
||||
|
||||
Icons = e("Frame", {
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.new(1, 0, 0, 32),
|
||||
LayoutOrder = 1,
|
||||
}, {
|
||||
Layout = e("UIListLayout", {
|
||||
FillDirection = Enum.FillDirection.Horizontal,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
Padding = UDim.new(0, 5),
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Center,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||
}),
|
||||
|
||||
ThirdPartyIcon = e("ImageLabel", {
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.new(0, 32, 0, 32),
|
||||
Image = thumbnail,
|
||||
LayoutOrder = 1,
|
||||
}),
|
||||
|
||||
TransactIcon = e("ImageLabel", {
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.new(0, 24, 0, 24),
|
||||
Image = Assets.Images.Icons.Transact,
|
||||
ImageColor3 = theme.Setting.DescriptionColor,
|
||||
LayoutOrder = 2,
|
||||
}),
|
||||
|
||||
RojoIcon = e("ImageLabel", {
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.new(0, 32, 0, 32),
|
||||
Image = Assets.Images.PluginButton,
|
||||
LayoutOrder = 3,
|
||||
}),
|
||||
}),
|
||||
|
||||
Info = e("TextLabel", {
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.new(1, 0, 0, 0),
|
||||
AutomaticSize = Enum.AutomaticSize.Y,
|
||||
Text = string.format(
|
||||
"A third-party plugin, %s, is asking to use the following parts of the Rojo API. Please grant/deny access.",
|
||||
self.props.name or "[Unknown]"
|
||||
),
|
||||
Font = Enum.Font.GothamMedium,
|
||||
Text = string.format("%s is asking to use the Rojo API", self.props.name or "[Unknown]"),
|
||||
Font = Enum.Font.GothamBold,
|
||||
TextSize = 17,
|
||||
TextColor3 = theme.Setting.NameColor,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextXAlignment = Enum.TextXAlignment.Center,
|
||||
TextWrapped = true,
|
||||
TextTransparency = self.props.transparency,
|
||||
LayoutOrder = 1,
|
||||
LayoutOrder = 2,
|
||||
|
||||
[Roact.Change.AbsoluteSize] = function(rbx)
|
||||
self.setInfoSize(rbx.AbsoluteSize)
|
||||
end,
|
||||
}),
|
||||
|
||||
Submit = e(TextButton, {
|
||||
text = "Submit",
|
||||
style = "Solid",
|
||||
transparency = self.props.transparency,
|
||||
layoutOrder = 3,
|
||||
onClick = function()
|
||||
self.props.responseEvent:Fire(self.state.response)
|
||||
end,
|
||||
Divider = e("Frame", {
|
||||
LayoutOrder = 3,
|
||||
BackgroundColor3 = theme.DividerColor,
|
||||
BackgroundTransparency = self.props.transparency,
|
||||
Size = UDim2.new(1, 0, 0, 1),
|
||||
Position = UDim2.new(0, 0, 0, -2),
|
||||
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),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
|
||||
ScrollingFrame = e(ScrollingFrame, {
|
||||
size = self.infoSize:map(function(infoSize)
|
||||
return UDim2.new(1, 0, 1, -infoSize.Y - 44)
|
||||
return UDim2.new(0.9, 0, 1, -infoSize.Y - 140)
|
||||
end),
|
||||
layoutOrder = 2,
|
||||
layoutOrder = 9,
|
||||
contentSize = self.contentSize,
|
||||
transparency = self.props.transparency,
|
||||
}, {
|
||||
APIToggles = Roact.createFragment(apiToggles),
|
||||
|
||||
Padding = e("UIPadding", {
|
||||
PaddingRight = UDim.new(0, 15),
|
||||
}),
|
||||
|
||||
Layout = e("UIListLayout", {
|
||||
FillDirection = Enum.FillDirection.Vertical,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
Padding = UDim.new(0, 18),
|
||||
|
||||
[Roact.Change.AbsoluteContentSize] = function(object)
|
||||
self.setContentSize(object.AbsoluteContentSize)
|
||||
end,
|
||||
}),
|
||||
|
||||
PropertyRequests = e("Frame", {
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.new(1, 0, 0, 0),
|
||||
AutomaticSize = Enum.AutomaticSize.Y,
|
||||
LayoutOrder = 1,
|
||||
}, {
|
||||
APIs = Roact.createFragment(apiRequests.Property),
|
||||
Layout = e("UIListLayout", {
|
||||
FillDirection = Enum.FillDirection.Vertical,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
Padding = UDim.new(0, 4),
|
||||
}),
|
||||
}),
|
||||
|
||||
EventRequests = e("Frame", {
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.new(1, 0, 0, 0),
|
||||
AutomaticSize = Enum.AutomaticSize.Y,
|
||||
LayoutOrder = 2,
|
||||
}, {
|
||||
APIs = Roact.createFragment(apiRequests.Event),
|
||||
Layout = e("UIListLayout", {
|
||||
FillDirection = Enum.FillDirection.Vertical,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
Padding = UDim.new(0, 4),
|
||||
}),
|
||||
}),
|
||||
|
||||
MethodRequests = e("Frame", {
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.new(1, 0, 0, 0),
|
||||
AutomaticSize = Enum.AutomaticSize.Y,
|
||||
LayoutOrder = 3,
|
||||
}, {
|
||||
APIs = Roact.createFragment(apiRequests.Method),
|
||||
Layout = e("UIListLayout", {
|
||||
FillDirection = Enum.FillDirection.Vertical,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
Padding = UDim.new(0, 4),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
|
||||
Actions = e("Frame", {
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.new(1, 0, 0, 34),
|
||||
LayoutOrder = 10,
|
||||
}, {
|
||||
Layout = e("UIListLayout", {
|
||||
FillDirection = Enum.FillDirection.Horizontal,
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Center,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
Padding = UDim.new(0, 10),
|
||||
}),
|
||||
|
||||
Deny = e(TextButton, {
|
||||
text = "Deny",
|
||||
style = "Bordered",
|
||||
transparency = self.props.transparency,
|
||||
layoutOrder = 1,
|
||||
onClick = function()
|
||||
self.props.responseEvent:Fire(false)
|
||||
end,
|
||||
}),
|
||||
|
||||
Allow = e(TextButton, {
|
||||
text = "Allow",
|
||||
style = "Solid",
|
||||
transparency = self.props.transparency,
|
||||
layoutOrder = 2,
|
||||
onClick = function()
|
||||
self.props.responseEvent:Fire(true)
|
||||
end,
|
||||
}),
|
||||
}),
|
||||
})
|
||||
end)
|
||||
|
|
|
|||
|
|
@ -84,7 +84,35 @@ function PermissionsPage:render()
|
|||
theme = theme.Settings
|
||||
|
||||
local sources = {}
|
||||
if next(self.state.permissions) == nil then
|
||||
for source, permissions in self.state.permissions do
|
||||
if next(permissions) == nil then
|
||||
continue
|
||||
end
|
||||
|
||||
local meta = self.props.headlessAPI:_getMetaFromSource(source)
|
||||
sources[source] = e(Listing, {
|
||||
layoutOrder = string.byte(source),
|
||||
transparency = self.props.transparency,
|
||||
|
||||
name = meta.Name,
|
||||
description = string.format(
|
||||
"%s plugin%s",
|
||||
meta.Type,
|
||||
if meta.Creator then " by " .. meta.Creator else ""
|
||||
),
|
||||
|
||||
onClick = function()
|
||||
self.props.onEdit(
|
||||
self.props.headlessAPI._sourceToPlugin[source],
|
||||
source,
|
||||
meta,
|
||||
self.props.headlessAPI._permissions[source] or {}
|
||||
)
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
if next(sources) == nil then
|
||||
sources.noSources = e("TextLabel", {
|
||||
Text = "No third-party plugins have been granted permissions.",
|
||||
Font = Enum.Font.Gotham,
|
||||
|
|
@ -98,34 +126,6 @@ function PermissionsPage:render()
|
|||
|
||||
BackgroundTransparency = 1,
|
||||
})
|
||||
else
|
||||
for source, permissions in self.state.permissions do
|
||||
if next(permissions) == nil then
|
||||
continue
|
||||
end
|
||||
|
||||
local meta = self.props.headlessAPI:_getMetaFromSource(source)
|
||||
sources[source] = e(Listing, {
|
||||
layoutOrder = string.byte(source),
|
||||
transparency = self.props.transparency,
|
||||
|
||||
name = meta.Name,
|
||||
description = string.format(
|
||||
"%s plugin%s",
|
||||
meta.Type,
|
||||
if meta.Creator then " by " .. meta.Creator else ""
|
||||
),
|
||||
|
||||
onClick = function()
|
||||
self.props.onEdit(
|
||||
self.props.headlessAPI._sourceToPlugin[source],
|
||||
source,
|
||||
meta,
|
||||
self.props.headlessAPI._permissions[source] or {}
|
||||
)
|
||||
end,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
return e("Frame", {
|
||||
|
|
|
|||
|
|
@ -459,7 +459,7 @@ function App:requestPermission(
|
|||
source: string,
|
||||
name: string,
|
||||
apis: { string },
|
||||
initialState: { [string]: boolean? }
|
||||
initialState: boolean?
|
||||
): { [string]: boolean }
|
||||
local responseEvent = Instance.new("BindableEvent")
|
||||
|
||||
|
|
@ -471,7 +471,7 @@ function App:requestPermission(
|
|||
"Cancelling API permission request for '{}' because the third-party plugin has been removed.",
|
||||
name
|
||||
)
|
||||
responseEvent:Fire(initialState)
|
||||
responseEvent:Fire(initialState or false)
|
||||
end)
|
||||
else nil
|
||||
|
||||
|
|
@ -480,7 +480,6 @@ function App:requestPermission(
|
|||
name = name,
|
||||
content = e(PermissionPopup, {
|
||||
responseEvent = responseEvent,
|
||||
initialState = initialState,
|
||||
source = source,
|
||||
name = name,
|
||||
apis = apis,
|
||||
|
|
@ -488,7 +487,7 @@ function App:requestPermission(
|
|||
transparency = Roact.createBinding(0),
|
||||
}),
|
||||
onClose = function()
|
||||
responseEvent:Fire(initialState)
|
||||
responseEvent:Fire(initialState or false)
|
||||
end,
|
||||
}
|
||||
return state
|
||||
|
|
@ -833,7 +832,7 @@ function App:render()
|
|||
initEnabled = true,
|
||||
overridePreviousState = true,
|
||||
floatingSize = Vector2.new(400, 300),
|
||||
minimumSize = Vector2.new(390, 240),
|
||||
minimumSize = Vector2.new(390, 240) or popup.minimumSize,
|
||||
|
||||
zIndexBehavior = Enum.ZIndexBehavior.Sibling,
|
||||
|
||||
|
|
@ -961,8 +960,12 @@ function App:render()
|
|||
for api in apiMap do
|
||||
table.insert(apiList, api)
|
||||
end
|
||||
local response = self:requestPermission(plugin, source, name, apiList, apiMap)
|
||||
self.headlessAPI:_setPermissions(source, name, response)
|
||||
local granted = self:requestPermission(plugin, source, name, apiList, true)
|
||||
if granted then
|
||||
self.headlessAPI:_setPermissions(source, name, apiList)
|
||||
else
|
||||
self.headlessAPI:_removePermissions(source, name)
|
||||
end
|
||||
end,
|
||||
}),
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ local Assets = {
|
|||
Reset = "rbxassetid://10142422327",
|
||||
Expand = "rbxassetid://12045401097",
|
||||
Settings = "rbxassetid://12046309515",
|
||||
Transact = "rbxassetid://16350762910",
|
||||
},
|
||||
Diff = {
|
||||
Add = "rbxassetid://10434145835",
|
||||
|
|
|
|||
|
|
@ -29,22 +29,40 @@ function API.new(app)
|
|||
Rojo._changedEvent = Instance.new("BindableEvent")
|
||||
Rojo._apiDescriptions = {}
|
||||
|
||||
Rojo._apiDescriptions.Changed = "An event that fires when a headless API property changes"
|
||||
Rojo._apiDescriptions.Changed = {
|
||||
Type = "Event",
|
||||
Description = "An event that fires when a headless API property changes",
|
||||
}
|
||||
Rojo.Changed = Rojo._changedEvent.Event
|
||||
|
||||
Rojo._apiDescriptions.Connected = "Whether or not the plugin is connected to a Rojo server"
|
||||
Rojo._apiDescriptions.Connected = {
|
||||
Type = "Property",
|
||||
Description = "Whether or not the plugin is connected to a Rojo server",
|
||||
}
|
||||
Rojo.Connected = if app.serveSession then app.serveSession:getStatus() == "Connected" else false
|
||||
|
||||
Rojo._apiDescriptions.Address = "The address (host:port) that the plugin is connected to"
|
||||
Rojo._apiDescriptions.Address = {
|
||||
Type = "Property",
|
||||
Description = "The address (host:port) that the plugin is connected to",
|
||||
}
|
||||
Rojo.Address = nil
|
||||
|
||||
Rojo._apiDescriptions.ProjectName = "The name of the project that the plugin is connected to"
|
||||
Rojo._apiDescriptions.ProjectName = {
|
||||
Type = "Property",
|
||||
Description = "The name of the project that the plugin is connected to",
|
||||
}
|
||||
Rojo.ProjectName = nil
|
||||
|
||||
Rojo._apiDescriptions.Version = "The version of the plugin"
|
||||
Rojo._apiDescriptions.Version = {
|
||||
Type = "Property",
|
||||
Description = "The version of the plugin",
|
||||
}
|
||||
Rojo.Version = table.clone(Config.version)
|
||||
|
||||
Rojo._apiDescriptions.ProtocolVersion = "The protocol version that the plugin is using"
|
||||
Rojo._apiDescriptions.ProtocolVersion = {
|
||||
Type = "Property",
|
||||
Description = "The protocol version that the plugin is using",
|
||||
}
|
||||
Rojo.ProtocolVersion = Config.protocolVersion
|
||||
|
||||
function Rojo:_updateProperty(property: string, value: any?)
|
||||
|
|
@ -187,52 +205,49 @@ function API.new(app)
|
|||
|
||||
local source = Rojo:_getCallerSource()
|
||||
if Rojo._permissions[source] == nil then
|
||||
Rojo._permissions[source] = {}
|
||||
return false
|
||||
end
|
||||
|
||||
return not not Rojo._permissions[source][key]
|
||||
end
|
||||
|
||||
function Rojo:_setPermissions(source, name, permissions)
|
||||
-- Ensure permissions exist for this source
|
||||
if Rojo._permissions[source] == nil then
|
||||
Rojo._permissions[source] = {}
|
||||
if next(permissions) == nil then
|
||||
Rojo:_removePermissions(source, name)
|
||||
return
|
||||
end
|
||||
|
||||
-- Set permissions
|
||||
for api, granted in permissions do
|
||||
Log.warn(
|
||||
string.format(
|
||||
"%s Rojo.%s for '%s'",
|
||||
granted and "Granting permission to" or "Denying permission to",
|
||||
api,
|
||||
name
|
||||
)
|
||||
)
|
||||
Rojo._permissions[source][api] = granted
|
||||
local sourcePermissions = {}
|
||||
for _, api in permissions do
|
||||
Log.info(string.format("Granting '%s' access to Rojo.%s", name, api))
|
||||
sourcePermissions[api] = true
|
||||
end
|
||||
|
||||
-- Clear out source if no permissions are granted
|
||||
local hasAnyPermissions = false
|
||||
for _, granted in Rojo._permissions[source] do
|
||||
if granted then
|
||||
hasAnyPermissions = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not hasAnyPermissions then
|
||||
Rojo._permissions[source] = nil
|
||||
end
|
||||
-- Update stored permissions
|
||||
Rojo._permissions[source] = sourcePermissions
|
||||
Settings:set("apiPermissions", Rojo._permissions)
|
||||
|
||||
-- Share changes
|
||||
Rojo._permissionsChangedEvent:Fire(source, sourcePermissions)
|
||||
end
|
||||
|
||||
function Rojo:_removePermissions(source, name)
|
||||
Rojo._permissions[source] = nil
|
||||
Log.info(string.format("Denying access to Rojo APIs for '%s'", name))
|
||||
|
||||
-- Update stored permissions
|
||||
Settings:set("apiPermissions", Rojo._permissions)
|
||||
|
||||
-- Share changes
|
||||
Rojo._permissionsChangedEvent:Fire(source, Rojo._permissions[source])
|
||||
Rojo._permissionsChangedEvent:Fire(source, nil)
|
||||
end
|
||||
|
||||
Rojo._apiDescriptions.RequestAccess = "Used to gain access to Rojo API members"
|
||||
function Rojo:RequestAccess(plugin: Plugin, apis: { string }): { [string]: boolean }
|
||||
Rojo._apiDescriptions.RequestAccess = {
|
||||
Type = "Method",
|
||||
Description = "Used to gain access to Rojo API members",
|
||||
}
|
||||
function Rojo:RequestAccess(plugin: Plugin, apis: { string }): boolean
|
||||
assert(type(apis) == "table", "Rojo:RequestAccess expects an array of valid API names as the second argument")
|
||||
assert(
|
||||
typeof(plugin) == "Instance" and plugin:IsA("Plugin"),
|
||||
|
|
@ -244,7 +259,7 @@ function API.new(app)
|
|||
|
||||
if Rojo:_checkRateLimit("RequestAccess") then
|
||||
-- Because this opens a popup, we dont want to let users get spammed by it
|
||||
return {}
|
||||
return false
|
||||
end
|
||||
|
||||
if Rojo._activePermissionRequests[source] then
|
||||
|
|
@ -256,10 +271,6 @@ function API.new(app)
|
|||
end
|
||||
Rojo._activePermissionRequests[source] = true
|
||||
|
||||
if Rojo._permissions[source] == nil then
|
||||
Rojo._permissions[source] = {}
|
||||
end
|
||||
|
||||
-- Sanitize request
|
||||
local sanitizedApis = {}
|
||||
for _, api in apis do
|
||||
|
|
@ -270,33 +281,40 @@ function API.new(app)
|
|||
end
|
||||
end
|
||||
assert(#sanitizedApis > 0, "Rojo:RequestAccess expects an array of valid API names")
|
||||
table.sort(sanitizedApis)
|
||||
|
||||
local alreadyAllowed = true
|
||||
for _, api in sanitizedApis do
|
||||
if not Rojo._permissions[source][api] then
|
||||
alreadyAllowed = false
|
||||
break
|
||||
if Rojo._permissions[source] == nil then
|
||||
alreadyAllowed = false
|
||||
else
|
||||
for _, api in sanitizedApis do
|
||||
if not Rojo._permissions[source][api] then
|
||||
alreadyAllowed = false
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if alreadyAllowed then
|
||||
local response = {}
|
||||
for _, api in sanitizedApis do
|
||||
response[api] = true
|
||||
end
|
||||
Rojo._activePermissionRequests[source] = nil
|
||||
return response
|
||||
return true
|
||||
end
|
||||
|
||||
local response = app:requestPermission(plugin, source, name, sanitizedApis, Rojo._permissions[source])
|
||||
|
||||
Rojo:_setPermissions(source, name, response)
|
||||
local granted = app:requestPermission(plugin, source, name, sanitizedApis, false)
|
||||
if granted then
|
||||
Rojo:_setPermissions(source, name, sanitizedApis)
|
||||
else
|
||||
Rojo:_removePermissions(source, name)
|
||||
end
|
||||
|
||||
Rojo._activePermissionRequests[source] = nil
|
||||
return response
|
||||
return granted
|
||||
end
|
||||
|
||||
Rojo._apiDescriptions.Test = "Prints the given arguments to the console"
|
||||
Rojo._apiDescriptions.Test = {
|
||||
Type = "Method",
|
||||
Description = "Prints the given arguments to the console",
|
||||
}
|
||||
function Rojo:Test(...)
|
||||
local args = table.pack(...)
|
||||
for i = 1, args.n do
|
||||
|
|
@ -319,7 +337,10 @@ function API.new(app)
|
|||
)
|
||||
end
|
||||
|
||||
Rojo._apiDescriptions.ConnectAsync = "Connects to a Rojo server"
|
||||
Rojo._apiDescriptions.ConnectAsync = {
|
||||
Type = "Method",
|
||||
Description = "Connects to a Rojo server",
|
||||
}
|
||||
function Rojo:ConnectAsync(host: string?, port: string?)
|
||||
assert(type(host) == "string" or host == nil, "Host must be type `string?`")
|
||||
assert(type(port) == "string" or port == nil, "Port must be type `string?`")
|
||||
|
|
@ -331,7 +352,10 @@ function API.new(app)
|
|||
app:startSession(host, port)
|
||||
end
|
||||
|
||||
Rojo._apiDescriptions.DisconnectAsync = "Disconnects from the Rojo server"
|
||||
Rojo._apiDescriptions.DisconnectAsync = {
|
||||
Type = "Method",
|
||||
Description = "Disconnects from the Rojo server",
|
||||
}
|
||||
function Rojo:DisconnectAsync()
|
||||
if Rojo:_checkRateLimit("DisconnectAsync") then
|
||||
return
|
||||
|
|
@ -340,14 +364,20 @@ function API.new(app)
|
|||
app:endSession()
|
||||
end
|
||||
|
||||
Rojo._apiDescriptions.GetSetting = "Gets a Rojo setting"
|
||||
Rojo._apiDescriptions.GetSetting = {
|
||||
Type = "Method",
|
||||
Description = "Gets a Rojo setting",
|
||||
}
|
||||
function Rojo:GetSetting(setting: string): any
|
||||
assert(type(setting) == "string", "Setting must be type `string`")
|
||||
|
||||
return Settings:get(setting)
|
||||
end
|
||||
|
||||
Rojo._apiDescriptions.SetSetting = "Sets a Rojo setting"
|
||||
Rojo._apiDescriptions.SetSetting = {
|
||||
Type = "Method",
|
||||
Description = "Sets a Rojo setting",
|
||||
}
|
||||
function Rojo:SetSetting(setting: string, value: any)
|
||||
assert(type(setting) == "string", "Setting must be type `string`")
|
||||
assert(type(value) == type(Settings:get(setting)), "Value must be the same type as the setting")
|
||||
|
|
@ -359,7 +389,10 @@ function API.new(app)
|
|||
return Settings:set(setting, value)
|
||||
end
|
||||
|
||||
Rojo._apiDescriptions.Notify = "Shows a notification in the Rojo UI"
|
||||
Rojo._apiDescriptions.Notify = {
|
||||
Type = "Method",
|
||||
Description = "Shows a notification in the Rojo UI",
|
||||
}
|
||||
function Rojo:Notify(
|
||||
msg: string,
|
||||
timeout: number?,
|
||||
|
|
@ -403,12 +436,18 @@ function API.new(app)
|
|||
return app:addThirdPartyNotification(Rojo:_getCallerName(), msg, timeout, sanitizedActions)
|
||||
end
|
||||
|
||||
Rojo._apiDescriptions.GetHostAndPort = "Gets the host and port that Rojo is set to"
|
||||
Rojo._apiDescriptions.GetHostAndPort = {
|
||||
Type = "Method",
|
||||
Description = "Gets the host and port that Rojo is set to",
|
||||
}
|
||||
function Rojo:GetHostAndPort(): (string, string)
|
||||
return app:getHostAndPort()
|
||||
end
|
||||
|
||||
Rojo._apiDescriptions.CreateApiContext = "Creates a new API context"
|
||||
Rojo._apiDescriptions.CreateApiContext = {
|
||||
Type = "Method",
|
||||
Description = "Creates a new API context",
|
||||
}
|
||||
function Rojo:CreateApiContext(baseUrl: string)
|
||||
assert(type(baseUrl) == "string", "Base URL must be type `string`")
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue