mirror of
https://github.com/arthur-pbty/pomodoro.git
synced 2026-06-17 21:39:54 +02:00
feat: implement TodoList component with filtering and priority management
feat: add useAmbientSound hook for ambient sound management feat: create useLocalStorage hook for persistent state management feat: develop useTheme hook for theme switching functionality feat: implement useTimer hook for Pomodoro timer logic feat: create useTodos hook for managing todo list functionality style: add global styles and custom scrollbar for better UI experience chore: set up main entry point for the application feat: define types for Timer, Todo, and Statistics feat: create utility function for class name merging chore: configure Tailwind CSS for styling chore: set up TypeScript configuration for the project chore: configure Vite for development and build process
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
import { useState, useRef, useCallback, useEffect } from 'react';
|
||||
|
||||
export type SoundType = 'none' | 'rain' | 'cafe' | 'whitenoise' | 'forest' | 'ocean';
|
||||
|
||||
export function useAmbientSound() {
|
||||
const [currentSound, setCurrentSound] = useState<SoundType>('none');
|
||||
const [volume, setVolume] = useState(0.5);
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
|
||||
const audioContextRef = useRef<AudioContext | null>(null);
|
||||
const nodesRef = useRef<{
|
||||
sources: AudioBufferSourceNode[];
|
||||
gains: GainNode[];
|
||||
masterGain: GainNode | null;
|
||||
}>({ sources: [], gains: [], masterGain: null });
|
||||
|
||||
const stopSound = useCallback(() => {
|
||||
nodesRef.current.sources.forEach(source => {
|
||||
try { source.stop(); } catch (e) {}
|
||||
});
|
||||
nodesRef.current.sources = [];
|
||||
nodesRef.current.gains = [];
|
||||
setIsPlaying(false);
|
||||
}, []);
|
||||
|
||||
const createNoiseBuffer = useCallback((ctx: AudioContext, type: 'white' | 'brown' | 'pink') => {
|
||||
const bufferSize = ctx.sampleRate * 2;
|
||||
const buffer = ctx.createBuffer(1, bufferSize, ctx.sampleRate);
|
||||
const output = buffer.getChannelData(0);
|
||||
|
||||
if (type === 'white') {
|
||||
for (let i = 0; i < bufferSize; i++) {
|
||||
output[i] = Math.random() * 2 - 1;
|
||||
}
|
||||
} else if (type === 'brown') {
|
||||
let lastOut = 0;
|
||||
for (let i = 0; i < bufferSize; i++) {
|
||||
const white = Math.random() * 2 - 1;
|
||||
output[i] = (lastOut + 0.02 * white) / 1.02;
|
||||
lastOut = output[i];
|
||||
output[i] *= 3.5;
|
||||
}
|
||||
} else if (type === 'pink') {
|
||||
let b0 = 0, b1 = 0, b2 = 0, b3 = 0, b4 = 0, b5 = 0, b6 = 0;
|
||||
for (let i = 0; i < bufferSize; i++) {
|
||||
const white = Math.random() * 2 - 1;
|
||||
b0 = 0.99886 * b0 + white * 0.0555179;
|
||||
b1 = 0.99332 * b1 + white * 0.0750759;
|
||||
b2 = 0.96900 * b2 + white * 0.1538520;
|
||||
b3 = 0.86650 * b3 + white * 0.3104856;
|
||||
b4 = 0.55000 * b4 + white * 0.5329522;
|
||||
b5 = -0.7616 * b5 - white * 0.0168980;
|
||||
output[i] = b0 + b1 + b2 + b3 + b4 + b5 + b6 + white * 0.5362;
|
||||
output[i] *= 0.11;
|
||||
b6 = white * 0.115926;
|
||||
}
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}, []);
|
||||
|
||||
const playSound = useCallback((type: SoundType) => {
|
||||
stopSound();
|
||||
|
||||
if (type === 'none') {
|
||||
setCurrentSound('none');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!audioContextRef.current) {
|
||||
audioContextRef.current = new AudioContext();
|
||||
}
|
||||
const ctx = audioContextRef.current;
|
||||
|
||||
const masterGain = ctx.createGain();
|
||||
masterGain.gain.value = volume;
|
||||
masterGain.connect(ctx.destination);
|
||||
nodesRef.current.masterGain = masterGain;
|
||||
|
||||
const createLoopingSource = (buffer: AudioBuffer, gainValue: number = 1) => {
|
||||
const source = ctx.createBufferSource();
|
||||
const gain = ctx.createGain();
|
||||
source.buffer = buffer;
|
||||
source.loop = true;
|
||||
gain.gain.value = gainValue;
|
||||
source.connect(gain);
|
||||
gain.connect(masterGain);
|
||||
source.start();
|
||||
nodesRef.current.sources.push(source);
|
||||
nodesRef.current.gains.push(gain);
|
||||
return { source, gain };
|
||||
};
|
||||
|
||||
switch (type) {
|
||||
case 'rain': {
|
||||
// Bruit de pluie = bruit rose + bruit brun
|
||||
const pinkBuffer = createNoiseBuffer(ctx, 'pink');
|
||||
const brownBuffer = createNoiseBuffer(ctx, 'brown');
|
||||
createLoopingSource(pinkBuffer, 0.6);
|
||||
createLoopingSource(brownBuffer, 0.3);
|
||||
break;
|
||||
}
|
||||
case 'cafe': {
|
||||
// Ambiance café = bruit brun léger + variations
|
||||
const brownBuffer = createNoiseBuffer(ctx, 'brown');
|
||||
const pinkBuffer = createNoiseBuffer(ctx, 'pink');
|
||||
createLoopingSource(brownBuffer, 0.4);
|
||||
createLoopingSource(pinkBuffer, 0.15);
|
||||
break;
|
||||
}
|
||||
case 'whitenoise': {
|
||||
const whiteBuffer = createNoiseBuffer(ctx, 'white');
|
||||
createLoopingSource(whiteBuffer, 0.3);
|
||||
break;
|
||||
}
|
||||
case 'forest': {
|
||||
// Forêt = bruit rose doux
|
||||
const pinkBuffer = createNoiseBuffer(ctx, 'pink');
|
||||
const brownBuffer = createNoiseBuffer(ctx, 'brown');
|
||||
createLoopingSource(pinkBuffer, 0.25);
|
||||
createLoopingSource(brownBuffer, 0.15);
|
||||
break;
|
||||
}
|
||||
case 'ocean': {
|
||||
// Océan = bruit brun
|
||||
const brownBuffer = createNoiseBuffer(ctx, 'brown');
|
||||
createLoopingSource(brownBuffer, 0.7);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
setCurrentSound(type);
|
||||
setIsPlaying(true);
|
||||
}, [volume, stopSound, createNoiseBuffer]);
|
||||
|
||||
const changeVolume = useCallback((newVolume: number) => {
|
||||
setVolume(newVolume);
|
||||
if (nodesRef.current.masterGain) {
|
||||
nodesRef.current.masterGain.gain.value = newVolume;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const toggleSound = useCallback(() => {
|
||||
if (isPlaying) {
|
||||
stopSound();
|
||||
} else if (currentSound !== 'none') {
|
||||
playSound(currentSound);
|
||||
}
|
||||
}, [isPlaying, currentSound, playSound, stopSound]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
stopSound();
|
||||
if (audioContextRef.current) {
|
||||
audioContextRef.current.close();
|
||||
}
|
||||
};
|
||||
}, [stopSound]);
|
||||
|
||||
return {
|
||||
currentSound,
|
||||
volume,
|
||||
isPlaying,
|
||||
playSound,
|
||||
stopSound,
|
||||
changeVolume,
|
||||
toggleSound,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user