diff --git a/build.project.json b/build.project.json index 446d3be9..42b0edf5 100644 --- a/build.project.json +++ b/build.project.json @@ -7,6 +7,9 @@ "Example": { "$path": "example" }, + "wally.toml": { + "$path": "wally.toml" + }, "$path": "src" } -} \ No newline at end of file +} diff --git a/default.project.json b/default.project.json index 9d407fbb..d10e409e 100644 --- a/default.project.json +++ b/default.project.json @@ -4,6 +4,9 @@ "Packages": { "$path": "Packages" }, + "wally.toml": { + "$path": "wally.toml" + }, "$path": "build" } -} \ No newline at end of file +} diff --git a/img/GitHubMark.png b/img/GitHubMark.png new file mode 100644 index 00000000..50b81752 Binary files /dev/null and b/img/GitHubMark.png differ diff --git a/src/About/AboutView.luau b/src/About/AboutView.luau new file mode 100644 index 00000000..fec7ba36 --- /dev/null +++ b/src/About/AboutView.luau @@ -0,0 +1,147 @@ +local React = require("@pkg/React") + +local RobloxProfile = require("@root/About/RobloxProfile") +local Sprite = require("@root/Common/Sprite") +local assets = require("@root/assets") +local nextLayoutOrder = require("@root/Common/nextLayoutOrder") +local useTheme = require("@root/Common/useTheme") +local wally = require(script.Parent.Parent["wally.toml"]) + +local useMemo = React.useMemo + +local AUTHOR_USER_IDS = { + 1343930, + 731053179, +} + +local function AboutView() + local theme = useTheme() + local currentYear = useMemo(function() + return (DateTime.now():ToUniversalTime() :: any).Year + end, {}) + + local authors: { [string]: React.Node } = {} + for _, userId in AUTHOR_USER_IDS do + authors[`Author{userId}`] = React.createElement(RobloxProfile, { + userId = userId, + LayoutOrder = nextLayoutOrder(), + }) + end + + return React.createElement("Frame", { + Size = UDim2.fromScale(1, 1), + BackgroundColor3 = theme.background, + BorderSizePixel = 0, + }, { + Layout = React.createElement("UIListLayout", { + SortOrder = Enum.SortOrder.LayoutOrder, + HorizontalAlignment = Enum.HorizontalAlignment.Center, + Padding = theme.paddingLarge, + }), + + Padding = React.createElement("UIPadding", { + PaddingTop = theme.paddingLarge, + PaddingRight = theme.paddingLarge, + PaddingBottom = theme.paddingLarge, + PaddingLeft = theme.paddingLarge, + }), + + Logo = React.createElement(Sprite, { + layoutOrder = nextLayoutOrder(), + image = assets.IconLight, + size = UDim2.fromOffset(42, 42), + }), + + Title = React.createElement("TextLabel", { + AutomaticSize = Enum.AutomaticSize.XY, + BackgroundTransparency = 1, + Font = theme.font, + LayoutOrder = nextLayoutOrder(), + Size = UDim2.fromScale(0, 0), + Text = `flipbook v{wally.package.version}`, + TextColor3 = theme.text, + TextSize = theme.headerTextSize, + TextWrapped = true, + }), + + GitHub = React.createElement("Frame", { + AutomaticSize = Enum.AutomaticSize.XY, + BackgroundTransparency = 1, + LayoutOrder = nextLayoutOrder(), + }, { + Layout = React.createElement("UIListLayout", { + SortOrder = Enum.SortOrder.LayoutOrder, + FillDirection = Enum.FillDirection.Horizontal, + Padding = theme.paddingSmall, + }), + + Icon = React.createElement(Sprite, { + layoutOrder = nextLayoutOrder(), + color = theme.github, + image = assets.GitHubMark, + size = UDim2.fromOffset(theme.textSize, theme.textSize), + }), + + Label = React.createElement("TextLabel", { + AutomaticSize = Enum.AutomaticSize.XY, + BackgroundTransparency = 1, + Font = theme.font, + LayoutOrder = nextLayoutOrder(), + Size = UDim2.fromScale(0, 0), + Text = "flipbook-labs/flipbook", + TextColor3 = theme.text, + TextSize = theme.textSize, + }), + }), + + Authors = React.createElement("Frame", { + AutomaticSize = Enum.AutomaticSize.XY, + BackgroundTransparency = 1, + LayoutOrder = nextLayoutOrder(), + }, { + Layout = React.createElement("UIListLayout", { + SortOrder = Enum.SortOrder.LayoutOrder, + HorizontalAlignment = Enum.HorizontalAlignment.Center, + Padding = theme.padding, + }), + + Title = React.createElement("TextLabel", { + AutomaticSize = Enum.AutomaticSize.XY, + BackgroundTransparency = 1, + Font = theme.font, + LayoutOrder = nextLayoutOrder(), + Size = UDim2.fromScale(0, 0), + Text = `Created by:`, + TextColor3 = theme.text, + TextSize = theme.textSize, + TextWrapped = true, + }), + + AuthorList = React.createElement("Frame", { + AutomaticSize = Enum.AutomaticSize.XY, + BackgroundTransparency = 1, + LayoutOrder = nextLayoutOrder(), + }, { + Layout = React.createElement("UIListLayout", { + SortOrder = Enum.SortOrder.LayoutOrder, + FillDirection = Enum.FillDirection.Horizontal, + Padding = theme.padding, + }), + }, authors), + }), + + Copy = React.createElement("TextLabel", { + AutomaticSize = Enum.AutomaticSize.XY, + BackgroundTransparency = 1, + Font = theme.font, + LayoutOrder = nextLayoutOrder(), + Size = UDim2.fromScale(0, 0), + Text = `Copyright © 2021—{currentYear} flipbook-labs`, + TextColor3 = theme.text, + TextSize = theme.textSize, + TextWrapped = true, + }), + }) +end + +return AboutView diff --git a/src/About/AboutView.story.luau b/src/About/AboutView.story.luau new file mode 100644 index 00000000..4b7d0b1b --- /dev/null +++ b/src/About/AboutView.story.luau @@ -0,0 +1,15 @@ +local React = require("@pkg/React") + +local AboutView = require("./AboutView") +local ContextProviders = require("@root/Common/ContextProviders") +local MockPlugin = require("@root/Testing/MockPlugin") + +return { + story = function() + return React.createElement(ContextProviders, { + plugin = MockPlugin.new(), + }, { + AboutView = React.createElement(AboutView), + }) + end, +} diff --git a/src/About/RobloxProfile.luau b/src/About/RobloxProfile.luau new file mode 100644 index 00000000..645b9684 --- /dev/null +++ b/src/About/RobloxProfile.luau @@ -0,0 +1,79 @@ +local Players = game:GetService("Players") + +local React = require("@pkg/React") +local ReactSpring = require("@pkg/ReactSpring") + +local nextLayoutOrder = require("@root/Common/nextLayoutOrder") +local useTheme = require("@root/Common/useTheme") + +local useState = React.useState +local useEffect = React.useEffect + +local AVATAR_SIZE = Enum.ThumbnailSize.Size48x48 + +export type Props = { + userId: number, + LayoutOrder: number?, +} + +local function RobloxProfile(props: Props) + local theme = useTheme() + local avatar, setAvatar = useState(nil :: string?) + local username, setUsername = useState(nil :: string?) + + local isLoading = avatar == nil and username == nil + + local styles = ReactSpring.useSpring({ + alpha = if isLoading then 1 else 0, + }) + + useEffect(function() + task.spawn(function() + setAvatar(Players:GetUserThumbnailAsync(props.userId, Enum.ThumbnailType.HeadShot, AVATAR_SIZE)) + end) + + task.spawn(function() + setUsername(Players:GetNameFromUserIdAsync(props.userId)) + end) + end, { props.userId }) + + return React.createElement("Frame", { + LayoutOrder = props.LayoutOrder, + AutomaticSize = Enum.AutomaticSize.XY, + BackgroundTransparency = 1, + }, { + Layout = React.createElement("UIListLayout", { + SortOrder = Enum.SortOrder.LayoutOrder, + FillDirection = Enum.FillDirection.Horizontal, + VerticalAlignment = Enum.VerticalAlignment.Center, + Padding = theme.paddingSmall, + }), + + Avatar = React.createElement("ImageLabel", { + LayoutOrder = nextLayoutOrder(), + Size = UDim2.fromOffset(48, 48), + BackgroundColor3 = theme.sidebar, + BackgroundTransparency = 0.6, + ImageTransparency = styles.alpha, + BorderSizePixel = 0, + Image = avatar, + }, { + Corner = React.createElement("UICorner", { + CornerRadius = UDim.new(0.5, 0), + }), + }), + + Username = React.createElement("TextLabel", { + LayoutOrder = nextLayoutOrder(), + Text = `@{username}`, + Font = theme.font, + TextColor3 = theme.text, + TextSize = theme.textSize, + BackgroundTransparency = 1, + TextTransparency = styles.alpha, + AutomaticSize = Enum.AutomaticSize.XY, + }), + }) +end + +return RobloxProfile diff --git a/src/About/RobloxProfile.story.luau b/src/About/RobloxProfile.story.luau new file mode 100644 index 00000000..c45ee2ce --- /dev/null +++ b/src/About/RobloxProfile.story.luau @@ -0,0 +1,27 @@ +local React = require("@pkg/React") + +local ContextProviders = require("@root/Common/ContextProviders") +local MockPlugin = require("@root/Testing/MockPlugin") +local RobloxProfile = require("./RobloxProfile") + +local controls = { + userId = 1, +} + +type Props = { + controls: typeof(controls), +} +return { + controls = controls, + story = function(props: Props) + local userId = assert(tonumber(props.controls.userId)) + + return React.createElement(ContextProviders, { + plugin = MockPlugin.new(), + }, { + RobloxProfiler = React.createElement(RobloxProfile, { + userId = userId, + }), + }) + end, +} diff --git a/src/Navigation/NavigationContext.luau b/src/Navigation/NavigationContext.luau index 93ed501b..ed183267 100644 --- a/src/Navigation/NavigationContext.luau +++ b/src/Navigation/NavigationContext.luau @@ -13,6 +13,7 @@ export type NavigationContext = { navigateTo: (newScreen: Screen) -> (), goBack: () -> (), getBreadcrumbs: () -> { string }, + canGoBack: () -> boolean, currentScreen: Screen, } diff --git a/src/Navigation/Screen.luau b/src/Navigation/Screen.luau index 70ee4a45..90cef980 100644 --- a/src/Navigation/Screen.luau +++ b/src/Navigation/Screen.luau @@ -1,6 +1,7 @@ local ModuleLoader = require("@pkg/ModuleLoader") local React = require("@pkg/React") +local AboutView = require("@root/About/AboutView") local NavigationContext = require("@root/Navigation/NavigationContext") local SettingsView = require("@root/UserSettings/SettingsView") local StoryCanvas = require("@root/Storybook/StoryCanvas") @@ -32,8 +33,10 @@ local function Screen(props: Props) end elseif currentScreen == "Settings" then return React.createElement(SettingsView) + elseif currentScreen == "About" then + return React.createElement(AboutView) end - return nil + return nil :: never end, { props, currentScreen } :: { unknown }) return screenElement diff --git a/src/Navigation/enums.luau b/src/Navigation/enums.luau index 5c54ab06..0514994d 100644 --- a/src/Navigation/enums.luau +++ b/src/Navigation/enums.luau @@ -1,9 +1,10 @@ local enums = {} -export type Screen = "Home" | "Settings" +export type Screen = "Home" | "Settings" | "About" enums.Screen = { Home = "Home" :: Screen, Settings = "Settings" :: Screen, + About = "About" :: Screen, } return enums diff --git a/src/Panels/Topbar.luau b/src/Panels/Topbar.luau index 251bd327..1261f726 100644 --- a/src/Panels/Topbar.luau +++ b/src/Panels/Topbar.luau @@ -17,12 +17,12 @@ local function Topbar(props: Props) local navigation = NavigationContext.use() local navigateToSettings = useCallback(function() - if navigation.currentScreen == "Settings" then - navigation.goBack() - else - navigation.navigateTo("Settings") - end - end, { navigation.navigateTo, navigation.currentScreen } :: { unknown }) + navigation.navigateTo("Settings") + end, { navigation.navigateTo }) + + local navigateToAbout = useCallback(function() + navigation.navigateTo("About") + end, { navigation.navigateTo }) return React.createElement("Frame", { BackgroundColor3 = theme.sidebar, @@ -43,6 +43,21 @@ local function Topbar(props: Props) PaddingLeft = theme.paddingSmall, }), + About = React.createElement(Navigation.Item, { + layoutOrder = nextLayoutOrder(), + onClick = navigateToAbout, + }, { + Text = React.createElement("TextLabel", { + AutomaticSize = Enum.AutomaticSize.XY, + BackgroundTransparency = 1, + Font = theme.font, + Size = UDim2.fromScale(0, 0), + Text = "About", + TextColor3 = theme.textFaded, + TextSize = theme.textSize, + }), + }), + Settings = React.createElement(Navigation.Item, { layoutOrder = nextLayoutOrder(), onClick = navigateToSettings, @@ -55,8 +70,6 @@ local function Topbar(props: Props) Text = "Settings", TextColor3 = theme.textFaded, TextSize = theme.textSize, - TextXAlignment = Enum.TextXAlignment.Left, - TextYAlignment = Enum.TextYAlignment.Top, }), }), }), @@ -64,33 +77,3 @@ local function Topbar(props: Props) end return Topbar - --- local function Topbar(props: Props) --- local theme = useTheme() --- local navigation = NavigationContext.use() - --- return React.createElement("Frame", { --- Size = UDim2.fromScale(1, 1), --- BackgroundColor3 = theme.sidebar, --- BorderSizePixel = 0, --- }, { --- LayoutOrder = React.createElement("UIListLayout", { --- SortOrder = Enum.SortOrder.LayoutOrder, --- HorizontalAlignment = Enum.HorizontalAlignment.Right, --- }), - --- Navbar = React.createElement("Frame", {}, { --- LayoutOrder = React.createElement("UIListLayout", { --- SortOrder = Enum.SortOrder.LayoutOrder, --- HorizontalAlignment = Enum.HorizontalAlignment.Right, --- }), - --- Settings = React.createElement(Button, { --- layoutOrder = nextLayoutOrder(), --- text = "Settings", --- }), --- }), --- }) --- end - --- return Topbar diff --git a/src/Testing/MockPlugin.luau b/src/Testing/MockPlugin.luau index 46ccd9e6..cd6752c4 100644 --- a/src/Testing/MockPlugin.luau +++ b/src/Testing/MockPlugin.luau @@ -1,12 +1,12 @@ local MockPlugin = {} MockPlugin.__index = MockPlugin -function MockPlugin.new() +function MockPlugin.new(): Plugin local self = { _settings = {}, } - return setmetatable(self, MockPlugin) + return setmetatable(self, MockPlugin) :: any end function MockPlugin:GetSetting(settingName: string) diff --git a/src/assets.luau b/src/assets.luau index 69f1667c..d00fe303 100644 --- a/src/assets.luau +++ b/src/assets.luau @@ -1,48 +1,53 @@ -- This file was @generated by Tarmac. It is not intended for manual editing. return { ChevronRight = { - Image = "rbxassetid://10175334645", - ImageRectOffset = Vector2.new(65, 49), + Image = "rbxassetid://18940815650", + ImageRectOffset = Vector2.new(49, 226), ImageRectSize = Vector2.new(32, 32), }, Component = { - Image = "rbxassetid://10175334645", - ImageRectOffset = Vector2.new(0, 114), + Image = "rbxassetid://18940815650", + ImageRectOffset = Vector2.new(0, 275), ImageRectSize = Vector2.new(32, 32), }, Folder = { - Image = "rbxassetid://10175334645", - ImageRectOffset = Vector2.new(157, 0), + Image = "rbxassetid://18940815650", + ImageRectOffset = Vector2.new(345, 0), ImageRectSize = Vector2.new(32, 32), }, + GitHubMark = { + Image = "rbxassetid://18940815650", + ImageRectOffset = Vector2.new(0, 0), + ImageRectSize = Vector2.new(230, 225), + }, IconLight = { - Image = "rbxassetid://10175334645", - ImageRectOffset = Vector2.new(114, 0), + Image = "rbxassetid://18940815650", + ImageRectOffset = Vector2.new(231, 65), ImageRectSize = Vector2.new(42, 42), }, Magnify = { - Image = "rbxassetid://10175334645", - ImageRectOffset = Vector2.new(65, 0), + Image = "rbxassetid://18940815650", + ImageRectOffset = Vector2.new(0, 226), ImageRectSize = Vector2.new(48, 48), }, Minify = { - Image = "rbxassetid://10175334645", - ImageRectOffset = Vector2.new(0, 65), + Image = "rbxassetid://18940815650", + ImageRectOffset = Vector2.new(296, 0), ImageRectSize = Vector2.new(48, 48), }, Search = { - Image = "rbxassetid://10175334645", - ImageRectOffset = Vector2.new(114, 43), + Image = "rbxassetid://18940815650", + ImageRectOffset = Vector2.new(296, 49), ImageRectSize = Vector2.new(32, 32), }, Storybook = { - Image = "rbxassetid://10175334645", - ImageRectOffset = Vector2.new(65, 82), + Image = "rbxassetid://18940815650", + ImageRectOffset = Vector2.new(231, 108), ImageRectSize = Vector2.new(32, 32), }, flipbook = { - Image = "rbxassetid://10175334645", - ImageRectOffset = Vector2.new(0, 0), + Image = "rbxassetid://18940815650", + ImageRectOffset = Vector2.new(231, 0), ImageRectSize = Vector2.new(64, 64), }, } diff --git a/src/themes.luau b/src/themes.luau index da8314c0..a2b57a8d 100644 --- a/src/themes.luau +++ b/src/themes.luau @@ -23,6 +23,8 @@ export type Theme = { directory: Color3, alert: Color3, + github: Color3, + padding: UDim, paddingSmall: UDim, paddingLarge: UDim, @@ -30,64 +32,71 @@ export type Theme = { corner: UDim, } +local Light: Theme = { + textSize = 18, + font = Enum.Font.BuilderSansMedium, + headerTextSize = 32, + headerFont = Enum.Font.BuilderSansExtraBold, + buttonTextSize = 14, + buttonFont = Enum.Font.BuilderSansBold, + + background = tailwind.white, + sidebar = tailwind.gray100, + canvas = tailwind.white, + scrollbar = tailwind.gray800, + button = tailwind.gray800, + buttonText = tailwind.white, + divider = tailwind.gray300, + text = tailwind.gray800, + textFaded = tailwind.gray600, + textSubtitle = tailwind.gray500, + selection = tailwind.purple500, + story = tailwind.green500, + directory = tailwind.purple500, + alert = tailwind.rose500, + + github = Color3.fromHex("#333333"), + + padding = UDim.new(0, 12), + paddingSmall = UDim.new(0, 6), + paddingLarge = UDim.new(0, 24), + + corner = UDim.new(0, 4), +} + +local Dark: Theme = { + textSize = 18, + font = Enum.Font.BuilderSansMedium, + headerTextSize = 32, + headerFont = Enum.Font.BuilderSansExtraBold, + buttonTextSize = 14, + buttonFont = Enum.Font.BuilderSansBold, + + background = tailwind.zinc800, + sidebar = tailwind.zinc900, + canvas = tailwind.zinc800, + scrollbar = tailwind.zinc100, + button = tailwind.zinc300, + buttonText = tailwind.zinc800, + divider = tailwind.zinc700, + text = tailwind.zinc200, + textFaded = tailwind.zinc300, + textSubtitle = tailwind.zinc400, + selection = tailwind.purple500, + story = tailwind.green500, + directory = tailwind.purple500, + alert = tailwind.rose500, + + github = Color3.fromHex("#ffffff"), + + padding = UDim.new(0, 12), + paddingSmall = UDim.new(0, 6), + paddingLarge = UDim.new(0, 24), + + corner = UDim.new(0, 6), +} + return { - Light = { - textSize = 18, - font = Enum.Font.BuilderSansMedium, - headerTextSize = 32, - headerFont = Enum.Font.BuilderSansExtraBold, - buttonTextSize = 14, - buttonFont = Enum.Font.BuilderSansBold, - - background = tailwind.white, - sidebar = tailwind.gray100, - canvas = tailwind.white, - scrollbar = tailwind.gray800, - button = tailwind.gray800, - buttonText = tailwind.white, - divider = tailwind.gray300, - text = tailwind.gray800, - textFaded = tailwind.gray600, - textSubtitle = tailwind.gray500, - selection = tailwind.purple500, - story = tailwind.green500, - directory = tailwind.purple500, - alert = tailwind.rose500, - - padding = UDim.new(0, 12), - paddingSmall = UDim.new(0, 6), - paddingLarge = UDim.new(0, 24), - - corner = UDim.new(0, 4), - } :: Theme, - - Dark = { - textSize = 18, - font = Enum.Font.BuilderSansMedium, - headerTextSize = 32, - headerFont = Enum.Font.BuilderSansExtraBold, - buttonTextSize = 14, - buttonFont = Enum.Font.BuilderSansBold, - - background = tailwind.zinc800, - sidebar = tailwind.zinc900, - canvas = tailwind.zinc800, - scrollbar = tailwind.zinc100, - button = tailwind.zinc300, - buttonText = tailwind.zinc800, - divider = tailwind.zinc700, - text = tailwind.zinc200, - textFaded = tailwind.zinc300, - textSubtitle = tailwind.zinc400, - selection = tailwind.purple500, - story = tailwind.green500, - directory = tailwind.purple500, - alert = tailwind.rose500, - - padding = UDim.new(0, 12), - paddingSmall = UDim.new(0, 6), - paddingLarge = UDim.new(0, 24), - - corner = UDim.new(0, 6), - } :: Theme, + Light = Light, + Dark = Dark, } diff --git a/tarmac-manifest.toml b/tarmac-manifest.toml index 0fb1e0df..9dd6cda0 100644 --- a/tarmac-manifest.toml +++ b/tarmac-manifest.toml @@ -1,53 +1,59 @@ [inputs."img/ChevronRight.png"] hash = "4c91853652b4e0a6317804c54db6397ac0c710c819b282f7be2179e3abdc879c" -id = 10175334645 -slice = [[65, 49], [97, 81]] +id = 18940815650 +slice = [[49, 226], [81, 258]] packable = true [inputs."img/Component.png"] hash = "d51b19d5f35ac87fd8156500828f3e217b6fc420b70fee5d97b3577e4e6a8843" -id = 10175334645 -slice = [[0, 114], [32, 146]] +id = 18940815650 +slice = [[0, 275], [32, 307]] packable = true [inputs."img/Folder.png"] hash = "4c0e80a363d36a4d127d410795cb13eec74d55871b70174636c0c962c242835c" -id = 10175334645 -slice = [[157, 0], [189, 32]] +id = 18940815650 +slice = [[345, 0], [377, 32]] +packable = true + +[inputs."img/GitHubMark.png"] +hash = "a0fb973cd9bf0c1123dbe783f7a4093dbcf9b4910d6e385c16a837a04a02306c" +id = 18940815650 +slice = [[0, 0], [230, 225]] packable = true [inputs."img/IconLight.png"] hash = "237c66813c2b5a58ae9c46166f72db8698fbb046922e2473ebaca8f7d7376ce1" -id = 10175334645 -slice = [[114, 0], [156, 42]] +id = 18940815650 +slice = [[231, 65], [273, 107]] packable = true [inputs."img/Magnify.png"] hash = "351db4b6132a9e02d01bc4cdc3c850099dce358673daaa0da66745b675ad38b1" -id = 10175334645 -slice = [[65, 0], [113, 48]] +id = 18940815650 +slice = [[0, 226], [48, 274]] packable = true [inputs."img/Minify.png"] hash = "694db8ce141475fc0f42980d2a6dfd058dfa155348a579fab29ca6dd5684ec14" -id = 10175334645 -slice = [[0, 65], [48, 113]] +id = 18940815650 +slice = [[296, 0], [344, 48]] packable = true [inputs."img/Search.png"] hash = "0ab89c0309f577f3ccae0e03b45292f6e2bca51cc9b8250f56ad756e04231c57" -id = 10175334645 -slice = [[114, 43], [146, 75]] +id = 18940815650 +slice = [[296, 49], [328, 81]] packable = true [inputs."img/Storybook.png"] hash = "2a4b8bd513a8fa7eebae482943ab3435a659359bd94b24eff1c3dcc6f1244799" -id = 10175334645 -slice = [[65, 82], [97, 114]] +id = 18940815650 +slice = [[231, 108], [263, 140]] packable = true [inputs."img/flipbook.png"] hash = "423e3dcff54fce8824f14d7cadcfb90eef2abecd817df2bee13951bd2674f79a" -id = 10175334645 -slice = [[0, 0], [64, 64]] +id = 18940815650 +slice = [[231, 0], [295, 64]] packable = true