Skip to content

Commit 2e0fd7d

Browse files
committed
feat: enhance OpenAPI source management with JSON support and improved UI interactions
1 parent 7eaedec commit 2e0fd7d

File tree

5 files changed

+191
-43
lines changed

5 files changed

+191
-43
lines changed

src/vsc-extension/openapi-ui/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "openapi-ui",
33
"displayName": "OpenAPI UI",
44
"description": "OpenAPI UI viewer for VS Code",
5-
"version": "1.0.13",
5+
"version": "1.0.14",
66
"publisher": "JakubKozera",
77
"icon": "openapi-ui.png",
88
"repository": {

src/vsc-extension/openapi-ui/src/extension.ts

Lines changed: 158 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ export function activate(context: vscode.ExtensionContext) {
4040
);
4141
}
4242
);
43-
4443
// Register command to add OpenAPI source
4544
let addSourceDisposable = vscode.commands.registerCommand(
4645
"openapi-ui.addSource",
@@ -54,28 +53,76 @@ export function activate(context: vscode.ExtensionContext) {
5453
return;
5554
}
5655

57-
const url = await vscode.window.showInputBox({
58-
prompt: "Enter the URL of the OpenAPI specification",
59-
placeHolder: "e.g., https://api.example.com/openapi.json",
60-
validateInput: (value) => {
61-
if (!value) {
62-
return "URL is required";
63-
}
64-
try {
65-
new URL(value);
66-
return null;
67-
} catch {
68-
return "Please enter a valid URL";
69-
}
70-
},
71-
});
56+
// Ask user to choose between URL and JSON content
57+
const sourceType = await vscode.window.showQuickPick(
58+
[
59+
{
60+
label: "URL",
61+
description: "Load from a URL endpoint",
62+
value: "url",
63+
},
64+
{
65+
label: "JSON Content",
66+
description: "Paste JSON content directly",
67+
value: "json",
68+
},
69+
],
70+
{
71+
placeHolder: "Choose how to provide the OpenAPI specification",
72+
}
73+
);
7274

73-
if (!url) {
75+
if (!sourceType) {
7476
return;
7577
}
7678

77-
storage.addSource(name, url);
78-
vscode.window.showInformationMessage(`Added OpenAPI source: ${name}`);
79+
if (sourceType.value === "url") {
80+
const url = await vscode.window.showInputBox({
81+
prompt: "Enter the URL of the OpenAPI specification",
82+
placeHolder: "e.g., https://api.example.com/openapi.json",
83+
validateInput: (value) => {
84+
if (!value) {
85+
return "URL is required";
86+
}
87+
try {
88+
new URL(value);
89+
return null;
90+
} catch {
91+
return "Please enter a valid URL";
92+
}
93+
},
94+
});
95+
96+
if (!url) {
97+
return;
98+
}
99+
100+
storage.addSource(name, url);
101+
vscode.window.showInformationMessage(`Added OpenAPI source: ${name}`);
102+
} else {
103+
const jsonContent = await vscode.window.showInputBox({
104+
prompt: "Paste the OpenAPI JSON specification",
105+
placeHolder: "Paste your OpenAPI JSON content here...",
106+
validateInput: (value) => {
107+
if (!value) {
108+
return "JSON content is required";
109+
}
110+
try {
111+
JSON.parse(value);
112+
return null;
113+
} catch {
114+
return "Please enter valid JSON";
115+
}
116+
},
117+
});
118+
119+
if (!jsonContent) {
120+
return;
121+
}
122+
123+
storage.addJsonSource(name, jsonContent);
124+
vscode.window.showInformationMessage(`Added OpenAPI source: ${name}`);
125+
}
79126
}
80127
);
81128

@@ -128,13 +175,11 @@ export function activate(context: vscode.ExtensionContext) {
128175
}
129176
});
130177
return;
131-
}
132-
133-
// Create quick pick items
178+
} // Create quick pick items
134179
const quickPickItems = sources.map((source) => ({
135180
label: source.name,
136-
description: source.url,
137-
detail: `Created: ${source.createdAt.toLocaleString()}`,
181+
description: source.type === "url" ? source.url : "JSON Content",
182+
detail: `Type: ${source.type.toUpperCase()} | Created: ${source.createdAt.toLocaleString()}`,
138183
sourceId: source.id,
139184
}));
140185

@@ -156,12 +201,22 @@ export function activate(context: vscode.ExtensionContext) {
156201
vscode.window.showErrorMessage("OpenAPI source not found");
157202
return;
158203
}
159-
160-
if (!source.name || !source.url) {
204+
if (!source.name) {
161205
vscode.window.showErrorMessage("Invalid OpenAPI source data");
162206
return;
163207
}
164208

209+
if (source.type === "url" && !source.url) {
210+
vscode.window.showErrorMessage("Invalid OpenAPI source: missing URL");
211+
return;
212+
}
213+
214+
if (source.type === "json" && !source.content) {
215+
vscode.window.showErrorMessage(
216+
"Invalid OpenAPI source: missing JSON content"
217+
);
218+
return;
219+
}
165220
try {
166221
const panel = vscode.window.createWebviewPanel(
167222
"openapiSourceUI",
@@ -175,11 +230,21 @@ export function activate(context: vscode.ExtensionContext) {
175230
}
176231
);
177232

178-
panel.webview.html = getWebviewContent(
179-
panel.webview,
180-
context.extensionPath,
181-
source.url
182-
);
233+
if (source.type === "url") {
234+
panel.webview.html = getWebviewContent(
235+
panel.webview,
236+
context.extensionPath,
237+
source.url
238+
);
239+
} else {
240+
// For JSON content, create a blob URL
241+
panel.webview.html = getWebviewContent(
242+
panel.webview,
243+
context.extensionPath,
244+
undefined,
245+
source.content
246+
);
247+
}
183248

184249
vscode.window.showInformationMessage(
185250
`Loaded OpenAPI source: ${source.name}`
@@ -191,12 +256,64 @@ export function activate(context: vscode.ExtensionContext) {
191256
}
192257
}
193258
);
259+
// Register command to add JSON OpenAPI source
260+
let addJsonSourceDisposable = vscode.commands.registerCommand(
261+
"openapi-ui.addJsonSource",
262+
async () => {
263+
const name = await vscode.window.showInputBox({
264+
prompt: "Enter a name for the OpenAPI source",
265+
placeHolder: "e.g., My Custom API",
266+
});
267+
268+
if (!name) {
269+
return;
270+
}
271+
272+
const jsonContent = await vscode.window.showInputBox({
273+
prompt: "Paste the OpenAPI JSON specification",
274+
placeHolder: "Paste your OpenAPI JSON content here...",
275+
validateInput: (value) => {
276+
if (!value) {
277+
return "JSON content is required";
278+
}
279+
try {
280+
JSON.parse(value);
281+
return null;
282+
} catch {
283+
return "Please enter valid JSON";
284+
}
285+
},
286+
});
287+
288+
if (!jsonContent) {
289+
return;
290+
}
291+
try {
292+
storage.addJsonSource(name, jsonContent);
293+
vscode.window.showInformationMessage(
294+
`Added and opened OpenAPI source: ${name}`
295+
);
296+
297+
// Automatically load the source we just added
298+
const sources = storage.getSources();
299+
const newSource = sources.find((s) => s.name === name);
300+
if (newSource) {
301+
vscode.commands.executeCommand("openapi-ui.loadSource", newSource.id);
302+
}
303+
} catch (error) {
304+
vscode.window.showErrorMessage(
305+
`Failed to add OpenAPI source: ${error}`
306+
);
307+
}
308+
}
309+
);
194310
context.subscriptions.push(
195311
openViewDisposable,
196312
addSourceDisposable,
197313
removeSourceDisposable,
198314
refreshSourcesDisposable,
199-
loadSourceDisposable
315+
loadSourceDisposable,
316+
addJsonSourceDisposable
200317
);
201318
}
202319

@@ -205,7 +322,8 @@ export function deactivate() {}
205322
function getWebviewContent(
206323
webview: vscode.Webview,
207324
extensionPath: string,
208-
openapiUrl?: string
325+
openapiUrl?: string,
326+
jsonContent?: string
209327
): string {
210328
// Path to the core/dist directory
211329
const distPath = path.join(extensionPath, "core-dist");
@@ -233,9 +351,15 @@ function getWebviewContent(
233351
`src="${imgUri}"`
234352
);
235353

236-
// Replace the OpenAPI URL placeholder if provided
354+
// Handle OpenAPI source replacement
237355
if (openapiUrl) {
356+
// For URL sources, replace with the provided URL
238357
htmlContent = htmlContent.replace("#swagger_path#", openapiUrl);
358+
} else if (jsonContent) {
359+
// For JSON sources, create a blob URL and inject the content
360+
const encodedContent = Buffer.from(jsonContent).toString("base64");
361+
const dataUrl = `data:application/json;base64,${encodedContent}`;
362+
htmlContent = htmlContent.replace("#swagger_path#", dataUrl);
239363
}
240364

241365
return htmlContent;

src/vsc-extension/openapi-ui/src/models.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
export interface OpenAPISource {
22
id: string;
33
name: string;
4-
url: string;
4+
type: "url" | "json";
5+
url?: string;
6+
content?: string;
57
createdAt: Date;
68
}
79

src/vsc-extension/openapi-ui/src/storage.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,11 @@ export class OpenAPIStorage {
5555
public getSources(): OpenAPISource[] {
5656
return [...this.sources];
5757
}
58-
5958
public addSource(name: string, url: string): void {
6059
const newSource: OpenAPISource = {
6160
id: this.generateId(),
6261
name,
62+
type: "url",
6363
url,
6464
createdAt: new Date(),
6565
};
@@ -68,6 +68,19 @@ export class OpenAPIStorage {
6868
this.saveSources();
6969
}
7070

71+
public addJsonSource(name: string, content: string): void {
72+
const newSource: OpenAPISource = {
73+
id: this.generateId(),
74+
name,
75+
type: "json",
76+
content,
77+
createdAt: new Date(),
78+
};
79+
80+
this.sources.push(newSource);
81+
this.saveSources();
82+
}
83+
7184
public removeSource(id: string): boolean {
7285
const initialLength = this.sources.length;
7386
this.sources = this.sources.filter((source) => source.id !== id);

src/vsc-extension/openapi-ui/src/treeDataProvider.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,23 @@ export class OpenAPITreeItem extends vscode.TreeItem {
4646
) {
4747
super(source.name, collapsibleState);
4848

49-
this.tooltip = `${source.name}\n${
50-
source.url
51-
}\nCreated: ${source.createdAt.toLocaleString()}`;
52-
this.description = undefined;
49+
// Set tooltip based on source type
50+
if (source.type === "url") {
51+
this.tooltip = `${source.name}\nURL: ${
52+
source.url
53+
}\nCreated: ${source.createdAt.toLocaleString()}`;
54+
this.iconPath = new vscode.ThemeIcon("globe");
55+
} else {
56+
this.tooltip = `${
57+
source.name
58+
}\nType: JSON Content\nCreated: ${source.createdAt.toLocaleString()}`;
59+
this.iconPath = new vscode.ThemeIcon("file-code");
60+
}
61+
62+
this.description = source.type === "url" ? "URL" : "JSON";
5363
this.contextValue = "openapi-source";
5464

55-
// Add icons
56-
this.iconPath = new vscode.ThemeIcon("globe"); // Add command to load the source when clicked
65+
// Add command to load the source when clicked
5766
this.command = {
5867
command: "openapi-ui.loadSource",
5968
title: "Load OpenAPI Source",

0 commit comments

Comments
 (0)