From 8a946e1bf9a6bbf0977d436383729cdffd091e43 Mon Sep 17 00:00:00 2001 From: antoine-coulon Date: Thu, 20 Jul 2023 09:21:23 +0200 Subject: [PATCH] feat: implement FileExplorer section --- apps/web/public/folder.svg | 1 + apps/web/public/opened_folder.svg | 1 + apps/web/src/events.ts | 12 + apps/web/src/header/Header.tsx | 80 +++-- apps/web/src/sidebar/FileExplorer.tsx | 467 ++++++++++++++++++-------- apps/web/src/sidebar/Layout.tsx | 4 +- apps/web/src/sidebar/Summary.tsx | 3 + apps/web/styles/index.css | 13 + 8 files changed, 399 insertions(+), 182 deletions(-) create mode 100644 apps/web/public/folder.svg create mode 100644 apps/web/public/opened_folder.svg diff --git a/apps/web/public/folder.svg b/apps/web/public/folder.svg new file mode 100644 index 000000000..c6f9b035e --- /dev/null +++ b/apps/web/public/folder.svg @@ -0,0 +1 @@ +default_folder \ No newline at end of file diff --git a/apps/web/public/opened_folder.svg b/apps/web/public/opened_folder.svg new file mode 100644 index 000000000..8aab2ff5c --- /dev/null +++ b/apps/web/public/opened_folder.svg @@ -0,0 +1 @@ +default_folder_opened \ No newline at end of file diff --git a/apps/web/src/events.ts b/apps/web/src/events.ts index f66c2707c..43e2b0ec0 100644 --- a/apps/web/src/events.ts +++ b/apps/web/src/events.ts @@ -12,6 +12,18 @@ export type UiEvents = | { action: "toggle_thirdparty"; payload: { enabled: boolean } } | { action: "open_search"; + } + | { + action: "focus_on_node"; + payload: { + nodeId: string; + }; + } + | { + action: "isolate_node"; + payload: { + nodeId: string; + }; }; export type DataStore = SkottStructureWithCycles; diff --git a/apps/web/src/header/Header.tsx b/apps/web/src/header/Header.tsx index 776c04822..1ea84cc5d 100644 --- a/apps/web/src/header/Header.tsx +++ b/apps/web/src/header/Header.tsx @@ -8,7 +8,14 @@ import { Title, ThemeIcon, Box, - Paper, + Text, + Badge, + UnstyledButton, + Button, + Switch, + useMantineTheme, + Kbd, + Code, } from "@mantine/core"; import { IconBrandGithub, IconMoonStars, IconSun } from "@tabler/icons-react"; @@ -18,7 +25,9 @@ const metadata = { }; export default function Header() { + const theme = useMantineTheme(); const { colorScheme, toggleColorScheme } = useMantineColorScheme(); + const isDarkMode = colorScheme === "dark"; return ( @@ -27,41 +36,44 @@ export default function Header() { - - - - {metadata.name} - - - @ - - - {metadata.version} - - - - - toggleColorScheme()} - size={30} - > - - - - - toggleColorScheme()} - size={30} + + + + + toggleColorScheme()} + size="lg" + onLabel={ + + } + offLabel={ + + } + /> diff --git a/apps/web/src/sidebar/FileExplorer.tsx b/apps/web/src/sidebar/FileExplorer.tsx index 19dd7199d..9bbb2f660 100644 --- a/apps/web/src/sidebar/FileExplorer.tsx +++ b/apps/web/src/sidebar/FileExplorer.tsx @@ -1,29 +1,47 @@ import { Navbar, - Group, - Tooltip, TextInput, Code, - ThemeIcon, Text, - ScrollArea, createStyles, rem, - Checkbox, - Title, Accordion, + Box, + ActionIcon, + Flex, + Image, + Menu, + ScrollArea, } from "@mantine/core"; import { - IconSearch, - IconBrandTypescript, - IconBrandJavascript, + IconFilterCog, + IconDots, + IconFilterPlus, + IconFocusCentered, + IconLoadBalancer, + IconReportSearch, } from "@tabler/icons-react"; import { fakeSkottData } from "../fake-data"; import { isJavaScriptModule, isTypeScriptModule } from "../util.js"; +import React from "react"; +import { UiEvents } from "../events.js"; const data = fakeSkottData; +const skottPathSeparator = "#sk#"; + +function parseFilePath(fileId: string): string { + return fileId.split(skottPathSeparator).join("/"); +} + +type FileExplorerEvents = { + action: "filter_by_glob"; + payload: { + glob: string; + }; +}; + const useStyles = createStyles((theme) => ({ searchCode: { fontWeight: 700, @@ -36,96 +54,290 @@ const useStyles = createStyles((theme) => ({ theme.colorScheme === "dark" ? theme.colors.dark[7] : theme.colors.gray[2] }`, }, +})); - mainLinks: { - paddingLeft: `calc(${theme.spacing.md} - ${theme.spacing.xs})`, - paddingRight: `calc(${theme.spacing.md} - ${theme.spacing.xs})`, - paddingBottom: theme.spacing.md, - }, - - mainLink: { - display: "flex", - alignItems: "center", - width: "100%", - fontSize: theme.fontSizes.xs, - padding: `${rem(8)} ${theme.spacing.xs}`, - borderRadius: theme.radius.sm, - fontWeight: 500, - color: - theme.colorScheme === "dark" - ? theme.colors.dark[0] - : theme.colors.gray[7], - - "&:hover": { - backgroundColor: - theme.colorScheme === "dark" - ? theme.colors.dark[6] - : theme.colors.gray[0], - color: theme.colorScheme === "dark" ? theme.white : theme.black, +const fileTree = { + lib: { + "index.js": {}, + "some-dir": { + "index.js": {}, + "some-folder": { + "index.js": {}, + "deep-folder": { + "index.js": {}, + "some-folder": { + "index.js": {}, + "deep-folder": { + "index.js": {}, + "some-folder": { + "index.js": {}, + "deep-folder": { + "index.js": {}, + "some-folder": { + "index.js": {}, + "deep-folder": { + "index.js": {}, + }, + }, + }, + }, + }, + }, + }, + }, }, }, - - mainLinkInner: { - display: "flex", - alignItems: "center", - flex: 1, + app: { + "main.ts": {}, }, + "index.js": {}, +}; - mainLinkIcon: { - marginRight: theme.spacing.sm, - color: - theme.colorScheme === "dark" - ? theme.colors.dark[2] - : theme.colors.gray[6], - }, +function AccordionControl({ + children, + onAction, +}: { + children: React.ReactNode; + onAction: (action: "filter") => void; +}) { + return ( + + + {children} + + + + + + + + + onAction("filter")} + icon={} + > + Add to filter pattern + + + + + ); +} - mainLinkBadge: { - padding: 0, - width: rem(20), - height: rem(20), - pointerEvents: "none", - }, +function File({ + name, + isRoot, + fileId, + actionDispatcher, +}: { + name: string; + isRoot: boolean; + fileId: string; + actionDispatcher: (action: UiEvents) => void; +}) { + return ( + + + + + {name} + + + + + + + + + + + actionDispatcher({ + action: "focus_on_node", + payload: { nodeId: parseFilePath(fileId) }, + }) + } + icon={ + + } + > + Focus + + + actionDispatcher({ + action: "isolate_node", + payload: { nodeId: parseFilePath(fileId) }, + }) + } + icon={ + + } + > + Isolate + + {}} + icon={ + + } + > + Open details + + + + + ); +} - collections: { - paddingLeft: `calc(${theme.spacing.md} - ${rem(6)})`, - paddingRight: `calc(${theme.spacing.md} - ${rem(6)})`, - paddingBottom: theme.spacing.md, - }, +function Folder({ + name, + children, + openedFolders, + fileId, + onOpen, + onClose, + actionDispatcher, +}: { + name: string; + children: Record; + fileId: string; + openedFolders: Set; + onOpen: (filename: string) => void; + onClose: (filename: string) => void; + actionDispatcher: (action: UiEvents | FileExplorerEvents) => void; +}) { + return ( + { + if (values.length > 0) { + onOpen(fileId); + } else { + onClose(fileId); + } + }} + > + + { + if (event === "filter") { + actionDispatcher({ + action: "filter_by_glob", + payload: { glob: `${parseFilePath(fileId)}/**/*` }, + }); + } + }} + > + + {openedFolders.has(fileId) ? ( + + ) : ( + + )} + + {name} + + + + + {Object.entries(children).map(([name, value]) => { + if (isTypeScriptModule(name) || isJavaScriptModule(name)) { + return ( + + ); + } + return ( + + ); + })} + + + + ); +} - collectionsHeader: { - paddingLeft: `calc(${theme.spacing.md} + ${rem(2)})`, - paddingRight: theme.spacing.md, - marginBottom: rem(5), - }, +function FileExplorerAccordion({ + actionDispatcher, +}: { + actionDispatcher: (action: UiEvents | FileExplorerEvents) => void; +}) { + const [openedFolders, setOpenedFolders] = React.useState(new Set()); - collectionLink: { - display: "block", - padding: `${rem(8)} ${theme.spacing.xs}`, - textDecoration: "none", - borderRadius: theme.radius.sm, - fontSize: theme.fontSizes.xs, - color: - theme.colorScheme === "dark" - ? theme.colors.dark[0] - : theme.colors.gray[7], - lineHeight: 1, - fontWeight: 500, - - "&:hover": { - backgroundColor: - theme.colorScheme === "dark" - ? theme.colors.dark[6] - : theme.colors.gray[0], - color: theme.colorScheme === "dark" ? theme.white : theme.black, - }, - }, -})); + return ( + <> + {Object.entries(fileTree).map(([leafName, value]) => { + if (isTypeScriptModule(leafName) || isJavaScriptModule(leafName)) { + return ( + + ); + } + return Folder({ + name: leafName, + children: value, + fileId: leafName, + openedFolders, + onOpen: (fileId) => { + openedFolders.add(fileId); + setOpenedFolders(new Set(openedFolders)); + }, + onClose: (fileId) => { + openedFolders.delete(fileId); + setOpenedFolders(new Set(openedFolders)); + }, + actionDispatcher, + }); + })} + + ); +} export function FileExplorer() { const { classes } = useStyles(); - const builtinRegistry = new Set(); - const npmRegistry = new Set(); const typescriptFiles: string[] = []; const javascriptFiles: string[] = []; @@ -139,69 +351,32 @@ export function FileExplorer() { } } - for (const node of Object.values(data.graph)) { - node.body.thirdPartyDependencies.forEach((dep) => { - npmRegistry.add(dep); - }); - node.body.builtinDependencies.forEach((dep) => { - builtinRegistry.add(dep); - }); + function applyFilter(globPattern: string) {} + + function dispatchAction(event: UiEvents | FileExplorerEvents) { + if (event.action === "filter_by_glob") { + applyFilter(event.payload.glob); + } else { + } } return ( - - } - rightSectionWidth={70} - rightSection={CMD + K} - styles={{ rightSection: { pointerEvents: "none" } }} - mb="sm" - /> - - - - - - - } + + + + } + rightSectionWidth={60} + rightSection={Filter} + styles={{ rightSection: { pointerEvents: "none" } }} + mb="sm" /> - - - - lmao.js - - - - - - src/lib/some-dir/index.js - - - - - - - - - - } - > - Content - - - + + + + + ); } diff --git a/apps/web/src/sidebar/Layout.tsx b/apps/web/src/sidebar/Layout.tsx index a0ff523b1..fdbc1af7d 100644 --- a/apps/web/src/sidebar/Layout.tsx +++ b/apps/web/src/sidebar/Layout.tsx @@ -101,7 +101,7 @@ type MenuKeys = (typeof menus)[number]["key"]; export function DoubleNavbar() { const { classes, cx } = useStyles(); - const [active, setActive] = useState("summary"); + const [active, setActive] = useState("file_explorer"); const mainMenus = menus.map((link) => ( { switch (active) { case "circular": - return ; + return ; case "graph_configuration": return ; case "summary": diff --git a/apps/web/src/sidebar/Summary.tsx b/apps/web/src/sidebar/Summary.tsx index bbbb1b626..02fee6a37 100644 --- a/apps/web/src/sidebar/Summary.tsx +++ b/apps/web/src/sidebar/Summary.tsx @@ -142,6 +142,7 @@ export function Summary() { if (index % 2 === 0) { return ( 9+ @@ -155,6 +156,7 @@ export function Summary() { return ( @@ -180,6 +182,7 @@ export function Summary() { {[...builtinRegistry].map((dep) => { return ( 5 diff --git a/apps/web/styles/index.css b/apps/web/styles/index.css index df80e64af..70735d05c 100644 --- a/apps/web/styles/index.css +++ b/apps/web/styles/index.css @@ -225,6 +225,19 @@ div.skott-logo { text-align: center; } +.mantine-Accordion-chevron { + margin-right: 2px; +} + +.mantine-Accordion-content { + padding-bottom: 0; + padding-right: 0; +} + +.mantine-Accordion-label { + padding: 0; +} + #skott-logo { width: 60%; margin: 0 auto;