feat(secrets): add native file dialog support and documentation

Adds support for native file dialogs through the ui:dialogs permission. Includes comprehensive documentation and demo methods showing how to use saveFile, showSaveDialog, and showOpenDialog APIs.

The dialogs API allows addons to:
- Show native save dialogs and write files directly
- Show open dialogs to select files/directories
- Configure file filters, default paths, and dialog titles

Also adds demo methods (saveFileDemo, openFileDemo) to the starter addon that demonstrate proper usage patterns including permission checks and error handling.
This commit is contained in:
2026-01-23 10:17:35 -05:00
parent 3bd09651de
commit 0275df601d
3 changed files with 163 additions and 2 deletions

View File

@@ -104,7 +104,8 @@ The manifest defines everything about your addon.
"network:localhost", // Make local network requests "network:localhost", // Make local network requests
"network:external", // Make external network requests "network:external", // Make external network requests
"settings:store", // Store addon settings "settings:store", // Store addon settings
"notifications:show" // Show system notifications "notifications:show", // Show system notifications
"ui:dialogs" // Access native file dialogs
] ]
``` ```
@@ -187,6 +188,7 @@ context.settings // Settings storage
context.events // Event subscription context.events // Event subscription
context.ipc // IPC communication context.ipc // IPC communication
context.hostPort // Port of native host (if configured) context.hostPort // Port of native host (if configured)
context.dialogs // Native file dialogs (requires ui:dialogs permission)
``` ```
### Invoke Method ### Invoke Method
@@ -263,6 +265,53 @@ context.settings.onDidChange((key, value) => {
}); });
``` ```
### Native File Dialogs
Addons with the `ui:dialogs` permission can access native file dialogs via `context.dialogs`:
```javascript
// Check if dialogs are available
if (!this.context?.dialogs) {
return { error: 'Dialogs not available' };
}
// Save file with native dialog
const filePath = await this.context.dialogs.saveFile(content, {
defaultPath: 'export.txt',
filters: [
{ name: 'Text Files', extensions: ['txt'] },
{ name: 'All Files', extensions: ['*'] }
],
title: 'Save Export'
});
// Returns: string (file path) or undefined if canceled
// Open file with native dialog
const result = await this.context.dialogs.showOpenDialog({
filters: [
{ name: 'Text Files', extensions: ['txt', 'md'] },
{ name: 'All Files', extensions: ['*'] }
],
title: 'Select File',
multiSelections: false, // Allow multiple file selection
openDirectory: false // Select directories instead of files
});
// Returns: { canceled: boolean, filePaths: string[] }
// Show save dialog without writing file
const saveResult = await this.context.dialogs.showSaveDialog({
defaultPath: 'document.txt',
filters: [{ name: 'Text', extensions: ['txt'] }],
title: 'Choose Save Location'
});
// Returns: { canceled: boolean, filePath?: string }
```
**Methods:**
- `saveFile(content, options)` - Show save dialog and write content to selected file
- `showSaveDialog(options)` - Show save dialog, returns path only (you handle writing)
- `showOpenDialog(options)` - Show open dialog, returns selected path(s)
### License Status (Optional) ### License Status (Optional)
For commercial addons: For commercial addons:

View File

@@ -53,7 +53,8 @@
"network:localhost", "network:localhost",
"network:external", "network:external",
"settings:store", "settings:store",
"notifications:show" "notifications:show",
"ui:dialogs"
], ],
"_comment_capabilities": "What your addon can do - GitCaddy uses these to find addons", "_comment_capabilities": "What your addon can do - GitCaddy uses these to find addons",

View File

@@ -201,6 +201,13 @@ class MyFirstAddon {
case 'performAction': case 'performAction':
return this.performAction(args[0]); return this.performAction(args[0]);
// ---- Dialog Methods ----
case 'saveFileDemo':
return this.saveFileDemo(args[0], args[1]);
case 'openFileDemo':
return this.openFileDemo();
// ---- Host Methods ---- // ---- Host Methods ----
// These call the .NET host process // These call the .NET host process
case 'callHelloWorld': case 'callHelloWorld':
@@ -429,7 +436,9 @@ class MyFirstAddon {
'Settings storage', 'Settings storage',
'IPC communication', 'IPC communication',
'Event handling', 'Event handling',
'Native file dialogs',
], ],
hasDialogs: !!this.context?.dialogs,
}; };
} }
@@ -483,6 +492,100 @@ class MyFirstAddon {
}; };
} }
// ============================================================
// DIALOG METHODS
// These methods demonstrate the native file dialogs API
// Requires "ui:dialogs" permission in addon.json
// ============================================================
/**
* Save content to a file using a native Save dialog.
*
* Demonstrates context.dialogs.saveFile() which shows a native
* file picker and saves the content to the selected location.
*
* @param {string} content - Content to save
* @param {string} [filename] - Suggested filename
* @returns {Promise<{success: boolean, filePath?: string, error?: string}>}
*/
async saveFileDemo(content, filename) {
// Check if dialogs API is available
if (!this.context?.dialogs) {
this.context?.log.warn('Dialogs API not available - missing ui:dialogs permission?');
return { success: false, error: 'Dialogs not available' };
}
try {
// Show native save dialog and write file
const filePath = await this.context.dialogs.saveFile(content || 'Hello from addon!', {
defaultPath: filename || 'addon-export.txt',
filters: [
{ name: 'Text Files', extensions: ['txt'] },
{ name: 'All Files', extensions: ['*'] }
],
title: 'Save File Demo'
});
if (filePath) {
this.context?.log.info(`File saved to: ${filePath}`);
this.context?.ipc.send('show-notification', {
title: 'File Saved',
body: `Saved to: ${filePath}`,
});
return { success: true, filePath };
} else {
// User canceled
return { success: false, canceled: true };
}
} catch (error) {
this.context?.log.error('Failed to save file:', error);
return { success: false, error: error.message };
}
}
/**
* Open a file using a native Open dialog.
*
* Demonstrates context.dialogs.showOpenDialog() which shows a native
* file picker and returns the selected file path(s).
*
* @returns {Promise<{success: boolean, filePaths?: string[], error?: string}>}
*/
async openFileDemo() {
// Check if dialogs API is available
if (!this.context?.dialogs) {
this.context?.log.warn('Dialogs API not available - missing ui:dialogs permission?');
return { success: false, error: 'Dialogs not available' };
}
try {
// Show native open dialog
const result = await this.context.dialogs.showOpenDialog({
filters: [
{ name: 'Text Files', extensions: ['txt', 'md', 'json'] },
{ name: 'All Files', extensions: ['*'] }
],
title: 'Open File Demo',
multiSelections: false
});
if (!result.canceled && result.filePaths.length > 0) {
this.context?.log.info(`File selected: ${result.filePaths[0]}`);
this.context?.ipc.send('show-notification', {
title: 'File Selected',
body: `Selected: ${result.filePaths[0]}`,
});
return { success: true, filePaths: result.filePaths };
} else {
// User canceled
return { success: false, canceled: true };
}
} catch (error) {
this.context?.log.error('Failed to open file:', error);
return { success: false, error: error.message };
}
}
// ============================================================ // ============================================================
// HOST METHODS // HOST METHODS
// These methods communicate with the .NET host process // These methods communicate with the .NET host process
@@ -560,6 +663,14 @@ class MyFirstAddon {
* @property {IAddonIPC} ipc - IPC communication * @property {IAddonIPC} ipc - IPC communication
* @property {number} [hostPort] - Port of native host (if configured) * @property {number} [hostPort] - Port of native host (if configured)
* @property {IAppStateProxy} [appState] - Access to app state * @property {IAppStateProxy} [appState] - Access to app state
* @property {IAddonDialogs} [dialogs] - Native file dialogs (requires ui:dialogs permission)
*/
/**
* @typedef {Object} IAddonDialogs
* @property {function} showSaveDialog - Show native save dialog
* @property {function} showOpenDialog - Show native open dialog
* @property {function} saveFile - Show save dialog and write content to file
*/ */
/** /**