Skip to content

Commit 4f8a51e

Browse files
authored
Merge pull request #727 from BogdanLi/feature-bogdan-706
Community Audio
2 parents 7cc9ad0 + f3b99dd commit 4f8a51e

33 files changed

Lines changed: 1255 additions & 4 deletions
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import AudioPause from 'public/icons/audioPause.svg'
2+
import AudioPlay from 'public/icons/audioPlay.svg'
3+
import Download from 'public/icons/download-audio.svg'
4+
import Loading from 'public/icons/progress.svg'
5+
6+
export default function AudioPreview({
7+
audioUrl,
8+
onPlay,
9+
onPause,
10+
isPlaying,
11+
audioName,
12+
loading,
13+
}) {
14+
return (
15+
<div
16+
className={`flex items-center gap-2 rounded-full border border-th-text-primary p-2 ${
17+
!audioUrl || loading ? 'opacity-70' : ''
18+
}`}
19+
>
20+
<p className="text-sm">{audioName || 'audio'}</p>
21+
<a
22+
href={audioUrl || ''}
23+
className="disabled:fill-gray-400"
24+
download={`${audioName || 'audio'}.mp3`}
25+
>
26+
{loading && !audioUrl ? (
27+
<Loading className="progress-custom-colors h-5 w-5 animate-spin stroke-th-secondary-100" />
28+
) : (
29+
<Download />
30+
)}
31+
</a>
32+
<button
33+
disabled={!audioUrl}
34+
className="rounded-full bg-th-secondary-400 p-2 disabled:bg-th-text-primary"
35+
onClick={isPlaying ? onPause : onPlay}
36+
>
37+
{loading ? (
38+
<Loading className="progress-custom-colors h-5 w-5 animate-spin stroke-th-secondary-100" />
39+
) : isPlaying ? (
40+
<AudioPause className="h-5 w-5" />
41+
) : (
42+
<AudioPlay className="h-5 w-5" />
43+
)}
44+
</button>
45+
</div>
46+
)
47+
}
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react'
2+
3+
import { useRouter } from 'next/router'
4+
5+
import { Disclosure, Tab } from '@headlessui/react'
6+
import { useTranslation } from 'next-i18next'
7+
8+
import ChecksIcon from 'components/Project/BookList/ChecksIcon'
9+
import Card from 'components/Project/Card'
10+
11+
import { checkChapterVersesExist } from 'utils/helper'
12+
import { useGetChaptersTranslate } from 'utils/hooks'
13+
14+
import Down from 'public/icons/arrow-down.svg'
15+
16+
function BookListReader({ books, setReference, reference, project, code }) {
17+
const [currentBook, setCurrentBook] = useState(null)
18+
const [createdOldTestamentBooks, createdNewTestamentBooks] = books
19+
const { query, replace } = useRouter()
20+
const { t } = useTranslation(['common', 'books'])
21+
const refs = useRef([])
22+
const [chapters] = useGetChaptersTranslate({ code })
23+
24+
const scrollRefs = useRef({})
25+
const handleClose = (index) => {
26+
refs.current.map((closeFunction, refIndex) => {
27+
if (refIndex !== index) {
28+
closeFunction()
29+
}
30+
})
31+
}
32+
33+
const handleScroll = (bookid) => {
34+
if (scrollRefs?.current && Object.keys(scrollRefs?.current).length) {
35+
verseRef(scrollRefs.current[bookid])
36+
}
37+
}
38+
39+
const scrollTo = (currentBook, position) => {
40+
let offset = 0
41+
const top = currentBook.offsetTop - 95
42+
switch (position) {
43+
case 'center':
44+
offset = currentBook.clientHeight / 2 - currentBook.parentNode.clientHeight / 2
45+
break
46+
case 'top':
47+
default:
48+
break
49+
}
50+
51+
currentBook.parentNode.scrollTo({ left: 0, top: top + offset, behavior: 'smooth' })
52+
}
53+
54+
const { tabs, defaultIndex } = useMemo(() => {
55+
const index = [createdNewTestamentBooks, createdOldTestamentBooks]?.findIndex(
56+
(list) => list?.find((el) => el.code === query.bookid)
57+
)
58+
59+
const tabs =
60+
project?.type === 'obs' ? ['OpenBibleStories'] : ['NewTestament', 'OldTestament']
61+
62+
const defaultIndex = index === -1 ? 0 : index
63+
64+
return { defaultIndex, tabs }
65+
}, [createdNewTestamentBooks, createdOldTestamentBooks, query.bookid, project?.type])
66+
67+
const verseRef = useCallback((node) => {
68+
if (node !== null) {
69+
setCurrentBook(node)
70+
}
71+
}, [])
72+
73+
useEffect(() => {
74+
if (currentBook) {
75+
scrollTo(currentBook, 'top')
76+
}
77+
}, [currentBook])
78+
79+
useEffect(() => {
80+
if (reference?.bookid) {
81+
handleScroll(reference.bookid)
82+
}
83+
// eslint-disable-next-line react-hooks/exhaustive-deps
84+
}, [reference?.bookid])
85+
return (
86+
<Card>
87+
<div className="flex flex-col gap-7 bg-th-secondary-10">
88+
<Tab.Group defaultIndex={defaultIndex}>
89+
<Tab.List className="-mt-6 flex w-full rounded-3xl border border-th-secondary-300 bg-th-secondary-10 p-1 shadow-md">
90+
{tabs.map((tab) => (
91+
<Tab as={Fragment} key={tab}>
92+
{({ selected }) => (
93+
<div
94+
className={`w-full cursor-pointer rounded-3xl p-2 text-center ${
95+
selected ? 'bg-th-primary-100 text-th-text-secondary-100' : ''
96+
} `}
97+
>
98+
{t(tab)}
99+
</div>
100+
)}
101+
</Tab>
102+
))}
103+
</Tab.List>
104+
105+
<Tab.Panels className="text-sm font-bold">
106+
{[
107+
...(createdNewTestamentBooks !== undefined
108+
? [createdNewTestamentBooks]
109+
: []),
110+
...(createdOldTestamentBooks !== undefined
111+
? [createdOldTestamentBooks]
112+
: []),
113+
].map((list, idx) => (
114+
<Tab.Panel key={idx} className="max-h-[70vh] overflow-y-scroll pr-4">
115+
{list?.map((book, index) => (
116+
<Disclosure
117+
as={'div'}
118+
key={book.code}
119+
defaultOpen={query?.bookid === book.code}
120+
ref={(ref) => (scrollRefs.current[book.code] = ref)}
121+
>
122+
{({ open, close }) => {
123+
return (
124+
<>
125+
<Disclosure.Button
126+
ref={() => (refs.current[index] = close)}
127+
onClick={() => {
128+
handleClose(index)
129+
replace(
130+
{
131+
query: { ...query, bookid: book.code },
132+
},
133+
undefined,
134+
{ shallow: true }
135+
)
136+
}}
137+
className={`flex w-full items-center justify-between py-2 hover:opacity-70 ${
138+
!open ? 'border-b border-th-secondary-300' : ''
139+
}`}
140+
>
141+
<div className="flex items-center gap-4 text-base">
142+
<ChecksIcon levelCheck={book?.level_checks} />
143+
<div>{t('books:' + book.code)}</div>
144+
</div>
145+
<Down
146+
className={`w-6 max-w-[1.5rem] ${
147+
open ? 'rotate-180 transform' : ''
148+
}`}
149+
/>
150+
</Disclosure.Button>
151+
<Disclosure.Panel>
152+
<div className="flex w-full flex-wrap gap-4 border-b border-th-secondary-300 pb-5">
153+
{[...Array(Object.keys(book.chapters).length).keys()]
154+
.map((el) => el + 1)
155+
.map((index) => (
156+
<button
157+
disabled={
158+
!checkChapterVersesExist(
159+
book.code,
160+
index,
161+
chapters
162+
) && !reference?.checks
163+
}
164+
className={`flex h-10 w-10 items-center justify-center rounded-md ${
165+
checkChapterVersesExist(
166+
book.code,
167+
index,
168+
chapters
169+
) || reference?.checks
170+
? 'cursor-pointer bg-th-primary-100'
171+
: 'disabled cursor-default rounded-md bg-th-secondary-200 text-th-text-secondary-100'
172+
} ${
173+
index === reference?.chapter
174+
? 'cursor-default rounded-md bg-th-primary-100 text-th-text-secondary-100'
175+
: checkChapterVersesExist(
176+
book.code,
177+
index,
178+
chapters
179+
) || reference?.checks
180+
? 'bg-th-secondary-200 hover:opacity-70'
181+
: ''
182+
}`}
183+
key={index}
184+
onClick={() =>
185+
setReference((prev) => ({
186+
...prev,
187+
chapter: index,
188+
}))
189+
}
190+
>
191+
{index}
192+
</button>
193+
))}
194+
</div>
195+
</Disclosure.Panel>
196+
</>
197+
)
198+
}}
199+
</Disclosure>
200+
))}
201+
</Tab.Panel>
202+
))}
203+
</Tab.Panels>
204+
</Tab.Group>
205+
</div>
206+
</Card>
207+
)
208+
}
209+
210+
export default BookListReader

0 commit comments

Comments
 (0)