Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add tree controller and --collapse option #2

Merged
merged 12 commits into from
Jul 1, 2024
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ npm run import-visualizer

To use the library without installation, enter the command as follows.

```
npx import-visualizer --root <rootFilePath> --targetDir <dir>
```sh
npx import-visualizer --root <rootFilePath> --targetDir <dir> --collapse
```

## Options
Expand All @@ -45,9 +45,11 @@ npx import-visualizer --root <rootFilePath> --targetDir <dir>

`--targetDir <dir>` (default `src`) - Directory path to be included in the tree. If you want to include the entire project file, set it to `.`

`--collapse` - Collapse the tree and render it

## Generation Mechanism

```
```sh
+---------------------------+
| |
+-------> generateTree | { name: 'App.tsx', attributes: { dir: 'src' }, children: [] }
Expand Down
4 changes: 2 additions & 2 deletions bin/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ import { getConfigFile } from '../plugin/getConfigFile';
import { FileTree } from '../plugin/FileTree';
import { __dirname, generateTemplate } from '../plugin/generateTemplate';

const { root, targetDir } = cliConfig(process.argv.slice(2));
const { root, targetDir, collapse } = cliConfig(process.argv.slice(2));

const { compilerOptions } = getConfigFile();
const { baseUrl, paths } = compilerOptions;

const fileTree = new FileTree(root, targetDir, baseUrl, paths);
generateTemplate(fileTree.tree);
generateTemplate(fileTree.tree, collapse);

const resultFilePath = resolve(__dirname, '../index.html');
await open(resultFilePath, { wait: true });
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

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

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "import-visualizer",
"version": "1.0.4",
"version": "1.1.0",
"main": "./dist/bin/cli.js",
"type": "module",
"bin": "./dist/bin/cli.js",
Expand All @@ -9,6 +9,7 @@
],
"scripts": {
"test": "jest -c jest.config.js --coverage",
"dev": "npm run build && node dist/bin/cli.js --root dist/bin/cli.js --targetDir dist --collapse",
"build": "run-p build:*",
"build:plugin": "rollup -c rollup.config.js",
"build:template": "rollup -c rollup.config-tree.js",
Expand Down
8 changes: 5 additions & 3 deletions plugin/cliConfig.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import type { CliOptions, cliArgType } from './types';

export function cliConfig(argv: NodeJS.Process['argv']): CliOptions {
const options = {
const options: CliOptions = {
root: 'src/App.tsx',
targetDir: 'src',
collapse: false,
};

argv.forEach((arg: cliArgType) => {
if (arg == '--root') options.root = argv[argv.indexOf(arg) + 1];
if (arg == '--targetDir') options.targetDir = argv[argv.indexOf(arg) + 1];
if (arg === '--root') options.root = argv[argv.indexOf(arg) + 1];
if (arg === '--targetDir') options.targetDir = argv[argv.indexOf(arg) + 1];
if (arg === '--collapse') options.collapse = true;
});

return options;
Expand Down
11 changes: 7 additions & 4 deletions plugin/generateTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import { readFileSync, writeFileSync } from 'fs';
import { dirname, resolve } from 'path';
import { fileURLToPath } from 'url';

export const __dirname = dirname(fileURLToPath(import.meta.url))
import type { FileNode } from './types';

const template = (data: string) => {
export const __dirname = dirname(fileURLToPath(import.meta.url));

const template = (data: string, collapse: boolean) => {
const script = readFileSync(resolve(__dirname, '../lib/tree.js'));

return `<!DOCTYPE html>
Expand All @@ -18,11 +20,12 @@ const template = (data: string) => {
<div id="root"></div>
</body>
<script>const data = ${data}</script>
<script>const collapse = ${collapse}</script>
<script>${script}</script>
</html>
`;
};

export function generateTemplate(data: any) {
writeFileSync(resolve(__dirname, '../index.html'), template(JSON.stringify(data)));
export function generateTemplate(data: FileNode, collapse: boolean) {
writeFileSync(resolve(__dirname, '../index.html'), template(JSON.stringify(data), collapse));
}
3 changes: 2 additions & 1 deletion plugin/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ interface FileNode {
children: FileNode[];
}

type cliArgType = '--root' | '--targetDir';
type cliArgType = '--root' | '--targetDir' | '--collapse';
interface CliOptions {
root: string;
targetDir: string;
collapse: boolean;
}

type configPath = Record<string, string[]>;
Expand Down
2 changes: 1 addition & 1 deletion rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export default [
dir: 'dist',
preserveModules: true,
},
external: ['open', 'json5', '@babel/parser', '@babel/traverse', 'fs', 'path', 'fs/promises'],
external: ['open', 'json5', '@babel/parser', '@babel/traverse', 'fs', 'path', 'fs/promises', 'url'],
plugins: [
typescript({
tsconfig: 'tsconfig.json',
Expand Down
74 changes: 69 additions & 5 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,77 @@
import React from 'react';
import React, { useEffect, useState } from 'react';
import ReactDOM from 'react-dom';
import Tree from 'react-d3-tree';
import TextSizeController from './TextSizeController.jsx';
import Controller from './Controller.jsx';
import CustomNode from './CustomNode.jsx';

const SEPERATION_INTERVAL = 0.5;
const DEPTH_INTERVAL = 50;

function App() {
const [orientation, setOrientation] = useState('horizontal');
const [depthFactor, setDepthFactor] = useState(500);
const [seperation, setSeperation] = useState(0.5);

const changeOrientation = () => {
if (orientation === 'vertical') setOrientation('horizontal');
else setOrientation('vertical');
};

const increaseDepthFactor = () => {
setDepthFactor((prev) => prev + DEPTH_INTERVAL);
};

const decreaseDepthFactor = () => {
setDepthFactor((prev) => (prev - DEPTH_INTERVAL > 0 ? prev - DEPTH_INTERVAL : prev));
};

const increaseSeperation = () => {
setSeperation((prev) => prev + SEPERATION_INTERVAL);
};

const decreaseSeperation = () => {
setSeperation((prev) => (prev - SEPERATION_INTERVAL > 0 ? prev - SEPERATION_INTERVAL : prev));
};

useEffect(() => {
if (orientation === 'horizontal') {
setDepthFactor(500);
setSeperation(0.5);
} else {
setDepthFactor(100);
setSeperation(2);
}
}, [orientation]);

return (
<div style={{ width: '98vw', height: '98vh' }}>
<Tree data={data} pathFunc='step' orientation='vertical' translate={{ x: 500, y: 250 }} separation={{ nonSiblings: 3, siblings: 3 }} />
<div style={{ width: '98vw', height: '95vh' }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 10 }}>
<div style={{ display: 'flex', gap: 5, alignItems: 'center', justifyContent: 'center' }}>
<span>Orientation</span>
<button type='button' onClick={changeOrientation}>
{orientation}
</button>
</div>
<TextSizeController />
<Controller title='Depth Factor' onClickIncrease={increaseDepthFactor} onClickDecrease={decreaseDepthFactor} />
<Controller title='Seperation' onClickIncrease={increaseSeperation} onClickDecrease={decreaseSeperation} />
</div>
<Tree
data={data}
pathFunc='step'
orientation={orientation}
translate={{ x: 500, y: 250 }}
separation={{ nonSiblings: seperation, siblings: seperation }}
rootNodeClassName='node__root'
branchNodeClassName='node__branch'
leafNodeClassName='node__leaf'
initialDepth={collapse ? 1 : undefined}
depthFactor={depthFactor}
renderCustomNodeElement={(rd3Props) => CustomNode({ ...rd3Props })}
/>
</div>
)
);
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
15 changes: 15 additions & 0 deletions src/Controller.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';

export default function Controller({ title, onClickIncrease, onClickDecrease }) {
return (
<div style={{ display: 'flex', gap: 5, alignItems: 'center', justifyContent: 'center' }}>
<span>{title}</span>
<button type='button' onClick={onClickIncrease}>
up
</button>
<button type='button' onClick={onClickDecrease}>
down
</button>
</div>
);
}
19 changes: 19 additions & 0 deletions src/CustomNode.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';

export default function CustomNode({ nodeDatum, toggleNode }) {
return (
<>
<circle r='15' onClick={toggleNode}></circle>
<g class='rd3t-label'>
<text class='rd3t-label__title' text-anchor='center' x='20' y='20'>
{nodeDatum.name}
</text>
<text class='rd3t-label__attributes'>
<tspan x='20' y='20' dy='1.2em'>
dir: {nodeDatum.attributes.dir}
</tspan>
</text>
</g>
</>
);
}
64 changes: 64 additions & 0 deletions src/TextSizeController.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React, { useState } from 'react';
import Controller from './Controller.jsx';

const TITLE_SIZE_INTERVAL = 1;
const ATTRIBUTE_SIZE_INTERVAL = 1;

export default function TextSizeController() {
const [titleSize, setTitleSize] = useState(20);
const [attributeSize, setAttributeSize] = useState(15);

const increaseTitleSize = () => {
setTitleSize((prev) => prev + TITLE_SIZE_INTERVAL);
};

const decreaseTitleSize = () => {
setTitleSize((prev) => (prev - TITLE_SIZE_INTERVAL > 0 ? prev - TITLE_SIZE_INTERVAL : prev));
};

const increaseAttributeSize = () => {
setAttributeSize((prev) => prev + ATTRIBUTE_SIZE_INTERVAL);
};

const decreaseAttributeSize = () => {
setAttributeSize((prev) => (prev - ATTRIBUTE_SIZE_INTERVAL > 0 ? prev - ATTRIBUTE_SIZE_INTERVAL : prev));
};

return (
<>
<Controller title='Title Size' onClickIncrease={increaseTitleSize} onClickDecrease={decreaseTitleSize} />
<Controller
title='Attribute Size'
onClickIncrease={increaseAttributeSize}
onClickDecrease={decreaseAttributeSize}
/>
<style jsx>
{`
.node__root > g > .rd3t-label__title {
font-size: ${titleSize}px;
}

.node__branch > g > .rd3t-label__title {
font-size: ${titleSize}px;
}

.node__leaf > g > .rd3t-label__title {
font-size: ${titleSize}px;
}

.node__root > g > .rd3t-label__attributes {
font-size: ${attributeSize}px;
}

.node__branch > g > .rd3t-label__attributes {
font-size: ${attributeSize}px;
}

.node__leaf > g > .rd3t-label__attributes {
font-size: ${attributeSize}px;
}
`}
</style>
</>
);
}