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: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:
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
111
main/index.js
111
main/index.js
@@ -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
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user