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
`;
}
/**
* 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();
}
}