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:
51
README.md
51
README.md
@@ -104,7 +104,8 @@ The manifest defines everything about your addon.
|
||||
"network:localhost", // Make local network requests
|
||||
"network:external", // Make external network requests
|
||||
"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.ipc // IPC communication
|
||||
context.hostPort // Port of native host (if configured)
|
||||
context.dialogs // Native file dialogs (requires ui:dialogs permission)
|
||||
```
|
||||
|
||||
### 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)
|
||||
|
||||
For commercial addons:
|
||||
|
||||
@@ -53,7 +53,8 @@
|
||||
"network:localhost",
|
||||
"network:external",
|
||||
"settings:store",
|
||||
"notifications:show"
|
||||
"notifications:show",
|
||||
"ui:dialogs"
|
||||
],
|
||||
|
||||
"_comment_capabilities": "What your addon can do - GitCaddy uses these to find addons",
|
||||
|
||||
111
main/index.js
111
main/index.js
@@ -201,6 +201,13 @@ class MyFirstAddon {
|
||||
case 'performAction':
|
||||
return this.performAction(args[0]);
|
||||
|
||||
// ---- Dialog Methods ----
|
||||
case 'saveFileDemo':
|
||||
return this.saveFileDemo(args[0], args[1]);
|
||||
|
||||
case 'openFileDemo':
|
||||
return this.openFileDemo();
|
||||
|
||||
// ---- Host Methods ----
|
||||
// These call the .NET host process
|
||||
case 'callHelloWorld':
|
||||
@@ -429,7 +436,9 @@ class MyFirstAddon {
|
||||
'Settings storage',
|
||||
'IPC communication',
|
||||
'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
|
||||
// These methods communicate with the .NET host process
|
||||
@@ -560,6 +663,14 @@ class MyFirstAddon {
|
||||
* @property {IAddonIPC} ipc - IPC communication
|
||||
* @property {number} [hostPort] - Port of native host (if configured)
|
||||
* @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
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user