Skip to content

Commit 3633ecb

Browse files
committed
Sketchbook sidebar & examples UX
1 parent 43ed2b6 commit 3633ecb

File tree

6 files changed

+124
-28
lines changed

6 files changed

+124
-28
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ client/server
44
.vscode-test
55

66

7-
install-locator-*
7+
install-locator-*
8+
/install-locator/bin

client/src/setupCommands.ts

Lines changed: 75 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { dirname } from 'path/posix';
2-
import { ExtensionContext, commands, Uri, window } from 'vscode';
2+
import { ExtensionContext, commands, Uri, window, workspace } from 'vscode';
33
import { state } from './extension';
44

55
export function setupCommands(context: ExtensionContext) {
@@ -33,9 +33,7 @@ export function setupCommands(context: ExtensionContext) {
3333

3434
// Send the command to the terminal
3535
terminal.sendText(
36-
`${path} cli --sketch="${dirname(
37-
resource.fsPath
38-
)}" --run`,
36+
`${path} cli --sketch="${dirname(resource.fsPath)}" --run`,
3937
true
4038
);
4139
});
@@ -49,5 +47,77 @@ export function setupCommands(context: ExtensionContext) {
4947
state.terminal.sendText('\x03', false);
5048
});
5149

52-
context.subscriptions.push(runSketch, stopSketch);
50+
const openSketch = commands.registerCommand('processing.sketch.open', async (folder) => {
51+
if (!folder) {
52+
window.showErrorMessage("No sketch folder provided.");
53+
return;
54+
}
55+
56+
workspace.updateWorkspaceFolders(
57+
workspace.workspaceFolders ? workspace.workspaceFolders.length : 0,
58+
null,
59+
{ uri: Uri.file(folder) }
60+
);
61+
await commands.executeCommand('workbench.view.explorer');
62+
const folderName = folder.split('/').pop();
63+
const sketchFile = Uri.file(`${folder}/${folderName}.pde`);
64+
try {
65+
if (!workspace.fs.stat(sketchFile)) {
66+
return;
67+
}
68+
69+
const doc = await workspace.openTextDocument(sketchFile);
70+
if (!doc) {
71+
window.showErrorMessage(`Could not open sketch file: ${sketchFile.fsPath}`);
72+
return;
73+
}
74+
await window.showTextDocument(doc, { preview: false });
75+
window.activeTextEditor?.revealRange(doc.lineAt(0).range);
76+
} catch (error) {
77+
window.showErrorMessage(`Could not open sketch file: ${sketchFile.fsPath}`);
78+
console.error(error);
79+
}
80+
});
81+
82+
const newSketch = commands.registerCommand('processing.sketch.new', async () => {
83+
const folder = await window.showOpenDialog({
84+
canSelectFiles: false,
85+
canSelectFolders: true,
86+
canSelectMany: false,
87+
title: "Select a folder to create a new sketch in",
88+
defaultUri: workspace.workspaceFolders ? workspace.workspaceFolders[0].uri : undefined,
89+
});
90+
if (!folder || folder.length === 0) {
91+
return;
92+
}
93+
// TODO: increment a,b,c if a sketch with the same name already exists
94+
const sketchName = await window.showInputBox({
95+
prompt: "Enter the name of the new sketch",
96+
placeHolder: "Sketch name",
97+
value: `sketch_${new Date().toISOString().slice(2, 10).replace(/-/g, '')}a`,
98+
validateInput: (value) => {
99+
if (!value || value.trim() === "") {
100+
return "Sketch name cannot be empty.";
101+
}
102+
return null;
103+
}
104+
});
105+
if (!sketchName) {
106+
window.showErrorMessage("No sketch name provided.");
107+
return;
108+
}
109+
const sketchPath = Uri.joinPath(folder[0], sketchName);
110+
try {
111+
await workspace.fs.createDirectory(sketchPath);
112+
const sketchFile = Uri.joinPath(sketchPath, `${sketchName}.pde`);
113+
await workspace.fs.writeFile(sketchFile, new Uint8Array());
114+
await commands.executeCommand('processing.sketch.open', sketchPath.fsPath);
115+
window.showInformationMessage(`New sketch created: ${sketchName}`);
116+
} catch (error) {
117+
window.showErrorMessage(`Could not create sketch: ${error instanceof Error ? error.message : error}`);
118+
console.error(error);
119+
}
120+
});
121+
122+
context.subscriptions.push(runSketch, stopSketch, openSketch, newSketch);
53123
}

client/src/setupDecorators.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ class PdeFileDecorationProvider implements vscode.FileDecorationProvider {
44
onDidChangeFileDecorations?: vscode.Event<vscode.Uri | vscode.Uri[] | undefined>;
55

66
provideFileDecoration(uri: vscode.Uri): vscode.ProviderResult<vscode.FileDecoration> {
7-
// if (uri.path.endsWith('.pde')) {
8-
// return {
9-
// badge: '$(play)',
10-
// tooltip: 'Processing Sketch',
11-
// propagate: false
12-
// };
13-
// }
7+
if (uri.path.endsWith('.pde')) {
8+
return {
9+
badge: '$(play)',
10+
tooltip: 'Processing Sketch',
11+
propagate: false
12+
};
13+
}
1414
return undefined;
1515
}
1616
}

client/src/setupSidebar.ts

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { exec } from 'child_process';
22
import { join } from 'path';
33
import { ProviderResult, TreeDataProvider, TreeItem, TreeItemCollapsibleState, window } from 'vscode';
44
import { state } from './extension';
5+
import { existsSync } from 'fs';
56

67

78
export interface Sketch {
@@ -20,9 +21,12 @@ export interface Folder {
2021
sketches?: Sketch[];
2122
}
2223

23-
24-
2524
export async function setupSidebar() {
25+
setupExamples();
26+
setupSketchbook();
27+
}
28+
29+
async function setupExamples() {
2630
const examples = await new Promise<Folder[]>((resolve) => {
2731
exec(`${state.selectedVersion.path} contributions examples list`, (error, stdout, stderr) => {
2832
if (error) {
@@ -36,7 +40,9 @@ export async function setupSidebar() {
3640

3741
const examplesProvider = new ProcessingWindowDataProvider(examples);
3842
window.createTreeView('processingSidebarExamplesView', { treeDataProvider: examplesProvider });
43+
}
3944

45+
async function setupSketchbook() {
4046
const sketchbook = await new Promise<Folder[]>((resolve) => {
4147
exec(`${state.selectedVersion.path} sketchbook list`, (error, stdout, stderr) => {
4248
if (error) {
@@ -49,7 +55,6 @@ export async function setupSidebar() {
4955
});
5056
const sketchbookProvider = new ProcessingWindowDataProvider(sketchbook);
5157
window.createTreeView('processingSidebarSketchbookView', { treeDataProvider: sketchbookProvider });
52-
5358
}
5459

5560
class FolderTreeItem extends TreeItem {
@@ -59,7 +64,6 @@ class FolderTreeItem extends TreeItem {
5964
const label = folder.name;
6065
super(label, TreeItemCollapsibleState.Collapsed);
6166
this.tooltip = `${this.label}`;
62-
this.iconPath = join(__dirname, "..", "..", "media/processing.svg");
6367
}
6468
}
6569

@@ -70,14 +74,20 @@ class SketchTreeItem extends TreeItem {
7074
const label = sketch.name;
7175
super(label, TreeItemCollapsibleState.None);
7276
this.tooltip = `${this.label}`;
73-
this.iconPath = join(__dirname, "..", "..", "media/processing.svg");
77+
this.iconPath = join(__dirname, "..", "..", "media/processing-flat-color.svg");
78+
this.command = {
79+
command: 'processing.sketch.open',
80+
title: 'Open Sketch',
81+
arguments: [this.sketch.path]
82+
};
83+
84+
const preview = `${sketch.path}/${sketch.name}.png`;
85+
if (existsSync(preview)) {
86+
this.iconPath = preview;
87+
}
7488
}
7589
}
7690

77-
// TODO: Top level items: [examples, sketchbook]
78-
// TODO: Add examples from libraries
79-
// TODO: Connect to Processing and request where the sketchbook is located
80-
8191
class ProcessingWindowDataProvider implements TreeDataProvider<FolderTreeItem | SketchTreeItem> {
8292
constructor(
8393
public readonly folders: Folder[],
@@ -89,9 +99,7 @@ class ProcessingWindowDataProvider implements TreeDataProvider<FolderTreeItem |
8999
}
90100
getChildren(element?: FolderTreeItem): ProviderResult<(FolderTreeItem | SketchTreeItem)[]> {
91101
if (element === undefined) {
92-
return this.folders.map((folder) => {
93-
return new FolderTreeItem(folder);
94-
});
102+
return this.folders.flatMap((folder) => folder.children?.map(child => new FolderTreeItem(child)) ?? []);
95103
} else {
96104
const sketches = element.folder.sketches?.map((sketch) => {
97105
return new SketchTreeItem(sketch);
@@ -107,4 +115,5 @@ class ProcessingWindowDataProvider implements TreeDataProvider<FolderTreeItem |
107115
return [...sketches, ...folders];
108116
}
109117
}
110-
}
118+
}
119+

client/tsconfig.tsbuildinfo

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"root":["./src/compareversions.ts","./src/extension.ts","./src/setupcommands.ts","./src/setupdecorators.ts","./src/setuplanguageserver.ts","./src/setupselectedversion.ts","./src/setupsidebar.ts"],"errors":true,"version":"5.8.3"}
1+
{"root":["./src/compareversions.ts","./src/extension.ts","./src/setupcommands.ts","./src/setupdecorators.ts","./src/setuplanguageserver.ts","./src/setupselectedversion.ts","./src/setupsidebar.ts"],"version":"5.8.3"}

package.json

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,15 @@
3131
"command": "processing.sketch.stop",
3232
"title": "Stop the Processing Sketch",
3333
"icon": "media/stop.svg"
34+
},
35+
{
36+
"command": "processing.sketch.open",
37+
"title": "Open a Processing Sketch"
38+
},
39+
{
40+
"command": "processing.sketch.new",
41+
"title": "Create a new Processing Sketch",
42+
"icon": "$(new-file)"
3443
}
3544
],
3645
"menus": {
@@ -53,6 +62,13 @@
5362
"group": "navigation"
5463
}
5564
],
65+
"view/title": [
66+
{
67+
"command": "processing.sketch.new",
68+
"when": "view == processingSidebarSketchbookView",
69+
"group": "navigation"
70+
}
71+
],
5672
"terminal/context": [
5773
{
5874
"command": "processing.sketch.run",
@@ -114,7 +130,7 @@
114130
"icon": {
115131
"light": "media/processing-icon.svg",
116132
"dark": "media/processing-icon.svg"
117-
}
133+
}
118134
}
119135
],
120136
"grammars": [

0 commit comments

Comments
 (0)