import * as vscode from 'vscode'; import { Database } from '../core/database'; import { Pet } from '../core/pet'; import { ItemType, getItem } from '../types/items'; import { getPetAscii } from './ascii-art'; interface QuickPickItemWithId extends vscode.QuickPickItem { itemId: string; } /** * WebviewViewProvider for the Pet Terminal Dashboard * Displays pet ASCII art, stats, and interactive buttons in VS Code sidebar */ export class PetPanelProvider implements vscode.WebviewViewProvider { private _view?: vscode.WebviewView; constructor( private readonly _extensionUri: vscode.Uri, private readonly _db: Database ) {} /** * Called when VS Code resolves the webview view */ public resolveWebviewView(webviewView: vscode.WebviewView): void { this._view = webviewView; // Configure webview options webviewView.webview.options = { enableScripts: true, localResourceRoots: [this._extensionUri], }; // Set the HTML content webviewView.webview.html = this._getHtmlForWebview(webviewView.webview); // Handle messages from the webview webviewView.webview.onDidReceiveMessage(async (data) => { switch (data.command) { case 'feed': await this.handleFeed(); break; case 'play': await this.handlePlay(); break; case 'ready': // Webview is ready, send initial data this.update(); break; } }); // Load initial pet data this.update(); } /** * Update the webview with current pet data * Call this whenever pet state changes */ public update(): void { if (this._view) { const pet = this._db.getPet(); if (pet) { const ascii = getPetAscii(pet.species, pet.mood, pet.isSleeping); this._view.webview.postMessage({ type: 'update', pet: pet, ascii: ascii, }); } else { // No pet exists this._view.webview.postMessage({ type: 'update', pet: null, ascii: '', }); } } } /** * Generate the HTML content for the webview */ private _getHtmlForWebview(_webview: vscode.Webview): string { return ` Pet Terminal

Loading pet...

`; } /** * Handle feed command from webview */ private async handleFeed(): Promise { if (!this._db.hasPet()) { vscode.window.showErrorMessage("You don't have a pet yet! Run 'pet init' in terminal."); return; } const pet = await Pet.loadOrCreate(this._db); const data = pet.getData(); if (data.isSleeping) { vscode.window.showWarningMessage(`${data.name} is sleeping! Wake them up first.`); this.update(); return; } const inventory = pet.getInventory(); const foodItems = inventory.getItemsByType(ItemType.FOOD); if (foodItems.length === 0) { vscode.window.showErrorMessage('No food in inventory! Buy some from the shop.'); return; } // Build quick pick items const quickPickItems: QuickPickItemWithId[] = []; for (const item of foodItems) { const itemDef = getItem(item.itemId); if (itemDef) { quickPickItems.push({ label: `\${itemDef.emoji} \${itemDef.name}`, description: `x\${item.quantity}`, itemId: item.itemId, }); } } // Show quick pick const selected = await vscode.window.showQuickPick(quickPickItems, { placeHolder: 'What would you like to feed your pet?', }); if (!selected) { return; // User cancelled } // Use the item const result = pet.useItem(selected.itemId); if (result.success) { const itemDef = getItem(selected.itemId); vscode.window.showInformationMessage( `${data.name} enjoyed the ${itemDef?.name || 'food'}! ${itemDef?.emoji || ''} (XP: +${result.xpGained})` ); if (result.levelUp) { vscode.window.showInformationMessage(`🎉 Level ${result.newLevel} reached!`); } } else { vscode.window.showWarningMessage(result.message); } // Update webview to reflect changes this.update(); } /** * Handle play command from webview */ private async handlePlay(): Promise { if (!this._db.hasPet()) { vscode.window.showErrorMessage("You don't have a pet yet! Run 'pet init' in terminal."); return; } const pet = await Pet.loadOrCreate(this._db); const data = pet.getData(); if (data.isSleeping) { vscode.window.showWarningMessage(`${data.name} is sleeping! Wake them up first.`); this.update(); return; } if (data.stats.energy < 15) { vscode.window.showWarningMessage(`${data.name} is too tired to play! Let them rest.`); this.update(); return; } const inventory = pet.getInventory(); const toyItems = inventory.getItemsByType(ItemType.TOY); if (toyItems.length === 0) { vscode.window.showErrorMessage('No toys! Get some from the shop.'); return; } // Build quick pick items const quickPickItems: QuickPickItemWithId[] = []; for (const item of toyItems) { const itemDef = getItem(item.itemId); if (itemDef) { quickPickItems.push({ label: `\${itemDef.emoji} \${itemDef.name}`, description: `x\${item.quantity}`, itemId: item.itemId, }); } } // Show quick pick const selected = await vscode.window.showQuickPick(quickPickItems, { placeHolder: 'What would you like to play with?', }); if (!selected) { return; // User cancelled } // Use the item const result = pet.useItem(selected.itemId); if (result.success) { const itemDef = getItem(selected.itemId); vscode.window.showInformationMessage( `${data.name} had fun with the ${itemDef?.name || 'toy'}! ${itemDef?.emoji || ''} (XP: +${result.xpGained})` ); if (result.levelUp) { vscode.window.showInformationMessage(`🎉 Level ${result.newLevel} reached!`); } } else { vscode.window.showWarningMessage(result.message); } // Update webview to reflect changes this.update(); } }