diff --git a/package-lock.json b/package-lock.json index cbe18d8a..a6de6f4c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "supercmd", - "version": "1.0.25-PMH", + "version": "1.0.26", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "supercmd", - "version": "1.0.25-PMH", + "version": "1.0.26", "hasInstallScript": true, "license": "ISC", "dependencies": { @@ -39,6 +39,10 @@ "typescript": "^5.3.3", "vite": "^5.0.11", "wait-on": "^7.2.0" + }, + "optionalDependencies": { + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12" } }, "node_modules/@alloc/quick-lru": { diff --git a/package.json b/package.json index f262a8c1..364e3e7f 100644 --- a/package.json +++ b/package.json @@ -42,8 +42,6 @@ }, "dependencies": { "@aptabase/electron": "^0.3.1", - "@esbuild/darwin-arm64": "0.19.12", - "@esbuild/darwin-x64": "0.19.12", "@phosphor-icons/react": "^2.1.10", "@raycast/api": "^1.104.5", "electron-builder-notarize": "^1.5.2", @@ -57,6 +55,10 @@ "react-dom": "^18.2.0", "transliteration": "^2.6.1" }, + "optionalDependencies": { + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12" + }, "build": { "appId": "com.supercmd.app", "productName": "SuperCmd", diff --git a/src/main/commands.ts b/src/main/commands.ts index 4a89f301..2a10ce71 100644 --- a/src/main/commands.ts +++ b/src/main/commands.ts @@ -67,6 +67,22 @@ const SHUTDOWN_ICON_DATA_URL = svgToBase64DataUrl( '' ); +const CONFETTI_ICON_DATA_URL = svgToBase64DataUrl( + '' +); + +const FIREWORKS_ICON_DATA_URL = svgToBase64DataUrl( + '' +); + +const SNOW_ICON_DATA_URL = svgToBase64DataUrl( + '' +); + +const RAIN_ICON_DATA_URL = svgToBase64DataUrl( + '' +); + export interface CommandInfo { id: string; title: string; @@ -1308,6 +1324,38 @@ async function discoverAndBuildCommands(): Promise { keywords: ['emoji', 'picker', 'trigger', 'smiley', 'emoticon'], category: 'system', }, + { + id: 'system-confetti', + title: 'Confetti', + subtitle: 'Celebrate with a burst of confetti', + keywords: ['confetti', 'celebrate', 'party', 'fun', 'congrats', 'congratulations', 'celebration'], + iconDataUrl: CONFETTI_ICON_DATA_URL, + category: 'system', + }, + { + id: 'system-fireworks', + title: 'Fireworks', + subtitle: 'Launch a fireworks show', + keywords: ['fireworks', 'rockets', 'celebrate', 'party', 'fun', 'show', 'pyrotechnics'], + iconDataUrl: FIREWORKS_ICON_DATA_URL, + category: 'system', + }, + { + id: 'system-snow', + title: 'Snow', + subtitle: 'Let it snow on your screen', + keywords: ['snow', 'snowfall', 'winter', 'christmas', 'fun', 'weather'], + iconDataUrl: SNOW_ICON_DATA_URL, + category: 'system', + }, + { + id: 'system-rain', + title: 'Rain', + subtitle: 'Make it rain on your screen', + keywords: ['rain', 'rainfall', 'storm', 'weather', 'fun'], + iconDataUrl: RAIN_ICON_DATA_URL, + category: 'system', + }, { id: 'system-reset-launcher-position', title: 'Reset Launcher Position', diff --git a/src/main/main.ts b/src/main/main.ts index 94985e40..bcc7e968 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -2481,8 +2481,6 @@ let memoryStatusHideTimer: NodeJS.Timeout | null = null; let memoryStatusFadeFinalizeTimer: NodeJS.Timeout | null = null; let memoryStatusRenderSeq = 0; let memoryStatusHideTimerSeq = 0; -let confettiWindow: InstanceType | null = null; -let confettiCloseTimer: NodeJS.Timeout | null = null; let settingsWindow: InstanceType | null = null; let extensionStoreWindow: InstanceType | null = null; let notesWindow: InstanceType | null = null; @@ -2841,24 +2839,25 @@ function getConfettiWindowHtml(): string { const COLORS = ['#ff4d6d', '#ffd166', '#06d6a0', '#4cc9f0', '#f72585', '#b8f2e6', '#ffffff']; const particles = []; - // Two bursts from lower-left and lower-right, plus a center shower + // Two wide bursts from the bottom corners, plus a center shower, sized + // to cover most of the screen rather than three small isolated clumps. const bursts = [ - { x: canvas.width * 0.15, y: canvas.height * 0.85, angle: -Math.PI * 0.30, spread: 0.9 }, - { x: canvas.width * 0.85, y: canvas.height * 0.85, angle: -Math.PI * 0.70, spread: 0.9 }, - { x: canvas.width * 0.50, y: canvas.height * 0.55, angle: -Math.PI * 0.50, spread: 1.1 }, + { x: canvas.width * 0.06, y: canvas.height * 0.95, angle: -Math.PI * 0.28, spread: 1.0 }, + { x: canvas.width * 0.94, y: canvas.height * 0.95, angle: -Math.PI * 0.72, spread: 1.0 }, + { x: canvas.width * 0.50, y: canvas.height * 0.55, angle: -Math.PI * 0.50, spread: 1.3 }, ]; - const gravity = 0.32 * dpr; + const gravity = 0.28 * dpr; for (const b of bursts) { - const count = 110; + const count = 220; for (let i = 0; i < count; i++) { const a = b.angle + (Math.random() - 0.5) * b.spread; - const speed = (9 + Math.random() * 12) * dpr; + const speed = (14 + Math.random() * 22) * dpr; particles.push({ x: b.x, y: b.y, vx: Math.cos(a) * speed, vy: Math.sin(a) * speed, - size: (4 + Math.random() * 7) * dpr, + size: (6 + Math.random() * 10) * dpr, color: COLORS[Math.floor(Math.random() * COLORS.length)], rot: Math.random() * Math.PI * 2, rotSpeed: (Math.random() - 0.5) * 0.3, @@ -2866,7 +2865,7 @@ function getConfettiWindowHtml(): string { } } - const durationMs = 1800; + const durationMs = 2400; const start = performance.now(); function tick(now) { const elapsed = now - start; @@ -2876,7 +2875,7 @@ function getConfettiWindowHtml(): string { p.x += p.vx; p.y += p.vy; p.vy += gravity; - p.vx *= 0.993; + p.vx *= 0.996; p.rot += p.rotSpeed; ctx.save(); ctx.translate(p.x, p.y); @@ -2899,24 +2898,139 @@ function getConfettiWindowHtml(): string { `; } -function closeConfettiWindow(): void { - if (confettiCloseTimer) { - clearTimeout(confettiCloseTimer); - confettiCloseTimer = null; - } - if (confettiWindow && !confettiWindow.isDestroyed()) { - try { confettiWindow.close(); } catch {} - } - confettiWindow = null; +function getFireworksWindowHtml(): string { + return ` + + + + + + + + + +`; } -async function showConfettiBurst(): Promise { +async function showFullScreenOverlayEffect(html: string, durationMs: number, label: string): Promise { + let win: InstanceType | null = null; try { - closeConfettiWindow(); const cursor = screen.getCursorScreenPoint(); const display = screen.getDisplayNearestPoint(cursor) || screen.getPrimaryDisplay(); const { x, y, width, height } = display.bounds; - confettiWindow = new BrowserWindow({ + win = new BrowserWindow({ x, y, width, @@ -2940,30 +3054,253 @@ async function showConfettiBurst(): Promise { sandbox: true, }, }); - disableWindowAnimation(confettiWindow); - try { confettiWindow.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true }); } catch {} - try { confettiWindow.setIgnoreMouseEvents(true, { forward: true }); } catch {} - try { confettiWindow.setAlwaysOnTop(true, 'screen-saver'); } catch {} - const win = confettiWindow; - win.on('closed', () => { - if (confettiWindow === win) confettiWindow = null; - }); - await win.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(getConfettiWindowHtml())}`); - if (win.isDestroyed()) return; + disableWindowAnimation(win); + try { win.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true }); } catch {} + try { win.setIgnoreMouseEvents(true, { forward: true }); } catch {} + try { win.setAlwaysOnTop(true, 'screen-saver'); } catch {} + const thisWin = win; + await thisWin.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(html)}`); + if (thisWin.isDestroyed()) return; try { - if (typeof (win as any).showInactive === 'function') (win as any).showInactive(); - else win.show(); + if (typeof (thisWin as any).showInactive === 'function') (thisWin as any).showInactive(); + else thisWin.show(); } catch {} - confettiCloseTimer = setTimeout(() => { - confettiCloseTimer = null; - closeConfettiWindow(); - }, 2200); + setTimeout(() => { + if (!thisWin.isDestroyed()) { + try { thisWin.close(); } catch {} + } + }, durationMs); } catch (error) { - console.warn('[Confetti] Failed to show confetti burst:', error); - closeConfettiWindow(); + console.warn(`[${label}] Failed to show overlay effect:`, error); + if (win && !win.isDestroyed()) { + try { win.close(); } catch {} + } } } +function getSnowWindowHtml(): string { + return ` + + + + + + + + + +`; +} + +function getRainWindowHtml(): string { + return ` + + + + + + + + + +`; +} + +// Each burst gets its own overlay window rather than replacing a previous +// one — firing the command again while a burst is still animating layers a +// new one on top, matching Raycast's behavior instead of wiping the screen +// clean first. +async function showConfettiBurst(): Promise { + await showFullScreenOverlayEffect(getConfettiWindowHtml(), 2800, 'Confetti'); +} + +async function showFireworksBurst(): Promise { + await showFullScreenOverlayEffect(getFireworksWindowHtml(), 4500, 'Fireworks'); +} + +async function showSnowBurst(): Promise { + await showFullScreenOverlayEffect(getSnowWindowHtml(), 7000, 'Snow'); +} + +async function showRainBurst(): Promise { + await showFullScreenOverlayEffect(getRainWindowHtml(), 7000, 'Rain'); +} + type AppUpdaterState = | 'idle' | 'unsupported' @@ -7493,6 +7830,34 @@ function ensureAppTray(): void { }, }, { type: 'separator' }, + { + label: 'Extension Store...', + click: () => { + openExtensionStoreWindow(); + }, + }, + { + label: 'SuperCmd Settings...', + click: () => { + openSettingsWindow(); + }, + }, + { type: 'separator' }, + { + label: 'Launch at Login', + type: 'checkbox', + checked: Boolean((loadSettings() as any).openAtLogin), + click: (menuItem: any) => { + setOpenAtLogin(menuItem.checked); + }, + }, + { + label: 'Check for Updates...', + click: () => { + void runAppUpdaterCheckAndInstall(); + }, + }, + { type: 'separator' }, { label: 'Quit SuperCmd', click: () => { @@ -10868,6 +11233,30 @@ async function runCommandById(commandId: string, source: 'launcher' | 'hotkey' | return true; } + if (commandId === 'system-confetti') { + if (source === 'launcher') hideWindow(); + void showConfettiBurst(); + return true; + } + + if (commandId === 'system-fireworks') { + if (source === 'launcher') hideWindow(); + void showFireworksBurst(); + return true; + } + + if (commandId === 'system-snow') { + if (source === 'launcher') hideWindow(); + void showSnowBurst(); + return true; + } + + if (commandId === 'system-rain') { + if (source === 'launcher') hideWindow(); + void showRainBurst(); + return true; + } + if (commandId === 'system-reset-launcher-position') { clearWindowState(); if (mainWindow && !mainWindow.isDestroyed()) { @@ -13344,6 +13733,55 @@ async function restartAndInstallAppUpdate(): Promise { return appUpdaterRestartPromise; } +async function runAppUpdaterCheckAndInstall(): Promise<{ success: boolean; error?: string; message?: string; state?: string }> { + ensureAppUpdaterConfigured(); + if (!appUpdater) { + void showMemoryStatusBar('error', 'Updater not available.'); + return { success: false, error: 'Updater not available' }; + } + + try { + // Step 1: Check for updates + void showMemoryStatusBar('processing', 'Checking for updates...'); + const checkStatus = await checkForAppUpdates(); + if (checkStatus.state === 'not-available') { + void showMemoryStatusBar('success', 'Already on latest version.'); + return { success: true, message: 'Already on latest version', state: checkStatus.state }; + } + if (checkStatus.state === 'error') { + void showMemoryStatusBar('error', checkStatus.message || 'Failed to check for updates'); + return { success: false, error: checkStatus.message || 'Failed to check for updates' }; + } + + // Step 2: Download if available + if (checkStatus.state === 'available') { + void showMemoryStatusBar('processing', 'Downloading update...'); + const downloadStatus = await downloadAppUpdate(); + if (downloadStatus.state === 'error') { + void showMemoryStatusBar('error', downloadStatus.message || 'Failed to download update'); + return { success: false, error: downloadStatus.message || 'Failed to download update' }; + } + } + + // Step 3: Restart if downloaded + if (appUpdaterStatusSnapshot.state === 'downloaded') { + void showMemoryStatusBar('processing', 'Restarting to install update...'); + const installed = await restartAndInstallAppUpdate(); + if (installed) { + return { success: true, message: 'Restarting to install update...', state: 'restarting' }; + } + void showMemoryStatusBar('error', 'Failed to restart for update installation'); + return { success: false, error: 'Failed to restart for update installation' }; + } + + void showMemoryStatusBar('success', checkStatus.message || 'Update check complete'); + return { success: true, message: checkStatus.message || 'Update check complete', state: checkStatus.state }; + } catch (error: any) { + void showMemoryStatusBar('error', String(error?.message || error || 'Update flow failed')); + return { success: false, error: String(error?.message || error || 'Update flow failed') }; + } +} + // ─── Shortcut Management ──────────────────────────────────────────── function applyOpenAtLogin(enabled: boolean): boolean { @@ -13359,6 +13797,14 @@ function applyOpenAtLogin(enabled: boolean): boolean { } } +function setOpenAtLogin(enabled: boolean): boolean { + const applied = applyOpenAtLogin(Boolean(enabled)); + if (applied) { + saveSettings({ openAtLogin: Boolean(enabled) } as Partial); + } + return applied; +} + function disableMacSpotlightShortcuts(): boolean { if (process.platform !== 'darwin') return false; try { @@ -14389,52 +14835,7 @@ app.whenReady().then(async () => { // Full update flow: check → download → restart ipcMain.handle('app-updater-check-and-install', async () => { - ensureAppUpdaterConfigured(); - if (!appUpdater) { - void showMemoryStatusBar('error', 'Updater not available.'); - return { success: false, error: 'Updater not available' }; - } - - try { - // Step 1: Check for updates - void showMemoryStatusBar('processing', 'Checking for updates...'); - const checkStatus = await checkForAppUpdates(); - if (checkStatus.state === 'not-available') { - void showMemoryStatusBar('success', 'Already on latest version.'); - return { success: true, message: 'Already on latest version', state: checkStatus.state }; - } - if (checkStatus.state === 'error') { - void showMemoryStatusBar('error', checkStatus.message || 'Failed to check for updates'); - return { success: false, error: checkStatus.message || 'Failed to check for updates' }; - } - - // Step 2: Download if available - if (checkStatus.state === 'available') { - void showMemoryStatusBar('processing', 'Downloading update...'); - const downloadStatus = await downloadAppUpdate(); - if (downloadStatus.state === 'error') { - void showMemoryStatusBar('error', downloadStatus.message || 'Failed to download update'); - return { success: false, error: downloadStatus.message || 'Failed to download update' }; - } - } - - // Step 3: Restart if downloaded - if (appUpdaterStatusSnapshot.state === 'downloaded') { - void showMemoryStatusBar('processing', 'Restarting to install update...'); - const installed = await restartAndInstallAppUpdate(); - if (installed) { - return { success: true, message: 'Restarting to install update...', state: 'restarting' }; - } - void showMemoryStatusBar('error', 'Failed to restart for update installation'); - return { success: false, error: 'Failed to restart for update installation' }; - } - - void showMemoryStatusBar('success', checkStatus.message || 'Update check complete'); - return { success: true, message: checkStatus.message || 'Update check complete', state: checkStatus.state }; - } catch (error: any) { - void showMemoryStatusBar('error', String(error?.message || error || 'Update flow failed')); - return { success: false, error: String(error?.message || error || 'Update flow failed') }; - } + return await runAppUpdaterCheckAndInstall(); }); function broadcastSettingsToAllWindows(result: AppSettings): void { @@ -14753,11 +15154,7 @@ app.whenReady().then(async () => { ); ipcMain.handle('set-open-at-login', (_event: any, enabled: boolean) => { - const applied = applyOpenAtLogin(Boolean(enabled)); - if (applied) { - saveSettings({ openAtLogin: Boolean(enabled) } as Partial); - } - return applied; + return setOpenAtLogin(Boolean(enabled)); }); ipcMain.handle('replace-spotlight-with-supercmd', async () => {