Skip to content

Commit

Permalink
Fetch more when scrolling down
Browse files Browse the repository at this point in the history
  • Loading branch information
vocksel committed Jan 22, 2024
1 parent b38ca6c commit 8f8c77e
Show file tree
Hide file tree
Showing 8 changed files with 249 additions and 33 deletions.
5 changes: 4 additions & 1 deletion plugin/src/components/App.luau
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ local function App(_props: Props)
setView("ThemeDetails")
end, {})

return React.createElement("Folder", nil, {
return React.createElement("Frame", {
Size = UDim2.fromScale(1, 1),
BackgroundTransparency = 1,
}, {
Home = if view == "Home"
then React.createElement(HomeWrapper, {
onViewExtension = onViewExtension,
Expand Down
27 changes: 24 additions & 3 deletions plugin/src/components/ExtensionsList.luau
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
local React = require("@pkg/React")
local types = require("@root/types")
local ViewportTrigger = require("./ViewportTrigger")
local getLayoutOrder = require("./getLayoutOrder")

type PublishedExtension = types.PublishedExtension

export type Props = {
extensions: { PublishedExtension },
onView: (extension: PublishedExtension) -> (),
onScrollToBottom: (() -> ())?,
LayoutOrder: number?,
}

Expand All @@ -19,6 +21,11 @@ local function ExtensionsList(props: Props)
Padding = PADDING,
SortOrder = Enum.SortOrder.LayoutOrder,
}),

Padding = React.createElement("UIPadding", {
PaddingBottom = PADDING,
PaddingRight = PADDING,
}),
}

for i, extension in props.extensions do
Expand Down Expand Up @@ -108,12 +115,26 @@ local function ExtensionsList(props: Props)
}),
}),
})

if props.onScrollToBottom then
children.BottomTrigger = React.createElement(ViewportTrigger, {
onTrigger = props.onScrollToBottom,
LayoutOrder = getLayoutOrder(),
})
end
end

return React.createElement("Frame", {
return React.createElement("ScrollingFrame", {
LayoutOrder = props.LayoutOrder,
AutomaticSize = Enum.AutomaticSize.Y,
Size = UDim2.fromScale(1, 0),
Size = UDim2.fromScale(1, 1),
AutomaticCanvasSize = Enum.AutomaticSize.Y,
CanvasSize = UDim2.fromScale(1, 0),
ScrollingDirection = Enum.ScrollingDirection.Y,
ScrollBarImageColor3 = Color3.fromRGB(255, 255, 255),
ScrollBarThickness = 4,
ScrollBarImageTransparency = 0.2,
VerticalScrollBarInset = Enum.ScrollBarInset.None,
BorderSizePixel = 0,
BackgroundTransparency = 1,
}, children)
end
Expand Down
57 changes: 31 additions & 26 deletions plugin/src/components/Home.luau
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ local types = require("@root/types")
local LoadingSpinner = require("./LoadingSpinner")
local ExtensionsList = require("./ExtensionsList")
local getLayoutOrder = require("./getLayoutOrder")
local useCombinedSize = require("./useCombinedSize")

type PublishedExtension = types.PublishedExtension
type ExtensionTheme = types.ExtensionTheme
Expand All @@ -16,12 +17,13 @@ export type Props = {
extensions: { PublishedExtension },
onSearch: (searchTerm: string) -> (),
onViewExtension: (extension: PublishedExtension) -> (),
onFetchMore: (() -> ())?,
searchTerm: string?,
err: string?,
}

local function Home(props: Props)
local isLoading = useMemo(function()
local isLoading: boolean = useMemo(function()
return #props.extensions == 0 and not props.err
end, { props.extensions, props.err })

Expand All @@ -31,12 +33,12 @@ local function Home(props: Props)
end
end, {})

return React.createElement("ScrollingFrame", {
local combinedSize = useCombinedSize()

return React.createElement("Frame", {
Size = UDim2.fromScale(1, 0),
AutomaticSize = Enum.AutomaticSize.Y,
BackgroundTransparency = 1,
CanvasSize = UDim2.fromScale(1, 0),
AutomaticCanvasSize = Enum.AutomaticSize.Y,
}, {
Layout = React.createElement("UIListLayout", {
SortOrder = Enum.SortOrder.LayoutOrder,
Expand All @@ -55,6 +57,7 @@ local function Home(props: Props)
Size = UDim2.fromScale(1, 0),
AutomaticSize = Enum.AutomaticSize.Y,
BackgroundTransparency = 1,
[React.Tag] = combinedSize.tag,
}, {
Input = React.createElement("TextBox", {
Text = if props.searchTerm then props.searchTerm else "",
Expand Down Expand Up @@ -93,32 +96,34 @@ local function Home(props: Props)
})
else nil,

ExtensionsListWrapper = React.createElement(
"Frame",
{
ExtensionsListWrapper = React.createElement("Frame", {
Size = UDim2.fromScale(1, 1) - combinedSize.height,
BackgroundTransparency = 1,
LayoutOrder = getLayoutOrder(),
}, {
ExtensionList = React.createElement(ExtensionsList, {
extensions = props.extensions,
onScrollToBottom = props.onFetchMore,
onView = props.onViewExtension,
}),
}),

Loading = if isLoading
then React.createElement("Frame", {
LayoutOrder = getLayoutOrder(),
Size = UDim2.fromScale(1, 0),
AutomaticSize = Enum.AutomaticSize.Y,
BackgroundTransparency = 1,
LayoutOrder = getLayoutOrder(),
},
if isLoading
then {
Layout = React.createElement("UIListLayout", {
HorizontalAlignment = Enum.HorizontalAlignment.Center,
VerticalAlignment = Enum.VerticalAlignment.Center,
}),
[React.Tag] = combinedSize.tag,
}, {
Layout = React.createElement("UIListLayout", {
HorizontalAlignment = Enum.HorizontalAlignment.Center,
VerticalAlignment = Enum.VerticalAlignment.Center,
}),

LoadingSpinner = React.createElement(LoadingSpinner),
}
else {
ExtensionList = if not isLoading
then React.createElement(ExtensionsList, {
extensions = props.extensions,
onView = props.onViewExtension,
})
else nil,
}
),
LoadingSpinner = React.createElement(LoadingSpinner),
})
else nil,
})
end

Expand Down
26 changes: 23 additions & 3 deletions plugin/src/components/HomeWrapper.luau
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
local React = require("@pkg/React")
local Sift = require("@pkg/Sift")
local fetchVisualStudioExtensions = require("@root/requests/fetchVisualStudioExtensions")
local types = require("@root/types")
local Home = require("./Home")

type PublishedExtension = types.PublishedExtension

local useCallback = React.useCallback
local useEffect = React.useEffect
local useState = React.useState

Expand All @@ -13,30 +15,48 @@ export type Props = {
}

local function HomeWrapper(props: Props)
local page, setPage = useState(1)
local extensions, setExtensions = useState({} :: { PublishedExtension })
local searchTerm, setSearchTerm = useState(nil :: string?)
local err, setErr = useState(nil :: string?)

local onFetchMore = useCallback(function()
setPage(function(prev)
return prev + 1
end)
end, {})

useEffect(function()
setPage(1)
end, { searchTerm })

useEffect(function()
setErr(nil)
fetchVisualStudioExtensions({
-- page = page, -- TODO: Increment the page when scrolling to the bottom of the list
page = page,
pageSize = 20,
searchTerm = if searchTerm then searchTerm else "theme",
})
:andThen(function(newExtensions)
setExtensions(newExtensions)
setExtensions(function(prev)
if page > 1 then
return Sift.Array.join(prev, newExtensions)
else
return newExtensions
end
end)
end)
:catch(function()
setErr(`No extensions found. Please try again later`)
end)
end, { searchTerm })
end, { searchTerm, page })

return React.createElement(Home, {
extensions = extensions,
err = err,
searchTerm = searchTerm,
onSearch = setSearchTerm,
onFetchMore = onFetchMore,
onViewExtension = props.onViewExtension,
})
end
Expand Down
67 changes: 67 additions & 0 deletions plugin/src/components/ViewportTrigger.luau
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
local React = require("@pkg/React")
local usePrevious = require("./usePrevious")

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

local function useScreen(ancestor: Instance?): LayerCollector?
local screen: LayerCollector?, setScreen = useState(nil :: LayerCollector?)

useEffect(function()
if ancestor then
setScreen(ancestor:FindFirstAncestorWhichIsA("LayerCollector"))
end
end, { ancestor })

return screen
end

export type Props = {
onTrigger: () -> (),
LayoutOrder: number?,
}

local function ViewportTrigger(props: Props)
local ref: { current: Frame? } = useRef()
local screen = useScreen(ref.current)
local position: Vector2, setPosition = useState(Vector2.zero)

local onPositionChange = useCallback(function(rbx: Frame)
setPosition(rbx.AbsolutePosition)
end, {})

local isInBounds = useMemo(function()
if screen then
if
position.X > screen.AbsolutePosition.X
and position.X < screen.AbsolutePosition.X + screen.AbsoluteSize.X
and position.Y > screen.AbsolutePosition.Y
and position.Y < screen.AbsolutePosition.Y + screen.AbsoluteSize.Y
then
return true
end
end
return false
end, { position, screen })

local wasInBounds = usePrevious(isInBounds)

useEffect(function()
if isInBounds and not wasInBounds then
props.onTrigger()
end
end, { isInBounds })

return React.createElement("Frame", {
LayoutOrder = props.LayoutOrder,
Size = UDim2.fromOffset(1, 1),
BackgroundTransparency = 1,
ref = ref,
[React.Change.AbsolutePosition] = onPositionChange,
})
end

return ViewportTrigger
74 changes: 74 additions & 0 deletions plugin/src/components/useCombinedSize.luau
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
local HttpService = game:GetService("HttpService")
local CollectionService = game:GetService("CollectionService")

local React = require("@pkg/React")
local Sift = require("@pkg/Sift")
local useEvent = require("./useEvent")

local useCallback = React.useCallback
local useEffect = React.useEffect
local useMemo = React.useMemo
local useState = React.useState

type SizeMap = {
[string]: number,
}

local function useCombinedSize()
local sizeMap: SizeMap, setSizeMap = useState({} :: SizeMap)
local tag = useMemo(function()
local guid = HttpService:GenerateGUID()
return `combined-size-{guid}`
end, {})

local onAdded = useCallback(function(instance: Instance)
if instance:IsA("GuiObject") then
setSizeMap(function(prev)
return Sift.Dictionary.merge(prev, {
[instance.Name] = instance.AbsoluteSize,
})
end)
end
end, {})

local onRemoved = useCallback(function(instance: Instance)
if instance:IsA("GuiObject") then
setSizeMap(function(prev)
return Sift.Dictionary.merge(prev, {
[instance.Name] = Sift.None,
})
end)
end
end, {})

useEffect(function()
for _, instance in CollectionService:GetTagged(tag) do
onAdded(instance)
end

local added = CollectionService:GetInstanceAddedSignal(tag):Connect(onAdded)
local removed = CollectionService:GetInstanceRemovedSignal(tag):Connect(onRemoved)

return function()
added:Disconnect()
removed:Disconnect()
end
end, {})

local size = useMemo(function()
local vec = Vector2.zero
for _, value in sizeMap do
vec += value
end
return UDim2.fromOffset(vec.X, vec.Y)
end, { sizeMap })

return {
size = size,
width = UDim2.new(size.X, UDim.new()),
height = UDim2.new(UDim.new(), size.Y),
tag = tag,
}
end

return useCombinedSize
13 changes: 13 additions & 0 deletions plugin/src/components/useEvent.luau
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
local React = require("@pkg/React")

local function useEvent(event: RBXScriptSignal, callback: (...any) -> ())
React.useEffect(function()
local conn = event:Connect(callback)

return function()
conn:Disconnect()
end
end)
end

return useEvent
Loading

0 comments on commit 8f8c77e

Please sign in to comment.