Add notification sound picker with live preview

Notification sound toggle now reveals a Sound sub-row with a dropdown
(Chime, Bell, Ping, Ding, Pop, Classic, Subtle) and a Preview button
that plays the selected tone via Web Audio API — no audio files needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Devon
2026-05-25 10:29:23 -04:00
parent f7873d14c7
commit dc096207ae
+48
View File
@@ -785,9 +785,57 @@ function SettingsTheme({ls,setLS,lC,C,loginBg,setLoginBg,loginDark,setLoginDark}
} }
function SettingsNotifications({ls,setLS,C}){ function SettingsNotifications({ls,setLS,C}){
const SOUNDS=[
{id:"chime", label:"Chime"},
{id:"bell", label:"Bell"},
{id:"ping", label:"Ping"},
{id:"ding", label:"Ding"},
{id:"pop", label:"Pop"},
{id:"classic",label:"Classic"},
{id:"subtle", label:"Subtle"},
];
function playSound(id){
try{
const ctx=new (window.AudioContext||window.webkitAudioContext)();
const tone=(freq,start,dur,type="sine",vol=0.28)=>{
const osc=ctx.createOscillator(),g=ctx.createGain();
osc.connect(g);g.connect(ctx.destination);
osc.type=type;osc.frequency.setValueAtTime(freq,ctx.currentTime+start);
g.gain.setValueAtTime(0,ctx.currentTime+start);
g.gain.linearRampToValueAtTime(vol,ctx.currentTime+start+0.015);
g.gain.exponentialRampToValueAtTime(0.001,ctx.currentTime+start+dur);
osc.start(ctx.currentTime+start);osc.stop(ctx.currentTime+start+dur+0.05);
};
if(id==="chime") {tone(1319,0,0.5);tone(1047,0.18,0.45);tone(1568,0.36,0.7);}
else if(id==="bell") {tone(440,0,1.2);tone(880,0.05,0.9,undefined,0.15);}
else if(id==="ping") {tone(1400,0,0.25,undefined,0.22);}
else if(id==="ding") {tone(880,0,0.18);tone(660,0.2,0.4);}
else if(id==="pop") {tone(220,0,0.08,"triangle",0.45);}
else if(id==="classic"){tone(660,0,0.18);tone(880,0.22,0.28);}
else if(id==="subtle") {tone(600,0,0.35,undefined,0.12);}
setTimeout(()=>ctx.close(),2500);
}catch(e){}
}
const selSt={background:C.inputBg,border:`1px solid ${C.inBorder}`,borderRadius:5,
padding:"4px 8px",color:C.text,fontFamily:FONT,fontSize:13,outline:"none"};
return <SCard C={C}> return <SCard C={C}>
<SRow label="Desktop notifications" sub="Show popup for new messages" C={C}><Toggle on={ls.notifications} onChange={v=>setLS(p=>({...p,notifications:v}))} C={C}/></SRow> <SRow label="Desktop notifications" sub="Show popup for new messages" C={C}><Toggle on={ls.notifications} onChange={v=>setLS(p=>({...p,notifications:v}))} C={C}/></SRow>
<SRow label="Notification sound" sub="Play a sound on new mail" C={C}><Toggle on={ls.sound} onChange={v=>setLS(p=>({...p,sound:v}))} C={C}/></SRow> <SRow label="Notification sound" sub="Play a sound on new mail" C={C}><Toggle on={ls.sound} onChange={v=>setLS(p=>({...p,sound:v}))} C={C}/></SRow>
{ls.sound&&<SRow label="Sound" sub="Choose notification tone" C={C}>
<div style={{display:"flex",gap:6,alignItems:"center"}}>
<select value={ls.soundChoice||"chime"} onChange={e=>setLS(p=>({...p,soundChoice:e.target.value}))} style={selSt}>
{SOUNDS.map(s=><option key={s.id} value={s.id}>{s.label}</option>)}
</select>
<button onClick={()=>playSound(ls.soundChoice||"chime")}
style={{background:C.accent,border:"none",borderRadius:5,padding:"4px 10px",
color:"#fff",fontFamily:FONT,fontSize:12,cursor:"pointer",display:"flex",alignItems:"center",gap:4}}>
<i className="ti ti-player-play" style={{fontSize:11}} aria-hidden="true"/> Preview
</button>
</div>
</SRow>}
<SRow label="Badge count" sub="Show unread count on taskbar" C={C}><Toggle on={true} C={C}/></SRow> <SRow label="Badge count" sub="Show unread count on taskbar" C={C}><Toggle on={true} C={C}/></SRow>
<SRow label="Notification preview" sub="Show sender and subject in popup" C={C}><Toggle on={true} C={C}/></SRow> <SRow label="Notification preview" sub="Show sender and subject in popup" C={C}><Toggle on={true} C={C}/></SRow>
<SRow label="Do not disturb" sub="Silence all notifications" C={C}><Toggle on={false} C={C}/></SRow> <SRow label="Do not disturb" sub="Silence all notifications" C={C}><Toggle on={false} C={C}/></SRow>