diff --git a/.github/workflows/run-luau-tests.yml b/.github/workflows/run-luau-tests.yml index e9a9c7b..c4bb002 100644 --- a/.github/workflows/run-luau-tests.yml +++ b/.github/workflows/run-luau-tests.yml @@ -8,14 +8,14 @@ on: paths: - "src/**" - "pesde.toml" - - "development.project.json" + - "test.project.json" - ".github/workflows/run-luau-tests.yml" pull_request: types: [opened, reopened, synchronize, ready_for_review] paths: - "src/**" - "pesde.toml" - - "development.project.json" + - "test.project.json" - ".github/workflows/run-luau-tests.yml" concurrency: @@ -42,7 +42,7 @@ jobs: run: pesde install - name: Build Rojo project - run: rojo build development.project.json -o build.rbxl + run: rojo build test.project.json -o build.rbxl - name: Upload project uses: actions/upload-artifact@v4.4.0 diff --git a/pesde.lock b/pesde.lock index e7ad2dc..d7facd3 100644 --- a/pesde.lock +++ b/pesde.lock @@ -18,16 +18,16 @@ frktest = [{ name = "itsfrank/frktest", version = "^0.0.2", index = "https://git luau-lsp = [{ name = "pesde/luau_lsp", version = "=1.39.2", index = "https://github.com/pesde-pkg/index", target = "lune" }, "dev"] stylua = [{ name = "pesde/stylua", version = "=2.0.2", index = "https://github.com/pesde-pkg/index", target = "lune" }, "dev"] -[graph."beastslash/ijw@1.1.2 roblox"] -direct = ["ijw", { name = "beastslash/ijw", version = "^1.1.2", index = "default" }, "dev"] +[graph."beastslash/ijw@1.1.3 roblox"] +direct = ["ijw", { name = "beastslash/ijw", version = "^1.1.3", index = "default" }, "dev"] -[graph."beastslash/ijw@1.1.2 roblox".pkg_ref] +[graph."beastslash/ijw@1.1.3 roblox".pkg_ref] ref_ty = "pesde" index_url = "https://github.com/pesde-pkg/index" -[graph."beastslash/ijw@1.1.2 roblox".pkg_ref.dependencies] -luau_lsp = [{ name = "pesde/luau_lsp", version = "^1.47.0", index = "https://github.com/pesde-pkg/index", target = "lune" }, "dev"] -rojo = [{ name = "pesde/rojo", version = "^7.4.4", index = "https://github.com/pesde-pkg/index", target = "lune" }, "dev"] +[graph."beastslash/ijw@1.1.3 roblox".pkg_ref.dependencies] +luau_lsp = [{ name = "pesde/luau_lsp", version = "^1.48.0", index = "https://github.com/pesde-pkg/index", target = "lune" }, "dev"] +rojo = [{ name = "pesde/rojo", version = "^7.5.1", index = "https://github.com/pesde-pkg/index", target = "lune" }, "dev"] scripts = [{ name = "pesde/scripts_rojo", version = "^0.1.0", index = "https://github.com/pesde-pkg/index", target = "lune" }, "dev"] [graph."corecii/greentea@0.4.11 lune".pkg_ref] @@ -85,25 +85,25 @@ index_url = "https://github.com/pesde-pkg/index" ref_ty = "pesde" index_url = "https://github.com/pesde-pkg/index" -[graph."pesde/luau_lsp@1.47.0 lune"] -direct = ["luau_lsp", { name = "pesde/luau_lsp", version = "^1.47.0", index = "default", target = "lune" }, "dev"] +[graph."pesde/luau_lsp@1.48.0 lune"] +direct = ["luau_lsp", { name = "pesde/luau_lsp", version = "^1.48.0", index = "default", target = "lune" }, "dev"] -[graph."pesde/luau_lsp@1.47.0 lune".dependencies] +[graph."pesde/luau_lsp@1.48.0 lune".dependencies] core = ["pesde/toolchainlib@0.1.15 lune", "standard"] option = ["lukadev_0/option@1.2.0 lune", "standard"] result = ["lukadev_0/result@1.2.0 lune", "standard"] -[graph."pesde/luau_lsp@1.47.0 lune".pkg_ref] +[graph."pesde/luau_lsp@1.48.0 lune".pkg_ref] ref_ty = "pesde" index_url = "https://github.com/pesde-pkg/index" -[graph."pesde/luau_lsp@1.47.0 lune".pkg_ref.dependencies] +[graph."pesde/luau_lsp@1.48.0 lune".pkg_ref.dependencies] core = [{ name = "pesde/toolchainlib", version = "^0.1.15", index = "https://github.com/pesde-pkg/index", target = "lune" }, "standard"] option = [{ name = "lukadev_0/option", version = "^1.2.0", index = "https://github.com/pesde-pkg/index" }, "standard"] result = [{ name = "lukadev_0/result", version = "^1.2.0", index = "https://github.com/pesde-pkg/index" }, "standard"] [graph."pesde/rojo@7.5.1 lune"] -direct = ["rojo", { name = "pesde/rojo", version = "^7.4.4", index = "default", target = "lune" }, "dev"] +direct = ["rojo", { name = "pesde/rojo", version = "^7.5.1", index = "default", target = "lune" }, "dev"] [graph."pesde/rojo@7.5.1 lune".dependencies] core = ["pesde/toolchainlib@0.1.15 lune", "standard"] @@ -163,7 +163,7 @@ result = [{ name = "lukadev_0/result", version = "^1.2.0", index = "https://gith unzip = [{ name = "0x5eal/unzip", version = "^0.1.0", index = "https://github.com/pesde-pkg/index", target = "luau" }, "standard"] [graph."wally#chriscerie/react-error-boundary@0.2.0 roblox"] -direct = ["react-error-boundary", { wally = "wally#chriscerie/react-error-boundary", version = "^0.2.0", index = "default" }, "standard"] +direct = ["ReactErrorBoundary", { wally = "wally#chriscerie/react-error-boundary", version = "^0.2.0", index = "default" }, "standard"] [graph."wally#chriscerie/react-error-boundary@0.2.0 roblox".dependencies] Collections = ["wally#jsdotlua/collections@1.2.7 roblox", "standard"] diff --git a/pesde.toml b/pesde.toml index 7880fd3..c9a5bab 100644 --- a/pesde.toml +++ b/pesde.toml @@ -34,11 +34,11 @@ sourcemap_generator = ".pesde/scripts/sourcemap_generator.luau" [dev_dependencies] scripts = { name = "pesde/scripts_rojo", version = "^0.1.0", target = "lune" } -rojo = { name = "pesde/rojo", version = "^7.4.4", target = "lune" } -luau_lsp = { name = "pesde/luau_lsp", version = "^1.47.0", target = "lune" } -ijw = { name = "beastslash/ijw", version = "^1.1.2" } +rojo = { name = "pesde/rojo", version = "^7.5.1", target = "lune" } +luau_lsp = { name = "pesde/luau_lsp", version = "^1.48.0", target = "lune" } +ijw = { name = "beastslash/ijw", version = "^1.1.3" } [dependencies] react = { wally = "jsdotlua/react", version = "^17.2.1" } react-roblox = { wally = "jsdotlua/react-roblox", version = "^17.2.1" } -react-error-boundary = { wally = "chriscerie/react-error-boundary", version = "^0.2.0" } +ReactErrorBoundary = { wally = "chriscerie/react-error-boundary", version = "^0.2.0" } diff --git a/src/DialogueEditor/components/Explorer/components/DialogueGroupContainer/DialogueGroupContainer.test.luau b/src/DialogueEditor/components/Explorer/components/DialogueGroupContainer/DialogueGroupContainer.test.luau new file mode 100644 index 0000000..1efd013 --- /dev/null +++ b/src/DialogueEditor/components/Explorer/components/DialogueGroupContainer/DialogueGroupContainer.test.luau @@ -0,0 +1,174 @@ +--!strict + +local root = script.Parent.Parent.Parent.Parent.Parent.Parent; +local DialogueGroupContainer = require(script.Parent); +local VirtualService = require(root.VirtualService); +local React = require(root.roblox_packages.react); +local ReactErrorBoundary = require(root.roblox_packages.ReactErrorBoundary); +local ErrorBoundary = ReactErrorBoundary.ErrorBoundary; +local ReactRoblox = require(root.roblox_packages["react-roblox"]); +local IJW = require(root.roblox_packages.ijw); +local expect = IJW.expect; +local describe = IJW.describe; +local it = IJW.it; + +local screenGui: ScreenGui?; +local reactRoot: ReactRoblox.RootType?; + +return { + describe("DialogueGroupContainer", function() + + local function MockComponent(properties: any) + + local dialogueType = properties.type or "Message"; + local onRendered = properties.onRendered; + local selectedScript = properties.selectedScript; + local onErrored = properties.onErrored or function() end; + + React.useEffect(function() + + if onRendered then + + onRendered(); + + end; + + end, {}); + + return React.createElement(ErrorBoundary, { + FallbackComponent = React.Fragment; + onError = onErrored; + }, { + React.createElement(DialogueGroupContainer, { + name = dialogueType; + plugin = VirtualService.mocks.globals.plugin; + selectedScript = selectedScript; + layoutOrder = 1; + }) + }); + + end; + + return { + + it("refreshes the selected dialogue group if a dialogue script is added to a dialogue folder", function() + + expect(function() + + -- Render the component and wait for it to finish rendering. + assert(screenGui, "ScreenGui should be initialized before running tests."); + assert(reactRoot, "React root should be initialized before running tests."); + + local messageCount = 4; + local dialogueFolder = Instance.new("Folder"); + dialogueFolder.Name = "Messages"; + + for i = 1, messageCount do + + local dialogueScript = Instance.new("ModuleScript"); + dialogueScript.Name = tostring(i); + dialogueScript:SetAttribute("DialogueType", "Message"); + dialogueScript:AddTag("DialogueMakerDialogueScript"); + dialogueScript.Parent = dialogueFolder; + + end; + + local selectedScript = Instance.new("ModuleScript"); + selectedScript:SetAttribute("DialogueType", "Message"); + selectedScript:AddTag("DialogueMakerDialogueScript"); + dialogueFolder.Parent = selectedScript; + + local propagatedErrorMessage; + local didRender = false; + local element = React.createElement(MockComponent, { + selectedScript = selectedScript; + onErrored = function(errorMessage) + + propagatedErrorMessage = errorMessage; + + end; + onRendered = function() + + didRender = true; + + end; + }); + + reactRoot:render(element); + repeat task.wait() until didRender or propagatedErrorMessage; + if propagatedErrorMessage then + + error(propagatedErrorMessage); + + end; + + -- Verify that there are four dialogue items rendered. + local dialogueGroupContainer = screenGui:FindFirstChildOfClass("Frame"); + assert(dialogueGroupContainer, "DialogueGroupContainer should be rendered in the ScreenGui."); + + local dialogueGroup = dialogueGroupContainer:FindFirstChild("DialogueGroup"); + assert(dialogueGroup, "DialogueGroup should be rendered in the DialogueGroupContainer."); + + local function getDialogueItems() + + local dialogueItems = {}; + for _, child in dialogueGroup:GetChildren() do + + if child:IsA("Frame") then + + table.insert(dialogueItems, child); + + end; + + end; + + return dialogueItems; + + end; + + expect(#getDialogueItems()).toBe(4); + + -- Add a new dialogue script to the folder and verify that the component refreshes. + local newDialogueScript = Instance.new("ModuleScript"); + newDialogueScript.Name = tostring(messageCount + 1); + newDialogueScript:SetAttribute("DialogueType", "Message"); + newDialogueScript:AddTag("DialogueMakerDialogueScript"); + newDialogueScript.Parent = dialogueFolder; + dialogueGroup.ChildAdded:Wait(); + expect(#getDialogueItems()).toBe(5); + + end).toFinishBeforeSeconds(1); + + end); + + } + + end, { + beforeEach = function() + + VirtualService.mocks.isEnabled = true; + + local newScreenGui = Instance.new("ScreenGui"); + screenGui = newScreenGui; + reactRoot = ReactRoblox.createRoot(newScreenGui); + + end; + afterEach = function() + + VirtualService.mocks.isEnabled = false; + + if reactRoot then + + reactRoot:unmount(); + + end; + + if screenGui then + + screenGui:Destroy(); + + end; + + end; + }) +}; \ No newline at end of file diff --git a/src/DialogueEditor/components/Explorer/components/DialogueGroupContainer/components/DialogueGroup/components/DialogueItem/DialogueItem.test.luau b/src/DialogueEditor/components/Explorer/components/DialogueGroupContainer/components/DialogueGroup/components/DialogueItem/DialogueItem.test.luau index 9fcfae2..83d5e43 100644 --- a/src/DialogueEditor/components/Explorer/components/DialogueGroupContainer/components/DialogueGroup/components/DialogueItem/DialogueItem.test.luau +++ b/src/DialogueEditor/components/Explorer/components/DialogueGroupContainer/components/DialogueGroup/components/DialogueItem/DialogueItem.test.luau @@ -4,6 +4,8 @@ local root = script.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Pare local DialogueItem = require(script.Parent); local VirtualService = require(root.VirtualService); local React = require(root.roblox_packages.react); +local ReactErrorBoundary = require(root.roblox_packages.ReactErrorBoundary); +local ErrorBoundary = ReactErrorBoundary.ErrorBoundary; local ReactRoblox = require(root.roblox_packages["react-roblox"]); local IJW = require(root.roblox_packages.ijw); local expect = IJW.expect; @@ -22,6 +24,7 @@ return { local onRendered = properties.onRendered; local dialogueScript = properties.dialogueScript or Instance.new("ModuleScript"); local layoutOrder = properties.layoutOrder or 1; + local onErrored = properties.onErrored or function() end; React.useEffect(function() @@ -33,12 +36,17 @@ return { end, {}); - return React.createElement(DialogueItem, { - type = dialogueType; - dialogueScript = dialogueScript; - dialogueScriptCount = if dialogueScript.Parent then #dialogueScript.Parent:GetChildren() else 1; - layoutOrder = layoutOrder; - setSettingsTarget = function() end; + return React.createElement(ErrorBoundary, { + FallbackComponent = React.Fragment; + onError = onErrored; + }, { + React.createElement(DialogueItem, { + type = dialogueType; + dialogueScript = dialogueScript; + dialogueScriptCount = if dialogueScript.Parent then #dialogueScript.Parent:GetChildren() else 1; + layoutOrder = layoutOrder; + setSettingsTarget = function() end; + }) }); end; @@ -70,18 +78,29 @@ return { assert(screenGui, "ScreenGui should be initialized before running tests."); assert(reactRoot, "React root should be initialized before running tests."); - local renderedEvent = Instance.new("BindableEvent"); - + local didRender = false; + local propagatedErrorMessage; local element = React.createElement(MockComponent, { + onErrored = function(errorMessage) + + propagatedErrorMessage = errorMessage; + + end; onRendered = function() - renderedEvent:Fire(); + didRender = true; end; }); reactRoot:render(element); - renderedEvent.Event:Wait(); + repeat task.wait() until didRender or propagatedErrorMessage; + + if propagatedErrorMessage then + + error(propagatedErrorMessage); + + end; -- Simulate a click on the component. local dialogueItem = screenGui:FindFirstChildOfClass("Frame"); diff --git a/src/DialogueEditor/components/Explorer/components/DialogueGroupContainer/init.luau b/src/DialogueEditor/components/Explorer/components/DialogueGroupContainer/init.luau index 53ccc92..50dc69e 100644 --- a/src/DialogueEditor/components/Explorer/components/DialogueGroupContainer/init.luau +++ b/src/DialogueEditor/components/Explorer/components/DialogueGroupContainer/init.luau @@ -3,11 +3,14 @@ local Selection = game:GetService("Selection"); local root = script.Parent.Parent.Parent.Parent.Parent; +local VirtualService = require(root.VirtualService); local React = require(root.roblox_packages.react); +local ReactErrorBoundary = require(root.roblox_packages.ReactErrorBoundary); local DialogueGroup = require(script.components.DialogueGroup); local useRefreshDialogueMakerScripts = require(root.DialogueEditor.hooks.useRefreshDialogueMakerScripts); local TabSelector = require(root.DialogueEditor.components.TabSelector); local TabSelectorButton = require(root.DialogueEditor.components.TabSelector.TabSelectorButton); +local useErrorBoundary = ReactErrorBoundary.useErrorBoundary; type DialogueItemType = DialogueGroup.DialogueItemType; @@ -15,25 +18,82 @@ export type DialogueTableBodyProperties = { layoutOrder: number; selectedScript: ModuleScript?; plugin: Plugin; - setSettingsTarget: (target: ModuleScript?) -> (); } local function DialogueGroupContainer(props: DialogueTableBodyProperties) + Selection = if VirtualService.mocks.isEnabled then VirtualService.mocks.services.Selection else Selection; + local layoutOrder = props.layoutOrder; local selectedScript = props.selectedScript; - local setSettingsTarget = props.setSettingsTarget; local refreshDialogueMakerScripts = useRefreshDialogueMakerScripts(); + local errorBoundary = useErrorBoundary(); + + local getSelectedTab = React.useCallback(function(): DialogueItemType? + + if not selectedScript then + + return "Conversation" :: DialogueItemType; + + end; + + for _, dialogueType in {"Message", "Response", "Redirect"} do + + local messagesFolder = selectedScript:FindFirstChild(`{dialogueType}s`); + if messagesFolder and #messagesFolder:GetChildren() > 0 then + + return dialogueType :: DialogueItemType; + + end; + + end; + + return; + + end, {selectedScript}); + + local getDialogueScripts = React.useCallback(function(folderName: string): {ModuleScript} + + local dialogueFolder = if selectedScript then selectedScript:FindFirstChild(folderName) else nil; + if not selectedScript or not dialogueFolder then + + return {}; + + end; + + local dialogueScripts: {ModuleScript} = {}; + for _, possibleDialogueScript in dialogueFolder:GetChildren() do + + if possibleDialogueScript:IsA("ModuleScript") and possibleDialogueScript:HasTag(if folderName == "Conversations" then "DialogueMakerConversationScript" else "DialogueMakerDialogueScript") then + + table.insert(dialogueScripts, possibleDialogueScript); + + end; + + end; + + local function sortByMessagePriority(dialogueA: ModuleScript, dialogueB: ModuleScript) + + local messageAPriority = tonumber(dialogueA.Name) or math.huge; + local messageBPriority = tonumber(dialogueB.Name) or math.huge; + + return messageAPriority < messageBPriority; + + end; + + table.sort(dialogueScripts, sortByMessagePriority); + + return dialogueScripts; + + end, {selectedScript}); - local conversations, setConversations = React.useState({} :: {ModuleScript}); - local redirects, setRedirects = React.useState({} :: {ModuleScript}); - local responses, setResponses = React.useState({} :: {ModuleScript}); - local messages, setMessages = React.useState({} :: {ModuleScript}); - local selectedTab: DialogueItemType?, setSelectedTab = React.useState(nil :: DialogueItemType?); + local conversations, setConversations = React.useState(getDialogueScripts("Conversations")); + local redirects, setRedirects = React.useState(getDialogueScripts("Redirects")); + local responses, setResponses = React.useState(getDialogueScripts("Responses")); + local messages, setMessages = React.useState(getDialogueScripts("Messages")); + local selectedTab: DialogueItemType?, setSelectedTab = React.useState(getSelectedTab()); React.useEffect(function(): () - - setSelectedTab(nil); local contentScriptConnections: {RBXScriptConnection} = {}; @@ -51,104 +111,104 @@ local function DialogueGroupContainer(props: DialogueTableBodyProperties) local function refreshTable() - cleanupConnections(); - refreshDialogueMakerScripts(); + local didRefresh, errorMessage = pcall(function() - -- Separate the dialogue item types. - local conversations: {ModuleScript} = {}; - local responses: {ModuleScript} = {}; - local messages: {ModuleScript} = {}; - local redirects: {ModuleScript} = {}; + cleanupConnections(); + refreshDialogueMakerScripts(); + setConversations(getDialogueScripts("Conversations")); + setRedirects(getDialogueScripts("Redirects")); + setResponses(getDialogueScripts("Responses")); + setMessages(getDialogueScripts("Messages")); - if selectedScript then + if selectedTab and selectedScript then - for _, folderName in {"Messages", "Responses", "Redirects"} do - - local dialogueFolder = selectedScript:FindFirstChild(folderName); - if not dialogueFolder then + local folder = selectedScript:FindFirstChild(selectedTab); + setSelectedTab(if folder then selectedTab else getSelectedTab()); - continue; + else - end; + setSelectedTab(getSelectedTab()); - for _, possibleDialogueScript in dialogueFolder:GetChildren() do + end; - if not possibleDialogueScript:HasTag("DialogueMakerDialogueScript") then + -- Separate the dialogue item types. + if selectedScript then + + for _, folderName in {"Messages", "Responses", "Redirects"} do + + local dialogueFolder = selectedScript:FindFirstChild(folderName); + if not dialogueFolder then continue; end; - - local dialogueType = possibleDialogueScript:GetAttribute("DialogueType"); - local targetTable = if dialogueType == "Message" then messages elseif dialogueType == "Response" then responses else redirects; - table.insert(targetTable, possibleDialogueScript); - table.insert(contentScriptConnections, possibleDialogueScript:GetAttributeChangedSignal("DialogueType"):Connect(refreshTable)); - table.insert(contentScriptConnections, possibleDialogueScript:GetAttributeChangedSignal("DialogueContent"):Connect(refreshTable)); - table.insert(contentScriptConnections, possibleDialogueScript:GetPropertyChangedSignal("Name"):Connect(refreshTable)); + for _, possibleDialogueScript in dialogueFolder:GetChildren() do + + if not possibleDialogueScript:HasTag("DialogueMakerDialogueScript") then + + continue; + + end; + + table.insert(contentScriptConnections, possibleDialogueScript:GetAttributeChangedSignal("DialogueType"):Connect(refreshTable)); + table.insert(contentScriptConnections, possibleDialogueScript:GetAttributeChangedSignal("DialogueContent"):Connect(refreshTable)); + table.insert(contentScriptConnections, possibleDialogueScript:GetPropertyChangedSignal("Name"):Connect(refreshTable)); + + end; + + table.insert(contentScriptConnections, dialogueFolder.ChildAdded:Connect(refreshTable)); + table.insert(contentScriptConnections, dialogueFolder.ChildRemoved:Connect(refreshTable)); + end; - table.insert(contentScriptConnections, dialogueFolder.ChildAdded:Connect(refreshTable)); - table.insert(contentScriptConnections, dialogueFolder.ChildRemoved:Connect(refreshTable)); + table.insert(contentScriptConnections, selectedScript.ChildAdded:Connect(refreshTable)); + table.insert(contentScriptConnections, selectedScript.ChildRemoved:Connect(refreshTable)); + + else - end; + local selection = Selection:Get(); + if #selection == 1 then - table.insert(contentScriptConnections, selectedScript.ChildAdded:Connect(refreshTable)); - table.insert(contentScriptConnections, selectedScript.ChildRemoved:Connect(refreshTable)); + local conversationsParent = if selection[1]:IsA("Folder") then selection[1] else selection[1]:FindFirstChild("Conversations"); - else - - local selection = Selection:Get(); - if #selection == 1 then + if not conversationsParent then - local conversationsParent = if selection[1]:IsA("Folder") then selection[1] else selection[1]:FindFirstChild("Conversations"); + table.insert(contentScriptConnections, selection[1].ChildAdded:Connect(refreshTable)); - if not conversationsParent then + else - table.insert(contentScriptConnections, selection[1].ChildAdded:Connect(refreshTable)); - return; + table.insert(contentScriptConnections, conversationsParent.ChildAdded:Connect(refreshTable)); + table.insert(contentScriptConnections, conversationsParent.ChildRemoved:Connect(refreshTable)); - end; + for _, possibleConversationScript in conversationsParent:GetChildren() do - table.insert(contentScriptConnections, conversationsParent.ChildAdded:Connect(refreshTable)); - table.insert(contentScriptConnections, conversationsParent.ChildRemoved:Connect(refreshTable)); + if possibleConversationScript:IsA("ModuleScript") and possibleConversationScript:HasTag("DialogueMakerConversationScript") then + + table.insert(conversations, possibleConversationScript); + + end; - for _, possibleConversationScript in conversationsParent:GetChildren() do + end; - if possibleConversationScript:IsA("ModuleScript") and possibleConversationScript:HasTag("DialogueMakerConversationScript") then - - table.insert(conversations, possibleConversationScript); - end; end; end; - end; + end); + + if not didRefresh then + + print("Error refreshing dialogue scripts: ", errorMessage); + errorBoundary.showBoundary(errorMessage); - local function sortByMessagePriority(dialogueA: ModuleScript, dialogueB: ModuleScript) - - local messageAPriority = tonumber(dialogueA.Name) or math.huge; - local messageBPriority = tonumber(dialogueB.Name) or math.huge; - - return messageAPriority < messageBPriority; - end; - - table.sort(conversations, sortByMessagePriority); - table.sort(redirects, sortByMessagePriority); - table.sort(responses, sortByMessagePriority); - table.sort(messages, sortByMessagePriority); - - setConversations(conversations); - setRedirects(redirects); - setResponses(responses); - setMessages(messages); end; - refreshTable(); + task.spawn(refreshTable); return function() @@ -156,10 +216,10 @@ local function DialogueGroupContainer(props: DialogueTableBodyProperties) end; - end, {selectedScript}); + end, {selectedScript :: unknown, getSelectedTab, refreshDialogueMakerScripts, errorBoundary}); local tabs = {}; - local dialogueGroups = {}; + local dialogueGroup = React.createElement(React.Fragment); for categoryIndex, scriptList in {conversations, redirects, responses, messages} do if (not selectedScript and categoryIndex > 1) or (selectedScript and categoryIndex == 1) then @@ -169,13 +229,6 @@ local function DialogueGroupContainer(props: DialogueTableBodyProperties) end; local dialogueType: DialogueItemType = ({"Conversation", "Redirect", "Response", "Message"})[categoryIndex] :: DialogueItemType; - if not selectedTab and #scriptList > 0 then - - setSelectedTab(dialogueType); - break; - - end; - local tab = React.createElement(TabSelectorButton, { key = dialogueType; text = `{dialogueType}s` :: string; @@ -197,16 +250,13 @@ local function DialogueGroupContainer(props: DialogueTableBodyProperties) end; - local dialogueGroup = React.createElement(DialogueGroup, { + dialogueGroup = React.createElement(DialogueGroup, { name = dialogueType :: DialogueItemType; layoutOrder = categoryIndex; plugin = props.plugin; - key = dialogueType; dialogueScripts = scriptList; }); - table.insert(dialogueGroups, dialogueGroup); - end; return React.createElement("Frame", { @@ -220,7 +270,7 @@ local function DialogueGroupContainer(props: DialogueTableBodyProperties) Padding = UDim.new(0, 15); }); TabSelector = React.createElement(TabSelector, {}, tabs); - DialogueGroups = React.createElement(React.Fragment, {}, dialogueGroups); + DialogueGroup = dialogueGroup; }); end; diff --git a/src/DialogueEditor/hooks/useRefreshDialogueMakerScripts.luau b/src/DialogueEditor/hooks/useRefreshDialogueMakerScripts.luau index 03d947c..916b0f9 100644 --- a/src/DialogueEditor/hooks/useRefreshDialogueMakerScripts.luau +++ b/src/DialogueEditor/hooks/useRefreshDialogueMakerScripts.luau @@ -27,147 +27,143 @@ local function useRefreshDialogueMakerScripts() continue; end; - - task.spawn(function() - -- Determine the correct template and target script based on the type. - local isLoader = autoGeneratedScript:HasTag("DialogueMakerLoader"); - local templateScript = ( - if autoGeneratedScript:HasTag("DialogueMakerConversationScript") then root.Templates.ConversationTemplate - elseif autoGeneratedScript:HasTag("DialogueMakerDialogueScript") then root.Templates.DialogueTemplate - elseif isLoader then root.Templates.LoaderTemplate - else nil - ); - - if not templateScript then - - warn(`No template found for script: {autoGeneratedScript:GetFullName()}.`); - return; - - end; + -- Determine the correct template and target script based on the type. + local isLoader = autoGeneratedScript:HasTag("DialogueMakerLoader"); + local templateScript = ( + if autoGeneratedScript:HasTag("DialogueMakerConversationScript") then root.Templates.ConversationTemplate + elseif autoGeneratedScript:HasTag("DialogueMakerDialogueScript") then root.Templates.DialogueTemplate + elseif isLoader then root.Templates.LoaderTemplate + else nil + ); + + if not templateScript then - -- Reset the script contents to the template. - autoGeneratedScript.Source = templateScript.Source; + warn(`No template found for script: {autoGeneratedScript:GetFullName()}.`); + return; + + end; - -- Replace packages. - autoGeneratedScript.Source = autoGeneratedScript.Source:gsub("local packages = [^\n]*", `local packages = game.{dialogueMakerPackages:GetFullName()};\n`); + -- Reset the script contents to the template. + autoGeneratedScript.Source = templateScript.Source; - -- Replace the properties string in the script. - -- First, edit the settings. - local settingsFolder = autoGeneratedScript:FindFirstChild("Settings"); - settingsFolder = if isLoader then settingsFolder or Instance.new("Folder") else nil; - local settingsString = "settings = {};"; - if settingsFolder then + -- Replace packages. + autoGeneratedScript.Source = autoGeneratedScript.Source:gsub("local packages = [^\n]*", `local packages = game.{dialogueMakerPackages:GetFullName()};\n`); - settingsFolder = settingsFolder:Clone(); + -- Replace the properties string in the script. + -- First, edit the settings. + local settingsFolder = autoGeneratedScript:FindFirstChild("Settings"); + settingsFolder = if isLoader then settingsFolder or Instance.new("Folder") else nil; + local settingsString = "settings = {};"; + if settingsFolder then - if isLoader then + settingsFolder = settingsFolder:Clone(); - local themesFolder = settingsFolder:FindFirstChild("theme"); + if isLoader then - if not themesFolder then + local themesFolder = settingsFolder:FindFirstChild("theme"); - themesFolder = Instance.new("Folder"); - themesFolder.Name = "theme"; - themesFolder.Parent = settingsFolder; + if not themesFolder then - end; + themesFolder = Instance.new("Folder"); + themesFolder.Name = "theme"; + themesFolder.Parent = settingsFolder; - assert(themesFolder); + end; - local componentScriptValue = themesFolder:FindFirstChild("componentScript"); + assert(themesFolder); - if not componentScriptValue then + local componentScriptValue = themesFolder:FindFirstChild("componentScript"); - componentScriptValue = Instance.new("ObjectValue"); - componentScriptValue.Name = "componentScript"; - componentScriptValue.Parent = themesFolder; + if not componentScriptValue then - end; + componentScriptValue = Instance.new("ObjectValue"); + componentScriptValue.Name = "componentScript"; + componentScriptValue.Parent = themesFolder; end; - settingsString = "settings = {"; + end; - local extraSpaces = if isLoader then " " else ""; + settingsString = "settings = {"; - for _, categoryFolder in settingsFolder:GetChildren() do + local extraSpaces = if isLoader then " " else ""; - if settingsFolder:IsA("Folder") then + for _, categoryFolder in settingsFolder:GetChildren() do - settingsString = `{settingsString}\n {extraSpaces}{categoryFolder.Name} = \{`; + if settingsFolder:IsA("Folder") then - for _, settingInstance in categoryFolder:GetChildren() do + settingsString = `{settingsString}\n {extraSpaces}{categoryFolder.Name} = \{`; - if settingInstance:IsA("ValueBase") then + for _, settingInstance in categoryFolder:GetChildren() do - local key = settingInstance.Name; - key = if key == "componentScript" then "component" else key; + if settingInstance:IsA("ValueBase") then - local enumName = settingInstance:GetAttribute("Enum"); - local enumPrefix = if enumName then `Enum.{enumName}:FromName(` else ""; - local valueFullName = `script.Settings.{categoryFolder.Name}.{settingInstance.Name}.Value`; - local enumSuffix = if enumName then ")" else ""; - local value = `{enumPrefix}{valueFullName}{enumSuffix}`; - value = if key == "component" then `require({if settingInstance.Value then value else "packages.StandardTheme"})` else value; - settingsString = `{settingsString}\n {extraSpaces}{key} = {value};`; + local key = settingInstance.Name; + key = if key == "componentScript" then "component" else key; - end; + local enumName = settingInstance:GetAttribute("Enum"); + local enumPrefix = if enumName then `Enum.{enumName}:FromName(` else ""; + local valueFullName = `script.Settings.{categoryFolder.Name}.{settingInstance.Name}.Value`; + local enumSuffix = if enumName then ")" else ""; + local value = `{enumPrefix}{valueFullName}{enumSuffix}`; + value = if key == "component" then `require({if settingInstance.Value then value else "packages.StandardTheme"})` else value; + settingsString = `{settingsString}\n {extraSpaces}{key} = {value};`; end; - settingsString = `{settingsString}\n {extraSpaces}};`; - end; - end; + settingsString = `{settingsString}\n {extraSpaces}};`; - settingsString = `{settingsString}\n {extraSpaces}};`; + end; end; - autoGeneratedScript.Source = autoGeneratedScript.Source:gsub("-- START SETTINGS REPLACEMENT.*-- END SETTINGS REPLACEMENT[^\n]*", settingsString); + settingsString = `{settingsString}\n {extraSpaces}};`; - -- Then, replace the content. - local contentScript = autoGeneratedScript:FindFirstChild("ContentScript"); - if contentScript then + end; - local contentString = "local content = require(script.ContentScript);"; - autoGeneratedScript.Source = autoGeneratedScript.Source:gsub("local content = [^\n]*", contentString); + autoGeneratedScript.Source = autoGeneratedScript.Source:gsub("-- START SETTINGS REPLACEMENT.*-- END SETTINGS REPLACEMENT[^\n]*", settingsString); - contentScript.Source = contentScript.Source:gsub("local packages = [^\n]*", `local packages = game.{dialogueMakerPackages:GetFullName()}; -- Automatically generated by plugin.`); + -- Then, replace the content. + local contentScript = autoGeneratedScript:FindFirstChild("ContentScript"); + if contentScript then - end; + local contentString = "local content = require(script.ContentScript);"; + autoGeneratedScript.Source = autoGeneratedScript.Source:gsub("local content = [^\n]*", contentString); + + contentScript.Source = contentScript.Source:gsub("local packages = [^\n]*", `local packages = game.{dialogueMakerPackages:GetFullName()}; -- Automatically generated by plugin.`); - -- Finally, replace the scripts. - local functionMap = { - runInitializationAction = "InitializationActionScript"; - runCleanupAction = "CleanupActionScript"; - runCompletionAction = "CompletionActionScript"; - verifyCondition = "ConditionScript"; - }; - local actionString = ""; - for functionName, scriptName in functionMap do - - if actionString ~= "" then + end; - actionString = `{actionString}\n`; + -- Finally, replace the scripts. + local functionMap = { + runInitializationAction = "InitializationActionScript"; + runCleanupAction = "CleanupActionScript"; + runCompletionAction = "CompletionActionScript"; + verifyCondition = "ConditionScript"; + }; + local actionString = ""; + for functionName, scriptName in functionMap do + + if actionString ~= "" then - end; + actionString = `{actionString}\n`; - local actionScript = autoGeneratedScript:FindFirstChild(scriptName); - if actionScript then + end; - actionScript.Source = actionScript.Source:gsub("local packages = [^\n]*", `local packages = game.{dialogueMakerPackages:GetFullName()}; -- Automatically generated by plugin.`); - actionString = `{actionString}{if actionString == "" then "" else " "}{functionName} = require(script.{scriptName});`; + local actionScript = autoGeneratedScript:FindFirstChild(scriptName); + if actionScript then - end; + actionScript.Source = actionScript.Source:gsub("local packages = [^\n]*", `local packages = game.{dialogueMakerPackages:GetFullName()}; -- Automatically generated by plugin.`); + actionString = `{actionString}{if actionString == "" then "" else " "}{functionName} = require(script.{scriptName});`; end; - autoGeneratedScript.Source = autoGeneratedScript.Source:gsub("-- START SCRIPT REPLACEMENT.*-- END SCRIPT REPLACEMENT[^\n]*", actionString); + end; - end); + autoGeneratedScript.Source = autoGeneratedScript.Source:gsub("-- START SCRIPT REPLACEMENT.*-- END SCRIPT REPLACEMENT[^\n]*", actionString); end; diff --git a/test.project.json b/test.project.json new file mode 100644 index 0000000..4bdc0cb --- /dev/null +++ b/test.project.json @@ -0,0 +1,25 @@ +{ + "name": "DialogueMakerPlugin", + "tree": { + "$className": "DataModel", + "ReplicatedStorage": { + "DialogueMakerKit": { + "$path": "src/DialogueMakerKit", + "$properties": { + "Tags": ["DialogueMakerKit"] + }, + "roblox_packages": { + "$path": "roblox_packages" + } + } + }, + "ServerStorage": { + "DialogueMakerPlugin": { + "$path": "src", + "roblox_packages": { + "$path": "roblox_packages" + } + } + } + } +} \ No newline at end of file