Add ability to pause, and resume the stream
This change allows the stream to be paused, and resumed on-demand.
This commit is contained in:
parent
44fdb443f8
commit
13789768a1
10 changed files with 125 additions and 30 deletions
38
src/css/components/TheQuran/Shape.scss
Normal file
38
src/css/components/TheQuran/Shape.scss
Normal file
|
@ -0,0 +1,38 @@
|
|||
.shape-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: row;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 5px;
|
||||
background: #606850;
|
||||
cursor: pointer;
|
||||
|
||||
}
|
||||
|
||||
.shape-box .play-shape {
|
||||
position: relative;
|
||||
left: 1px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: #FFF;
|
||||
clip-path: polygon(0 0, 100% 50%, 0 100%);
|
||||
}
|
||||
|
||||
.shape-box .pause-shape {
|
||||
width: 3px;
|
||||
height: 12px;
|
||||
background: #FFF;
|
||||
clip-path: stroke-box;
|
||||
|
||||
&:first-child {
|
||||
position: relative;
|
||||
right: 2px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
position: relative;
|
||||
left: 1.5px;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
@import "fonts";
|
||||
@import "components/Select";
|
||||
@import "components/TheQuran/Shape";
|
||||
|
||||
$black: #454545;
|
||||
|
||||
|
@ -104,17 +105,24 @@ body {
|
|||
}
|
||||
|
||||
.surah .timer {
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
font-size: 65%;
|
||||
font-family: "Roboto Mono Regular";
|
||||
font-family: "Kanit Regular";
|
||||
text-align: center;
|
||||
width: 30px;
|
||||
width: 32px;
|
||||
height: 18px;
|
||||
font-weight: bold;
|
||||
border-radius: 10px;
|
||||
padding: 3px;
|
||||
border-radius: 5px;
|
||||
padding: 2px;
|
||||
position: relative;
|
||||
top: 4px;
|
||||
}
|
||||
|
||||
.surah.ar .timer {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,9 +24,13 @@
|
|||
}
|
||||
}
|
||||
|
||||
.timer {
|
||||
.surah-row .timer {
|
||||
color: $white;
|
||||
background-color: lighten($gold, 5%);
|
||||
background: $gold;
|
||||
}
|
||||
|
||||
.surah-row .container.shape {
|
||||
background: $gold;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -35,8 +35,7 @@ const createOption = (e: ChangeEvent, children: JSX.Element[]): SelectOption =>
|
|||
};
|
||||
};
|
||||
|
||||
export function Select(props: Props) {
|
||||
const { children, className, value, onChange } = props;
|
||||
export function Select({ value, children, onChange, className }: Props) {
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
const [activeOption, setActiveOption] = useState<string | null>(findOption(value, children));
|
||||
const openSelect = (e: React.MouseEvent<HTMLSpanElement>) => {
|
||||
|
|
|
@ -6,13 +6,15 @@ interface Props {
|
|||
locale: string
|
||||
surah: Surah
|
||||
stream: Ayah[]
|
||||
isPaused: boolean
|
||||
}
|
||||
|
||||
export function LanguageSelect(props: Props) {
|
||||
const { locale, surah, stream } = props;
|
||||
export function LanguageSelect({ locale, surah, stream, isPaused }: Props) {
|
||||
const changeLanguage = (o: SelectOption) => {
|
||||
const locale = o.value;
|
||||
location.replace(`/${locale}/${surah.slug}/?ayah=${stream.length}`);
|
||||
location.replace(
|
||||
`/${locale}/${surah.slug}/?ayah=${stream.length}&paused=${isPaused ? 't' : 'f'}`
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
22
src/js/components/TheQuran/Shape.tsx
Normal file
22
src/js/components/TheQuran/Shape.tsx
Normal file
|
@ -0,0 +1,22 @@
|
|||
import React from 'react';
|
||||
|
||||
interface Props {
|
||||
onClick: () => void
|
||||
}
|
||||
|
||||
export function PlayShape({ onClick }: Props) {
|
||||
return (
|
||||
<div className="shape-box" onClick={onClick}>
|
||||
<div className="play-shape" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function PauseShape({ onClick }: Props) {
|
||||
return (
|
||||
<div className="shape-box" onClick={onClick}>
|
||||
<div className="pause-shape" />
|
||||
<div className="pause-shape" />
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -10,12 +10,13 @@ interface Props {
|
|||
locale: Locale
|
||||
slice: Slice
|
||||
endOfStream: boolean
|
||||
isPaused: boolean
|
||||
}
|
||||
|
||||
export function Stream({ surah, stream, locale, slice, endOfStream }: Props) {
|
||||
export function Stream({ surah, stream, locale, slice, endOfStream, isPaused }: Props) {
|
||||
const n = numbers(locale);
|
||||
const s = strings(locale);
|
||||
const className = classNames('stream', { 'scroll-y': endOfStream });
|
||||
const className = classNames('stream', { 'scroll-y': endOfStream || isPaused });
|
||||
const ayat = stream.map((ayah: Ayah) => {
|
||||
return (
|
||||
<li key={ayah.id.number} className="ayah fade">
|
||||
|
|
|
@ -7,19 +7,22 @@ interface Props {
|
|||
locale: Locale
|
||||
stream: Ayat
|
||||
setStream: (stream: Ayat) => void
|
||||
isPaused: boolean
|
||||
}
|
||||
|
||||
export function Timer ({ surah, stream, setStream, locale }: Props) {
|
||||
export function Timer ({ surah, stream, setStream, locale, isPaused }: Props) {
|
||||
const ayah = stream[stream.length - 1];
|
||||
const [ms, setMs] = useState(ayah.readTimeMs);
|
||||
useEffect(() => setMs(ayah.readTimeMs), [ayah.id]);
|
||||
useEffect(() => {
|
||||
if (ms <= 0) {
|
||||
if (isPaused) {
|
||||
return;
|
||||
} else if (ms <= 0) {
|
||||
setStream([...stream, surah.ayat[ayah.id.number]]);
|
||||
} else {
|
||||
setTimeout(() => setMs(ms - 100), 100);
|
||||
}
|
||||
}, [ms]);
|
||||
}, [ms, isPaused]);
|
||||
return (
|
||||
<div className='timer'>
|
||||
{numberToDecimal(ms / 1000, locale)}
|
||||
|
|
|
@ -67,5 +67,5 @@ export function numberToDecimal(number: number, locale: Locale): string {
|
|||
const n = numbers(locale);
|
||||
return decimal.split('.')
|
||||
.map((num: Digit) => n(num))
|
||||
.join(s('decimal'));
|
||||
.join(` ${s('decimal')} `);
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import { Timer } from 'components/TheQuran/Timer';
|
|||
import { Stream } from 'components/TheQuran/Stream';
|
||||
import { ThemeSelect } from 'components/TheQuran/ThemeSelect';
|
||||
import { LanguageSelect } from 'components/TheQuran/LanguageSelect';
|
||||
import { PlayShape, PauseShape } from 'components/TheQuran/Shape';
|
||||
import { Locale, Surah } from 'lib/Quran';
|
||||
import { Slice } from 'lib/Quran/slice';
|
||||
|
||||
|
@ -13,12 +14,14 @@ interface Props {
|
|||
locale: Locale
|
||||
surahId: number
|
||||
slice: Slice
|
||||
paused: boolean
|
||||
}
|
||||
|
||||
function TheSurahPage({ locale, surahId, slice }: Props) {
|
||||
function TheSurahPage({ locale, surahId, slice, paused }: Props) {
|
||||
const path = `/${locale}/${surahId}/surah.json`;
|
||||
const node: HTMLScriptElement = document.querySelector(`script[src="${path}"]`);
|
||||
const [stream, setStream] = useState([]);
|
||||
const [isPaused, setIsPaused] = useState<boolean>(paused);
|
||||
const [theme, setTheme] = useState(getCookie('theme') || 'moon');
|
||||
const [surah] = useState<Surah>(Surah.fromDOMNode(locale, node));
|
||||
const readyToRender = stream.length > 0;
|
||||
|
@ -52,11 +55,16 @@ function TheSurahPage({ locale, surahId, slice }: Props) {
|
|||
{readyToRender && (
|
||||
<div className="surah-row theme-language">
|
||||
<ThemeSelect theme={theme} setTheme={setTheme} />
|
||||
<LanguageSelect locale={locale} surah={surah} stream={stream} />
|
||||
<LanguageSelect
|
||||
locale={locale}
|
||||
surah={surah}
|
||||
stream={stream}
|
||||
isPaused={isPaused}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{readyToRender && (
|
||||
<div className='surah-row surah-details'>
|
||||
<div className="surah-row surah-details">
|
||||
<span lang={locale}>{surahName}</span>
|
||||
<span>{surah.transliteratedName}</span>
|
||||
</div>
|
||||
|
@ -68,27 +76,36 @@ function TheSurahPage({ locale, surahId, slice }: Props) {
|
|||
stream={stream}
|
||||
locale={locale}
|
||||
endOfStream={endOfStream}
|
||||
isPaused={isPaused}
|
||||
/>
|
||||
}
|
||||
{readyToRender && !endOfStream && (
|
||||
<div className="surah-row">
|
||||
{ readyToRender && isPaused && !endOfStream &&
|
||||
<PlayShape onClick={() => setIsPaused(false)}/> }
|
||||
{ readyToRender && !isPaused && !endOfStream &&
|
||||
<PauseShape onClick={() => setIsPaused(true)}/> }
|
||||
{ readyToRender && !endOfStream &&
|
||||
<Timer
|
||||
surah={surah}
|
||||
setStream={setStream}
|
||||
stream={stream}
|
||||
locale={locale}
|
||||
/>
|
||||
)}
|
||||
isPaused={isPaused}
|
||||
/> }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
(function() {
|
||||
const toBoolean = (str: string | null): boolean => ['1', 't', 'true', 'yes'].includes(str);
|
||||
const rootBox: HTMLElement = document.querySelector('.root-box');
|
||||
const locale = rootBox.getAttribute('data-locale') as Locale;
|
||||
const surahId = parseInt(rootBox.getAttribute('data-surah-id'));
|
||||
const params = new URLSearchParams(location.search);
|
||||
const slice = Slice.fromParam(params.get('ayah'));
|
||||
const paused = toBoolean(params.get('paused'));
|
||||
|
||||
ReactDOM
|
||||
.createRoot(rootBox)
|
||||
|
@ -97,6 +114,7 @@ function TheSurahPage({ locale, surahId, slice }: Props) {
|
|||
locale={locale}
|
||||
surahId={surahId}
|
||||
slice={slice}
|
||||
paused={paused}
|
||||
/>
|
||||
);
|
||||
})();
|
||||
|
|
Loading…
Reference in a new issue