Skip to content

Commit

Permalink
implement nested directory browser in CodeBrowser
Browse files Browse the repository at this point in the history
  • Loading branch information
ayan4m1 committed Dec 23, 2023
1 parent a540f4a commit 1434ce2
Show file tree
Hide file tree
Showing 11 changed files with 316 additions and 83 deletions.
13 changes: 13 additions & 0 deletions gatsby-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ module.exports = {
path: `${__dirname}/articles`
}
},
{
resolve: 'gatsby-source-filesystem',
options: {
name: 'snippets',
path: `${__dirname}/snippets`
}
},
{
resolve: 'gatsby-source-filesystem',
options: {
Expand Down Expand Up @@ -100,6 +107,12 @@ module.exports = {
'gatsby-plugin-sitemap',
'gatsby-transformer-json',
'gatsby-transformer-sharp',
{
resolve: 'gatsby-transformer-source-code',
options: {
mimeTypes: ['application/javascript', 'text/jsx', 'text/x-scss']
}
},
{
resolve: 'gatsby-transformer-remark',
options: {
Expand Down
5 changes: 4 additions & 1 deletion gatsby-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,10 @@ const createArticlePages = async ({ actions, graphql, reporter }) => {
counter++;
createPage({
component: `${mdxComponent}?__contentFilePath=${contentFilePath}`,
path
path,
context: {
pathGlob: `${path.substring(1)}/**/*`
}
});
});

Expand Down
79 changes: 79 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"@fortawesome/free-solid-svg-icons": "^6.5.1",
"@fortawesome/react-fontawesome": "^0.2.0",
"@mdx-js/react": "^2.3.0",
"@react-spring/web": "^9.7.3",
"babel-plugin-prismjs": "^2.1.0",
"bootstrap": "^5.3.2",
"bootswatch": "^5.3.2",
Expand Down Expand Up @@ -54,7 +55,9 @@
"gatsby-transformer-json": "^5.12.0",
"gatsby-transformer-remark": "^6.12.3",
"gatsby-transformer-sharp": "^5.12.3",
"gatsby-transformer-source-code": "^0.1.0",
"graphql": "^16.8.1",
"lodash-es": "^4.17.21",
"prismjs": "^1.29.0",
"prop-types": "^15.8.1",
"react": "^18.2.0",
Expand Down
Empty file added snippets/.gitkeep
Empty file.
112 changes: 83 additions & 29 deletions src/components/codeBrowser.js
Original file line number Diff line number Diff line change
@@ -1,60 +1,115 @@
import Prism from 'prismjs';
import { uniq } from 'lodash-es';
import PropTypes from 'prop-types';
import { useState, useEffect, useRef } from 'react';
import { useState, useEffect, useMemo } from 'react';
import { Row, Tab, Col, Card } from 'react-bootstrap';

import TreeNode from 'components/treeNode';
import DirectoryTree from 'components/directoryTree';
import useSnippets from 'hooks/useSnippets';

const getExtension = (filename) =>
filename.substring(filename.lastIndexOf('.') + 1);
const getDirName = (path) => path.substring(0, path.lastIndexOf('/'));

export default function CodeBrowser({ id, documents }) {
const docRef = useRef(null);
const getExtension = (path) => path.substring(path.lastIndexOf('.') + 1);

export default function CodeBrowser({ id }) {
const { snippets } = useSnippets();
const [activeDocument, setActiveDocument] = useState(null);

useEffect(() => {
const activeDoc = documents.find((doc) => doc.filename === activeDocument);
const activeDoc = snippets.find(
(doc) => doc.path.replace(`${id}/`, '') === activeDocument
);

if (activeDoc) {
docRef.current = document.getElementById(`doc-${activeDoc.filename}`);
Prism.highlightElement(docRef.current);
Prism.highlightElement(
document.getElementById(`doc-${activeDoc.path.replace(`${id}/`, '')}`)
);
}
}, [activeDocument]);

const snippetTree = useMemo(() => {
const result = {
path: '.',
files: [],
children: []
};
const strippedSnippets = snippets.map((doc) => ({
...doc,
path: doc.path.replace(`${id}/`, '')
}));
const directories = uniq(
strippedSnippets.map((doc) => getDirName(doc.path))
);

for (const dir of directories) {
const segments = dir.split('/');

if (!segments.join('')) {
continue;
}

for (let i = 0; i < segments.length; i++) {
let node = result;
const path = segments.slice(0, i + 1);

for (const segment of path) {
const nextNode = node.children.find((dir) => dir.path === segment);

if (nextNode) {
node = nextNode;
} else {
node.children.push({
path: segment,
files: [],
children: []
});
}
}
}
}

for (const doc of strippedSnippets) {
let node = result;
const segments = getDirName(doc.path).split('/');

for (const segment of segments) {
const nextNode = node.children.find((dir) => dir.path === segment);

if (nextNode) {
node = nextNode;
}
}

node.files.push(doc);
}

return result;
}, [snippets]);

return (
<Tab.Container
id={id}
activeKey={activeDocument}
onSelect={(filename) => setActiveDocument(filename)}
onSelect={(path) => setActiveDocument(path)}
>
<Row className="g-2">
<Col xs={3}>
<Card body>
<TreeNode title="./">
{documents.map((doc) => (
<TreeNode
active={doc.filename === activeDocument}
level={1}
key={doc.filename}
title={doc.filename}
/>
))}
</TreeNode>
<DirectoryTree activeDocument={activeDocument} node={snippetTree} />
</Card>
</Col>
<Col xs={9}>
<Tab.Content>
{documents.map((doc) => (
{snippets.map((doc) => (
<Tab.Pane
eventKey={doc.filename}
key={doc.filename}
title={doc.filename}
eventKey={doc.path.replace(`${id}/`, '')}
key={doc.path}
>
<pre
id={`doc-${doc.filename}`}
className={`language-${getExtension(doc.filename)} mt-0`}
id={`doc-${doc.path.replace(`${id}/`, '')}`}
className={`language-${getExtension(doc.path)} mt-0`}
>
{doc.content}
{doc.code.content}
</pre>
</Tab.Pane>
))}
Expand All @@ -66,6 +121,5 @@ export default function CodeBrowser({ id, documents }) {
}

CodeBrowser.propTypes = {
id: PropTypes.string.isRequired,
documents: PropTypes.arrayOf(PropTypes.object).isRequired
id: PropTypes.string.isRequired
};
66 changes: 66 additions & 0 deletions src/components/directoryTree.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import PropTypes from 'prop-types';
import { Fragment, useState } from 'react';
import { Row, Col, Nav } from 'react-bootstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import {
faFolder,
faCaretDown,
faCaretRight,
faFile
} from '@fortawesome/free-solid-svg-icons';

const getFileName = (path) => path.substring(path.lastIndexOf('/') + 1);

export default function DirectoryTree({ activeDocument, node, level = 0 }) {
const { path, children, files } = node;
const isFile = !Array.isArray(files);

const [expanded, setExpanded] = useState(false);

return (
<Fragment>
<Row onClick={isFile ? null : () => setExpanded((prevVal) => !prevVal)}>
<Col xs={1}>
{Boolean(children) && (
<FontAwesomeIcon icon={expanded ? faCaretDown : faCaretRight} />
)}
</Col>
<Col xs={11} style={level > 0 ? { paddingLeft: (level + 1) * 12 } : {}}>
<Nav.Item className={activeDocument === path && 'text-primary'}>
<Nav.Link eventKey={path}>
<FontAwesomeIcon
icon={isFile ? faFile : faFolder}
className="me-1"
/>{' '}
{path ? getFileName(path) : '.'}
</Nav.Link>
</Nav.Item>
</Col>
</Row>
{expanded &&
children.map((child) => (
<DirectoryTree
key={child.path}
node={child}
level={level + 1}
activeDocument={activeDocument}
/>
))}
{expanded &&
files.map((file) => (
<DirectoryTree
key={file.path}
node={file}
level={level + 1}
activeDocument={activeDocument}
/>
))}
</Fragment>
);
}

DirectoryTree.propTypes = {
activeDocument: PropTypes.string,
node: PropTypes.object.isRequired,
level: PropTypes.number
};
Loading

0 comments on commit 1434ce2

Please sign in to comment.