diff --git a/plugin/src/App/Notifications.lua b/plugin/src/App/Notifications.lua index 1204625d..98dbb133 100644 --- a/plugin/src/App/Notifications.lua +++ b/plugin/src/App/Notifications.lua @@ -12,6 +12,7 @@ local Theme = require(Plugin.App.Theme) local Assets = require(Plugin.Assets) local bindingUtil = require(Plugin.App.bindingUtil) local getTextBoundsAsync = require(Plugin.App.getTextBoundsAsync) +local getThirdPartyIcon = require(Plugin.getThirdPartyIcon) local BorderedContainer = require(Plugin.App.Components.BorderedContainer) local TextButton = require(Plugin.App.Components.TextButton) @@ -128,6 +129,10 @@ function Notification:render() ) end) + local logoAssetId = if self.props.thirdParty + then getThirdPartyIcon(self.props.source) + else Assets.Images.PluginButton + return e("TextButton", { BackgroundTransparency = 1, Size = size, @@ -149,7 +154,7 @@ function Notification:render() }, { Logo = e("ImageLabel", { ImageTransparency = transparency, - Image = Assets.Images.PluginButton, + Image = logoAssetId, BackgroundTransparency = 1, Size = UDim2.new(0, logoSize, 0, logoSize), Position = UDim2.new(0, 0, 0, 0), @@ -225,14 +230,15 @@ function Notifications:render() for id, notif in self.props.notifications do notifs["NotifID_" .. id] = e(Notification, { - soundPlayer = self.props.soundPlayer, + layoutOrder = (notif.timestamp - baseClock), text = notif.text, timestamp = notif.timestamp, timeout = notif.timeout, actions = notif.actions, - source = notif.source, + soundPlayer = self.props.soundPlayer, thirdParty = notif.thirdParty, - layoutOrder = (notif.timestamp - baseClock), + name = notif.name, + source = notif.source, onClose = function() self.props.onClose(id) end, diff --git a/plugin/src/App/PermissionPopup.lua b/plugin/src/App/PermissionPopup.lua index 8a01dc44..662575a1 100644 --- a/plugin/src/App/PermissionPopup.lua +++ b/plugin/src/App/PermissionPopup.lua @@ -7,6 +7,8 @@ local Roact = require(Packages.Roact) local Assets = require(Plugin.Assets) local Theme = require(Plugin.App.Theme) +local getThirdPartyIcon = require(Plugin.getThirdPartyIcon) + local ScrollingFrame = require(Plugin.App.Components.ScrollingFrame) local TextButton = require(Plugin.App.Components.TextButton) @@ -25,11 +27,7 @@ function PermissionPopup:render() return Theme.with(function(theme) local settingsTheme = theme.Settings - 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 iconAsset = getThirdPartyIcon(self.props.source) local apiRequests = { Event = {}, @@ -171,7 +169,7 @@ function PermissionPopup:render() ThirdPartyIcon = e("ImageLabel", { BackgroundTransparency = 1, Size = UDim2.new(0, 32, 0, 32), - Image = thumbnail, + Image = iconAsset, LayoutOrder = 1, }), diff --git a/plugin/src/App/StatusPages/Permissions/Listing.lua b/plugin/src/App/StatusPages/Permissions/SourceListing.lua similarity index 59% rename from plugin/src/App/StatusPages/Permissions/Listing.lua rename to plugin/src/App/StatusPages/Permissions/SourceListing.lua index d82f62e9..579a8229 100644 --- a/plugin/src/App/StatusPages/Permissions/Listing.lua +++ b/plugin/src/App/StatusPages/Permissions/SourceListing.lua @@ -11,19 +11,22 @@ local Theme = require(Plugin.App.Theme) local getTextBoundsAsync = require(Plugin.App.getTextBoundsAsync) local SlicedImage = require(Plugin.App.Components.SlicedImage) +local Tooltip = require(Plugin.App.Components.Tooltip) local e = Roact.createElement 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.containerSize, self.setContainerSize = Roact.createBinding(Vector2.new(0, 0)) end -function Listing:render() +function SourceListing:render() + local callerInfoFromSource = self.props.callerInfoFromSource + return Theme.with(function(theme) return e("Frame", { 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), BackgroundTransparency = 1, }, { - Name = e("TextLabel", { - Text = self.props.name, - FontFace = theme.Font.Bold, - TextSize = theme.TextSize.Medium, - TextColor3 = theme.Settings.Setting.NameColor, - TextXAlignment = Enum.TextXAlignment.Left, - TextTransparency = self.props.transparency, - - Size = UDim2.new(1, 0, 0, 17), - - LayoutOrder = 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", { + Text = callerInfoFromSource.Name, + FontFace = theme.Font.Bold, + TextSize = theme.TextSize.Medium, + TextColor3 = theme.Settings.Setting.NameColor, + TextXAlignment = Enum.TextXAlignment.Left, + TextTransparency = self.props.transparency, + AutomaticSize = Enum.AutomaticSize.X, + Size = UDim2.new(0, 0, 0, theme.TextSize.Medium), + Position = UDim2.new(0, theme.TextSize.Medium + theme.TextSize.Body + 8, 0, 0), + 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", { - Text = self.props.description, + Text = callerInfoFromSource.Description, FontFace = theme.Font.Thin, LineHeight = 1.2, TextSize = theme.TextSize.Body, @@ -98,7 +151,7 @@ function Listing:render() Size = self.containerSize:map(function(value) local textBounds = getTextBoundsAsync( - self.props.description, + callerInfoFromSource.Description, theme.Font.Thin, theme.TextSize.Body, value.X - 40, @@ -114,6 +167,7 @@ function Listing:render() Layout = e("UIListLayout", { VerticalAlignment = Enum.VerticalAlignment.Center, + HorizontalAlignment = Enum.HorizontalAlignment.Left, FillDirection = Enum.FillDirection.Vertical, SortOrder = Enum.SortOrder.LayoutOrder, Padding = UDim.new(0, 6), @@ -148,4 +202,4 @@ function Listing:render() end) end -return Listing +return SourceListing diff --git a/plugin/src/App/StatusPages/Permissions/init.lua b/plugin/src/App/StatusPages/Permissions/init.lua index 5a44ce27..7db4d180 100644 --- a/plugin/src/App/StatusPages/Permissions/init.lua +++ b/plugin/src/App/StatusPages/Permissions/init.lua @@ -6,11 +6,12 @@ local Roact = require(Packages.Roact) local Assets = require(Plugin.Assets) local Theme = require(Plugin.App.Theme) +local getThirdPartyIcon = require(Plugin.getThirdPartyIcon) local IconButton = require(Plugin.App.Components.IconButton) local ScrollingFrame = require(Plugin.App.Components.ScrollingFrame) local Tooltip = require(Plugin.App.Components.Tooltip) -local Listing = require(script.Listing) +local SourceListing = require(script.SourceListing) local e = Roact.createElement @@ -89,23 +90,20 @@ function PermissionsPage:render() continue end - local meta = self.props.headlessAPI:_getMetaFromSource(source) - sources[source] = e(Listing, { + local callerInfoFromSource = self.props.headlessAPI:_getCallerInfoFromSource(source) + sources[source] = e(SourceListing, { 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 "" - ), + callerInfoFromSource = callerInfoFromSource, + + icon = getThirdPartyIcon(source), onClick = function() self.props.onEdit( self.props.headlessAPI._sourceToPlugin[source], source, - meta, + callerInfoFromSource, self.props.headlessAPI._permissions[source] or {} ) end, diff --git a/plugin/src/App/init.lua b/plugin/src/App/init.lua index 877b2ff8..bc665116 100644 --- a/plugin/src/App/init.lua +++ b/plugin/src/App/init.lua @@ -294,6 +294,7 @@ function App:addNotification( end function App:addThirdPartyNotification( + name: string, source: string, text: string, timeout: number?, @@ -315,6 +316,7 @@ function App:addThirdPartyNotification( timeout = timeout or 3, actions = actions, thirdParty = true, + name = name, source = source, } @@ -988,8 +990,9 @@ function App:render() }) end, - onEdit = function(plugin, source, meta, apiMap) - local name = meta.Name .. if meta.Creator then " by " .. meta.Creator else "" + onEdit = function(plugin, source, callerInfo, apiMap) + local name = callerInfo.Name + .. if callerInfo.Creator then " by " .. callerInfo.Creator else "" local apiList = {} for api in apiMap do table.insert(apiList, api) diff --git a/plugin/src/HeadlessAPI.lua b/plugin/src/HeadlessAPI.lua index 9bdad496..2042fdc0 100644 --- a/plugin/src/HeadlessAPI.lua +++ b/plugin/src/HeadlessAPI.lua @@ -10,13 +10,21 @@ local Config = require(Plugin.Config) local Settings = require(Plugin.Settings) local ApiContext = require(Plugin.ApiContext) -local cloudIdInfoCache = {} +local cloudIdProductInfoCache = {} local apiPermissionAllowlist = { Version = true, ProtocolVersion = true, RequestAccess = true, } +export type CallerInfo = { + Type: "Local" | "Cloud" | "Studio", + Name: string, + Description: string, + Creator: string, + HasVerifiedBadge: boolean?, +} + local API = {} function API.new(app) @@ -99,14 +107,14 @@ function API.new(app) local cloudId, cloudInstance = string.match(topLevel, "cloud_(%d-)%.(.-)[^%w_%-]") if cloudId then - local info = cloudIdInfoCache[cloudId] + local info = cloudIdProductInfoCache[cloudId] if info then return info.Name .. " by " .. info.Creator.Name else local success, newInfo = pcall(MarketplaceService.GetProductInfo, MarketplaceService, tonumber(cloudId), Enum.InfoType.Asset) if success then - cloudIdInfoCache[cloudId] = newInfo + cloudIdProductInfoCache[cloudId] = newInfo return newInfo.Name .. " by " .. newInfo.Creator.Name end end @@ -119,41 +127,110 @@ function API.new(app) return "Command Bar" end - function Rojo:_getMetaFromSource(source) - local localPlugin = string.match(source, "user_(.+)") + function Rojo:_getCallerInfo(): CallerInfo + local traceback = string.split(debug.traceback(), "\n") + local topLevel = traceback[#traceback - 1] + + local localPlugin = string.match(topLevel, "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+)") + local cloudId, cloudInstance = string.match(topLevel, "cloud_(%d-)%.(.-)[^%w_%-]") 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 return { Type = "Cloud", Name = info.Name, + Description = info.Description, Creator = info.Creator.Name, + HasVerifiedBadge = info.Creator.HasVerifiedBadge, } else - local success, newInfo = - pcall(MarketplaceService.GetProductInfo, MarketplaceService, tonumber(cloudId), Enum.InfoType.Asset) - if success then - cloudIdInfoCache[cloudId] = newInfo - return { - Type = "Cloud", - Name = newInfo.Name, - Creator = newInfo.Creator.Name, - } - end + return { + Type = "Cloud", + Name = cloudInstance, + 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 + + 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 @@ -418,7 +495,13 @@ function API.new(app) end end - return app:addThirdPartyNotification(Rojo:_getCallerName(), msg, timeout, sanitizedActions) + return app:addThirdPartyNotification( + Rojo:_getCallerName(), + Rojo:_getCallerSource(), + msg, + timeout, + sanitizedActions + ) end Rojo._apiDescriptions.GetHostAndPort = { diff --git a/plugin/src/getThirdPartyIcon.lua b/plugin/src/getThirdPartyIcon.lua new file mode 100644 index 00000000..7c6c856c --- /dev/null +++ b/plugin/src/getThirdPartyIcon.lua @@ -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