Improve displayed info about third party plugins

This commit is contained in:
boatbomber 2024-12-22 15:18:29 -08:00
parent 12e3d2b1a5
commit 139b283919
7 changed files with 217 additions and 58 deletions

View file

@ -12,6 +12,7 @@ local Theme = require(Plugin.App.Theme)
local Assets = require(Plugin.Assets) local Assets = require(Plugin.Assets)
local bindingUtil = require(Plugin.App.bindingUtil) local bindingUtil = require(Plugin.App.bindingUtil)
local getTextBoundsAsync = require(Plugin.App.getTextBoundsAsync) local getTextBoundsAsync = require(Plugin.App.getTextBoundsAsync)
local getThirdPartyIcon = require(Plugin.getThirdPartyIcon)
local BorderedContainer = require(Plugin.App.Components.BorderedContainer) local BorderedContainer = require(Plugin.App.Components.BorderedContainer)
local TextButton = require(Plugin.App.Components.TextButton) local TextButton = require(Plugin.App.Components.TextButton)
@ -128,6 +129,10 @@ function Notification:render()
) )
end) end)
local logoAssetId = if self.props.thirdParty
then getThirdPartyIcon(self.props.source)
else Assets.Images.PluginButton
return e("TextButton", { return e("TextButton", {
BackgroundTransparency = 1, BackgroundTransparency = 1,
Size = size, Size = size,
@ -149,7 +154,7 @@ function Notification:render()
}, { }, {
Logo = e("ImageLabel", { Logo = e("ImageLabel", {
ImageTransparency = transparency, ImageTransparency = transparency,
Image = Assets.Images.PluginButton, Image = logoAssetId,
BackgroundTransparency = 1, BackgroundTransparency = 1,
Size = UDim2.new(0, logoSize, 0, logoSize), Size = UDim2.new(0, logoSize, 0, logoSize),
Position = UDim2.new(0, 0, 0, 0), Position = UDim2.new(0, 0, 0, 0),
@ -225,14 +230,15 @@ function Notifications:render()
for id, notif in self.props.notifications do for id, notif in self.props.notifications do
notifs["NotifID_" .. id] = e(Notification, { notifs["NotifID_" .. id] = e(Notification, {
soundPlayer = self.props.soundPlayer, layoutOrder = (notif.timestamp - baseClock),
text = notif.text, text = notif.text,
timestamp = notif.timestamp, timestamp = notif.timestamp,
timeout = notif.timeout, timeout = notif.timeout,
actions = notif.actions, actions = notif.actions,
source = notif.source, soundPlayer = self.props.soundPlayer,
thirdParty = notif.thirdParty, thirdParty = notif.thirdParty,
layoutOrder = (notif.timestamp - baseClock), name = notif.name,
source = notif.source,
onClose = function() onClose = function()
self.props.onClose(id) self.props.onClose(id)
end, end,

View file

@ -7,6 +7,8 @@ local Roact = require(Packages.Roact)
local Assets = require(Plugin.Assets) local Assets = require(Plugin.Assets)
local Theme = require(Plugin.App.Theme) local Theme = require(Plugin.App.Theme)
local getThirdPartyIcon = require(Plugin.getThirdPartyIcon)
local ScrollingFrame = require(Plugin.App.Components.ScrollingFrame) local ScrollingFrame = require(Plugin.App.Components.ScrollingFrame)
local TextButton = require(Plugin.App.Components.TextButton) local TextButton = require(Plugin.App.Components.TextButton)
@ -25,11 +27,7 @@ function PermissionPopup:render()
return Theme.with(function(theme) return Theme.with(function(theme)
local settingsTheme = theme.Settings local settingsTheme = theme.Settings
local thumbnail = Assets.Images.ThirdPartyPlugin local iconAsset = getThirdPartyIcon(self.props.source)
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 = { local apiRequests = {
Event = {}, Event = {},
@ -171,7 +169,7 @@ function PermissionPopup:render()
ThirdPartyIcon = e("ImageLabel", { ThirdPartyIcon = e("ImageLabel", {
BackgroundTransparency = 1, BackgroundTransparency = 1,
Size = UDim2.new(0, 32, 0, 32), Size = UDim2.new(0, 32, 0, 32),
Image = thumbnail, Image = iconAsset,
LayoutOrder = 1, LayoutOrder = 1,
}), }),

View file

@ -11,19 +11,22 @@ local Theme = require(Plugin.App.Theme)
local getTextBoundsAsync = require(Plugin.App.getTextBoundsAsync) local getTextBoundsAsync = require(Plugin.App.getTextBoundsAsync)
local SlicedImage = require(Plugin.App.Components.SlicedImage) local SlicedImage = require(Plugin.App.Components.SlicedImage)
local Tooltip = require(Plugin.App.Components.Tooltip)
local e = Roact.createElement local e = Roact.createElement
local DIVIDER_FADE_SIZE = 0.1 local DIVIDER_FADE_SIZE = 0.1
local Listing = Roact.Component:extend("Listing") local SourceListing = Roact.Component:extend("SourceListing")
function Listing:init() function SourceListing:init()
self.contentSize, self.setContentSize = Roact.createBinding(Vector2.new(0, 0)) self.contentSize, self.setContentSize = Roact.createBinding(Vector2.new(0, 0))
self.containerSize, self.setContainerSize = Roact.createBinding(Vector2.new(0, 0)) self.containerSize, self.setContainerSize = Roact.createBinding(Vector2.new(0, 0))
end end
function Listing:render() function SourceListing:render()
local callerInfoFromSource = self.props.callerInfoFromSource
return Theme.with(function(theme) return Theme.with(function(theme)
return e("Frame", { return e("Frame", {
Size = self.contentSize:map(function(value) Size = self.contentSize:map(function(value)
@ -68,26 +71,76 @@ function Listing:render()
}), }),
}), }),
Text = e("Frame", { Info = e("Frame", {
Size = UDim2.new(1, 0, 1, 0), Size = UDim2.new(1, 0, 1, 0),
BackgroundTransparency = 1, BackgroundTransparency = 1,
}, { }, {
PluginDetails = e("Frame", {
BackgroundTransparency = 1,
Size = UDim2.new(1, 0, 0, theme.TextSize.Medium + theme.TextSize.Body + 4),
}, {
Icon = e("ImageLabel", {
Image = self.props.icon,
BackgroundTransparency = 1,
SizeConstraint = Enum.SizeConstraint.RelativeYY,
AnchorPoint = Vector2.new(0, 0.5),
Position = UDim2.fromScale(0, 0.5),
Size = UDim2.fromScale(0.95, 0.95),
}),
Name = e("TextLabel", { Name = e("TextLabel", {
Text = self.props.name, Text = callerInfoFromSource.Name,
FontFace = theme.Font.Bold, FontFace = theme.Font.Bold,
TextSize = theme.TextSize.Medium, TextSize = theme.TextSize.Medium,
TextColor3 = theme.Settings.Setting.NameColor, TextColor3 = theme.Settings.Setting.NameColor,
TextXAlignment = Enum.TextXAlignment.Left, TextXAlignment = Enum.TextXAlignment.Left,
TextTransparency = self.props.transparency, TextTransparency = self.props.transparency,
AutomaticSize = Enum.AutomaticSize.X,
Size = UDim2.new(1, 0, 0, 17), Size = UDim2.new(0, 0, 0, theme.TextSize.Medium),
Position = UDim2.new(0, theme.TextSize.Medium + theme.TextSize.Body + 8, 0, 0),
LayoutOrder = 1,
BackgroundTransparency = 1, BackgroundTransparency = 1,
}), }),
Creator = e(
"TextLabel",
{
Text = callerInfoFromSource.Creator,
FontFace = theme.Font.Main,
TextSize = theme.TextSize.Body,
TextColor3 = theme.Settings.Setting.NameColor,
TextXAlignment = Enum.TextXAlignment.Left,
TextTransparency = self.props.transparency,
AutomaticSize = Enum.AutomaticSize.X,
Size = UDim2.fromOffset(0, theme.TextSize.Body),
Position = UDim2.new(
0,
theme.TextSize.Medium + theme.TextSize.Body + 8,
0,
theme.TextSize.Medium + 2
),
BackgroundTransparency = 1,
},
if callerInfoFromSource.HasVerifiedBadge
then e(
"ImageLabel",
{
Image = Assets.Images.Icons.Verified,
BackgroundTransparency = 1,
SizeConstraint = Enum.SizeConstraint.RelativeYY,
AnchorPoint = Vector2.new(0, 0.5),
Position = UDim2.new(1, 3, 0.5, 0),
Size = UDim2.fromScale(0.8, 0.8),
},
e(Tooltip.Trigger, {
text = "Creator has a verified badge",
})
)
else nil
),
}),
Description = e("TextLabel", { Description = e("TextLabel", {
Text = self.props.description, Text = callerInfoFromSource.Description,
FontFace = theme.Font.Thin, FontFace = theme.Font.Thin,
LineHeight = 1.2, LineHeight = 1.2,
TextSize = theme.TextSize.Body, TextSize = theme.TextSize.Body,
@ -98,7 +151,7 @@ function Listing:render()
Size = self.containerSize:map(function(value) Size = self.containerSize:map(function(value)
local textBounds = getTextBoundsAsync( local textBounds = getTextBoundsAsync(
self.props.description, callerInfoFromSource.Description,
theme.Font.Thin, theme.Font.Thin,
theme.TextSize.Body, theme.TextSize.Body,
value.X - 40, value.X - 40,
@ -114,6 +167,7 @@ function Listing:render()
Layout = e("UIListLayout", { Layout = e("UIListLayout", {
VerticalAlignment = Enum.VerticalAlignment.Center, VerticalAlignment = Enum.VerticalAlignment.Center,
HorizontalAlignment = Enum.HorizontalAlignment.Left,
FillDirection = Enum.FillDirection.Vertical, FillDirection = Enum.FillDirection.Vertical,
SortOrder = Enum.SortOrder.LayoutOrder, SortOrder = Enum.SortOrder.LayoutOrder,
Padding = UDim.new(0, 6), Padding = UDim.new(0, 6),
@ -148,4 +202,4 @@ function Listing:render()
end) end)
end end
return Listing return SourceListing

View file

@ -6,11 +6,12 @@ local Roact = require(Packages.Roact)
local Assets = require(Plugin.Assets) local Assets = require(Plugin.Assets)
local Theme = require(Plugin.App.Theme) local Theme = require(Plugin.App.Theme)
local getThirdPartyIcon = require(Plugin.getThirdPartyIcon)
local IconButton = require(Plugin.App.Components.IconButton) local IconButton = require(Plugin.App.Components.IconButton)
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)
local Listing = require(script.Listing) local SourceListing = require(script.SourceListing)
local e = Roact.createElement local e = Roact.createElement
@ -89,23 +90,20 @@ function PermissionsPage:render()
continue continue
end end
local meta = self.props.headlessAPI:_getMetaFromSource(source) local callerInfoFromSource = self.props.headlessAPI:_getCallerInfoFromSource(source)
sources[source] = e(Listing, { sources[source] = e(SourceListing, {
layoutOrder = string.byte(source), layoutOrder = string.byte(source),
transparency = self.props.transparency, transparency = self.props.transparency,
name = meta.Name, callerInfoFromSource = callerInfoFromSource,
description = string.format(
"%s plugin%s", icon = getThirdPartyIcon(source),
meta.Type,
if meta.Creator then " by " .. meta.Creator else ""
),
onClick = function() onClick = function()
self.props.onEdit( self.props.onEdit(
self.props.headlessAPI._sourceToPlugin[source], self.props.headlessAPI._sourceToPlugin[source],
source, source,
meta, callerInfoFromSource,
self.props.headlessAPI._permissions[source] or {} self.props.headlessAPI._permissions[source] or {}
) )
end, end,

View file

@ -294,6 +294,7 @@ function App:addNotification(
end end
function App:addThirdPartyNotification( function App:addThirdPartyNotification(
name: string,
source: string, source: string,
text: string, text: string,
timeout: number?, timeout: number?,
@ -315,6 +316,7 @@ function App:addThirdPartyNotification(
timeout = timeout or 3, timeout = timeout or 3,
actions = actions, actions = actions,
thirdParty = true, thirdParty = true,
name = name,
source = source, source = source,
} }
@ -988,8 +990,9 @@ function App:render()
}) })
end, end,
onEdit = function(plugin, source, meta, apiMap) onEdit = function(plugin, source, callerInfo, apiMap)
local name = meta.Name .. if meta.Creator then " by " .. meta.Creator else "" local name = callerInfo.Name
.. if callerInfo.Creator then " by " .. callerInfo.Creator else ""
local apiList = {} local apiList = {}
for api in apiMap do for api in apiMap do
table.insert(apiList, api) table.insert(apiList, api)

View file

@ -10,13 +10,21 @@ local Config = require(Plugin.Config)
local Settings = require(Plugin.Settings) local Settings = require(Plugin.Settings)
local ApiContext = require(Plugin.ApiContext) local ApiContext = require(Plugin.ApiContext)
local cloudIdInfoCache = {} local cloudIdProductInfoCache = {}
local apiPermissionAllowlist = { local apiPermissionAllowlist = {
Version = true, Version = true,
ProtocolVersion = true, ProtocolVersion = true,
RequestAccess = true, RequestAccess = true,
} }
export type CallerInfo = {
Type: "Local" | "Cloud" | "Studio",
Name: string,
Description: string,
Creator: string,
HasVerifiedBadge: boolean?,
}
local API = {} local API = {}
function API.new(app) function API.new(app)
@ -99,14 +107,14 @@ function API.new(app)
local cloudId, cloudInstance = string.match(topLevel, "cloud_(%d-)%.(.-)[^%w_%-]") local cloudId, cloudInstance = string.match(topLevel, "cloud_(%d-)%.(.-)[^%w_%-]")
if cloudId then if cloudId then
local info = cloudIdInfoCache[cloudId] local info = cloudIdProductInfoCache[cloudId]
if info then if info then
return info.Name .. " by " .. info.Creator.Name return info.Name .. " by " .. info.Creator.Name
else else
local success, newInfo = local success, newInfo =
pcall(MarketplaceService.GetProductInfo, MarketplaceService, tonumber(cloudId), Enum.InfoType.Asset) pcall(MarketplaceService.GetProductInfo, MarketplaceService, tonumber(cloudId), Enum.InfoType.Asset)
if success then if success then
cloudIdInfoCache[cloudId] = newInfo cloudIdProductInfoCache[cloudId] = newInfo
return newInfo.Name .. " by " .. newInfo.Creator.Name return newInfo.Name .. " by " .. newInfo.Creator.Name
end end
end end
@ -119,41 +127,110 @@ function API.new(app)
return "Command Bar" return "Command Bar"
end end
function Rojo:_getMetaFromSource(source) function Rojo:_getCallerInfo(): CallerInfo
local localPlugin = string.match(source, "user_(.+)") local traceback = string.split(debug.traceback(), "\n")
local topLevel = traceback[#traceback - 1]
local localPlugin = string.match(topLevel, "user_(.-)%.")
if localPlugin then if localPlugin then
return { return {
Type = "Local", Type = "Local",
Name = localPlugin, Name = localPlugin,
Description = "Locally installed plugin.",
Creator = "Unknown",
HasVerifiedBadge = false,
} }
end end
local cloudId = string.match(source, "cloud_(%d+)") local cloudId, cloudInstance = string.match(topLevel, "cloud_(%d-)%.(.-)[^%w_%-]")
if cloudId then if cloudId then
local info = cloudIdInfoCache[cloudId] local info = cloudIdProductInfoCache[cloudId]
if not info then
local success, newInfo =
pcall(MarketplaceService.GetProductInfo, MarketplaceService, tonumber(cloudId), Enum.InfoType.Asset)
if success then
cloudIdProductInfoCache[cloudId] = newInfo
info = newInfo
end
end
if info then if info then
return { return {
Type = "Cloud", Type = "Cloud",
Name = info.Name, Name = info.Name,
Description = info.Description,
Creator = info.Creator.Name, Creator = info.Creator.Name,
HasVerifiedBadge = info.Creator.HasVerifiedBadge,
} }
else else
local success, newInfo =
pcall(MarketplaceService.GetProductInfo, MarketplaceService, tonumber(cloudId), Enum.InfoType.Asset)
if success then
cloudIdInfoCache[cloudId] = newInfo
return { return {
Type = "Cloud", Type = "Cloud",
Name = newInfo.Name, Name = cloudInstance,
Creator = newInfo.Creator.Name, Description = "Could not retrieve plugin asset info.",
Creator = "Unknown",
HasVerifiedBadge = false,
} }
end end
end end
end
return { return {
Type = "Studio", Type = "Studio",
Name = "Command Bar", Name = "Command Bar",
Description = "Command bar in Roblox Studio.",
Creator = "Unknown",
HasVerifiedBadge = false,
}
end
function Rojo:_getCallerInfoFromSource(source: string): CallerInfo
local localPlugin = string.match(source, "user_(.+)")
if localPlugin then
return {
Type = "Local",
Name = localPlugin,
Description = "Locally installed plugin.",
Creator = "Unknown",
HasVerifiedBadge = false,
}
end
local cloudId = string.match(source, "cloud_(%d+)")
if cloudId then
local info = cloudIdProductInfoCache[cloudId]
if not info then
local success, newInfo =
pcall(MarketplaceService.GetProductInfo, MarketplaceService, tonumber(cloudId), Enum.InfoType.Asset)
if success then
cloudIdProductInfoCache[cloudId] = newInfo
info = newInfo
end
end
if info then
return {
Type = "Cloud",
Name = info.Name,
Description = info.Description,
Creator = info.Creator.Name,
HasVerifiedBadge = info.Creator.HasVerifiedBadge,
}
else
return {
Type = "Cloud",
Name = source,
Description = "Could not retrieve plugin asset info.",
Creator = "Unknown",
HasVerifiedBadge = false,
}
end
end
return {
Type = "Studio",
Name = "Command Bar",
Description = "Command bar in Roblox Studio.",
Creator = "Unknown",
HasVerifiedBadge = false,
} }
end end
@ -418,7 +495,13 @@ function API.new(app)
end end
end end
return app:addThirdPartyNotification(Rojo:_getCallerName(), msg, timeout, sanitizedActions) return app:addThirdPartyNotification(
Rojo:_getCallerName(),
Rojo:_getCallerSource(),
msg,
timeout,
sanitizedActions
)
end end
Rojo._apiDescriptions.GetHostAndPort = { Rojo._apiDescriptions.GetHostAndPort = {

View file

@ -0,0 +1,17 @@
local Rojo = script:FindFirstAncestor("Rojo")
local Plugin = Rojo.Plugin
local Assets = require(Plugin.Assets)
return function(source: string?)
if not source then
return Assets.Images.ThirdPartyPlugin
end
local sourceId = string.match(source, "cloud_(%d+)")
if not sourceId then
return Assets.Images.ThirdPartyPlugin
end
return string.format("rbxthumb://type=Asset&id=%s&w=150&h=150", sourceId)
end