diff --git a/e2e/verify_tip_proxy.spec.js b/e2e/verify_tip_proxy.spec.js new file mode 100644 index 00000000..af6b4740 --- /dev/null +++ b/e2e/verify_tip_proxy.spec.js @@ -0,0 +1,36 @@ +import { test, expect } from '@playwright/test'; + +test('launch tip-of-the-day proxy', async ({ page }) => { + await page.goto('/win98-web/'); + + // Wait for system to boot and Clippy to be available + await page.waitForSelector('.start-button'); + + // Launch Tip of the Day via console to trigger proxy + // Use a try-catch and wait for System.launchApp to be available if needed + await page.evaluate(async () => { + const waitForSystem = () => new Promise(resolve => { + if (window.System && typeof window.System.launchApp === 'function') { + resolve(); + } else { + const interval = setInterval(() => { + if (window.System && typeof window.System.launchApp === 'function') { + clearInterval(interval); + resolve(); + } + }, 100); + } + }); + await waitForSystem(); + await window.System.launchApp('tip-of-the-day'); + }); + + // Verify Assistant (Clippy) appears with a tip + // Clippy takes a moment to load and show the balloon + const balloon = page.locator('.clippy-balloon'); + await expect(balloon).toBeVisible({ timeout: 15000 }); + + const tipText = await balloon.innerText(); + console.log('Tip shown by Assistant:', tipText); + expect(tipText.length).toBeGreaterThan(0); +}); diff --git a/src/apps/clippy/clippy-app.js b/src/apps/clippy/clippy-app.js index 49433239..4cc51129 100644 --- a/src/apps/clippy/clippy-app.js +++ b/src/apps/clippy/clippy-app.js @@ -30,9 +30,9 @@ export class ClippyApp extends Application { return null; } - _onLaunch() { + _onLaunch(data) { // Call the legacy launch function. - launchClippyApp(this); + launchClippyApp(this, undefined, { showTip: data?.showTip }); } _cleanup() { diff --git a/src/apps/clippy/clippy.js b/src/apps/clippy/clippy.js index 832b9d45..10253c17 100644 --- a/src/apps/clippy/clippy.js +++ b/src/apps/clippy/clippy.js @@ -8,6 +8,8 @@ import { releaseBusyState, } from '../../system/busy-state-manager.js'; import { appManager } from '../../system/app-manager.js'; +import { apps } from '../../config/apps.js'; +import { getStartupApps, addStartupApp, removeStartupApp } from '../../system/startup-manager.js'; window.clippyAppInstance = null; let currentAgentName = @@ -60,6 +62,26 @@ async function askClippy(agent, question) { } } +async function showRandomTip(agent) { + const tips = apps.reduce((acc, app) => { + if (app.tips) { + return acc.concat(app.tips); + } + return acc; + }, []); + + if (tips.length === 0) return; + + const currentTipIndex = Math.floor(Math.random() * tips.length); + const tip = tips[currentTipIndex]; + const ttsEnabled = agent.isTTSEnabled(); + + // Create a temporary element to strip HTML for TTS if needed, + // though speakAndAnimate might handle it. + // The tips might contain tags. + await agent.speakAndAnimate(tip, "Explain", { useTTS: ttsEnabled }); +} + import { AGENT_NAMES } from '../../config/agents.js'; export function getClippyMenuItems(app) { @@ -76,6 +98,10 @@ export function getClippyMenuItems(app) { label: "&Animate", action: () => agent.animate(), }, + { + label: "&Show Tip", + action: () => showRandomTip(agent), + }, { label: "&Ask Clippy", default: true, @@ -100,6 +126,25 @@ export function getClippyMenuItems(app) { }, }, }, + { + label: "Show tips at startup", + checkbox: { + check: async () => { + const startupApps = await getStartupApps(); + return startupApps.includes('tip-of-the-day') || startupApps.includes('tipOfTheDay'); + }, + toggle: async () => { + const startupApps = await getStartupApps(); + const isEnabled = startupApps.includes('tip-of-the-day') || startupApps.includes('tipOfTheDay'); + if (isEnabled) { + await removeStartupApp('tip-of-the-day'); + await removeStartupApp('tipOfTheDay'); + } else { + await addStartupApp('tip-of-the-day'); + } + }, + }, + }, "MENU_DIVIDER", { label: "A&gent", @@ -144,11 +189,12 @@ export function showClippyContextMenu(event, app) { new window.ContextMenu(menuItems, event); } -export function launchClippyApp(app, agentName = currentAgentName) { +export function launchClippyApp(app, agentName = currentAgentName, options = {}) { if (app) { window.clippyAppInstance = app; } const appInstance = app || window.clippyAppInstance; + const showTip = options.showTip || false; if (window.clippyAgent) { // Gracefully hide and remove the current agent before loading a new one @@ -218,11 +264,15 @@ export function launchClippyApp(app, agentName = currentAgentName) { return originalSpeakAndAnimate.call(this, text, animation, newOptions); }; - agent.speakAndAnimate( - "Hey, there. Want quick answers to your questions? Just click me.", - "Explain", - { useTTS: ttsEnabled }, - ); + if (showTip) { + showRandomTip(agent); + } else { + agent.speakAndAnimate( + "Hey, there. Want quick answers to your questions? Just click me.", + "Explain", + { useTTS: ttsEnabled }, + ); + } let startX, startY; agent._el.on("mousedown", (e) => { diff --git a/src/apps/tip-of-the-day/tip-of-the-day-app.js b/src/apps/tip-of-the-day/tip-of-the-day-app.js index 7a385664..95b45834 100644 --- a/src/apps/tip-of-the-day/tip-of-the-day-app.js +++ b/src/apps/tip-of-the-day/tip-of-the-day-app.js @@ -1,8 +1,4 @@ import { Application } from '../../system/application.js'; -import { tipOfTheDayContent } from './tip-of-the-day.js'; -import { apps } from '../../config/apps.js'; -import { launchApp } from '../../system/app-manager.js'; -import { getStartupApps, addStartupApp, removeStartupApp } from '../../system/startup-manager.js'; import { ICONS } from '../../config/icons.js'; export class TipOfTheDayApp extends Application { @@ -28,81 +24,15 @@ export class TipOfTheDayApp extends Application { } _createWindow() { - const win = new $Window({ - id: this.id, - title: this.title, - outerWidth: this.width, - outerHeight: this.height, - resizable: this.resizable, - minimizeButton: this.minimizeButton, - maximizeButton: this.maximizeButton, - icons: this.icon, - }); - - win.$content.html(tipOfTheDayContent); - return win; + return null; } async _onLaunch() { - const tips = apps.reduce((acc, app) => { - if (app.tips) { - return acc.concat(app.tips); - } - return acc; - }, []); - - const contentElement = this.win.$content[0]; - let currentTipIndex = Math.floor(Math.random() * tips.length); - - const tipTextElement = contentElement.querySelector('#tip-text'); - const nextTipButton = contentElement.querySelector('#next-tip'); - const closeButton = contentElement.querySelector('.button-group button:last-child'); - - if (nextTipButton) { - nextTipButton.innerHTML = ''; - nextTipButton.appendChild(window.AccessKeys.toFragment('&Next Tip')); - } - if (closeButton) { - closeButton.innerHTML = ''; - closeButton.appendChild(window.AccessKeys.toFragment('&Close')); - closeButton.addEventListener('click', () => this.win.close()); - } - - const displayTip = (tipIndex) => { - if (tipTextElement) { - tipTextElement.innerHTML = tips[tipIndex]; - const links = tipTextElement.querySelectorAll('.tip-link'); - links.forEach(link => { - link.addEventListener('click', (e) => { - e.preventDefault(); - const appId = link.getAttribute('data-app'); - launchApp(appId); - }); - }); - } - }; - - displayTip(currentTipIndex); - - if (nextTipButton && tipTextElement) { - nextTipButton.addEventListener('click', () => { - currentTipIndex = (currentTipIndex + 1) % tips.length; - displayTip(currentTipIndex); - }); - } - - const showTipsCheckbox = contentElement.querySelector('#show-tips'); - if (showTipsCheckbox) { - const startupApps = await getStartupApps(); - showTipsCheckbox.checked = startupApps.includes('tipOfTheDay'); + const { launchApp, appManager } = await import('../../system/app-manager.js'); + // Delegate to the Assistant (Clippy) to show a tip + await launchApp("clippy", { showTip: true }); - showTipsCheckbox.addEventListener('change', async () => { - if (showTipsCheckbox.checked) { - await addStartupApp('tipOfTheDay'); - } else { - await removeStartupApp('tipOfTheDay'); - } - }); - } + // Close this proxy app + appManager.closeApp(this.instanceKey); } }