29 Commits

Author SHA1 Message Date
MAZE
75ff67c9e6 chore(release): 1.3.1 2024-02-01 23:40:29 +03:30
MAZE
1f806c4e56 chore: add donation link to README file 2024-02-01 23:39:59 +03:30
MAZE
e6f768a5e6 fix: complete donation links 2024-02-01 23:38:55 +03:30
MAZE
8e0291004a fix: coffee typo 2024-02-01 23:27:06 +03:30
MAZE
d449c29321 feat: add donate section 2024-02-01 22:23:49 +03:30
MAZE
f12ca4806c feat: add donate item 2024-02-01 22:05:42 +03:30
MAZE
17b4f25ff1 feat: add donation header 2024-02-01 21:59:08 +03:30
MAZE
f877e49763 chore(release): 1.3.0 2024-02-01 20:36:46 +03:30
MAZE
f1d212abc8 chore: add binaural beats 2024-02-01 20:16:47 +03:30
MAZE
38f6f7dbe6 chore: add more sounds 2024-01-30 15:29:55 +03:30
MAZE
937bf29d09 chore: add more sounds 2024-01-30 00:04:59 +03:30
MAZE
e2172fd2bb chore: add more sounds 2024-01-29 23:49:07 +03:30
MAZE
1f12afa394 chore: add more sounds 2024-01-29 23:36:44 +03:30
MAZE
d96461d1ea fix: remove fading 2024-01-29 19:16:46 +03:30
MAZE
5467bbbc24 feat: add fading to intro and outro 2024-01-29 19:01:36 +03:30
MAZE
32da26ccfc fix: undo changes 2024-01-28 19:39:57 +03:30
MAZE
81f33d9d37 fix: add media session 2024-01-28 19:33:52 +03:30
MAZE
463667c868 fix: connect audio context to audio element 2024-01-28 19:28:55 +03:30
MAZE
b77c817db2 refactor: remove unmute and media session 2024-01-28 15:45:27 +03:30
MAZE
216b913ccd fix: add media session 2024-01-28 15:40:51 +03:30
MAZE
e422b52436 fix: add unmute for iOS 2024-01-28 15:35:09 +03:30
MAZE
1f635348e3 refactor: remove media session 2024-01-28 15:23:58 +03:30
MAZE
889962babe fix: add audio element 2024-01-28 15:15:30 +03:30
MAZE
5e0a84259f feat: add media session 2024-01-28 15:05:13 +03:30
MAZE
8e4d0531e0 fix: resume audio 2024-01-28 14:44:31 +03:30
MAZE
cd05704a73 chore: add more sounds 2024-01-22 19:58:06 +03:30
MAZE
01b4bdbb57 chore: add more sounds 2024-01-22 18:31:23 +03:30
MAZE
f682a910da feat: add loader for favorites 2024-01-09 16:09:07 +03:30
MAZE
a33ae450cf fix: increase decimal 2024-01-09 15:59:57 +03:30
53 changed files with 482 additions and 35 deletions

View File

@@ -45,6 +45,7 @@
"sort-keys-fix/sort-keys-fix": ["warn", "asc"],
"sort-destructure-keys/sort-destructure-keys": "warn",
"jsx-a11y/no-static-element-interactions": "off",
"jsx-a11y/media-has-caption": "off",
"react/jsx-sort-props": [
"warn",
{

View File

@@ -2,6 +2,65 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [1.3.1](https://github.com/remvze/moodist/compare/v1.3.0...v1.3.1) (2024-02-01)
### ✨ Features
* add donate item ([f12ca48](https://github.com/remvze/moodist/commit/f12ca4806c9279f69f298bef770f8cac69a0860a))
* add donate section ([d449c29](https://github.com/remvze/moodist/commit/d449c29321024a43517e92cc59223b4b22fe2e82))
* add donation header ([17b4f25](https://github.com/remvze/moodist/commit/17b4f25ff10e09a917203e67cf963cac8358de1a))
### 🐛 Bug Fixes
* coffee typo ([8e02910](https://github.com/remvze/moodist/commit/8e0291004a90e55b67a921b9ffb483b409109ae4))
* complete donation links ([e6f768a](https://github.com/remvze/moodist/commit/e6f768a5e6dc983ae04b70f6c434fd4c13aeb506))
### 🚚 Chores
* add donation link to README file ([1f806c4](https://github.com/remvze/moodist/commit/1f806c4e561d79a00850130eda09376299d85ed2))
## [1.3.0](https://github.com/remvze/moodist/compare/v1.2.0...v1.3.0) (2024-02-01)
### ♻️ Code Refactoring
* remove media session ([1f63534](https://github.com/remvze/moodist/commit/1f635348e3e5cf73ee76e1c5fac7b5f5b7f7ea6a))
* remove unmute and media session ([b77c817](https://github.com/remvze/moodist/commit/b77c817db25e1a738b6770b1ae86d792e0d42240))
### ✨ Features
* add fading to intro and outro ([5467bbb](https://github.com/remvze/moodist/commit/5467bbbc2437a5504e157122a995ad7a565ff0b8))
* add loader for favorites ([f682a91](https://github.com/remvze/moodist/commit/f682a910da97eb53cfb90ce955e953f05088e686))
* add media session ([5e0a842](https://github.com/remvze/moodist/commit/5e0a84259ff5586700c4e10087485d905be7ccee))
### 🐛 Bug Fixes
* add audio element ([889962b](https://github.com/remvze/moodist/commit/889962babe6e940ff283a41b145620d2a0477c70))
* add media session ([81f33d9](https://github.com/remvze/moodist/commit/81f33d9d375f63b4dd0bf58ad28a72354d85706e))
* add media session ([216b913](https://github.com/remvze/moodist/commit/216b913ccd0a7dfe0d03575f842aac9711ef0216))
* add unmute for iOS ([e422b52](https://github.com/remvze/moodist/commit/e422b52436c7dfc0b6cf866afa2b74dc219dcf2f))
* connect audio context to audio element ([463667c](https://github.com/remvze/moodist/commit/463667c868371540c46c9007e686961f9a4be7e5))
* increase decimal ([a33ae45](https://github.com/remvze/moodist/commit/a33ae450cf2c883228c76d04df8df75839c12753))
* remove fading ([d96461d](https://github.com/remvze/moodist/commit/d96461d1ea83c72bfe651d84cf34fabc029c200e))
* resume audio ([8e4d053](https://github.com/remvze/moodist/commit/8e4d0531e0e9aaf4e52b3b3a8666b74ff0c0222e))
* undo changes ([32da26c](https://github.com/remvze/moodist/commit/32da26ccfc0c5bdbe031e26ea48363ea0d8a7b23))
### 🚚 Chores
* add binaural beats ([f1d212a](https://github.com/remvze/moodist/commit/f1d212abc8b69a614bbdc4a23876e2eab7cbb574))
* add more sounds ([38f6f7d](https://github.com/remvze/moodist/commit/38f6f7dbe6898ed78e51eb3f0c7936f003ddca08))
* add more sounds ([937bf29](https://github.com/remvze/moodist/commit/937bf29d09cbce20ea0b6b0c87879f3a7dd1d497))
* add more sounds ([e2172fd](https://github.com/remvze/moodist/commit/e2172fd2bbd0e12a705c9efc98c72ad99d86d006))
* add more sounds ([1f12afa](https://github.com/remvze/moodist/commit/1f12afa3943154d70145ef6adc6aeee79f7a7af3))
* add more sounds ([cd05704](https://github.com/remvze/moodist/commit/cd05704a73ffb33aa0ccf5d789328a4cefc320f1))
* add more sounds ([01b4bdb](https://github.com/remvze/moodist/commit/01b4bdbb572285984bcdc9bb94c1a1b6dd2630c5))
## [1.2.0](https://github.com/remvze/moodist/compare/v1.1.0...v1.2.0) (2024-01-04)

View File

@@ -2,5 +2,5 @@
<img src="/assets/banner.svg" alt="Moodist Logo Banner" />
<h2>Moodist 🌲</h2>
<p>Ambient sounds for focus and calm.</p>
<a href="https://moodist.app">Visit <strong>Moodist</strong></a>
<a href="https://moodist.app">Visit <strong>Moodist</strong></a> | <a href="https://buymeacoffee.com/remvze">Buy Me a Coffee</a>
</div>

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "moodist",
"version": "1.2.0",
"version": "1.3.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "moodist",
"version": "1.2.0",
"version": "1.3.1",
"dependencies": {
"@astrojs/react": "^3.0.3",
"@floating-ui/react": "0.26.0",

View File

@@ -1,7 +1,7 @@
{
"name": "moodist",
"type": "module",
"version": "1.2.0",
"version": "1.3.1",
"scripts": {
"dev": "astro dev",
"start": "astro dev",

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,6 +1,7 @@
import { useMemo } from 'react';
import { useMemo, useEffect } from 'react';
import { useShallow } from 'zustand/react/shallow';
import { BiSolidHeart } from 'react-icons/bi/index';
import { Howler } from 'howler';
import { useSoundStore } from '@/store';
@@ -36,6 +37,22 @@ export function App() {
);
}, [favorites, categories]);
useEffect(() => {
const onChange = () => {
const { ctx } = Howler;
if (ctx && !document.hidden) {
setTimeout(() => {
ctx.resume();
}, 100);
}
};
document.addEventListener('visibilitychange', onChange, false);
return () => document.removeEventListener('visibilitychange', onChange);
}, []);
const allCategories = useMemo(() => {
const favorites = [];

View File

@@ -1,6 +1,7 @@
import { AnimatePresence } from 'framer-motion';
import { Category } from '@/components/category';
import { Donate } from './donate';
import type { Categories } from '@/data/types';
@@ -11,12 +12,16 @@ interface CategoriesProps {
export function Categories({ categories }: CategoriesProps) {
return (
<AnimatePresence initial={false}>
{categories.map(category => (
<Category
functional={category.id !== 'favorites'}
{...category}
key={category.id}
/>
{categories.map((category, index) => (
<>
<Category
functional={category.id !== 'favorites'}
{...category}
key={category.id}
/>
{index === 3 && <Donate />}
</>
))}
</AnimatePresence>
);

View File

@@ -0,0 +1,63 @@
.donate {
& .iconContainer {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 15px;
& .tail {
width: 1px;
height: 75px;
background: linear-gradient(transparent, var(--color-neutral-300));
}
& .icon {
display: flex;
align-items: center;
justify-content: center;
width: 45px;
height: 45px;
font-size: var(--font-md);
background-color: var(--color-neutral-100);
border: 1px solid var(--color-neutral-300);
border-radius: 50%;
}
}
& .title {
font-family: var(--font-display);
font-size: var(--font-lg);
font-weight: 600;
text-align: center;
}
& .desc {
margin-top: 8px;
color: var(--color-foreground-subtle);
text-align: center;
}
.button {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: max-content;
height: 40px;
padding: 0 20px;
margin: 16px auto 0;
font-size: var(--font-xsm);
font-weight: 500;
color: var(--color-neutral-subtle);
text-decoration: none;
cursor: pointer;
background-color: var(--color-neutral-50);
border: 1px solid var(--color-neutral-200);
border-radius: 50px;
transition: 0.2s;
&:hover {
background-color: var(--color-neutral-100);
}
}
}

View File

@@ -0,0 +1,27 @@
import { FaCoffee } from 'react-icons/fa/index';
import styles from './donate.module.css';
export function Donate() {
return (
<div className={styles.donate}>
<div className={styles.iconContainer}>
<div className={styles.tail} />
<div className={styles.icon}>
<FaCoffee />
</div>
</div>
<h2 className={styles.title}>Support Me</h2>
<p className={styles.desc}>Help me keep Moodist ad-free.</p>
<a
className={styles.button}
href="https://buymeacoffee.com/remvze"
rel="noreferrer"
target="_blank"
>
Donate Today
</a>
</div>
);
}

View File

@@ -0,0 +1 @@
export { Donate } from './donate';

View File

@@ -0,0 +1,56 @@
---
import { Container } from '@/components/container';
---
<Container>
<section class="wrapper">
<p class="text">
Enjoy Moodist? <a
href="https://buymeacoffee.com/remvze"
rel="noreferrer"
target="_blank"
>
Support with a donation!
</a>
</p>
</section>
</Container>
<style>
.wrapper {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
padding: 16px;
font-size: var(--font-xsm);
color: var(--color-foreground-subtle);
&::after {
position: absolute;
bottom: 0;
left: 50%;
width: 80%;
height: 1px;
content: '';
background: linear-gradient(
90deg,
transparent,
var(--color-neutral-200),
transparent
);
transform: translateX(-50%);
}
& .text {
text-align: center;
& a {
font-weight: 500;
color: var(--color-foreground);
text-decoration: none;
}
}
}
</style>

View File

@@ -37,7 +37,7 @@ const count = soundCount();
<style>
.hero {
padding: 140px 0 60px;
padding: 100px 0 60px;
text-align: center;
& .logo {

View File

@@ -10,6 +10,7 @@
line-height: 1;
color: var(--color-foreground-subtle);
text-align: left;
text-decoration: none;
cursor: pointer;
background-color: transparent;
border: 1px solid var(--color-neutral-200);

View File

@@ -1,16 +1,30 @@
import styles from './item.module.css';
interface ItemProps {
disabled: boolean;
disabled?: boolean;
href?: string;
icon: React.ReactElement;
label: string;
onClick: () => void;
onClick?: () => void;
}
export function Item({ disabled = false, icon, label, onClick }: ItemProps) {
export function Item({
disabled = false,
href,
icon,
label,
onClick = () => {},
}: ItemProps) {
const Comp = href ? 'a' : 'button';
return (
<button className={styles.item} disabled={disabled} onClick={onClick}>
<Comp
className={styles.item}
disabled={disabled}
onClick={onClick}
{...(href ? { href, target: '_blank' } : {})}
>
<span className={styles.icon}>{icon}</span> {label}
</button>
</Comp>
);
}

View File

@@ -0,0 +1,13 @@
import { SiBuymeacoffee } from 'react-icons/si/index';
import { Item } from '../item';
export function Donate() {
return (
<Item
href="https://buymeacoffee.com/remvze"
icon={<SiBuymeacoffee />}
label="Buy Me a Coffee"
/>
);
}

View File

@@ -1,2 +1,3 @@
export { Shuffle as ShuffleItem } from './shuffle';
export { Share as ShareItem } from './share';
export { Donate as DonateItem } from './donate';

View File

@@ -14,7 +14,7 @@ import {
FloatingFocusManager,
} from '@floating-ui/react';
import { ShuffleItem, ShareItem } from './items';
import { ShuffleItem, ShareItem, DonateItem } from './items';
import { ShareLinkModal } from '@/components/modals/share-link';
import { slideY, fade, mix } from '@/lib/motion';
@@ -76,6 +76,7 @@ export function Menu() {
>
<ShareItem open={() => setShowShareLink(true)} />
<ShuffleItem />
<DonateItem />
</motion.div>
</div>
</FloatingFocusManager>

View File

@@ -23,7 +23,7 @@ export function ShareLinkModal({ onClose, show }: ShareLinkModalProps) {
.map(sound => ({
id: sound,
isSelected: sounds[sound].isSelected,
volume: sounds[sound].volume.toFixed(1),
volume: sounds[sound].volume.toFixed(2),
}))
.filter(sound => sound.isSelected);
}, [sounds, JSON.stringify(sounds)]); // eslint-disable-line

View File

@@ -5,7 +5,7 @@ import { Range } from './range';
import { Favorite } from './favorite';
import { useSound } from '@/hooks/use-sound';
import { useSoundStore } from '@/store';
import { useSoundStore, useLoadingStore } from '@/store';
import { cn } from '@/helpers/styles';
import styles from './sound.module.css';
@@ -37,6 +37,8 @@ export function Sound({
const volume = useSoundStore(state => state.sounds[id].volume);
const isSelected = useSoundStore(state => state.sounds[id].isSelected);
const isLoading = useLoadingStore(state => state.loaders[src]);
const sound = useSound(src, { loop: true, volume });
useEffect(() => {
@@ -80,7 +82,7 @@ export function Sound({
>
<Favorite id={id} />
<div className={styles.icon}>
{sound.isLoading ? (
{isLoading ? (
<span className={styles.spinner}>
<ImSpinner9 />
</span>

View File

@@ -6,11 +6,22 @@ import { places } from './sounds/places';
import { transport } from './sounds/transport';
import { things } from './sounds/things';
import { noise } from './sounds/noise';
import { binaural } from './sounds/binaural';
import type { Categories } from './types';
export const sounds: {
categories: Categories;
} = {
categories: [nature, rain, animals, urban, places, transport, things, noise],
categories: [
nature,
rain,
animals,
urban,
places,
transport,
things,
noise,
binaural,
],
};

View File

@@ -1,5 +1,17 @@
import { GiCricket, GiSeagull, GiWolfHead, GiOwl } from 'react-icons/gi/index';
import { FaDog, FaFrog, FaHorseHead, FaCat } from 'react-icons/fa/index';
import {
GiCricket,
GiSeagull,
GiWolfHead,
GiOwl,
GiWhaleTail,
} from 'react-icons/gi/index';
import {
FaDog,
FaFrog,
FaHorseHead,
FaCat,
FaCrow,
} from 'react-icons/fa/index';
import { PiBirdFill, PiDogBold } from 'react-icons/pi/index';
import type { Category } from '../types';
@@ -62,6 +74,18 @@ export const animals: Category = {
label: 'Cat Purring',
src: '/sounds/animals/cat-purring.mp3',
},
{
icon: <FaCrow />,
id: 'crows',
label: 'Crows',
src: '/sounds/animals/crows.mp3',
},
{
icon: <GiWhaleTail />,
id: 'whale',
label: 'Whale',
src: '/sounds/animals/whale.mp3',
},
],
title: 'Animals',
};

View File

@@ -0,0 +1,42 @@
import { TbWaveSine } from 'react-icons/tb/index';
import { BsSoundwave } from 'react-icons/bs/index';
import type { Category } from '../types';
export const binaural: Category = {
icon: <TbWaveSine />,
id: 'binaural',
sounds: [
{
icon: <BsSoundwave />,
id: 'binaural-delta',
label: 'Delta',
src: '/sounds/binaural/binaural-delta.wav',
},
{
icon: <BsSoundwave />,
id: 'binaural-theta',
label: 'Theta',
src: '/sounds/binaural/binaural-theta.wav',
},
{
icon: <BsSoundwave />,
id: 'binaural-alpha',
label: 'Alpha',
src: '/sounds/binaural/binaural-alpha.wav',
},
{
icon: <BsSoundwave />,
id: 'binaural-beta',
label: 'Beta',
src: '/sounds/binaural/binaural-beta.wav',
},
{
icon: <BsSoundwave />,
id: 'binaural-gamma',
label: 'Gamma',
src: '/sounds/binaural/binaural-gamma.wav',
},
],
title: 'Binaural Beats',
};

View File

@@ -40,7 +40,7 @@ export const nature: Category = {
src: '/sounds/nature/howling-wind.mp3',
},
{
icon: <FaWind />,
icon: <BiSolidTree />,
id: 'wind-in-trees',
label: 'Wind in Trees',
src: '/sounds/nature/wind-in-trees.mp3',

View File

@@ -1,11 +1,14 @@
import { BiSolidCoffeeAlt, BiSolidPlaneAlt } from 'react-icons/bi/index';
import { FaChurch } from 'react-icons/fa/index';
import { TbScubaMask } from 'react-icons/tb/index';
import { FaChurch, FaSubway, FaShoppingBasket } from 'react-icons/fa/index';
import { TbScubaMask, TbBeerFilled } from 'react-icons/tb/index';
import { GiVillage, GiCarousel } from 'react-icons/gi/index';
import {
MdTempleBuddhist,
MdConstruction,
MdLocationPin,
} from 'react-icons/md/index';
import { HiOfficeBuilding } from 'react-icons/hi/index';
import { AiFillExperiment } from 'react-icons/ai/index';
import type { Category } from '../types';
@@ -49,6 +52,48 @@ export const places: Category = {
label: 'Underwater',
src: '/sounds/places/underwater.mp3',
},
{
icon: <TbBeerFilled />,
id: 'crowded-bar',
label: 'Crowded Bar',
src: '/sounds/places/crowded-bar.mp3',
},
{
icon: <GiVillage />,
id: 'night-village',
label: 'Night Village',
src: '/sounds/places/night-village.mp3',
},
{
icon: <FaSubway />,
id: 'subway-station',
label: 'Subway Station',
src: '/sounds/places/subway-station.mp3',
},
{
icon: <HiOfficeBuilding />,
id: 'office',
label: 'Office',
src: '/sounds/places/office.mp3',
},
{
icon: <FaShoppingBasket />,
id: 'supermarket',
label: 'Supermarket',
src: '/sounds/places/supermarket.mp3',
},
{
icon: <GiCarousel />,
id: 'carousel',
label: 'Carousel',
src: '/sounds/places/carousel.mp3',
},
{
icon: <AiFillExperiment />,
id: 'laboratory',
label: 'Laboratory',
src: '/sounds/places/laboratory.mp3',
},
],
title: 'Places',
};

View File

@@ -1,9 +1,11 @@
import { GiWindchimes } from 'react-icons/gi/index';
import { GiWindchimes, GiFilmProjector } from 'react-icons/gi/index';
import { BsFillKeyboardFill } from 'react-icons/bs/index';
import { FaKeyboard, FaClock, FaFan } from 'react-icons/fa/index';
import { MdSmartToy } from 'react-icons/md/index';
import { MdSmartToy, MdWaterDrop, MdRadio } from 'react-icons/md/index';
import { TbBowlFilled } from 'react-icons/tb/index';
import { RiFilePaper2Fill } from 'react-icons/ri/index';
import { RiFilePaper2Fill, RiBubbleChartFill } from 'react-icons/ri/index';
import { BiSolidDryer } from 'react-icons/bi/index';
import { IoIosRadio } from 'react-icons/io/index';
import type { Category } from '../types';
@@ -53,6 +55,42 @@ export const things: Category = {
label: 'Ceiling Fan',
src: '/sounds/things/ceiling-fan.mp3',
},
{
icon: <BiSolidDryer />,
id: 'dryer',
label: 'Dryer',
src: '/sounds/things/dryer.mp3',
},
{
icon: <GiFilmProjector />,
id: 'slide-projector',
label: 'Slide Projector',
src: '/sounds/things/slide-projector.mp3',
},
{
icon: <MdWaterDrop />,
id: 'boiling-water',
label: 'Boiling Water',
src: '/sounds/things/boiling-water.mp3',
},
{
icon: <RiBubbleChartFill />,
id: 'bubbles',
label: 'Bubbles',
src: '/sounds/things/bubbles.mp3',
},
{
icon: <MdRadio />,
id: 'tuning-radio',
label: 'Tuning Radio',
src: '/sounds/things/tuning-radio.mp3',
},
{
icon: <IoIosRadio />,
id: 'morse-code',
label: 'Morse Code',
src: '/sounds/things/morse-code.mp3',
},
],
title: 'Things',
};

View File

@@ -2,6 +2,7 @@ import { BiSolidTraffic } from 'react-icons/bi/index';
import { FaCity, FaRoad } from 'react-icons/fa/index';
import { PiRoadHorizonFill, PiSirenBold } from 'react-icons/pi/index';
import { BsSoundwave, BsPeopleFill } from 'react-icons/bs/index';
import { RiSparkling2Fill } from 'react-icons/ri/index';
import type { Category } from '../types';
@@ -45,6 +46,12 @@ export const urban: Category = {
label: 'Traffic',
src: '/sounds/urban/traffic.mp3',
},
{
icon: <RiSparkling2Fill />,
id: 'fireworks',
label: 'Fireworks',
src: '/sounds/urban/fireworks.mp3',
},
],
title: 'Urban',
};

View File

@@ -1,6 +1,7 @@
import { useMemo, useEffect, useCallback, useState } from 'react';
import { Howl } from 'howler';
import { useLoadingStore } from '@/store';
import { useSSR } from './use-ssr';
export function useSound(
@@ -8,7 +9,9 @@ export function useSound(
options: { loop?: boolean; volume?: number } = {},
) {
const [hasLoaded, setHasLoaded] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const isLoading = useLoadingStore(state => state.loaders[src]);
const setIsLoading = useLoadingStore(state => state.set);
const { isBrowser } = useSSR();
const sound = useMemo<Howl | null>(() => {
let sound: Howl | null = null;
@@ -16,7 +19,7 @@ export function useSound(
if (isBrowser) {
sound = new Howl({
onload: () => {
setIsLoading(false);
setIsLoading(src, false);
setHasLoaded(true);
},
preload: false,
@@ -25,7 +28,7 @@ export function useSound(
}
return sound;
}, [src, isBrowser]);
}, [src, isBrowser, setIsLoading]);
useEffect(() => {
if (sound) {
@@ -41,7 +44,7 @@ export function useSound(
const play = useCallback(() => {
if (sound) {
if (!hasLoaded && !isLoading) {
setIsLoading(true);
setIsLoading(src, true);
sound.load();
}
@@ -49,7 +52,7 @@ export function useSound(
sound.play();
}
}
}, [sound, hasLoaded, isLoading]);
}, [src, setIsLoading, sound, hasLoaded, isLoading]);
const stop = useCallback(() => {
if (sound) sound.stop();

View File

@@ -1,5 +1,6 @@
---
import Layout from '@/layouts/layout.astro';
import Donate from '@/components/donate.astro';
import Hero from '@/components/hero.astro';
import Footer from '@/components/footer.astro';
import AboutSection from '@/components/sections/about.astro';
@@ -10,6 +11,7 @@ import { App } from '@/components/app';
---
<Layout title="Moodist: Ambient Sounds for Focus and Calm">
<Donate />
<Hero />
<App client:load />
<AboutSection />

View File

@@ -1 +1,2 @@
export { useSoundStore } from './sound';
export { useLoadingStore } from './loading';

View File

@@ -0,0 +1,13 @@
import { create } from 'zustand';
interface LoadingStore {
loaders: Record<string, boolean>;
set: (id: string, value: boolean) => void;
}
export const useLoadingStore = create<LoadingStore>()((set, get) => ({
loaders: {},
set(id: string, value: boolean) {
set({ loaders: { ...get().loaders, [id]: value } });
},
}));