mirror of
https://github.com/AeThex-Corporation/AeThex-OS.git
synced 2026-04-17 22:27:19 +00:00
Add a daily tip popup and improve system loading indicators
Introduce a daily tips pop-up on startup, refine loading states for API calls, and fix a sound playback initialization bug. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 279f1558-c0e3-40e4-8217-be7e9f4c6eca Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Event-Id: 041cfe5f-b8c3-4940-b6e3-280aa91a2a9f Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/b984cb14-1d19-4944-922b-bc79e821ed35/279f1558-c0e3-40e4-8217-be7e9f4c6eca/nBprVcr Replit-Helium-Checkpoint-Created: true
This commit is contained in:
parent
752e14e480
commit
c87d2227d1
1 changed files with 179 additions and 19 deletions
|
|
@ -74,6 +74,21 @@ interface ClearanceTheme {
|
|||
fontStyle: string;
|
||||
}
|
||||
|
||||
const DAILY_TIPS = [
|
||||
{ title: "Quick Launch", tip: "Press Ctrl+Space to open Spotlight search and quickly find apps." },
|
||||
{ title: "Virtual Desktops", tip: "Use the numbered buttons (1-4) in the taskbar to switch between virtual desktops." },
|
||||
{ title: "Window Management", tip: "Double-click a window title bar to maximize/restore it." },
|
||||
{ title: "Keyboard Shortcuts", tip: "Ctrl+T opens Terminal, Ctrl+S opens Settings, Ctrl+F opens Files." },
|
||||
{ title: "Theme Switching", tip: "Click the Start menu and use 'Switch Clearance' to change between Foundation and Corp modes." },
|
||||
{ title: "Sound Settings", tip: "Toggle system sounds in Settings to enable audio feedback for actions." },
|
||||
{ title: "Dock Apps", tip: "Your most-used apps are pinned to the quick-launch dock for easy access." },
|
||||
{ title: "Right-Click Menu", tip: "Right-click on the desktop to access quick options like refresh and settings." },
|
||||
{ title: "Calculator", tip: "Need quick math? Open Calculator from the app menu or dock." },
|
||||
{ title: "Notifications", tip: "Click the bell icon in the taskbar to view system notifications." },
|
||||
];
|
||||
|
||||
const PINNED_APPS = ['terminal', 'files', 'calculator', 'settings'];
|
||||
|
||||
const CLEARANCE_THEMES: Record<ClearanceMode, ClearanceTheme> = {
|
||||
foundation: {
|
||||
id: 'foundation',
|
||||
|
|
@ -168,6 +183,10 @@ export default function AeThexOS() {
|
|||
return (saved as ClearanceMode) || 'foundation';
|
||||
});
|
||||
const [isSwitchingClearance, setIsSwitchingClearance] = useState(false);
|
||||
const [showDailyTip, setShowDailyTip] = useState(false);
|
||||
const [dailyTip, setDailyTip] = useState(DAILY_TIPS[0]);
|
||||
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
|
||||
const audioContextRef = useRef<AudioContext | null>(null);
|
||||
const clearanceTheme = CLEARANCE_THEMES[clearanceMode];
|
||||
const desktopRef = useRef<HTMLDivElement>(null);
|
||||
const idleTimer = useRef<NodeJS.Timeout | null>(null);
|
||||
|
|
@ -175,7 +194,7 @@ export default function AeThexOS() {
|
|||
const { user, isAuthenticated, logout } = useAuth();
|
||||
const [, setLocation] = useLocation();
|
||||
|
||||
const { data: weatherData } = useQuery({
|
||||
const { data: weatherData, isFetching: weatherFetching } = useQuery({
|
||||
queryKey: ['weather'],
|
||||
queryFn: async () => {
|
||||
const res = await fetch('https://api.open-meteo.com/v1/forecast?latitude=40.7128&longitude=-74.0060¤t_weather=true&temperature_unit=fahrenheit');
|
||||
|
|
@ -203,17 +222,13 @@ export default function AeThexOS() {
|
|||
localStorage.setItem('aethex-clearance', clearanceMode);
|
||||
}, [clearanceMode]);
|
||||
|
||||
const switchClearance = useCallback(() => {
|
||||
const newMode: ClearanceMode = clearanceMode === 'foundation' ? 'corp' : 'foundation';
|
||||
setIsSwitchingClearance(true);
|
||||
setShowStartMenu(false);
|
||||
|
||||
setTimeout(() => {
|
||||
setClearanceMode(newMode);
|
||||
setIsSwitchingClearance(false);
|
||||
addToast(`Switched to ${CLEARANCE_THEMES[newMode].name}`, 'success');
|
||||
}, 600);
|
||||
}, [clearanceMode, addToast]);
|
||||
useEffect(() => {
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
setMousePosition({ x: e.clientX, y: e.clientY });
|
||||
};
|
||||
window.addEventListener('mousemove', handleMouseMove);
|
||||
return () => window.removeEventListener('mousemove', handleMouseMove);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const bootSequence = async () => {
|
||||
|
|
@ -235,6 +250,10 @@ export default function AeThexOS() {
|
|||
|
||||
await new Promise(r => setTimeout(r, 500));
|
||||
setIsBooting(false);
|
||||
|
||||
const randomTip = DAILY_TIPS[Math.floor(Math.random() * DAILY_TIPS.length)];
|
||||
setDailyTip(randomTip);
|
||||
setTimeout(() => setShowDailyTip(true), 1000);
|
||||
};
|
||||
|
||||
bootSequence();
|
||||
|
|
@ -347,6 +366,7 @@ export default function AeThexOS() {
|
|||
{ id: "arcade", title: "Arcade", icon: <Gamepad2 className="w-8 h-8" />, component: "arcade", defaultWidth: 420, defaultHeight: 520 },
|
||||
{ id: "profiles", title: "Architects", icon: <Users className="w-8 h-8" />, component: "profiles", defaultWidth: 650, defaultHeight: 550 },
|
||||
{ id: "chat", title: "Comms", icon: <MessageCircle className="w-8 h-8" />, component: "chat", defaultWidth: 400, defaultHeight: 500 },
|
||||
{ id: "calculator", title: "Calculator", icon: <Calculator className="w-8 h-8" />, component: "calculator", defaultWidth: 320, defaultHeight: 450 },
|
||||
{ id: "settings", title: "Settings", icon: <Settings className="w-8 h-8" />, component: "settings", defaultWidth: 550, defaultHeight: 500 },
|
||||
];
|
||||
|
||||
|
|
@ -365,15 +385,53 @@ export default function AeThexOS() {
|
|||
|
||||
const apps = clearanceMode === 'foundation' ? foundationApps : corpApps;
|
||||
|
||||
const playSound = useCallback((type: 'open' | 'close' | 'minimize' | 'click') => {
|
||||
const playSound = useCallback((type: 'open' | 'close' | 'minimize' | 'click' | 'notification' | 'switch') => {
|
||||
if (!soundEnabled) return;
|
||||
// Visual feedback instead of actual sound
|
||||
const flash = document.createElement('div');
|
||||
flash.className = 'fixed inset-0 bg-cyan-400/5 pointer-events-none z-[99999]';
|
||||
document.body.appendChild(flash);
|
||||
setTimeout(() => flash.remove(), 50);
|
||||
try {
|
||||
if (!audioContextRef.current) {
|
||||
audioContextRef.current = new AudioContext();
|
||||
}
|
||||
const ctx = audioContextRef.current;
|
||||
const oscillator = ctx.createOscillator();
|
||||
const gainNode = ctx.createGain();
|
||||
oscillator.connect(gainNode);
|
||||
gainNode.connect(ctx.destination);
|
||||
|
||||
const sounds: Record<string, { freq: number; duration: number; type: OscillatorType }> = {
|
||||
open: { freq: 523, duration: 0.1, type: 'sine' },
|
||||
close: { freq: 392, duration: 0.1, type: 'sine' },
|
||||
minimize: { freq: 330, duration: 0.08, type: 'sine' },
|
||||
click: { freq: 800, duration: 0.03, type: 'square' },
|
||||
notification: { freq: 880, duration: 0.15, type: 'sine' },
|
||||
switch: { freq: 440, duration: 0.2, type: 'sawtooth' },
|
||||
};
|
||||
|
||||
const sound = sounds[type] || sounds.click;
|
||||
oscillator.type = sound.type;
|
||||
oscillator.frequency.setValueAtTime(sound.freq, ctx.currentTime);
|
||||
gainNode.gain.setValueAtTime(0.1, ctx.currentTime);
|
||||
gainNode.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + sound.duration);
|
||||
|
||||
oscillator.start(ctx.currentTime);
|
||||
oscillator.stop(ctx.currentTime + sound.duration);
|
||||
} catch (e) {
|
||||
console.log('Audio not available');
|
||||
}
|
||||
}, [soundEnabled]);
|
||||
|
||||
const switchClearance = useCallback(() => {
|
||||
const newMode: ClearanceMode = clearanceMode === 'foundation' ? 'corp' : 'foundation';
|
||||
setIsSwitchingClearance(true);
|
||||
setShowStartMenu(false);
|
||||
playSound('switch');
|
||||
|
||||
setTimeout(() => {
|
||||
setClearanceMode(newMode);
|
||||
setIsSwitchingClearance(false);
|
||||
addToast(`Switched to ${CLEARANCE_THEMES[newMode].name}`, 'success');
|
||||
}, 600);
|
||||
}, [clearanceMode, addToast, playSound]);
|
||||
|
||||
const openApp = useCallback((app: DesktopApp) => {
|
||||
playSound('open');
|
||||
const existingWindow = windows.find(w => w.id === app.id);
|
||||
|
|
@ -633,10 +691,17 @@ export default function AeThexOS() {
|
|||
);
|
||||
}
|
||||
|
||||
const parallaxX = (mousePosition.x / window.innerWidth - 0.5) * 10;
|
||||
const parallaxY = (mousePosition.y / window.innerHeight - 0.5) * 10;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="h-screen w-screen overflow-hidden select-none relative transition-all duration-700"
|
||||
style={{ background: clearanceTheme.wallpaper }}
|
||||
style={{
|
||||
background: clearanceTheme.wallpaper,
|
||||
backgroundPosition: `${50 + parallaxX}% ${50 + parallaxY}%`,
|
||||
backgroundSize: '110% 110%'
|
||||
}}
|
||||
>
|
||||
<div
|
||||
ref={desktopRef}
|
||||
|
|
@ -789,6 +854,67 @@ export default function AeThexOS() {
|
|||
|
||||
<ToastContainer toasts={toasts} />
|
||||
|
||||
<AnimatePresence>
|
||||
{showDailyTip && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 50 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: 50 }}
|
||||
className="fixed bottom-20 right-4 z-[9998] w-80 rounded-xl overflow-hidden shadow-2xl"
|
||||
style={{
|
||||
background: clearanceTheme.id === 'foundation' ? 'rgba(26, 5, 5, 0.95)' : 'rgba(15, 23, 42, 0.95)',
|
||||
border: `1px solid ${clearanceTheme.accent}40`
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="px-4 py-3 flex items-center justify-between"
|
||||
style={{ borderBottom: `1px solid ${clearanceTheme.accent}30` }}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<Zap className="w-4 h-4" style={{ color: clearanceTheme.accent }} />
|
||||
<span className="text-sm font-semibold text-white">Daily Tip</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setShowDailyTip(false)}
|
||||
className="text-white/40 hover:text-white transition-colors"
|
||||
data-testid="close-daily-tip"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<div className="text-xs uppercase tracking-wider mb-2" style={{ color: clearanceTheme.accent }}>
|
||||
{dailyTip.title}
|
||||
</div>
|
||||
<p className="text-sm text-white/80 leading-relaxed">
|
||||
{dailyTip.tip}
|
||||
</p>
|
||||
</div>
|
||||
<div className="px-4 pb-4">
|
||||
<button
|
||||
onClick={() => setShowDailyTip(false)}
|
||||
className="w-full py-2 rounded-lg text-sm font-medium transition-all hover:scale-[1.02]"
|
||||
style={{
|
||||
background: `${clearanceTheme.accent}20`,
|
||||
color: clearanceTheme.accent,
|
||||
border: `1px solid ${clearanceTheme.accent}40`
|
||||
}}
|
||||
data-testid="dismiss-daily-tip"
|
||||
>
|
||||
Got it!
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
{weatherFetching && (
|
||||
<div className="fixed top-4 right-4 z-[9999] flex items-center gap-2 px-3 py-2 rounded-lg bg-black/50 backdrop-blur-sm" data-testid="loading-indicator">
|
||||
<Loader2 className="w-4 h-4 animate-spin" style={{ color: clearanceTheme.accent }} />
|
||||
<span className="text-xs text-white/60">Syncing...</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<AnimatePresence>
|
||||
{showOnboarding && (
|
||||
<OnboardingTour
|
||||
|
|
@ -1448,6 +1574,40 @@ function Taskbar({ windows, activeWindowId, apps, time, showStartMenu, user, isA
|
|||
|
||||
<div className="w-px h-6 bg-white/10" />
|
||||
|
||||
<div className="flex items-center gap-1 px-1">
|
||||
{PINNED_APPS.map(appId => {
|
||||
const app = apps.find(a => a.id === appId);
|
||||
if (!app) return null;
|
||||
const isOpen = windows.some(w => w.id === appId);
|
||||
return (
|
||||
<motion.button
|
||||
key={appId}
|
||||
onClick={() => onAppClick(app)}
|
||||
whileHover={{ scale: 1.1, y: -2 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="w-9 h-9 rounded-lg flex items-center justify-center relative transition-colors"
|
||||
style={{
|
||||
background: isOpen ? `${clearanceTheme.accent}20` : 'rgba(255,255,255,0.05)',
|
||||
border: isOpen ? `1px solid ${clearanceTheme.accent}40` : '1px solid transparent'
|
||||
}}
|
||||
data-testid={`dock-${appId}`}
|
||||
>
|
||||
<div className="w-5 h-5" style={{ color: isOpen ? clearanceTheme.accent : 'rgba(255,255,255,0.7)' }}>
|
||||
{app.icon}
|
||||
</div>
|
||||
{isOpen && (
|
||||
<div
|
||||
className="absolute -bottom-1 w-1 h-1 rounded-full"
|
||||
style={{ background: clearanceTheme.accent }}
|
||||
/>
|
||||
)}
|
||||
</motion.button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="w-px h-6 bg-white/10" />
|
||||
|
||||
<div className="flex-1 flex items-center gap-1 overflow-x-auto">
|
||||
{windows.map(window => (
|
||||
<motion.button
|
||||
|
|
|
|||
Loading…
Reference in a new issue