UNPKG

@bobfrankston/cmsgbox

Version:

Cross-platform dialog utility for Node.js using Python and tkinter

285 lines (250 loc) 9.88 kB
#!/usr/bin/env node import { spawn } from 'child_process'; /** * Options for the message box dialog. */ export interface MessageBoxOptions { /** The message text to display in the dialog. */ message: string; /** The title of the dialog window. Defaults to "Message". */ title?: string; /** Whether to show an input dialog instead of a message dialog. */ input?: boolean; /** The default text to pre-fill in the input field (only used if input is true). */ defaultText?: string; } const dirname = import.meta.dirname; /** * Displays a message dialog with the specified message and title. * @param message The message text to display. * @param title The title of the dialog window. */ export async function showMessage(message: string, title?: string): Promise<void> { return new Promise<void>((resolve, reject) => { const titleText = title || "Message"; // Create JSON parameters for the Python script const params = JSON.stringify({ type: 'message', message: message, title: titleText }); // Call external Python script with JSON parameters const pythonArgs = [ 'pyutils/msgbox.py', '-json', params ]; const child = spawn('python3', pythonArgs, { stdio: ['pipe', 'pipe', 'pipe'], cwd: dirname }); let output = ''; let errorOutput = ''; child.stdout.on('data', (data) => output += data.toString()); child.stderr.on('data', (data) => errorOutput += data.toString()); child.on('exit', (code) => { if (code === 0) { resolve(); } else { reject(new Error(`Python dialog failed: ${errorOutput}`)); } }); child.on('error', (err) => { // Try python instead of python3 const fallbackChild = spawn('python', pythonArgs, { stdio: ['pipe', 'pipe', 'pipe'], cwd: dirname }); fallbackChild.on('exit', (fallbackCode) => { if (fallbackCode === 0) { resolve(); } else { reject(new Error(`Python dialog failed: ${errorOutput}`)); } }); fallbackChild.on('error', reject); }); }); } /** * Displays an input dialog and returns the user's input. * @param message The prompt message for the input dialog. * @param title The title of the dialog window. * @param defaultText The default text to pre-fill in the input field. * @returns The user's input string, or undefined if canceled. */ /** * Displays an input dialog and returns the user's input. * @param message The prompt message for the input dialog. * @param title The title of the dialog window. * @param defaultText The default text to pre-fill in the input field. * @returns The user's input string, or undefined if canceled. */ export async function showInput(message?: string, title?: string, defaultText?: string): Promise<string | undefined> { return new Promise<string | undefined>((resolve, reject) => { const messageText = message || "Enter text:"; const titleText = title || "Input"; const defaultValue = defaultText || ""; // Create JSON parameters for the Python script const params = JSON.stringify({ type: 'input', message: messageText, title: titleText, defaultValue: defaultValue }); // Call external Python script with JSON parameters const pythonArgs = [ 'pyutils/msgbox.py', '-json', params ]; const child = spawn('python3', pythonArgs, { stdio: ['pipe', 'pipe', 'pipe'], cwd: dirname }); let output = ''; let errorOutput = ''; child.stdout.on('data', (data) => output += data.toString()); child.stderr.on('data', (data) => errorOutput += data.toString()); child.on('exit', (code) => { if (code === 0) { const result = output.trim(); if (result === "Cancel") { resolve(undefined); } else { resolve(result); } } else { reject(new Error(`Python dialog failed: ${errorOutput}`)); } }); child.on('error', (err) => { // Try python instead of python3 const fallbackChild = spawn('python', pythonArgs, { stdio: ['pipe', 'pipe', 'pipe'], cwd: dirname }); let fallbackOutput = ''; let fallbackError = ''; fallbackChild.stdout.on('data', (data) => fallbackOutput += data.toString()); fallbackChild.stderr.on('data', (data) => fallbackError += data.toString()); fallbackChild.on('exit', (fallbackCode) => { if (fallbackCode === 0) { const result = fallbackOutput.trim(); if (result === "Cancel") { resolve(undefined); } else { resolve(result); } } else { reject(new Error(`Python dialog failed: ${fallbackError}`)); } }); fallbackChild.on('error', reject); }); }); } /** * Unified function to display either a message or input dialog based on options. * @param options The options for the dialog. * @returns For input dialogs, returns the user's input string or undefined. For message dialogs, returns undefined. */ export async function messageBox(options: MessageBoxOptions): Promise<string | void> { if (options.input) { return await showInput(options.message, options.title, options.defaultText); } else { await showMessage(options.message, options.title); } } /** * Class for creating message box instances (alternative API). */ export class MessageBox { /** * Displays a message dialog. * @param message The message text. * @param title The dialog title. */ async show(message: string, title?: string): Promise<void> { return showMessage(message, title); } /** * Displays an input dialog. * @param message The prompt message. * @param title The dialog title. * @param defaultText The default input text. * @returns The user's input or undefined. */ async input(message?: string, title?: string, defaultText?: string): Promise<string | undefined> { return showInput(message, title, defaultText); } } export default { showMessage, showInput, messageBox, MessageBox }; // Command line interface if (import.meta.main) { function showUsageAndExit(msg?: string) { if (msg) console.error(`Error: ${msg}`); console.error('Usage: node index.js [-m <message>] [-t <title>] [-i] [-d <default>]'); console.error(' -m, --message Set the message text (can be multiple words)'); console.error(' -t, --title Set the title of the message box'); console.error(' -i, --input Show input dialog instead of message'); console.error(' -d, --default Default text for input dialog'); process.exit(1); } async function main() { const args = process.argv.slice(2); if (args.length === 0) showUsageAndExit(); let message: string | undefined = undefined; let title = 'Message'; let isInput = false; let defaultText = ''; for (let i = 0; i < args.length; i++) { const arg = args[i]; if (arg === '-m' || arg === '--message') { if (i + 1 < args.length) { i++; // Move to the message message = args[i] || ''; // Continue collecting message until next flag or end while (i + 1 < args.length && !args[i + 1].startsWith('-')) { i++; message += ' ' + (args[i] || ''); } } else { console.error('Error: -m requires a message argument'); showUsageAndExit(); } } else if (arg === '-t' || arg === '--title') { if (i + 1 < args.length) { title = args[++i]; } else { showUsageAndExit('Error: -t requires a title argument'); } } else if (arg === '-i' || arg === '--input') { isInput = true; } else if (arg === '-d' || arg === '--default') { if (i + 1 < args.length) { defaultText = args[++i]; } else { console.error('Error: -d requires a default text argument'); showUsageAndExit(); } } else if (arg.startsWith('-')) { showUsageAndExit(`Error: Unrecognized option '${arg}'`); } else { // If no -m flag and this is the first non-flag argument, treat as message if (message === undefined) { message = arg || ''; } else { showUsageAndExit(`Error: Unexpected argument '${arg}'`); } } } if (message === undefined || message === '') showUsageAndExit('Error: Message is required'); const result = await messageBox({ message, title, input: isInput, defaultText }); if (isInput && result) { console.log(result); } } await main(); }