kampfkarren / hoarcekat Goto Github PK
View Code? Open in Web Editor NEWA Storybook-like plugin that makes it easy to preview individual UI elements. Especially useful with Roact.
License: Other
A Storybook-like plugin that makes it easy to preview individual UI elements. Especially useful with Roact.
License: Other
When playtesting with Hoarcekat installed, switching to the server view and then back to the client view causes the Hoarcekat plugin GUI to become enabled (the UI will be empty, however, and the plugin state is disabled)
Something Roblox's internal Horsecat has is the ability to inspect the underlying Gui elements that are being rendered. Having a button somewhere that sets the Selection to the immediate descendant of the Preview frame would be great for easier debugging.
I have a created a test story using which utilizes the new UIListLayout Flex Feature
but Hoarcekat does not display it correctly.
If you create this same setup in Roblox studio StarterGUI it will display normally with the YellowTest
frame being the same size as it's parent GreenFill
.
But in Hoarcekat window view it shows the YellowTest
overflowing.
If you click the "UI" display it displays it incorrectly again (Yellow has disappeared). And then if you click "UI" again to bring it back to window preview the layout is different again (and still incorrect).
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local React = require(ReplicatedStorage:WaitForChild("Packages"):WaitForChild("react"))
local ReactRoblox = require(ReplicatedStorage:WaitForChild("Packages"):WaitForChild("react-roblox"))
return function(target)
local app = React.createElement("Frame", {
Size = UDim2.fromScale(0.5, 0.5),
AnchorPoint = Vector2.new(0.5, 0.5),
Position = UDim2.fromScale(0.5,0.5),
}, {
UIListLayout = React.createElement("UIListLayout", {
SortOrder = Enum.SortOrder.LayoutOrder
}),
RedTop = React.createElement("Frame", {
Size = UDim2.fromScale(1,1),
BackgroundColor3 = Color3.new(1,0,0),
BackgroundTransparency = 0.2,
LayoutOrder = 1
}, {
UIAspectRatioConstraint = React.createElement("UIAspectRatioConstraint", {
AspectRatio = 4
})
}),
GreenFill = React.createElement("Frame", {
Size = UDim2.fromScale(1,1),
BackgroundColor3 = Color3.new(0,1,0),
BackgroundTransparency = 0.2,
LayoutOrder = 2
}, {
UIFlexItem = React.createElement("UIFlexItem", {
FlexMode = Enum.UIFlexMode.Fill,
}),
YellowTest = React.createElement("Frame", {
Size = UDim2.fromScale(1,1),
BackgroundColor3 = Color3.new(1, 1, 0),
BackgroundTransparency = 0.5
})
}),
BlueTop = React.createElement("Frame", {
Size = UDim2.fromScale(1,1),
BackgroundColor3 = Color3.new(0,0,2),
BackgroundTransparency = 0.2,
LayoutOrder = 3
}, {
UIAspectRatioConstraint = React.createElement("UIAspectRatioConstraint", {
AspectRatio = 4
})
}),
})
local rootInstance = Instance.new("Folder")
rootInstance.Name = "Preview"
rootInstance.Parent = target
local gui = ReactRoblox.createBlockingRoot(rootInstance)
gui:render(app)
return function()
gui:unmount()
end
end
I tried using hoarcekat (latest release, not on the roblox marketplace) with modulescripts that use Nevermore or Ozzypig's Modules.
They load Roact in .story modulescripts and Roact component modulescripts.
Whenever I try to open a roact component in Hoarcekat, it only works if I don't use any module loader and just use the normal require() (and type out the entire path to the required modules).
However, if I override require() with one module loader (with correct paths/syntax), Hoarcekat spews out a huge wall of errors.
A consistent error message when using both Nevermore or Modules is:
Hoarcekat.Vendor.Rodux.NoYield:26: Attempted to yield inside changed event!
Oddly, this wasn't the case a few months ago (I think around october), where I used Nevermore in my story and component modules, meaning that I was apparently using the same release of Hoarcekat.
Hoarcekat stories cannot yield, but the error for this is a side effect of Roact, which makes the traceback harder to read.
Hoarcekat should catch yielding stories, then provide a clear error for this, rather than waiting for Roact to explode.
_G doesn't get cleared when Hoarcekat refreshes the preview in the same way other parts of the environment do
I'm probably awful at trying to explain the problem, so I'll show an example story thing.
local function anotherModule(thing)
if _G[thing] then
--[[Will occur the second time hoarcekat re-
renders the story, as _G[thing] is still there]]
error("whoops, this is assuming _G is clean")
end
_G[thing] = "value"
end
return function(target)
local irrelevantExampleElement = Instance.new("ImageButton")
irrelevantExampleElement.Parent = target
anotherModule("foo")
return function()
irrelevantExampleElement:Destory()
end
end
Now that LogService:ClearOutput()
is a thing, let's have a preference that clears the output every update, as I currently do it manually every time.
The same destructor can be called twice: Once when a story is closed, and again when it's re-opened.
The following story:
return function(target)
local f = Instance.new("Frame")
f.Size = UDim2.new(0, 100, 0, 100)
f.Parent = target
local destructorId = math.random(1, 9999)
return function()
print("unmount called", destructorId)
f:Destroy()
end
end
Will print the same value twice in a row when you keep "toggling" the story:
I feel like this shouldn't happen because the destructor should only be called when unloading a story, not when it's re-opened.
Happens frequently when dealing with stories that error.
Running hoarcekat while the game is running leads to performance impact as it tries to scan the game/run pre-existing scripts. Please don't enable the plugin during this mode.
When exiting from 'Run' mode, regardless of current or prior visibility or position, Hoarcekat's widget makes itself visible as a blank pane. It is not movable or removable at this time, and it can only be resized to a limited size. There are other cases where this behavior occurs, but difficult to pin down.
Because source is loaded with loadstring
, errors are hard to find because the source location is simply the source string.
If HoarceKat would pass the script location (like script:GetFullName()
) to the second parameter to loadstring
, it would enhance the error messages.
Let me know if you want me to submit a PR ๐
I have forked Roblox's chat and this has a bunch of stories. I don't want to touch those and it clutters my storybook page.
There should be an ignore list option (either in some settings option or a button at the top) where you can specify folders, paths or some other way of ignoring certain paths.
When expanding and then shrinking the main widget horizontally, the contents of the list frame expand horizontally but do not shrink, causing the canvas size of the scrolling frame containing the stories items to be larger than it should. This makes the scrollbar appear at the wrong time / at the wrong size.
I sent a pr which helps in many ways, including being able to have the correct mouse position, #19 (by being on-screen). But technically, since you are already setting the environment of each module, you should easily be able to create a mock UserInputService with GetMouseLocation and all the other mouse features, returning the plugin mouse. The problem with the current mouse is that when it goes onto a plugin gui, it just stops updating and acts as if it hasn't moved.
I need a Plugin
instance to test a component that changes Plugin:GetMouse().Icon
.
However, I can't save the Plugin
to _G
(stories in hoarcekat have a separate _G
)
I hope I can index the plugin
in the story easily :D
In Plugin.Components.Preview > #monkeyRequire
:
local fenv = setmetatable({
require = monkeyRequire,
script = otherScript,
_G = state.monkeyGlobalTable,
--add this code: plugin = plugin,
}, {
__index = getfenv(),
})
I believe the gif should be self-explanatory enough. This bug happens every time and is very easy to replicate.
The plugin seems to break when collaborative editing is enabled and it tries to access an unpublished draft. The component shows up in the preview window sidebar, however, you get errors when trying to toggle it. From my experimenting, this is 100% reproducible.
Test.story
module with the code from the exampleThis is the error I get at the bottom of my output window:
Error executing story: Hoarcekat.Plugin.Components.Preview:118: attempt to call a table value
Hoarcekat.Plugin.Components.Preview:118
Hoarcekat.Plugin.Components.Preview:117 function refreshPreview
Hoarcekat.Plugin.Components.Preview:83 function didUpdate
Hoarcekat.Vendor.Roact.Component:460 function __resolveUpdate
Hoarcekat.Vendor.Roact.Component:394 function __update
Hoarcekat.Vendor.Roact.createReconciler:227 function updateVirtualNode
Hoarcekat.Vendor.Roact.createReconciler:67 function updateChildren
Hoarcekat.Vendor.Roact.createReconciler:108 function updateVirtualNodeWithRenderResult
Hoarcekat.Vendor.Roact.Component:456 function __resolveUpdate
Hoarcekat.Vendor.Roact.Component:394 function __update
Hoarcekat.Vendor.Roact.Component:161 function setState
Hoarcekat.Vendor.RoactRodux.connect:162
Hoarcekat.Vendor.Rodux.Signal:70 function fire
Hoarcekat.Vendor.Rodux.Store:125
How could we interact with the player mouse position?
19:26:33.112 - Error requiring story: Source is not a valid member of Folder
19:26:33.112 - Hoarcekat.Plugin.Components.Preview:30
To recreate, open a story and click the UI floating button to put it into the viewport (sorry if that's not what it's called), then click the Preview button. The result is that it selects the location in the explorer (inside PluginService) where it would be if the story was inside the plugin window, instead of CoreGui.Hoarcekat display where it actually is.
Also I didn't see it noted anywhere that "Show Plugin Gui Service" had to be checked in studio settings for the preview button to work. Maybe this could be added to the README (plus the setting to show CoreGui).
I can't find any documentation about the buttons shown above. May I ask what the intention of these buttons are. The 'UI' button appears to just turn off the display; which leads me to question why we might need that? The magnifying glass appears to select the root object but I'm not able to dig down into the hierarchy which I feel would be useful.
Any help much appreciated.
If I have a component which includes a ScreenGui then it won't render in the hoarcekat preview window (only in the studio "UI" window).
For example, this doesn't render:
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local React = require(ReplicatedStorage:WaitForChild("Packages"):WaitForChild("react"))
local ReactRoblox = require(ReplicatedStorage:WaitForChild("Packages"):WaitForChild("react-roblox"))
return function(target)
local app = React.createElement("ScreenGui", nil, {
Hello = React.createElement("TextLabel", {
Size = UDim2.fromScale(0.5, 0.5),
Position = UDim2.fromScale(0.5, 0.5),
AnchorPoint = Vector2.new(0.5, 0.5),
Text = "Hello World"
})
})
local rootInstance = Instance.new("Folder")
rootInstance.Name = "Testing"
rootInstance.Parent = target
local gui = ReactRoblox.createBlockingRoot(rootInstance)
gui:render(app)
return function()
gui:unmount()
end
end
But if i remove the ScreenGui then it will:
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local React = require(ReplicatedStorage:WaitForChild("Packages"):WaitForChild("react"))
local ReactRoblox = require(ReplicatedStorage:WaitForChild("Packages"):WaitForChild("react-roblox"))
return function(target)
local app = React.createElement("TextLabel", {
Size = UDim2.fromScale(0.5, 0.5),
Position = UDim2.fromScale(0.5, 0.5),
AnchorPoint = Vector2.new(0.5, 0.5),
Text = "Hello World"
})
local rootInstance = Instance.new("Folder")
rootInstance.Name = "Testing"
rootInstance.Parent = target
local gui = ReactRoblox.createBlockingRoot(rootInstance)
gui:render(app)
return function()
gui:unmount()
end
end
I will be rewriting Hoarcekat entirely in React
as it's currently still in Roact
and also uses class components across the board.
Spoke with Kampf and discussion has started on this effort.
This should make it easier to maintain, and more enjoyable for developers who wish to contribute to Hoarcekat in the future.
When clicking on the example story component provided in the /examples/Counter.story.lua, this error surfaces :
Error executing story: PluginDebugService.user_Hoarcekat.rbxm.Hoarcekat.Plugin.Components.Preview:118: attempt to call a table value
PluginDebugService.user_Hoarcekat.rbxm.Hoarcekat.Plugin.Components.Preview:118
PluginDebugService.user_Hoarcekat.rbxm.Hoarcekat.Plugin.Components.Preview:117 function refreshPreview
PluginDebugService.user_Hoarcekat.rbxm.Hoarcekat.Plugin.Components.Preview:83 function didUpdate
PluginDebugService.user_Hoarcekat.rbxm.Hoarcekat.Vendor.Roact.Component:460 function __resolveUpdate
PluginDebugService.user_Hoarcekat.rbxm.Hoarcekat.Vendor.Roact.Component:394 function __update
PluginDebugService.user_Hoarcekat.rbxm.Hoarcekat.Vendor.Roact.createReconciler:227 function updateVirtualNode
PluginDebugService.user_Hoarcekat.rbxm.Hoarcekat.Vendor.Roact.createReconciler:67 function updateChildren
PluginDebugService.user_Hoarcekat.rbxm.Hoarcekat.Vendor.Roact.createReconciler:108 function updateVirtualNodeWithRenderResult
PluginDebugService.user_Hoarcekat.rbxm.Hoarcekat.Vendor.Roact.Component:456 function __resolveUpdate
PluginDebugService.user_Hoarcekat.rbxm.Hoarcekat.Vendor.Roact.Component:394 function __update
PluginDebugService.user_Hoarcekat.rbxm.Hoarcekat.Vendor.Roact.Component:161 function setState
PluginDebugService.user_Hoarcekat.rbxm.Hoarcekat.Vendor.RoactRodux.connect:162
PluginDebugService.user_Hoarcekat.rbxm.Hoarcekat.Vendor.Rodux.Signal:70 function fire
PluginDebugService.user_Hoarcekat.rbxm.Hoarcekat.Vendor.Rodux.Store:125
This error points to the xpcall in Preview.lua :
local execOk, cleanup = xpcall(function()
return result(self.expand and self.display or self.previewRef:getValue()) -- << 118 - error on this line
end, debug.traceback)
if not execOk then
warn("Error executing story: " .. cleanup)
return
end
At time of writing, that example is :
return function(target)
local counter = 0
local instance = Instance.new("TextButton")
instance.AnchorPoint = Vector2.new(0.5, 0.5)
instance.Position = UDim2.fromScale(0.5, 0.5)
instance.Size = UDim2.fromScale(0.3, 0.1)
instance.Text = counter
instance.MouseButton1Click:Connect(function()
counter = counter + 1
instance.Text = counter
end)
instance.Parent = target
return function()
instance:Destroy()
end
end
This error is present in version [1.2.0] and [1.1.1] just on different lines.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.