Skip to content

Commit 460c845

Browse files
committed
ShowComponentHierarchy can now save the generated graph to a png file.
1 parent 33908cf commit 460c845

File tree

7 files changed

+183
-17
lines changed

7 files changed

+183
-17
lines changed

package-lock.json

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
"onCommand:angulartools.componentHierarchyDgml",
1414
"onCommand:angulartools.listAllImports",
1515
"onCommand:angulartools.projectDirectoryStructure",
16-
"onCommand:angulartools.packageJsonToMarkdown",
17-
"onCommand:angulartools.showComponentHierarchy"
16+
"onCommand:angulartools.packageJsonToMarkdown",
17+
"onCommand:angulartools.showComponentHierarchy"
1818
],
1919
"main": "./out/extension.js",
2020
"contributes": {
@@ -34,11 +34,11 @@
3434
{
3535
"command": "angulartools.componentHierarchyDgml",
3636
"title": "AngularTools: Generate a Directed Graph file (dgml) showing the components in the current project."
37-
},
38-
{
39-
"command": "angulartools.showComponentHierarchy",
40-
"title": "AngularTools: Show a graph representing the component hierarchy."
41-
}
37+
},
38+
{
39+
"command": "angulartools.showComponentHierarchy",
40+
"title": "AngularTools: Show a graph representing the component hierarchy."
41+
}
4242
]
4343
},
4444
"scripts": {
@@ -64,6 +64,7 @@
6464
},
6565
"dependencies": {
6666
"@types/xmldom": "^0.1.30",
67+
"js-base64": "^3.6.0",
6768
"node-fetch": "^2.6.1",
6869
"prettify-xml": "^1.2.0",
6970
"xmldom": "^0.3.0",
Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,36 @@
1-
#network {
1+
.fullScreen {
22
position: absolute;
33
top: 0px;
44
right: 0px;
55
bottom: 0px;
66
left: 0px;
77
display: block;
8-
border: 1px solid lightgray;
8+
}
9+
10+
#buttons {
11+
position: absolute;
12+
top: 0px;
13+
left: 0px;
14+
border : 1px solid #000;
15+
border-radius: 5px;
16+
background-color: #aaa;
17+
z-index: 100;
18+
padding: 5px 5px 5px 10px;
19+
margin: 5px;
20+
}
21+
22+
#helpText {
23+
display: none;
24+
color: #000;
25+
z-index: 300;
26+
}
27+
28+
input[type=button] {
29+
margin-right: 10px;
30+
}
31+
32+
#selectionLayer {
33+
display: none;
34+
background-color: rgba(240, 240, 240, 0.4);
35+
z-index: 200;
936
}

src/commands/showComponentHierarchy/showComponentHierarchy.ts

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as vscode from 'vscode';
22
import * as fs from 'fs';
33
import * as path from 'path';
44
import { FileSystemUtils } from "../../filesystemUtils";
5+
import { Base64 } from 'js-base64';
56

67
class Component {
78

@@ -51,16 +52,29 @@ class Edge {
5152
export class ShowComponentHierarchy {
5253

5354
private extensionContext: vscode.ExtensionContext;
55+
private fsUtils = new FileSystemUtils();
5456
constructor(context: vscode.ExtensionContext) {
5557
this.extensionContext = context;
5658
}
5759
public static get commandName(): string { return 'showComponentHierarchy'; }
5860

5961
public execute(webview: vscode.Webview) {
60-
const fsUtils = new FileSystemUtils();
61-
var directoryPath: string = fsUtils.getWorkspaceFolder();
62+
63+
webview.onDidReceiveMessage(
64+
message => {
65+
switch (message.command) {
66+
case 'saveAsPng':
67+
this.saveAsPng(message.text);
68+
return;
69+
}
70+
},
71+
undefined,
72+
this.extensionContext.subscriptions
73+
);
74+
75+
var directoryPath: string = this.fsUtils.getWorkspaceFolder();
6276
const excludeDirectories = ['bin', 'obj', 'node_modules', 'dist', 'packages', '.git', '.vs', '.github'];
63-
const componentFilenames = fsUtils.listFiles(directoryPath, excludeDirectories, this.isComponentFile);
77+
const componentFilenames = this.fsUtils.listFiles(directoryPath, excludeDirectories, this.isComponentFile);
6478
const components = this.findComponents(componentFilenames);
6579
this.enrichComponentsFromComponentTemplates(components);
6680

@@ -98,8 +112,8 @@ export class ShowComponentHierarchy {
98112
const outputJsFilename = 'showComponentHierarchy/showComponentHierarchy.js';
99113
let htmlContent = this.generateHtmlContent(webview, outputJsFilename);
100114

101-
fsUtils.writeFile(this.extensionContext?.asAbsolutePath(path.join('src', 'commands', 'showComponentHierarchy/showComponentHierarchy.html')), htmlContent, () => { } ); // For debugging
102-
fsUtils.writeFile(
115+
this.fsUtils.writeFile(this.extensionContext?.asAbsolutePath(path.join('src', 'commands', 'showComponentHierarchy/showComponentHierarchy.html')), htmlContent, () => { }); // For debugging
116+
this.fsUtils.writeFile(
103117
this.extensionContext?.asAbsolutePath(path.join('src', 'commands', outputJsFilename)),
104118
jsContent,
105119
() => {
@@ -252,4 +266,20 @@ export class ShowComponentHierarchy {
252266
htmlContent = htmlContent.replace('showComponentHierarchy.js', jsUri.toString());
253267
return htmlContent;
254268
}
269+
270+
private saveAsPng(messageText: string) {
271+
const dataUrl = messageText.split(',');
272+
if (dataUrl.length > 0) {
273+
const u8arr = Base64.toUint8Array(dataUrl[1]);
274+
275+
const workspaceDirectory = this.fsUtils.getWorkspaceFolder();
276+
const newFilePath = path.join(workspaceDirectory, 'ComponentHierarchy.png');
277+
this.fsUtils.writeFile(newFilePath, u8arr, () => {});
278+
279+
const angularToolsOutput = vscode.window.createOutputChannel("Angular Tools");
280+
angularToolsOutput.clear();
281+
angularToolsOutput.appendLine(`The file ComponentHierarchy.png has been created in the root of the workspace.\n`);
282+
angularToolsOutput.show();
283+
}
284+
}
255285
}

src/commands/showComponentHierarchy/showComponentHierarchy_Template.html

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,19 @@
66
<meta name="viewport" content="width=device-width, initial-scale=1.0">
77

88
<link href="showComponentHierarchy.css" rel="stylesheet">
9-
9+
1010
<script type="text/javascript" src="vis-network.min.js"></script>
1111

1212
</head>
1313

1414
<body>
15-
<div id="network">Testing...</div>
15+
<div id="buttons">
16+
<input type="button" id="saveAsPngButton" value="Save as png">
17+
<input type="button" id="copyToClipboardButton" value="Copy to clipboard">
18+
<div id="helpText">Click and drag to select the area to be saved.</div>
19+
</div>
20+
<div id="selectionLayer" class="fullScreen"><canvas id="captureCanvas"></canvas></div>
21+
<div id="network" class="fullScreen"></div>
1622
<script type="text/javascript" src="showComponentHierarchy.js"></script>
1723
</body>
1824

src/commands/showComponentHierarchy/showComponentHierarchy_Template.js

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,101 @@
2424
var container = document.getElementById('network');
2525
var network = new vis.Network(container, data, options);
2626

27+
const vscode = acquireVsCodeApi();
28+
const helpTextDiv = document.getElementById('helpText');
29+
let lastMouseX = lastMouseY = 0;
30+
let mouseX = mouseY = 0;
31+
let selection;
32+
// get the vis.js canvas
33+
const graphDiv = document.getElementById('network');
34+
const visDiv = graphDiv.firstElementChild;
35+
const graphCanvas = visDiv.firstElementChild;
36+
const selectionLayer = document.getElementById('selectionLayer');
37+
const selectionCanvas = selectionLayer.firstElementChild;
38+
let selectionCanvasContext;
39+
40+
// add button event listeners
41+
const saveAsPngButton = document.getElementById('saveAsPngButton');
42+
saveAsPngButton.addEventListener('click', saveAsPng);
43+
const copyToClipboardButton = document.getElementById('copyToClipboardButton');
44+
copyToClipboardButton.addEventListener('click', copyToClipboard);
45+
copyToClipboardButton.style['display'] = 'none'; // TODO: Remove when copyToClipboard is implemented
46+
47+
function mouseUpEventListener(event) {
48+
// Convert the canvas to image data that can be saved
49+
const aspectRatioX = graphCanvas.width / selectionCanvas.width;
50+
const aspectRatioY = graphCanvas.height / selectionCanvas.height;
51+
const finalSelectionCanvas = document.createElement('canvas');
52+
finalSelectionCanvas.width = selection.width;
53+
finalSelectionCanvas.height = selection.height;
54+
const finalSelectionCanvasContext = finalSelectionCanvas.getContext('2d');
55+
finalSelectionCanvasContext.drawImage(graphCanvas, selection.top * aspectRatioX, selection.left * aspectRatioY, selection.width * aspectRatioX, selection.height * aspectRatioY, 0, 0, selection.width, selection.height);
56+
57+
// Call back to the extension context to save the selected image to the workspace folder.
58+
vscode.postMessage({
59+
command: 'saveAsPng',
60+
text: finalSelectionCanvas.toDataURL()
61+
});
62+
finalSelectionCanvas.remove();
63+
selectionCanvasContext = undefined;
64+
selection = {};
65+
// hide the help text
66+
helpTextDiv.style['display'] = 'none';
67+
// hide selection layer and remove event listeners
68+
selectionLayer.removeEventListener('mouseup', mouseUpEventListener);
69+
selectionLayer.removeEventListener('mousedown', mouseDownEventListener);
70+
selectionLayer.removeEventListener('mousemove', mouseMoveEventListener);
71+
selectionLayer.style['display'] = 'none';
72+
}
73+
74+
function mouseDownEventListener(event) {
75+
lastMouseX = parseInt(event.clientX - selectionCanvas.offsetLeft);
76+
lastMouseY = parseInt(event.clientY - selectionCanvas.offsetTop);
77+
selectionCanvasContext = selectionCanvas.getContext("2d");
78+
}
79+
80+
function mouseMoveEventListener(event) {
81+
mouseX = parseInt(event.clientX - selectionCanvas.offsetLeft);
82+
mouseY = parseInt(event.clientY - selectionCanvas.offsetTop);
83+
if (selectionCanvasContext != undefined) {
84+
selectionCanvasContext.clearRect(0, 0, window.innerWidth, window.innerHeight);
85+
selectionCanvasContext.beginPath();
86+
const width = mouseX - lastMouseX;
87+
const height = mouseY - lastMouseY;
88+
selectionCanvasContext.rect(lastMouseX, lastMouseY, width, height);
89+
selection = { // Save the current position and size to be used when the mouseup event is fired
90+
'top': lastMouseX,
91+
'left': lastMouseY,
92+
'height': height,
93+
'width': width
94+
};
95+
selectionCanvasContext.strokeStyle = 'red';
96+
selectionCanvasContext.lineWidth = 2;
97+
selectionCanvasContext.stroke();
98+
}
99+
}
100+
101+
function saveAsPng() {
102+
// show the help text
103+
helpTextDiv.style['display'] = 'block';
104+
105+
// show the selection layer
106+
selectionLayer.style['display'] = 'block';
107+
108+
// make sure the selection canvas covers the whole screen
109+
selectionCanvas.width = window.innerWidth;
110+
selectionCanvas.height = window.innerHeight;
111+
// reset the current context and selection
112+
selectionCanvasContext = undefined;
113+
selection = {};
114+
115+
selectionLayer.addEventListener("mouseup", mouseUpEventListener, true);
116+
selectionLayer.addEventListener("mousedown", mouseDownEventListener , true);
117+
selectionLayer.addEventListener("mousemove", mouseMoveEventListener, true);
118+
}
119+
120+
function copyToClipboard() {
121+
console.log('Not implemented yet...');
122+
}
123+
27124
}());

src/filesystemUtils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export class FileSystemUtils {
6161
return directoryPath;
6262
}
6363

64-
public writeFile(filename: string, content: string, callback: () => void) {
64+
public writeFile(filename: string, content: string | Uint8Array, callback: () => void) {
6565
fs.writeFile(filename, content, function (err) {
6666
if (err) {
6767
return console.error(err);

0 commit comments

Comments
 (0)