Merge pull request #137 from ReflectsLight/prettier
Add prettier GitHub action
This commit is contained in:
commit
c55ab60dfd
23 changed files with 429 additions and 337 deletions
|
@ -17,9 +17,10 @@ module.exports = {
|
|||
"@typescript-eslint/no-misused-promises": ["error", {"checksConditionals": false}],
|
||||
"@typescript-eslint/no-redeclare": 0,
|
||||
"@typescript-eslint/no-non-null-assertion": 0,
|
||||
"@typescript-eslint/member-delimiter-style": 0,
|
||||
"no-return-assign": 0,
|
||||
"no-useless-return": 0,
|
||||
"quotes": 2,
|
||||
"quotes": 0,
|
||||
"object-curly-spacing": 2,
|
||||
"n/no-callback-literal": 0,
|
||||
},
|
||||
|
|
9
.github/workflows/al-quran.yml
vendored
9
.github/workflows/al-quran.yml
vendored
|
@ -21,6 +21,15 @@ jobs:
|
|||
run: bundle exec rake lint:eslint
|
||||
- name: rubocop
|
||||
run: bundle exec rake lint:rubocop
|
||||
prettier:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v2
|
||||
- name: Prepare environment
|
||||
uses: './.github/actions/prepare-env'
|
||||
- name: prettier
|
||||
run: npm exec prettier -- --check src/js/
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"printWidth": 80,
|
||||
"printWidth": 90,
|
||||
"arrowParens": "avoid"
|
||||
}
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
"workspaces": ["./packages/typescript/*"],
|
||||
"scripts": {
|
||||
"eslint": "node ./node_modules/eslint/bin/eslint.js src/js/",
|
||||
"eslint-autofix": "node ./node_modules/eslint/bin/eslint.js --fix src/js/"
|
||||
"eslint-autofix": "node ./node_modules/eslint/bin/eslint.js --fix src/js/",
|
||||
"prettier-autofix": "npm exec prettier -- --write src/js/"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^18.2.0",
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import React from 'react';
|
||||
import { Select, SelectOption } from 'components/Select';
|
||||
import React from "react";
|
||||
import { Select, SelectOption } from "components/Select";
|
||||
|
||||
interface Props {
|
||||
locale: string
|
||||
onChange: (o: SelectOption) => void
|
||||
locale: string;
|
||||
onChange: (o: SelectOption) => void;
|
||||
}
|
||||
|
||||
export function LanguageSelect({ locale, onChange }: Props) {
|
||||
|
|
|
@ -1,23 +1,25 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import React, { useState, useEffect } from "react";
|
||||
import classnames from "classnames";
|
||||
|
||||
export type ChangeEvent = React.MouseEvent<HTMLLIElement> & {target: HTMLLIElement};
|
||||
export type ChangeEvent = React.MouseEvent<HTMLLIElement> & {
|
||||
target: HTMLLIElement;
|
||||
};
|
||||
|
||||
export interface SelectOption {
|
||||
innerText: string
|
||||
value: string
|
||||
reactEvent: ChangeEvent
|
||||
innerText: string;
|
||||
value: string;
|
||||
reactEvent: ChangeEvent;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
value: string
|
||||
children: JSX.Element[]
|
||||
onChange: (e: SelectOption) => void
|
||||
className?: string
|
||||
value: string;
|
||||
children: JSX.Element[];
|
||||
onChange: (e: SelectOption) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const findOption = (value: string, children: JSX.Element[]) => {
|
||||
const activeOption = children.find((o) => o.props.value === value);
|
||||
const activeOption = children.find(o => o.props.value === value);
|
||||
if (activeOption) {
|
||||
return activeOption.props.children;
|
||||
} else {
|
||||
|
@ -27,17 +29,19 @@ const findOption = (value: string, children: JSX.Element[]) => {
|
|||
|
||||
const createOption = (e: ChangeEvent, children: JSX.Element[]): SelectOption => {
|
||||
const { target } = e;
|
||||
const value = target.getAttribute('data-value')!;
|
||||
const value = target.getAttribute("data-value")!;
|
||||
return {
|
||||
innerText: findOption(value, children),
|
||||
value,
|
||||
reactEvent: e
|
||||
reactEvent: e,
|
||||
};
|
||||
};
|
||||
|
||||
export function Select({ value, children, onChange, className }: Props) {
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
const [activeOption, setActiveOption] = useState<string | null>(findOption(value, children));
|
||||
const [activeOption, setActiveOption] = useState<string | null>(
|
||||
findOption(value, children),
|
||||
);
|
||||
const openSelect = (e: React.MouseEvent<HTMLSpanElement>) => {
|
||||
e.stopPropagation();
|
||||
setOpen(true);
|
||||
|
@ -52,11 +56,11 @@ export function Select({ value, children, onChange, className }: Props) {
|
|||
};
|
||||
|
||||
useEffect(() => {
|
||||
document.body.addEventListener('click', () => setOpen(false));
|
||||
document.body.addEventListener("click", () => setOpen(false));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={classnames('react-select', className)}>
|
||||
<div className={classnames("react-select", className)}>
|
||||
<span className="active-option" onClick={openSelect}>
|
||||
{activeOption}
|
||||
</span>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import React from "react";
|
||||
|
||||
interface Props {
|
||||
onClick: () => void
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export function PlayShape({ onClick }: Props) {
|
||||
|
@ -25,10 +25,28 @@ export function SoundOnShape({ onClick }: Props) {
|
|||
return (
|
||||
<svg viewBox="0 0 100 100" className="svg sound-on" onClick={onClick}>
|
||||
<g>
|
||||
<polygon fill="none" stroke="#000000" strokeWidth="2" strokeMiterlimit="10" points="3,32 3,20 15,20 33,2 33,32 33,62 15,44
|
||||
3,44 "/>
|
||||
<path fill="none" stroke="#000000" strokeWidth="2" strokeMiterlimit="10" d="M41,42c5.522,0,10-4.478,10-10s-4.478-10-10-10"/>
|
||||
<path fill="none" stroke="#000000" strokeWidth="2" strokeMiterlimit="10" d="M41,12c11.046,0,20,8.954,20,20s-8.954,20-20,20"/>
|
||||
<polygon
|
||||
fill="none"
|
||||
stroke="#000000"
|
||||
strokeWidth="2"
|
||||
strokeMiterlimit="10"
|
||||
points="3,32 3,20 15,20 33,2 33,32 33,62 15,44
|
||||
3,44 "
|
||||
/>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="#000000"
|
||||
strokeWidth="2"
|
||||
strokeMiterlimit="10"
|
||||
d="M41,42c5.522,0,10-4.478,10-10s-4.478-10-10-10"
|
||||
/>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="#000000"
|
||||
strokeWidth="2"
|
||||
strokeMiterlimit="10"
|
||||
d="M41,12c11.046,0,20,8.954,20,20s-8.954,20-20,20"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
|
@ -38,10 +56,34 @@ export function SoundOffShape({ onClick }: Props) {
|
|||
return (
|
||||
<svg viewBox="0 0 100 100" className="svg sound-off" onClick={onClick}>
|
||||
<g>
|
||||
<polygon fill="none" stroke="#000000" strokeWidth="2" strokeMiterlimit="10" points="4,32 4,20 16,20 34,2 34,32 34,62 16,44
|
||||
4,44 "/>
|
||||
<line fill="none" stroke="#000000" strokeWidth="2" strokeMiterlimit="10" x1="42" y1="23" x2="60" y2="41"/>
|
||||
<line fill="none" stroke="#000000" strokeWidth="2" strokeMiterlimit="10" x1="42" y1="41" x2="60" y2="23"/>
|
||||
<polygon
|
||||
fill="none"
|
||||
stroke="#000000"
|
||||
strokeWidth="2"
|
||||
strokeMiterlimit="10"
|
||||
points="4,32 4,20 16,20 34,2 34,32 34,62 16,44
|
||||
4,44 "
|
||||
/>
|
||||
<line
|
||||
fill="none"
|
||||
stroke="#000000"
|
||||
strokeWidth="2"
|
||||
strokeMiterlimit="10"
|
||||
x1="42"
|
||||
y1="23"
|
||||
x2="60"
|
||||
y2="41"
|
||||
/>
|
||||
<line
|
||||
fill="none"
|
||||
stroke="#000000"
|
||||
strokeWidth="2"
|
||||
strokeMiterlimit="10"
|
||||
x1="42"
|
||||
y1="41"
|
||||
x2="60"
|
||||
y2="23"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
|
@ -49,9 +91,18 @@ export function SoundOffShape({ onClick }: Props) {
|
|||
|
||||
export function RefreshShape({ onClick }: Props) {
|
||||
return (
|
||||
<svg onClick={onClick} className="shape refresh" x="0px" y="0px" width="438.542px" height="438.542px" viewBox="0 0 438.542 438.542">
|
||||
<g>
|
||||
<path d="M427.408,19.697c-7.803-3.23-14.463-1.902-19.986,3.999l-37.116,36.834C349.94,41.305,326.672,26.412,300.5,15.848
|
||||
<svg
|
||||
onClick={onClick}
|
||||
className="shape refresh"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="438.542px"
|
||||
height="438.542px"
|
||||
viewBox="0 0 438.542 438.542"
|
||||
>
|
||||
<g>
|
||||
<path
|
||||
d="M427.408,19.697c-7.803-3.23-14.463-1.902-19.986,3.999l-37.116,36.834C349.94,41.305,326.672,26.412,300.5,15.848
|
||||
C274.328,5.285,247.251,0.003,219.271,0.003c-29.692,0-58.052,5.808-85.08,17.417c-27.03,11.61-50.347,27.215-69.951,46.82
|
||||
c-19.605,19.607-35.214,42.921-46.824,69.949C5.807,161.219,0,189.575,0,219.271c0,29.687,5.807,58.05,17.417,85.079
|
||||
c11.613,27.031,27.218,50.347,46.824,69.952c19.604,19.599,42.921,35.207,69.951,46.818c27.028,11.611,55.388,17.419,85.08,17.419
|
||||
|
@ -62,23 +113,24 @@ export function RefreshShape({ onClick }: Props) {
|
|||
c0-19.795,3.858-38.691,11.563-56.674c7.707-17.985,18.127-33.547,31.261-46.678c13.135-13.134,28.693-23.555,46.682-31.265
|
||||
c17.983-7.707,36.879-11.563,56.671-11.563c38.259,0,71.475,13.039,99.646,39.116l-39.409,39.394
|
||||
c-5.903,5.711-7.231,12.279-4.001,19.701c3.241,7.614,8.856,11.42,16.854,11.42h127.906c4.949,0,9.23-1.807,12.848-5.424
|
||||
c3.613-3.616,5.42-7.898,5.42-12.847V36.55C438.542,28.558,434.84,22.943,427.408,19.697z"/>
|
||||
</g>
|
||||
<g />
|
||||
<g />
|
||||
<g />
|
||||
<g />
|
||||
<g />
|
||||
<g />
|
||||
<g />
|
||||
<g />
|
||||
<g />
|
||||
<g />
|
||||
<g />
|
||||
<g />
|
||||
<g />
|
||||
<g />
|
||||
<g />
|
||||
</svg>
|
||||
c3.613-3.616,5.42-7.898,5.42-12.847V36.55C438.542,28.558,434.84,22.943,427.408,19.697z"
|
||||
/>
|
||||
</g>
|
||||
<g />
|
||||
<g />
|
||||
<g />
|
||||
<g />
|
||||
<g />
|
||||
<g />
|
||||
<g />
|
||||
<g />
|
||||
<g />
|
||||
<g />
|
||||
<g />
|
||||
<g />
|
||||
<g />
|
||||
<g />
|
||||
<g />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,33 +1,37 @@
|
|||
import * as Quran from 'lib/Quran';
|
||||
import React, { useEffect } from 'react';
|
||||
import { formatNumber, TFunction } from 'lib/i18n';
|
||||
import { Slice } from 'lib/Quran/Slice';
|
||||
import classNames from 'classnames';
|
||||
import * as Quran from "lib/Quran";
|
||||
import React, { useEffect } from "react";
|
||||
import { formatNumber, TFunction } from "lib/i18n";
|
||||
import { Slice } from "lib/Quran/Slice";
|
||||
import classNames from "classnames";
|
||||
|
||||
interface Props {
|
||||
surah: Quran.Surah
|
||||
stream: Quran.Ayat
|
||||
locale: Quran.Locale
|
||||
slice: Slice
|
||||
endOfStream: boolean
|
||||
isPaused: boolean
|
||||
t: TFunction
|
||||
surah: Quran.Surah;
|
||||
stream: Quran.Ayat;
|
||||
locale: Quran.Locale;
|
||||
slice: Slice;
|
||||
endOfStream: boolean;
|
||||
isPaused: boolean;
|
||||
t: TFunction;
|
||||
}
|
||||
|
||||
export function Stream({ surah, stream, locale, slice, endOfStream, isPaused, t }: Props) {
|
||||
const className = classNames('body', 'stream');
|
||||
const style: React.CSSProperties = endOfStream || isPaused ?
|
||||
{ 'overflowY': 'auto' } :
|
||||
{ 'overflowY': 'hidden' };
|
||||
export function Stream({
|
||||
surah,
|
||||
stream,
|
||||
locale,
|
||||
slice,
|
||||
endOfStream,
|
||||
isPaused,
|
||||
t,
|
||||
}: Props) {
|
||||
const className = classNames("body", "stream");
|
||||
const style: React.CSSProperties =
|
||||
endOfStream || isPaused ? { overflowY: "auto" } : { overflowY: "hidden" };
|
||||
const ayat = stream.map((ayah: Quran.Ayah) => {
|
||||
return (
|
||||
<li key={ayah.id} className="ayah fade">
|
||||
<span className="surah-id ayah-id">
|
||||
{t(locale, 'surah')}{' '}
|
||||
{formatNumber(surah.id, locale)}
|
||||
{t(locale, 'comma')}{' '}
|
||||
{t(locale, 'ayah')}{' '}
|
||||
{formatNumber(ayah.id, locale)}
|
||||
{t(locale, "surah")} {formatNumber(surah.id, locale)}
|
||||
{t(locale, "comma")} {t(locale, "ayah")} {formatNumber(ayah.id, locale)}
|
||||
</span>
|
||||
<p>{ayah.text}</p>
|
||||
</li>
|
||||
|
@ -35,12 +39,12 @@ export function Stream({ surah, stream, locale, slice, endOfStream, isPaused, t
|
|||
});
|
||||
|
||||
useEffect(() => {
|
||||
const ul: HTMLElement = document.querySelector('ul.stream')!;
|
||||
const ul: HTMLElement = document.querySelector("ul.stream")!;
|
||||
if (slice.coversOneAyah) {
|
||||
const li: HTMLLIElement = ul.querySelector('li:last-child')!;
|
||||
const li: HTMLLIElement = ul.querySelector("li:last-child")!;
|
||||
li.scrollIntoView();
|
||||
} else {
|
||||
ul.scroll({ top: ul.scrollHeight, behavior: 'smooth' });
|
||||
ul.scroll({ top: ul.scrollHeight, behavior: "smooth" });
|
||||
}
|
||||
}, [stream]);
|
||||
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
import React from 'react';
|
||||
import { Select, SelectOption } from 'components/Select';
|
||||
import { set as setCookie } from 'es-cookie';
|
||||
import React from "react";
|
||||
import { Select, SelectOption } from "components/Select";
|
||||
import { set as setCookie } from "es-cookie";
|
||||
|
||||
interface Props {
|
||||
setTheme: (theme: string) => void
|
||||
theme: string
|
||||
setTheme: (theme: string) => void;
|
||||
theme: string;
|
||||
}
|
||||
|
||||
export function ThemeSelect ({ setTheme, theme }: Props) {
|
||||
export function ThemeSelect({ setTheme, theme }: Props) {
|
||||
const onThemeChange = (o: SelectOption) => {
|
||||
setCookie('theme', o.value, { domain: location.host, expires: 365 });
|
||||
setCookie("theme", o.value, { domain: location.host, expires: 365 });
|
||||
setTheme(o.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<Select value={theme} onChange={onThemeChange}>
|
||||
<option value='moon'>🌛</option>
|
||||
<option value='leaf'>🌿</option>
|
||||
<option value="moon">🌛</option>
|
||||
<option value="leaf">🌿</option>
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,18 +1,26 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import * as Quran from 'lib/Quran';
|
||||
import { formatNumber } from 'lib/i18n';
|
||||
import React, { useEffect, useState } from "react";
|
||||
import * as Quran from "lib/Quran";
|
||||
import { formatNumber } from "lib/i18n";
|
||||
|
||||
interface Props {
|
||||
surah: Quran.Surah
|
||||
locale: Quran.Locale
|
||||
stream: Quran.Ayat
|
||||
soundOn: boolean
|
||||
setStream: (stream: Quran.Ayat) => void
|
||||
isPaused: boolean
|
||||
isStalled: boolean
|
||||
surah: Quran.Surah;
|
||||
locale: Quran.Locale;
|
||||
stream: Quran.Ayat;
|
||||
soundOn: boolean;
|
||||
setStream: (stream: Quran.Ayat) => void;
|
||||
isPaused: boolean;
|
||||
isStalled: boolean;
|
||||
}
|
||||
|
||||
export function Timer ({ surah, stream, isStalled, soundOn, setStream, locale, isPaused }: Props) {
|
||||
export function Timer({
|
||||
surah,
|
||||
stream,
|
||||
isStalled,
|
||||
soundOn,
|
||||
setStream,
|
||||
locale,
|
||||
isPaused,
|
||||
}: Props) {
|
||||
const ayah = stream[stream.length - 1];
|
||||
const [ms, setMs] = useState(ayah.readTimeMs);
|
||||
const [tid, setTid] = useState<ReturnType<typeof setTimeout>>();
|
||||
|
@ -35,9 +43,5 @@ export function Timer ({ surah, stream, isStalled, soundOn, setStream, locale, i
|
|||
}
|
||||
}, [isStalled, isPaused, soundOn, ms]);
|
||||
|
||||
return (
|
||||
<div className='timer'>
|
||||
{formatNumber(ms / 1000, locale)}
|
||||
</div>
|
||||
);
|
||||
return <div className="timer">{formatNumber(ms / 1000, locale)}</div>;
|
||||
}
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
export interface Locale {
|
||||
fromBrowser: () => string
|
||||
fromPath: () => string | undefined
|
||||
fromBrowser: () => string;
|
||||
fromPath: () => string | undefined;
|
||||
}
|
||||
|
||||
export function Locale(window: Window): Locale {
|
||||
const self = Object.create(null);
|
||||
const { navigator, location } = window;
|
||||
const locales = ['ar', 'en'];
|
||||
const locales = ["ar", "en"];
|
||||
|
||||
self.fromBrowser = () => {
|
||||
return navigator
|
||||
.languages
|
||||
.map((lang) => lang.substr(0, 2))
|
||||
.find((locale) => locales.includes(locale)) || 'en';
|
||||
return (
|
||||
navigator.languages
|
||||
.map(lang => lang.substr(0, 2))
|
||||
.find(locale => locales.includes(locale)) || "en"
|
||||
);
|
||||
};
|
||||
|
||||
self.fromPath = () => {
|
||||
return location
|
||||
.pathname
|
||||
.split('/')
|
||||
.filter((s) => s.length)
|
||||
return location.pathname
|
||||
.split("/")
|
||||
.filter(s => s.length)
|
||||
.at(0);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import * as JSON from 'lib/Quran/JSON';
|
||||
import { Ayah } from 'lib/Quran/Ayah';
|
||||
import { Surah } from 'lib/Quran/Surah';
|
||||
import * as JSON from "lib/Quran/JSON";
|
||||
import { Ayah } from "lib/Quran/Ayah";
|
||||
import { Surah } from "lib/Quran/Surah";
|
||||
|
||||
type Locale = 'ar' | 'en';
|
||||
type Locale = "ar" | "en";
|
||||
type Ayat = Ayah[];
|
||||
type Reciter = { id: string, name: string, nationality: string, url: string };
|
||||
type Reciter = { id: string; name: string; nationality: string; url: string };
|
||||
|
||||
export { Surah, Ayah, Ayat, Reciter, Locale, JSON };
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import * as Quran from 'lib/Quran';
|
||||
import * as Quran from "lib/Quran";
|
||||
|
||||
export type Ayah = {
|
||||
id: number
|
||||
text: string
|
||||
readTimeMs: number
|
||||
id: number;
|
||||
text: string;
|
||||
readTimeMs: number;
|
||||
};
|
||||
|
||||
export function Ayah(ayah: Quran.JSON.Ayah): Ayah {
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
interface Surah {
|
||||
id: string
|
||||
place_of_revelation: string
|
||||
transliterated_name: string
|
||||
translated_name: string
|
||||
ayahs: number
|
||||
slug: string
|
||||
codepoints: number[]
|
||||
id: string;
|
||||
place_of_revelation: string;
|
||||
transliterated_name: string;
|
||||
translated_name: string;
|
||||
ayahs: number;
|
||||
slug: string;
|
||||
codepoints: number[];
|
||||
}
|
||||
type Ayah = [number, string];
|
||||
type Ayat = Ayah[];
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
export interface Slice {
|
||||
begin: number
|
||||
end: number | null
|
||||
coversOneAyah: boolean
|
||||
coversOneSurah: boolean
|
||||
coversSubsetOfSurah: boolean
|
||||
subsetLength: number
|
||||
toParam: () => string | null
|
||||
begin: number;
|
||||
end: number | null;
|
||||
coversOneAyah: boolean;
|
||||
coversOneSurah: boolean;
|
||||
coversSubsetOfSurah: boolean;
|
||||
subsetLength: number;
|
||||
toParam: () => string | null;
|
||||
}
|
||||
|
||||
export function Slice(begin: number, end: number | null): Slice {
|
||||
|
@ -32,7 +32,7 @@ const getSubsetLength = (slice: Slice) => {
|
|||
|
||||
const digitsRange = /^(\d{1,3})\.\.(\d{1,3})$/;
|
||||
const digits = /^\d{1,3}$/;
|
||||
Slice.fromParam = function(param: string | null): Slice {
|
||||
Slice.fromParam = function (param: string | null): Slice {
|
||||
if (!param) {
|
||||
return Slice(1, 286);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import * as Quran from 'lib/Quran';
|
||||
import * as Quran from "lib/Quran";
|
||||
|
||||
type TimeSlot = [number, number];
|
||||
type TimeSlots = [TimeSlot];
|
||||
|
@ -8,20 +8,28 @@ export class Surah {
|
|||
ayat: Quran.Ayat;
|
||||
#surah: Quran.JSON.Surah;
|
||||
|
||||
static fromDOMNode(locale: Quran.Locale, node: HTMLScriptElement, timeNode: HTMLScriptElement) {
|
||||
static fromDOMNode(
|
||||
locale: Quran.Locale,
|
||||
node: HTMLScriptElement,
|
||||
timeNode: HTMLScriptElement,
|
||||
) {
|
||||
const json = JSON.parse(node.innerText);
|
||||
const timeSlots: TimeSlots = JSON.parse(timeNode.innerText);
|
||||
const surah = Surah.fromJSON(locale, json.shift(), json);
|
||||
surah.ayat.map((ayah, i) => ayah.readTimeMs = timeSlots[i][1] * 1000);
|
||||
surah.ayat.map((ayah, i) => (ayah.readTimeMs = timeSlots[i][1] * 1000));
|
||||
return surah;
|
||||
}
|
||||
|
||||
static fromJSON(locale: Quran.Locale, surah: Quran.JSON.Surah, ayat: Quran.JSON.Ayat = []) {
|
||||
static fromJSON(
|
||||
locale: Quran.Locale,
|
||||
surah: Quran.JSON.Surah,
|
||||
ayat: Quran.JSON.Ayat = [],
|
||||
) {
|
||||
return new Surah(locale, surah, this.mapFromJSON(ayat));
|
||||
}
|
||||
|
||||
static mapFromJSON(ayat: Quran.JSON.Ayat) {
|
||||
return ayat.map((ayah) => Quran.Ayah.fromJSON(ayah));
|
||||
return ayat.map(ayah => Quran.Ayah.fromJSON(ayah));
|
||||
}
|
||||
|
||||
constructor(locale: Quran.Locale, surah: Quran.JSON.Surah, ayat: Quran.Ayat) {
|
||||
|
@ -43,7 +51,7 @@ export class Surah {
|
|||
}
|
||||
|
||||
get localizedName() {
|
||||
if (this.locale === 'ar') {
|
||||
if (this.locale === "ar") {
|
||||
return this.name;
|
||||
} else {
|
||||
return this.#surah.translated_name;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import * as Quran from 'lib/Quran';
|
||||
import * as Quran from "lib/Quran";
|
||||
|
||||
type PhraseMap<T> = {
|
||||
[key: string]: undefined | string | PhraseMap<T>
|
||||
[key: string]: undefined | string | PhraseMap<T>;
|
||||
};
|
||||
|
||||
export type TFunction = (locale: Quran.Locale, key: string) => string;
|
||||
|
@ -9,20 +9,20 @@ export type TFunction = (locale: Quran.Locale, key: string) => string;
|
|||
export function i18n(json: string): TFunction {
|
||||
const phrases: PhraseMap<string> = JSON.parse(json);
|
||||
return function (locale: Quran.Locale, key: string) {
|
||||
const path = key.split('.');
|
||||
const path = key.split(".");
|
||||
const phrase = path.reduce(
|
||||
(o, k) => typeof(o) === 'object' ? o[k] : o,
|
||||
phrases[locale]
|
||||
(o, k) => (typeof o === "object" ? o[k] : o),
|
||||
phrases[locale],
|
||||
);
|
||||
return typeof phrase === 'string' ? phrase : key;
|
||||
return typeof phrase === "string" ? phrase : key;
|
||||
};
|
||||
}
|
||||
|
||||
export function formatNumber(number: number, locale: Quran.Locale): string {
|
||||
const numLocale = locale === 'ar' ? 'ar-SA' : locale;
|
||||
const numLocale = locale === "ar" ? "ar-SA" : locale;
|
||||
const options = { maximumFractionDigits: 1 };
|
||||
return new Intl.NumberFormat(numLocale, options)
|
||||
.format(number)
|
||||
.split(/([^\d])/)
|
||||
.join(' ');
|
||||
.format(number)
|
||||
.split(/([^\d])/)
|
||||
.join(" ");
|
||||
}
|
||||
|
|
|
@ -1,31 +1,31 @@
|
|||
import React, { useState } from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import classNames from 'classnames';
|
||||
import { get as getCookie } from 'es-cookie';
|
||||
import * as Quran from 'lib/Quran';
|
||||
import { SelectOption } from 'components/Select';
|
||||
import { ThemeSelect } from 'components/ThemeSelect';
|
||||
import { LanguageSelect } from 'components/LanguageSelect';
|
||||
import { i18n, formatNumber, TFunction } from 'lib/i18n';
|
||||
import React, { useState } from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import classNames from "classnames";
|
||||
import { get as getCookie } from "es-cookie";
|
||||
import * as Quran from "lib/Quran";
|
||||
import { SelectOption } from "components/Select";
|
||||
import { ThemeSelect } from "components/ThemeSelect";
|
||||
import { LanguageSelect } from "components/LanguageSelect";
|
||||
import { i18n, formatNumber, TFunction } from "lib/i18n";
|
||||
|
||||
interface Props {
|
||||
locale: Quran.Locale
|
||||
surahs: Quran.Surah[]
|
||||
t: TFunction
|
||||
locale: Quran.Locale;
|
||||
surahs: Quran.Surah[];
|
||||
t: TFunction;
|
||||
}
|
||||
|
||||
function SurahIndex({ locale, surahs, t }: Props) {
|
||||
const [theme, setTheme] = useState(getCookie('theme') || 'moon');
|
||||
const [theme, setTheme] = useState(getCookie("theme") || "moon");
|
||||
const onLanguageChange = (o: SelectOption) => {
|
||||
document.location.replace(`/${o.value}/`);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classNames('content', 'theme', theme, locale)}>
|
||||
<div className={classNames("content", "theme", theme, locale)}>
|
||||
<div className="header">
|
||||
<a href={'/' + locale} className="image" />
|
||||
<a href={"/" + locale} className="image" />
|
||||
</div>
|
||||
<div className="row title">{t(locale, 'TheNobleQuran')}</div>
|
||||
<div className="row title">{t(locale, "TheNobleQuran")}</div>
|
||||
<div className="row dropdown-row">
|
||||
<ThemeSelect theme={theme} setTheme={setTheme} />
|
||||
<LanguageSelect locale={locale} onChange={onLanguageChange} />
|
||||
|
@ -34,12 +34,8 @@ function SurahIndex({ locale, surahs, t }: Props) {
|
|||
{surahs.map((surah, key) => (
|
||||
<li className="surah" key={key}>
|
||||
<a href={`/${locale}/${surah.slug}`}>
|
||||
<div className="surah id">
|
||||
{formatNumber(surah.id, locale)}
|
||||
</div>
|
||||
<div className="surah name">
|
||||
{surah.localizedName}
|
||||
</div>
|
||||
<div className="surah id">{formatNumber(surah.id, locale)}</div>
|
||||
<div className="surah name">{surah.localizedName}</div>
|
||||
<div className="surah name transliterated" lang="en">
|
||||
{surah.transliteratedName}
|
||||
</div>
|
||||
|
@ -48,26 +44,22 @@ function SurahIndex({ locale, surahs, t }: Props) {
|
|||
))}
|
||||
</ul>
|
||||
<a href={`/${locale}/random`} className="row surah choose-random">
|
||||
{t(locale, 'ChooseRandomChapter')}
|
||||
{t(locale, "ChooseRandomChapter")}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
(function () {
|
||||
const root: HTMLElement = document.querySelector(".root")!;
|
||||
const locale = root.getAttribute("data-locale") as Quran.Locale;
|
||||
const script: HTMLScriptElement = document.querySelector(".json.surahs")!;
|
||||
const t = i18n(document.querySelector<HTMLElement>(".json.i18n")!.innerText);
|
||||
const surahs: Quran.Surah[] = JSON.parse(script.innerText).map(
|
||||
(el: Quran.JSON.Surah) => {
|
||||
return Quran.Surah.fromJSON(locale, el);
|
||||
},
|
||||
);
|
||||
|
||||
(function() {
|
||||
const root: HTMLElement = document.querySelector('.root')!;
|
||||
const locale = root.getAttribute('data-locale') as Quran.Locale;
|
||||
const script: HTMLScriptElement = document.querySelector('.json.surahs')!;
|
||||
const t = i18n(document.querySelector<HTMLElement>('.json.i18n')!.innerText);
|
||||
const surahs: Quran.Surah[] = JSON.parse(script.innerText)
|
||||
.map((el: Quran.JSON.Surah) => {
|
||||
return Quran.Surah.fromJSON(locale, el);
|
||||
});
|
||||
|
||||
ReactDOM
|
||||
.createRoot(root)
|
||||
.render(
|
||||
<SurahIndex locale={locale} surahs={surahs} t={t} />
|
||||
);
|
||||
ReactDOM.createRoot(root).render(<SurahIndex locale={locale} surahs={surahs} t={t} />);
|
||||
})();
|
||||
|
|
|
@ -1,29 +1,30 @@
|
|||
import postman, { item } from 'postman';
|
||||
import postman, { item } from "postman";
|
||||
|
||||
(function() {
|
||||
const parent: HTMLElement = document.querySelector('.postman.loader')!;
|
||||
const progressBar: HTMLProgressElement = parent.querySelector('progress')!;
|
||||
const progressNumber: HTMLSpanElement = parent.querySelector('.percentage')!;
|
||||
const inlineStyle: HTMLStyleElement = document.querySelector('.css.postman')!;
|
||||
(function () {
|
||||
const parent: HTMLElement = document.querySelector(".postman.loader")!;
|
||||
const progressBar: HTMLProgressElement = parent.querySelector("progress")!;
|
||||
const progressNumber: HTMLSpanElement = parent.querySelector(".percentage")!;
|
||||
const inlineStyle: HTMLStyleElement = document.querySelector(".css.postman")!;
|
||||
|
||||
postman(
|
||||
item.script('/js/pages/surah/index.js'),
|
||||
item.css('/css/pages/surah/index.css'),
|
||||
item.image('/images/moon.svg'),
|
||||
item.image('/images/leaf.svg'),
|
||||
item.font('Kanit Regular', 'url(/fonts/kanit-regular.ttf)'),
|
||||
item.font('Vazirmatn Regular', 'url(/fonts/vazirmatn-regular.ttf)'),
|
||||
item.font('Roboto Mono Regular', 'url(/fonts/roboto-mono-regular.ttf)'),
|
||||
item.script("/js/pages/surah/index.js"),
|
||||
item.css("/css/pages/surah/index.css"),
|
||||
item.image("/images/moon.svg"),
|
||||
item.image("/images/leaf.svg"),
|
||||
item.font("Kanit Regular", "url(/fonts/kanit-regular.ttf)"),
|
||||
item.font("Vazirmatn Regular", "url(/fonts/vazirmatn-regular.ttf)"),
|
||||
item.font("Roboto Mono Regular", "url(/fonts/roboto-mono-regular.ttf)"),
|
||||
item.progress((percent: number) => {
|
||||
progressBar.value = percent;
|
||||
progressNumber.innerText = `${percent.toFixed(0)}%`;
|
||||
})
|
||||
).fetch()
|
||||
.then((pkg) => {
|
||||
inlineStyle.remove();
|
||||
parent.remove();
|
||||
pkg.fonts.forEach((f) => document.fonts.add(f));
|
||||
pkg.css.forEach((s) => document.head.appendChild(s));
|
||||
pkg.scripts.forEach((s) => document.body.appendChild(s));
|
||||
});
|
||||
}),
|
||||
)
|
||||
.fetch()
|
||||
.then(pkg => {
|
||||
inlineStyle.remove();
|
||||
parent.remove();
|
||||
pkg.fonts.forEach(f => document.fonts.add(f));
|
||||
pkg.css.forEach(s => document.head.appendChild(s));
|
||||
pkg.scripts.forEach(s => document.body.appendChild(s));
|
||||
});
|
||||
})();
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { Locale } from 'lib/Locale';
|
||||
import { Locale } from "lib/Locale";
|
||||
|
||||
(function () {
|
||||
const surahId: number = Math.ceil(Math.random() * 114);
|
||||
const locale = Locale(window);
|
||||
const el: HTMLElement = document.querySelector('.json.slugs')!;
|
||||
const el: HTMLElement = document.querySelector(".json.slugs")!;
|
||||
const slugs = JSON.parse(el.innerText);
|
||||
location.replace(`/${locale.fromPath()}/${slugs[surahId]}`);
|
||||
})();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Locale } from 'lib/Locale';
|
||||
import { Locale } from "lib/Locale";
|
||||
|
||||
(function(window, location) {
|
||||
(function (window, location) {
|
||||
const locale = Locale(window).fromBrowser();
|
||||
location.replace(`/${locale}/`);
|
||||
})(window, location);
|
||||
|
|
|
@ -1,24 +1,30 @@
|
|||
import React, { useRef, useState, useEffect } from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import classNames from 'classnames';
|
||||
import { get as getCookie } from 'es-cookie';
|
||||
import { Timer } from 'components/Timer';
|
||||
import { Stream } from 'components/Stream';
|
||||
import { SelectOption } from 'components/Select';
|
||||
import { ThemeSelect } from 'components/ThemeSelect';
|
||||
import { LanguageSelect } from 'components/LanguageSelect';
|
||||
import { PlayShape, PauseShape, SoundOnShape, SoundOffShape, RefreshShape } from 'components/Shape';
|
||||
import * as Quran from 'lib/Quran';
|
||||
import { Slice } from 'lib/Quran/Slice';
|
||||
import { i18n, TFunction } from 'lib/i18n';
|
||||
import React, { useRef, useState, useEffect } from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import classNames from "classnames";
|
||||
import { get as getCookie } from "es-cookie";
|
||||
import { Timer } from "components/Timer";
|
||||
import { Stream } from "components/Stream";
|
||||
import { SelectOption } from "components/Select";
|
||||
import { ThemeSelect } from "components/ThemeSelect";
|
||||
import { LanguageSelect } from "components/LanguageSelect";
|
||||
import {
|
||||
PlayShape,
|
||||
PauseShape,
|
||||
SoundOnShape,
|
||||
SoundOffShape,
|
||||
RefreshShape,
|
||||
} from "components/Shape";
|
||||
import * as Quran from "lib/Quran";
|
||||
import { Slice } from "lib/Quran/Slice";
|
||||
import { i18n, TFunction } from "lib/i18n";
|
||||
|
||||
interface Props {
|
||||
node: HTMLScriptElement
|
||||
reciters: Quran.Reciter[]
|
||||
locale: Quran.Locale
|
||||
slice: Slice
|
||||
paused: boolean
|
||||
t: TFunction
|
||||
node: HTMLScriptElement;
|
||||
reciters: Quran.Reciter[];
|
||||
locale: Quran.Locale;
|
||||
slice: Slice;
|
||||
paused: boolean;
|
||||
t: TFunction;
|
||||
}
|
||||
|
||||
const getTimeSlots = (reciter: Quran.Reciter) => {
|
||||
|
@ -32,13 +38,15 @@ function SurahStream({ node, reciters, locale, slice, paused, t }: Props) {
|
|||
const [isPaused, setIsPaused] = useState<boolean>(paused);
|
||||
const [soundOn, setSoundOn] = useState<boolean>(false);
|
||||
const [isStalled, setIsStalled] = useState<boolean>(false);
|
||||
const [theme, setTheme] = useState(getCookie('theme') || 'moon');
|
||||
const [theme, setTheme] = useState(getCookie("theme") || "moon");
|
||||
const [reciter] = useState<Quran.Reciter>(reciters[0]);
|
||||
const [surah] = useState<Quran.Surah>(Quran.Surah.fromDOMNode(locale, node, getTimeSlots(reciter)));
|
||||
const [surah] = useState<Quran.Surah>(
|
||||
Quran.Surah.fromDOMNode(locale, node, getTimeSlots(reciter)),
|
||||
);
|
||||
const readyToRender = stream.length > 0;
|
||||
const audioRef = useRef<HTMLAudioElement>(null);
|
||||
const { url: baseUrl } = reciter;
|
||||
const ayah = stream[stream.length-1];
|
||||
const ayah = stream[stream.length - 1];
|
||||
const src = `${baseUrl}/${surah.id}/${ayah?.id}.mp3`;
|
||||
const getAyahParam = (slice: Slice, stream: Quran.Ayat) => {
|
||||
if (slice.coversSubsetOfSurah) {
|
||||
|
@ -50,13 +58,16 @@ function SurahStream({ node, reciters, locale, slice, paused, t }: Props) {
|
|||
const onLanguageChange = (o: SelectOption) => {
|
||||
const locale = o.value;
|
||||
const params = [
|
||||
['ayah', getAyahParam(slice, stream)],
|
||||
['paused', isPaused ? 't' : null]
|
||||
["ayah", getAyahParam(slice, stream)],
|
||||
["paused", isPaused ? "t" : null],
|
||||
];
|
||||
const query = params.filter(([, v]) => v).flatMap(([k, v]) => `${k}=${v}`).join('&');
|
||||
const query = params
|
||||
.filter(([, v]) => v)
|
||||
.flatMap(([k, v]) => `${k}=${v}`)
|
||||
.join("&");
|
||||
location.replace(`/${locale}/${surah.slug}/?${query}`);
|
||||
};
|
||||
const endOfStream = (function() {
|
||||
const endOfStream = (function () {
|
||||
if (slice.coversOneAyah || slice.coversOneSurah) {
|
||||
return stream.length === surah.ayat.length;
|
||||
} else if (slice.coversSubsetOfSurah) {
|
||||
|
@ -81,28 +92,27 @@ function SurahStream({ node, reciters, locale, slice, paused, t }: Props) {
|
|||
} else if (isPaused || !soundOn) {
|
||||
audio.pause();
|
||||
} else if (soundOn) {
|
||||
audio.play()
|
||||
.catch(() => setSoundOn(false));
|
||||
audio.play().catch(() => setSoundOn(false));
|
||||
}
|
||||
}, [stream, isPaused, soundOn]);
|
||||
|
||||
useEffect(() => {
|
||||
const audio = audioRef.current;
|
||||
audio.addEventListener('ended', () => audio.setAttribute('src', src));
|
||||
audio.addEventListener('stalled', () => setIsStalled(true));
|
||||
audio.addEventListener('waiting', () => setIsStalled(true));
|
||||
audio.addEventListener('playing', () => setIsStalled(false));
|
||||
audio.addEventListener('play', () => setIsStalled(false));
|
||||
audio.addEventListener("ended", () => audio.setAttribute("src", src));
|
||||
audio.addEventListener("stalled", () => setIsStalled(true));
|
||||
audio.addEventListener("waiting", () => setIsStalled(true));
|
||||
audio.addEventListener("playing", () => setIsStalled(false));
|
||||
audio.addEventListener("play", () => setIsStalled(false));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={classNames('content', 'theme', theme, locale)}>
|
||||
<div className={classNames("content", "theme", theme, locale)}>
|
||||
<div className="header">
|
||||
<a href={'/' + locale} className="image" />
|
||||
<a href={"/" + locale} className="image" />
|
||||
</div>
|
||||
{readyToRender && (
|
||||
<>
|
||||
<div className="row title">{t(locale, 'TheNobleQuran')}</div>
|
||||
<div className="row title">{t(locale, "TheNobleQuran")}</div>
|
||||
<div className="row dropdown-row">
|
||||
<ThemeSelect theme={theme} setTheme={setTheme} />
|
||||
<LanguageSelect locale={locale} onChange={onLanguageChange} />
|
||||
|
@ -115,66 +125,70 @@ function SurahStream({ node, reciters, locale, slice, paused, t }: Props) {
|
|||
<span lang="en">{surah.transliteratedName}</span>
|
||||
</div>
|
||||
)}
|
||||
{readyToRender &&
|
||||
<Stream
|
||||
slice={slice}
|
||||
surah={surah}
|
||||
stream={stream}
|
||||
locale={locale}
|
||||
endOfStream={endOfStream}
|
||||
isPaused={isPaused}
|
||||
t={t}
|
||||
/>
|
||||
}
|
||||
<div className={classNames({ 'justify-end': readyToRender && endOfStream }, 'row')}>
|
||||
{readyToRender && isPaused && !endOfStream &&
|
||||
<PlayShape onClick={() => setIsPaused(false)} />}
|
||||
{readyToRender && !isPaused && !endOfStream &&
|
||||
<PauseShape onClick={() => setIsPaused(true)} />}
|
||||
{readyToRender && !endOfStream && soundOn &&
|
||||
<SoundOnShape onClick={() => setSoundOn(false)} />}
|
||||
{readyToRender && !endOfStream && !soundOn &&
|
||||
<SoundOffShape onClick={() => setSoundOn(true)} />}
|
||||
{readyToRender && !endOfStream &&
|
||||
<Timer
|
||||
surah={surah}
|
||||
setStream={setStream}
|
||||
stream={stream}
|
||||
locale={locale}
|
||||
isPaused={isPaused}
|
||||
soundOn={soundOn}
|
||||
isStalled={isStalled}
|
||||
/>}
|
||||
{readyToRender && endOfStream &&
|
||||
<RefreshShape onClick={() => setStream([])} />}
|
||||
{readyToRender && (
|
||||
<Stream
|
||||
slice={slice}
|
||||
surah={surah}
|
||||
stream={stream}
|
||||
locale={locale}
|
||||
endOfStream={endOfStream}
|
||||
isPaused={isPaused}
|
||||
t={t}
|
||||
/>
|
||||
)}
|
||||
<div className={classNames({ "justify-end": readyToRender && endOfStream }, "row")}>
|
||||
{readyToRender && isPaused && !endOfStream && (
|
||||
<PlayShape onClick={() => setIsPaused(false)} />
|
||||
)}
|
||||
{readyToRender && !isPaused && !endOfStream && (
|
||||
<PauseShape onClick={() => setIsPaused(true)} />
|
||||
)}
|
||||
{readyToRender && !endOfStream && soundOn && (
|
||||
<SoundOnShape onClick={() => setSoundOn(false)} />
|
||||
)}
|
||||
{readyToRender && !endOfStream && !soundOn && (
|
||||
<SoundOffShape onClick={() => setSoundOn(true)} />
|
||||
)}
|
||||
{readyToRender && !endOfStream && (
|
||||
<Timer
|
||||
surah={surah}
|
||||
setStream={setStream}
|
||||
stream={stream}
|
||||
locale={locale}
|
||||
isPaused={isPaused}
|
||||
soundOn={soundOn}
|
||||
isStalled={isStalled}
|
||||
/>
|
||||
)}
|
||||
{readyToRender && endOfStream && <RefreshShape onClick={() => setStream([])} />}
|
||||
</div>
|
||||
<audio src={src} ref={audioRef} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
(function() {
|
||||
const root: HTMLElement = document.querySelector('.root')!;
|
||||
const locale = root.getAttribute('data-locale') as Quran.Locale;
|
||||
const node: HTMLScriptElement = document.querySelector('script.surah')!;
|
||||
const toBoolean = (str: string | null): boolean => str !== null && ['1', 't', 'true', 'yes'].includes(str);
|
||||
(function () {
|
||||
const root: HTMLElement = document.querySelector(".root")!;
|
||||
const locale = root.getAttribute("data-locale") as Quran.Locale;
|
||||
const node: HTMLScriptElement = document.querySelector("script.surah")!;
|
||||
const toBoolean = (str: string | null): boolean =>
|
||||
str !== null && ["1", "t", "true", "yes"].includes(str);
|
||||
const params = new URLSearchParams(location.search);
|
||||
const slice = Slice.fromParam(params.get('ayah'));
|
||||
const paused = toBoolean(params.get('paused'));
|
||||
const reciters = JSON.parse(document.querySelector<HTMLElement>('.json.reciters')!.innerText);
|
||||
const t = i18n(document.querySelector<HTMLElement>('.json.i18n')!.innerText);
|
||||
const slice = Slice.fromParam(params.get("ayah"));
|
||||
const paused = toBoolean(params.get("paused"));
|
||||
const reciters = JSON.parse(
|
||||
document.querySelector<HTMLElement>(".json.reciters")!.innerText,
|
||||
);
|
||||
const t = i18n(document.querySelector<HTMLElement>(".json.i18n")!.innerText);
|
||||
|
||||
ReactDOM
|
||||
.createRoot(root)
|
||||
.render(
|
||||
<SurahStream
|
||||
reciters={reciters}
|
||||
node={node}
|
||||
locale={locale}
|
||||
slice={slice}
|
||||
paused={paused}
|
||||
t={t}
|
||||
/>
|
||||
);
|
||||
ReactDOM.createRoot(root).render(
|
||||
<SurahStream
|
||||
reciters={reciters}
|
||||
node={node}
|
||||
locale={locale}
|
||||
slice={slice}
|
||||
paused={paused}
|
||||
t={t}
|
||||
/>,
|
||||
);
|
||||
})();
|
||||
|
|
|
@ -1,41 +1,43 @@
|
|||
import postman, { item } from 'postman';
|
||||
import * as Quran from 'lib/Quran';
|
||||
import postman, { item } from "postman";
|
||||
import * as Quran from "lib/Quran";
|
||||
|
||||
(function() {
|
||||
const parent: HTMLElement = document.querySelector('.postman.loader')!;
|
||||
const progressBar: HTMLProgressElement = parent.querySelector('progress')!;
|
||||
const progressNumber: HTMLSpanElement = parent.querySelector('.percentage')!;
|
||||
const inlineStyle: HTMLStyleElement = document.querySelector('.css.postman')!;
|
||||
const { locale, surahId } = document.querySelector<HTMLElement>('.root')!.dataset;
|
||||
const reciters = JSON.parse(document.querySelector<HTMLElement>('.json.reciters')!.innerText);
|
||||
(function () {
|
||||
const parent: HTMLElement = document.querySelector(".postman.loader")!;
|
||||
const progressBar: HTMLProgressElement = parent.querySelector("progress")!;
|
||||
const progressNumber: HTMLSpanElement = parent.querySelector(".percentage")!;
|
||||
const inlineStyle: HTMLStyleElement = document.querySelector(".css.postman")!;
|
||||
const { locale, surahId } = document.querySelector<HTMLElement>(".root")!.dataset;
|
||||
const reciters = JSON.parse(
|
||||
document.querySelector<HTMLElement>(".json.reciters")!.innerText,
|
||||
);
|
||||
|
||||
postman(
|
||||
item.script('/js/pages/surah/stream.js'),
|
||||
item.css('/css/pages/surah/stream.css'),
|
||||
item.image('/images/moon.svg'),
|
||||
item.image('/images/leaf.svg'),
|
||||
item.font('Kanit Regular', 'url(/fonts/kanit-regular.ttf)'),
|
||||
item.font('Vazirmatn Regular', 'url(/fonts/vazirmatn-regular.ttf)'),
|
||||
item.font('Roboto Mono Regular', 'url(/fonts/roboto-mono-regular.ttf)'),
|
||||
item.json(`/${locale}/${surahId}/surah.json`, { className: 'surah' }),
|
||||
item.script("/js/pages/surah/stream.js"),
|
||||
item.css("/css/pages/surah/stream.css"),
|
||||
item.image("/images/moon.svg"),
|
||||
item.image("/images/leaf.svg"),
|
||||
item.font("Kanit Regular", "url(/fonts/kanit-regular.ttf)"),
|
||||
item.font("Vazirmatn Regular", "url(/fonts/vazirmatn-regular.ttf)"),
|
||||
item.font("Roboto Mono Regular", "url(/fonts/roboto-mono-regular.ttf)"),
|
||||
item.json(`/${locale}/${surahId}/surah.json`, { className: "surah" }),
|
||||
...reciters.map((reciter: Quran.Reciter) => {
|
||||
const { url: baseUrl } = reciter;
|
||||
return item.json(
|
||||
`${baseUrl}/time_slots/${surahId}.json`,
|
||||
{ className: `reciter time-slots ${reciter.id}` }
|
||||
);
|
||||
return item.json(`${baseUrl}/time_slots/${surahId}.json`, {
|
||||
className: `reciter time-slots ${reciter.id}`,
|
||||
});
|
||||
}),
|
||||
item.progress((percent: number) => {
|
||||
progressBar.value = percent;
|
||||
progressNumber.innerText = `${percent.toFixed(0)}%`;
|
||||
})
|
||||
).fetch()
|
||||
.then((pkg) => {
|
||||
}),
|
||||
)
|
||||
.fetch()
|
||||
.then(pkg => {
|
||||
inlineStyle.remove();
|
||||
parent.remove();
|
||||
pkg.fonts.forEach((f) => document.fonts.add(f));
|
||||
pkg.css.forEach((s) => document.head.appendChild(s));
|
||||
pkg.json.forEach((o) => document.body.appendChild(o));
|
||||
pkg.scripts.forEach((s) => document.body.appendChild(s));
|
||||
pkg.fonts.forEach(f => document.fonts.add(f));
|
||||
pkg.css.forEach(s => document.head.appendChild(s));
|
||||
pkg.json.forEach(o => document.body.appendChild(o));
|
||||
pkg.scripts.forEach(s => document.body.appendChild(s));
|
||||
});
|
||||
})();
|
||||
|
|
Loading…
Reference in a new issue