72 lines
2.5 KiB
TypeScript
72 lines
2.5 KiB
TypeScript
export function deepEqual(obj1: any, obj2: any) {
|
|
if (obj1 === obj2) return true;
|
|
|
|
if (typeof obj1 !== "object" || obj1 === null || typeof obj2 !== "object" || obj2 === null) {
|
|
return false;
|
|
}
|
|
|
|
const keys1 = Object.keys(obj1);
|
|
const keys2 = Object.keys(obj2);
|
|
|
|
if (keys1.length !== keys2.length) return false;
|
|
|
|
for (let key of keys1) {
|
|
if (!keys2.includes(key)) return false;
|
|
if (!deepEqual(obj1[key], obj2[key])) return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
export function useBeep() {
|
|
const audioContext = ref(new (window.AudioContext)());
|
|
|
|
const playBeep = (frequency = 440, duration = 0.1) => {
|
|
const oscillator = audioContext.value.createOscillator();
|
|
const gainNode = audioContext.value.createGain();
|
|
|
|
oscillator.connect(gainNode);
|
|
gainNode.connect(audioContext.value.destination);
|
|
|
|
gainNode.gain.setValueAtTime(0, audioContext.value.currentTime);
|
|
gainNode.gain.linearRampToValueAtTime(1, audioContext.value.currentTime + 0.01);
|
|
gainNode.gain.linearRampToValueAtTime(0, audioContext.value.currentTime + duration);
|
|
|
|
oscillator.frequency.value = frequency;
|
|
oscillator.start(audioContext.value.currentTime);
|
|
oscillator.stop(audioContext.value.currentTime + duration);
|
|
};
|
|
|
|
return {playBeep};
|
|
}
|
|
|
|
|
|
export function toggleDark(event: MouseEvent) {
|
|
const isAppearanceTransition = document.startViewTransition && !window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
|
|
if (!isAppearanceTransition) {
|
|
useColorMode().preference = useColorMode().value == 'dark' ? 'light' : 'dark'
|
|
return
|
|
}
|
|
|
|
const x = event.clientX
|
|
const y = event.clientY
|
|
const endRadius = Math.hypot(Math.max(x, innerWidth - x), Math.max(y, innerHeight - y),)
|
|
// @ts-expect-error: Transition API
|
|
const transition = document.startViewTransition(async () => {
|
|
useColorMode().preference = useColorMode().value == 'dark' ? 'light' : 'dark'
|
|
await nextTick()
|
|
})
|
|
transition.ready
|
|
.then(() => {
|
|
const clipPath = [`circle(0px at ${x}px ${y}px)`, `circle(${endRadius}px at ${x}px ${y}px)`,]
|
|
document.documentElement.animate({
|
|
clipPath: useColorMode().value == 'dark' ? [...clipPath].reverse() : clipPath,
|
|
}, {
|
|
duration: 400,
|
|
easing: 'ease-out',
|
|
pseudoElement: useColorMode().value == 'dark' ? '::view-transition-old(root)' : '::view-transition-new(root)',
|
|
},)
|
|
})
|
|
} |