Skip to content

Commit

Permalink
Use a new function pair for saving and loading paths
Browse files Browse the repository at this point in the history
  • Loading branch information
vocksel committed Dec 20, 2024
1 parent 72ed253 commit 6caae9e
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 107 deletions.
64 changes: 0 additions & 64 deletions src/Common/getInstanceFromFullName.luau

This file was deleted.

44 changes: 44 additions & 0 deletions src/Common/getInstanceFromPath.luau
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
--[[
Gets an instance based off the result of getInstancePath.
The reason we use this over GetFullName is because it uses a dot (.) as the
path separator which makes it difficult to disambiguate instances stories
and test files (Foo.story and Foo.spec, respectively)
Returns `nil` if the instance is outside the DataModel or otherwise cannot
be found.
]]

local tryGetService = require("@root/RobloxInternal/tryGetService")

local PATH_SEPERATOR = "/"

local function getInstanceFromPath(path: string): Instance?
local parts = path:split(PATH_SEPERATOR)
local serviceName = parts[1]

if serviceName then
-- This function only works for instances in the DataModel. As such, the
-- first part of the path will always be a service, so if we can't find
-- one we exit out and return nil
local current = tryGetService(serviceName)

if current then
for i = 2, #parts do
local found = current:FindFirstChild(parts[i])

if found then
current = found
else
return nil
end
end

return current
end
end

return nil
end

return getInstanceFromPath
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ local ReplicatedStorage = game:GetService("ReplicatedStorage")
local JestGlobals = require("@pkg/JestGlobals")
local newFolder = require("@root/Testing/newFolder")

local getInstanceFromFullName = require("./getInstanceFromFullName")
local getInstanceFromPath = require("./getInstanceFromPath")
local getInstancePath = require("./getInstancePath")

local expect = JestGlobals.expect
local test = JestGlobals.test
Expand All @@ -18,7 +19,7 @@ afterEach(function()
end)

test("gets services", function()
local path = getInstanceFromFullName("ReplicatedStorage")
local path = getInstanceFromPath("ReplicatedStorage")
expect(path).toBe(ReplicatedStorage)
end)

Expand All @@ -32,8 +33,7 @@ test("works on nested instances", function()
})
folder.Parent = ReplicatedStorage

local path = getInstanceFromFullName(module:GetFullName())
expect(path).toBe(module)
expect(getInstanceFromPath(getInstancePath(module))).toBe(module)
end)

test("works with spec files", function()
Expand All @@ -46,7 +46,7 @@ test("works with spec files", function()
})
folder.Parent = ReplicatedStorage

local path = getInstanceFromFullName(module:GetFullName())
local path = getInstanceFromPath(module:GetFullName())
expect(path).toBe(module)
end)

Expand All @@ -61,18 +61,17 @@ test("finds spec files BEFORE the module it is associated with", function()
})
folder.Parent = ReplicatedStorage

local path = getInstanceFromFullName(module:GetFullName())
expect(path).toBe(module)
expect(getInstanceFromPath(getInstancePath(module))).toBe(module)
end)

test("returns nil if the first part of the path is not a service", function()
expect(getInstanceFromFullName("Part")).toBeUndefined()
expect(getInstanceFromPath("Part")).toBeUndefined()
end)

test("returns nil if the path does not exist", function()
expect(getInstanceFromFullName("Foo.story")).toBeUndefined()
expect(getInstanceFromFullName("Path.To.Foo.story")).toBeUndefined()
expect(getInstanceFromFullName("ReplicatedStorage.Foo.Bar.Baz")).toBeUndefined()
expect(getInstanceFromFullName("ReplicatedStorage.Sample.story")).toBeUndefined()
expect(getInstanceFromFullName("ReplicatedStorage.Sample.spec")).toBeUndefined()
expect(getInstanceFromPath("Foo.story")).toBeUndefined()
expect(getInstanceFromPath("Path/To/Foo.story")).toBeUndefined()
expect(getInstanceFromPath("ReplicatedStorage/Foo/Bar/Baz")).toBeUndefined()
expect(getInstanceFromPath("ReplicatedStorage/Sample.story")).toBeUndefined()
expect(getInstanceFromPath("ReplicatedStorage/Sample.spec")).toBeUndefined()
end)
18 changes: 18 additions & 0 deletions src/Common/getInstancePath.luau
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
local PATH_SEPARATOR = "/"

local function getInstancePath(instance: Instance, pathSeparator: string?): string
pathSeparator = if pathSeparator then pathSeparator else PATH_SEPARATOR
assert(pathSeparator, "Luau")

local path = {}
local current = instance

while current and current.Parent ~= nil do
table.insert(path, 1, current.Name)
current = current.Parent
end

return table.concat(path, "/")
end

return getInstancePath
48 changes: 48 additions & 0 deletions src/Common/getInstancePath.spec.luau
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local JestGlobals = require("@pkg/JestGlobals")
local newFolder = require("@root/Testing/newFolder")

local getInstancePath = require("./getInstancePath")

local expect = JestGlobals.expect
local test = JestGlobals.test
local afterEach = JestGlobals.afterEach

local folder: Folder

afterEach(function()
if folder then
folder:Destroy()
end
end)

test("services are treated as the root", function()
expect(getInstancePath(ReplicatedStorage)).toBe("ReplicatedStorage")
end)

test("works on nested instances", function()
local module = Instance.new("ModuleScript")

folder = newFolder({
foo = newFolder({
bar = module,
}),
})
folder.Parent = ReplicatedStorage

expect(getInstancePath(module)).toBe("ReplicatedStorage/Folder/foo/bar")
end)

test("works with spec files", function()
local module = Instance.new("ModuleScript")

folder = newFolder({
foo = newFolder({
["bar.spec"] = module,
}),
})
folder.Parent = ReplicatedStorage

expect(getInstancePath(module)).toBe("ReplicatedStorage/Folder/foo/bar.spec")
end)
34 changes: 15 additions & 19 deletions src/Plugin/LocalStorageContext.luau
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,9 @@ local React = require("@pkg/React")
local Sift = require("@pkg/Sift")

local PluginContext = require("@root/Plugin/PluginContext")
local usePrevious = require("@root/Common/usePrevious")

local useCallback = React.useCallback
local useContext = React.useContext
local useEffect = React.useEffect
local useRef = React.useRef
local useState = React.useState

export type LocalStorage = {
Expand Down Expand Up @@ -42,41 +39,40 @@ local function LocalStorageProvider(props: Props)
return {}
end, { plugin, props.storageKey } :: { unknown })

local storage, setStorage = useState(loadFromDisk)
local prevStorage = usePrevious(storage)

local saveToDisk = useCallback(function()
local data = HttpService:JSONEncode(storage)
local saveToDisk = useCallback(function(newStorage: { [any]: any })
local data = HttpService:JSONEncode(newStorage)
if data then
plugin:SetSetting(props.storageKey, data)
end
end, { plugin, props.storageKey, storage } :: { unknown })
end, { plugin, props.storageKey } :: { unknown })

local storage, setStorage = useState(function()
return loadFromDisk()
end)

local get = useCallback(function(key: string)
return storage[key]
end, { storage })

local set = useCallback(function(key: string, value: unknown?)
setStorage(function(prev)
return Sift.Dictionary.join(prev, {
local new = Sift.Dictionary.join(prev, {
[key] = if value == nil then Sift.None else value,
})

saveToDisk(new)

return new
end)
end, { storage })

useEffect(function()
if storage and storage ~= prevStorage then
saveToDisk()
end
end, { storage, prevStorage, saveToDisk } :: { unknown })

local context: LocalStorageContext = useRef({
local context: LocalStorageContext = {
get = get,
set = set,
})
}

return React.createElement(LocalStorageContext.Provider, {
value = context.current,
value = context,
}, props.children)
end

Expand Down
13 changes: 7 additions & 6 deletions src/Storybook/useLastOpenedStory.luau
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ local React = require("@pkg/React")

local LocalStorageContext = require("@root/Plugin/LocalStorageContext")
local SettingsContext = require("@root/UserSettings/SettingsContext")
local getInstanceFromFullName = require("@root/Common/getInstanceFromFullName")
local getInstanceFromPath = require("@root/Common/getInstanceFromPath")
local getInstancePath = require("@root/Common/getInstancePath")

local useCallback = React.useCallback
local useMemo = React.useMemo
Expand All @@ -15,8 +16,8 @@ local function useLastOpenedStory(): (ModuleScript?, (storyModule: ModuleScript?
local settingsContext = SettingsContext.use()

local setLastOpenedStory = useCallback(function(storyModule: ModuleScript?)
localStorage.set(LAST_OPENED_STORY_PATH_KEY, if storyModule then storyModule:GetFullName() else nil)
end, { localStorage })
localStorage.set(LAST_OPENED_STORY_PATH_KEY, if storyModule then getInstancePath(storyModule) else nil)
end, { localStorage.set })

local lastOpenedStory = useMemo(function(): ModuleScript?
local rememberLastOpenedStory = settingsContext.getSetting(REMEMBER_LAST_OPENED_STORY_KEY)
Expand All @@ -27,16 +28,16 @@ local function useLastOpenedStory(): (ModuleScript?, (storyModule: ModuleScript?

local lastOpenedStoryPath = localStorage.get(LAST_OPENED_STORY_PATH_KEY)

if lastOpenedStoryPath then
local instance = getInstanceFromFullName(lastOpenedStoryPath)
if lastOpenedStoryPath and typeof(lastOpenedStoryPath) == "string" then
local instance = getInstanceFromPath(lastOpenedStoryPath)

if instance and instance:IsA("ModuleScript") then
return instance
end
end

return nil
end, { settingsContext, localStorage })
end, { settingsContext, localStorage.get })

return lastOpenedStory, setLastOpenedStory
end
Expand Down
11 changes: 6 additions & 5 deletions src/TreeView/usePinnedInstances.luau
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ local React = require("@pkg/React")
local Sift = require("@pkg/Sift")

local LocalStorageContext = require("@root/Plugin/LocalStorageContext")
local getInstanceFromFullName = require("@root/Common/getInstanceFromFullName")
local getInstanceFromPath = require("@root/Common/getInstanceFromPath")
local getInstancePath = require("@root/Common/getInstancePath")
local usePrevious = require("@root/Common/usePrevious")

local useCallback = React.useCallback
Expand All @@ -26,14 +27,14 @@ local function usePinnedInstances(): (ModuleScript?, (storyModule: ModuleScript?

local pin = useCallback(function(instance: Instance)
setPinnedPaths(function(prev)
return Sift.List.append(prev, instance:GetFullName())
return Sift.List.append(prev, getInstancePath(instance))
end)
end, {})

local unpin = useCallback(function(instance: Instance)
setPinnedPaths(function(prev)
return Sift.List.filter(prev, function(pinnedPath)
return pinnedPath ~= instance:GetFullName()
return pinnedPath ~= getInstancePath(instance)
end)
end)
end, {})
Expand All @@ -44,7 +45,7 @@ local function usePinnedInstances(): (ModuleScript?, (storyModule: ModuleScript?
for _, pinnedPath in pinnedPaths do
table.insert(pinnedInstances, {
path = pinnedPath,
instance = getInstanceFromFullName(pinnedPath),
instance = getInstanceFromPath(pinnedPath),
})
end

Expand All @@ -67,7 +68,7 @@ local function usePinnedInstances(): (ModuleScript?, (storyModule: ModuleScript?
if prevPinnedPaths and pinnedPaths ~= prevPinnedPaths then
localStorage.set(PINNED_INSTANCES_KEY, pinnedPaths)
end
end, { localStorage, pinnedPaths, prevPinnedPaths })
end, { localStorage.set, pinnedPaths, prevPinnedPaths })

return {
pin = pin,
Expand Down

0 comments on commit 6caae9e

Please sign in to comment.