This commit is contained in:
louiscklaw
2025-04-28 08:03:21 +08:00
parent d0ea7e5452
commit 4c72861eda
20 changed files with 877 additions and 237 deletions

View File

@@ -1,82 +0,0 @@
import { IonButton, useIonRouter } from '@ionic/react';
import './Lesson.css';
import { useEffect, useState } from 'react';
import { LoadingScreen } from '../../components/LoadingScreen';
import { COLOR_TEXT } from '../../constants';
import { useMyIonStore } from '../../contexts/MyIonStore';
import { listLessonCategories } from '../../public_data/listLessonCategories';
import { getLessonConnectivesLink } from './getLessonConnectivesLink';
import { getLessonVocabularyLink } from './getLessonWordLink';
let lessonLinkProxy = [getLessonVocabularyLink, getLessonConnectivesLink];
interface ContainerProps {
test_active_lesson_idx: number;
}
const LessonContainer: React.FC<ContainerProps> = ({ test_active_lesson_idx = 1 }) => {
const router = useIonRouter();
const [loading, setLoading] = useState<boolean>(true);
const { lesson_contents, setLessonContent } = useMyIonStore();
const [active_lesson_idx, setActiveLessonIdx] = useState<number>(0);
const [selected_content, setSelectedContent] = useState<any>([]);
useEffect(() => {
listLessonCategories().then((cats: any) => {
console.log({ cats });
setLessonContent(cats);
setActiveLessonIdx(test_active_lesson_idx);
setLoading(false);
});
}, []);
useEffect(() => {
if (loading) return;
console.log('active_category changed', active_lesson_idx);
let selected_category = lesson_contents[test_active_lesson_idx];
setSelectedContent(selected_category['content']);
}, [active_lesson_idx, loading, test_active_lesson_idx]);
if (loading) return <LoadingScreen />;
return (
<>
<div style={{ display: 'flex', flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'center' }}>
{selected_content.map((content: any, cat_idx: number) => (
<IonButton
key={cat_idx}
style={{ width: '45vw', height: '45vw' }}
fill="clear"
// href={lessonLinkProxy[test_active_lesson_idx](test_active_lesson_idx.toString(), cat_idx.toString(), '0')}
onClick={() => {
router.push(
lessonLinkProxy[test_active_lesson_idx](test_active_lesson_idx.toString(), cat_idx.toString(), '0'),
undefined,
'replace',
);
}}
>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
<div
key={cat_idx}
style={{
width: '100px',
height: '100px',
backgroundImage: `url(${content.cat_image})`,
backgroundPosition: 'center',
backgroundSize: 'cover',
borderRadius: '0.5rem',
margin: '.5rem',
}}
></div>
<span style={{ color: COLOR_TEXT }}>{content.cat_name}</span>
</div>
</IonButton>
))}
</div>
{/* <EndOfList /> */}
</>
);
};
export default LessonContainer;

View File

@@ -0,0 +1,91 @@
import { IonButton, useIonRouter } from '@ionic/react';
import './Lesson.css';
import { useEffect, useState } from 'react';
import { LoadingScreen } from '../../../components/LoadingScreen';
import { COLOR_TEXT } from '../../../constants';
import { useMyIonStore } from '../../../contexts/MyIonStore';
import { listLessonCategories } from '../../../public_data/listLessonCategories';
import { getLessonConnectivesLink } from '../getLessonConnectivesLink';
import { getLessonVocabularyLink } from '../getLessonWordLink';
let lessonLinkProxy = [getLessonVocabularyLink, getLessonConnectivesLink];
interface ContainerProps {
test_active_lesson_idx: number;
lesson_type_id: string;
}
const LessonContainer: React.FC<ContainerProps> = ({
test_active_lesson_idx = 1,
lesson_type_id = '000000000000001',
}) => {
const router = useIonRouter();
const [loading, setLoading] = useState<boolean>(true);
const { lesson_contents, setLessonContent } = useMyIonStore();
const [active_lesson_idx, setActiveLessonIdx] = useState<number>(0);
const [selected_content, setSelectedContent] = useState<any>([]);
useEffect(() => {
listLessonCategories().then((cats: any) => {
console.log({ cats });
setLessonContent(cats);
setActiveLessonIdx(test_active_lesson_idx);
setLoading(false);
});
}, []);
useEffect(() => {
if (loading) return;
console.log('active_category changed', active_lesson_idx);
let selected_category = lesson_contents[test_active_lesson_idx];
setSelectedContent(selected_category['content']);
}, [active_lesson_idx, loading, test_active_lesson_idx]);
if (loading) return <LoadingScreen />;
return (
<>
<div
style={{
display: 'flex',
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'center',
//
}}
>
{selected_content.map((content: any, cat_idx: number) => (
<>
<IonButton
key={cat_idx}
style={{ width: '45vw', height: '45vw' }}
fill="clear"
onClick={() => {
router.push(`/lesson_word_page/v/${lesson_type_id}/${content.id}/0`, undefined, 'replace');
}}
>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
<div
key={cat_idx}
style={{
width: '100px',
height: '100px',
backgroundImage: `url(${content.cat_image})`,
backgroundPosition: 'center',
backgroundSize: 'cover',
borderRadius: '0.5rem',
margin: '.5rem',
}}
></div>
<span style={{ color: COLOR_TEXT }}>{content.cat_name}</span>
</div>
</IonButton>
</>
))}
</div>
{/* <EndOfList /> */}
</>
);
};
export default LessonContainer;

View File

@@ -0,0 +1,28 @@
import { FunctionComponent, useEffect } from 'react';
import { useGlobalAudioPlayer } from 'react-use-audio-player';
import { useListeningPracticeTimeSpent } from '../../../contexts/MyIonMetric/ListeningPracticeTimeSpent';
export const AudioControls: FunctionComponent<{ audio_src: string }> = ({ audio_src }) => {
const { play, pause, playing, duration } = useGlobalAudioPlayer();
const { load, src: loadedSrc } = useGlobalAudioPlayer();
let { myIonMetricIncListeningPracticeTimeSpent } = useListeningPracticeTimeSpent();
useEffect(() => {
if (audio_src) {
load(audio_src);
}
}, [audio_src]);
useEffect(() => {
if (loadedSrc) {
}
}, [loadedSrc]);
useEffect(() => {
if (playing) {
myIonMetricIncListeningPracticeTimeSpent(duration);
}
}, [playing]);
return <></>;
};

View File

@@ -0,0 +1,319 @@
import { IonButton, IonButtons, IonContent, IonIcon, IonModal, IonPage, IonToolbar, useIonRouter } from '@ionic/react';
import './style.css';
import {
arrowBackCircleOutline,
chevronBack,
chevronForward,
close,
heart,
heartOutline,
play,
volumeHighOutline,
} from 'ionicons/icons';
import { useEffect, useRef, useState } from 'react';
//
import Markdown from 'react-markdown';
import { useParams } from 'react-router';
import { useGlobalAudioPlayer } from 'react-use-audio-player';
import remarkGfm from 'remark-gfm';
import { LoadingScreen } from '../../../components/LoadingScreen';
import RemoveFavoritePrompt from '../../../components/RemoveFavoritePrompt';
import { LESSON_LINK } from '../../../constants';
import { useMyIonFavorite } from '../../../contexts/MyIonFavorite';
//
import { useMyIonStore } from '../../../contexts/MyIonStore';
import ILesson from '../../../interfaces/ILesson';
import ILessonCategory from '../../../interfaces/ILessonCategory';
import IWordCard from '../../../interfaces/IWordCard';
import { getFavLessonVocabularyLink, getLessonVocabularyLink } from '../getLessonWordLink';
import { AudioControls } from './AudioControls';
const LessonWordPageByDb: React.FC = () => {
const [loading, setLoading] = useState(true);
const router = useIonRouter();
const modal = useRef<HTMLIonModalElement>(null);
const { lesson_idx, cat_idx, word_idx } = useParams<{ lesson_idx: string; cat_idx: string; word_idx: string }>();
const [open_remove_modal, setOpenRemoveModal] = useState(false);
const [lesson_info, setLessonInfo] = useState<ILesson | undefined>(undefined);
const [cat_info, setCatInfo] = useState<ILessonCategory | undefined>(undefined);
const [word_info, setWordInfo] = useState<IWordCard | undefined>(undefined);
// const { play: play_word, playing } = useGlobalAudioPlayer();
const { myIonStoreAddFavoriteVocabulary, myIonStoreRemoveFavoriteVocabulary, myIonStoreFindInFavoriteVocabulary } =
useMyIonFavorite();
let [favorite_address, setFavoriteAddress] = useState(getFavLessonVocabularyLink(lesson_idx, cat_idx, word_idx));
useEffect(() => {
setFavoriteAddress(getFavLessonVocabularyLink(lesson_idx, cat_idx, word_idx));
// if (lesson_contents.length > 0) {
// let lesson_content: ILesson = lesson_contents[parseInt(lesson_idx)];
// let category_content: ILessonCategory = lesson_content.content[parseInt(cat_idx)];
// let word_content: IWordCard = category_content.content[parseInt(word_idx)];
// setWordInfo(word_content);
// }
}, [lesson_idx, cat_idx, word_idx]);
function dismiss() {
setOpenRemoveModal(false);
}
const [isOpen, setIsOpen] = useState(false);
let { lesson_contents } = useMyIonStore();
useEffect(() => {
// NOTES: lesson_content == [] during loading
if (lesson_contents.length > 0) {
let lesson_content: ILesson = lesson_contents[parseInt(lesson_idx)];
let category_content: ILessonCategory = lesson_content.content[parseInt(cat_idx)];
let word_content: IWordCard = category_content.content[parseInt(word_idx)];
setLessonInfo(lesson_content);
setCatInfo(category_content);
setWordInfo(word_content);
setLoading(false);
}
}, [lesson_contents]);
let [in_fav, setInFav] = useState(false);
const isInFavorite = async (string_to_search: string) => {
let result = await myIonStoreFindInFavoriteVocabulary(string_to_search);
setInFav(result);
};
const addToFavorite = async (string_to_add: string) => {
await myIonStoreAddFavoriteVocabulary(string_to_add);
await isInFavorite(string_to_add);
setInFav(!in_fav);
};
function handleUserRemoveFavorite() {
setOpenRemoveModal(true);
}
const removeFromFavorite = async (string_to_remove: string) => {
await myIonStoreRemoveFavoriteVocabulary(string_to_remove);
await isInFavorite(string_to_remove);
};
useEffect(() => {
(async () => {
await isInFavorite(getFavLessonVocabularyLink(lesson_idx, cat_idx, word_idx));
})();
}, [lesson_idx, cat_idx, word_idx]);
return <>should not see me</>;
if (lesson_info == undefined) return <LoadingScreen />;
if (!cat_info || !word_info) return <LoadingScreen />;
return (
<>
<IonPage>
<IonContent fullscreen>
<div style={{ position: 'fixed' }}>
<IonButton
size="large"
shape="round"
fill="clear"
color={'dark'}
// href={`${LESSON_LINK}/a/${lesson_info.name}`}
onClick={() => {
router.push(`${LESSON_LINK}/a/${lesson_info.name}`);
}}
>
<IonIcon size="large" icon={arrowBackCircleOutline} />
</IonButton>
</div>
<div style={{ marginTop: '3rem' }}>
<div style={{ textAlign: 'center', fontSize: '1.2rem' }}>{cat_info.cat_name}</div>
</div>
<div style={{ marginTop: '1rem', display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
{/* <LessonContainer name='Tab 1 page' /> */}
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
<div>
<IonButton
size="large"
shape="round"
fill="clear"
color={parseInt(word_idx) === 0 ? 'medium' : 'dark'}
disabled={parseInt(word_idx) === 0}
// href={getLessonVocabularyLink(lesson_idx, cat_idx, Math.max(0, parseInt(word_idx) - 1).toString())}
onClick={() => {
router.push(
getLessonVocabularyLink(lesson_idx, cat_idx, Math.max(0, parseInt(word_idx) - 1).toString()),
);
}}
>
<IonIcon slot="icon-only" size="large" icon={chevronBack}></IonIcon>
</IonButton>
</div>
<div
style={{
width: '66vw',
height: '66vw',
backgroundImage: `url(${word_info.image_url})`,
backgroundPosition: 'center',
backgroundSize: 'cover',
borderRadius: '0.5rem',
margin: '.5rem',
}}
></div>
<div>
<IonButton
size="large"
shape="round"
fill="clear"
color={parseInt(word_idx) === cat_info.content.length - 1 ? 'medium' : 'dark'}
disabled={parseInt(word_idx) === cat_info.content.length - 1}
// href={getLessonVocabularyLink(
// lesson_idx,
// cat_idx,
// Math.min(cat_info.content.length - 1, parseInt(word_idx) + 1).toString()
// )}
onClick={() => {
router.push(
getLessonVocabularyLink(
lesson_idx,
cat_idx,
Math.min(cat_info.content.length - 1, parseInt(word_idx) + 1).toString(),
),
);
}}
>
<IonIcon slot="icon-only" size="large" icon={chevronForward}></IonIcon>
</IonButton>
</div>
</div>
<div style={{ marginTop: '1rem' }}>
<div
style={{
width: '2rem',
height: '2rem',
display: 'inline-flex',
justifyContent: 'center',
alignItems: 'center',
borderRadius: '1rem',
backgroundColor: 'black',
color: 'white',
}}
>
{parseInt(word_idx) + 1}
</div>
</div>
<div>
<div
style={{ display: 'flex', flexDirection: 'row', gap: '1rem', alignItems: 'center', marginTop: '1rem' }}
>
<div>
<AudioControls audio_src={getFile(word_info.id, word_info.sound)} />
<IonButton
size="large"
color="dark"
shape="round"
fill="clear"
disabled={playing}
onClick={() => (playing ? null : play_word())}
>
<IonIcon
size="large"
color="dark"
slot="icon-only"
icon={playing ? play : volumeHighOutline}
></IonIcon>
</IonButton>
</div>
<div style={{ fontWeight: 'bold', fontSize: '1.5rem' }}>{word_info.word}</div>
<div>
<IonButton
color="danger"
shape="round"
size="large"
fill="clear"
// id='open-modal'
onClick={() => {
in_fav ? handleUserRemoveFavorite() : addToFavorite(favorite_address);
}}
>
<IonIcon
size="large"
color="danger"
slot="icon-only"
icon={in_fav ? heart : heartOutline}
></IonIcon>
</IonButton>
</div>
</div>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', marginTop: '1rem' }}>
<div style={{ fontWeight: 'bold', fontSize: '1.3rem' }}>{word_info.word_c}</div>
</div>
</div>
<div
style={{
display: 'flex',
flexDirection: 'column',
gap: '0.5rem',
alignItems: 'center',
marginTop: '2rem',
}}
>
<Markdown remarkPlugins={[remarkGfm]}>{word_info.sample_e}</Markdown>
<Markdown remarkPlugins={[remarkGfm]}>{word_info.sample_c}</Markdown>
</div>
</div>
</IonContent>
</IonPage>
{/* */}
<IonModal isOpen={open_remove_modal} id="example-modal" ref={modal}>
<IonContent>
<IonToolbar>
<IonButtons slot="end">
<IonButton onClick={() => dismiss()} shape="round" fill="clear">
<IonIcon size="large" slot="icon-only" icon={close}></IonIcon>
</IonButton>
</IonButtons>
</IonToolbar>
<div style={{ marginTop: '2rem' }}>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
<div>Are you sure to remove favorite ?</div>
<div style={{ display: 'flex', flexDirection: 'row', gap: '1rem', marginTop: '1rem' }}>
<div>
<IonButton color="dark" onClick={() => dismiss()} fill="outline">
Cancel
</IonButton>
</div>
<div>
<IonButton
onClick={() => {
removeFromFavorite(getFavLessonVocabularyLink(lesson_idx, cat_idx, word_idx));
setIsOpen(true);
dismiss();
}}
fill="solid"
color="danger"
>
Remove
</IonButton>
</div>
</div>
</div>
</div>
</IonContent>
</IonModal>
<RemoveFavoritePrompt open={isOpen} setIsOpen={setIsOpen} />
</>
);
};
export default LessonWordPageByDb;

View File

@@ -0,0 +1,31 @@
.bold {
font-weight: bold;
}
ion-modal#example-modal {
--height: 33%;
--width: 80%;
--border-radius: 16px;
--box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
}
ion-modal#example-modal::part(backdrop) {
/* background: rgba(209, 213, 219); */
opacity: 1;
}
ion-modal#example-modal ion-toolbar {
/* --background: rgb(14 116 144); */
/* --color: white; */
--color: black;
}
ion-toast.custom-toast::part(message) {
text-align: center;
font-size: 1.5rem;
color: rgba(0, 0, 0, 0.9);
}
ion-toast.custom-toast::part(container) {
bottom: 100px;
}

View File

@@ -10,8 +10,7 @@ import {
play,
volumeHighOutline,
} from 'ionicons/icons';
import { useEffect, useRef, useState } from 'react';
// import { StoreContext, useMyIonStore } from '../../contexts/store';
import React, { useEffect, useRef, useState } from 'react';
//
import Markdown from 'react-markdown';
import { useParams } from 'react-router';
@@ -26,19 +25,25 @@ import { useMyIonStore } from '../../../contexts/MyIonStore';
import ILesson from '../../../interfaces/ILesson';
import ILessonCategory from '../../../interfaces/ILessonCategory';
import IWordCard from '../../../interfaces/IWordCard';
import { getFavLessonVocabularyLink, getLessonVocabularyLink } from '../../Lesson/getLessonWordLink';
import {
getFavLessonVocabularyLink,
getLessonVocabularyLink,
getLessonVocabularyLinkString,
} from '../../Lesson/getLessonWordLink';
import { AudioControls } from './AudioControls';
import useGetVocabularyRoute from '../../../hooks/useGetVocabularyRoute';
import { UseQueryResult } from '@tanstack/react-query';
import { ListResult } from 'pocketbase';
import LessonsTypes from '../../../types/LessonsTypes';
//
const LessonWordPage: React.FC = () => {
const router = useIonRouter();
function WordPage(): React.JSX.Element {
const [loading, setLoading] = useState(true);
const [open_remove_modal, setOpenRemoveModal] = useState(false);
const router = useIonRouter();
const modal = useRef<HTMLIonModalElement>(null);
const { lesson_idx, cat_idx, word_idx } = useParams<{ lesson_idx: string; cat_idx: string; word_idx: string }>();
const [open_remove_modal, setOpenRemoveModal] = useState(false);
const [lesson_info, setLessonInfo] = useState<ILesson | undefined>(undefined);
const [cat_info, setCatInfo] = useState<ILessonCategory | undefined>(undefined);
const [word_info, setWordInfo] = useState<IWordCard | undefined>(undefined);
@@ -46,78 +51,64 @@ const LessonWordPage: React.FC = () => {
const { play: play_word, playing } = useGlobalAudioPlayer();
const { myIonStoreAddFavoriteVocabulary, myIonStoreRemoveFavoriteVocabulary, myIonStoreFindInFavoriteVocabulary } =
useMyIonFavorite();
//
// const lesson_vocab_address = `/lesson_word_page/v/${lesson_idx}/${cat_idx}/${word_idx}`;
let [favorite_address, setFavoriteAddress] = useState(getFavLessonVocabularyLink(lesson_idx, cat_idx, word_idx));
useEffect(() => {
setFavoriteAddress(getFavLessonVocabularyLink(lesson_idx, cat_idx, word_idx));
let { status, data: tempResult } = useGetVocabularyRoute(lesson_idx, cat_idx);
if (lesson_contents.length > 0) {
let lesson_content: ILesson = lesson_contents[parseInt(lesson_idx)];
let category_content: ILessonCategory = lesson_content.content[parseInt(cat_idx)];
let word_content: IWordCard = category_content.content[parseInt(word_idx)];
setWordInfo(word_content);
}
}, [lesson_idx, cat_idx, word_idx]);
//
function dismiss() {
setOpenRemoveModal(false);
}
const [isOpen, setIsOpen] = useState(false);
let { lesson_contents } = useMyIonStore();
useEffect(() => {
// NOTES: lesson_content == [] during loading
if (lesson_contents.length > 0) {
let lesson_content: ILesson = lesson_contents[parseInt(lesson_idx)];
let category_content: ILessonCategory = lesson_content.content[parseInt(cat_idx)];
let word_content: IWordCard = category_content.content[parseInt(word_idx)];
setLessonInfo(lesson_content);
setCatInfo(category_content);
setWordInfo(word_content);
setLoading(false);
}
}, [lesson_contents]);
let [in_fav, setInFav] = useState(false);
const isInFavorite = async (string_to_search: string) => {
let result = await myIonStoreFindInFavoriteVocabulary(string_to_search);
setInFav(result);
};
const addToFavorite = async (string_to_add: string) => {
await myIonStoreAddFavoriteVocabulary(string_to_add);
await isInFavorite(string_to_add);
setInFav(!in_fav);
};
function handleUserRemoveFavorite() {
setOpenRemoveModal(true);
function getFile(recordId: string, fileName: string) {
return `http://127.0.0.1:8090/api/files/Vocabularies/${recordId}/${fileName}`;
}
const removeFromFavorite = async (string_to_remove: string) => {
await myIonStoreRemoveFavoriteVocabulary(string_to_remove);
await isInFavorite(string_to_remove);
};
useEffect(() => {
setLoading(!(status === 'success'));
}, [status]);
// useEffect(() => {
// // console.log({ lesson_idx, cat_idx, word_idx });
// if (tempResult) {
// try {
// setCatInfo(tempResult.items[parseInt(word_idx)].expand.cat_id as unknown as ILessonCategory);
// //
// setWordInfo(tempResult.items[parseInt(word_idx)] as unknown as IWordCard);
// } catch (error) {
// console.error(error);
// }
// }
// }, [lesson_idx, cat_idx, word_idx]);
let [lastWord, setLastWord] = useState<boolean>(false);
useEffect(() => {
(async () => {
await isInFavorite(getFavLessonVocabularyLink(lesson_idx, cat_idx, word_idx));
})();
if (tempResult) {
try {
setCatInfo(tempResult.items[parseInt(word_idx)].expand.cat_id as unknown as ILessonCategory);
//
setWordInfo(tempResult.items[parseInt(word_idx)] as unknown as IWordCard);
//
setLastWord(parseInt(word_idx) === tempResult.items.length - 1);
} catch (error) {
console.error(error);
}
}
}, [lesson_idx, cat_idx, word_idx]);
// if (loading) return <>loading</>;
// if (!word_info) return <>loading</>;
if (lesson_info == undefined) return <LoadingScreen />;
useEffect(() => {
// console.log({ lesson_idx, cat_idx, word_idx });
if (tempResult) {
try {
setCatInfo(tempResult.items[parseInt(word_idx)].expand.cat_id as unknown as ILessonCategory);
//
setWordInfo(tempResult.items[parseInt(word_idx)] as unknown as IWordCard);
//
setLastWord(parseInt(word_idx) === tempResult.items.length - 1);
} catch (error) {
console.error(error);
}
}
}, [tempResult]);
if (!cat_info || !word_info) return <LoadingScreen />;
if (!tempResult) return <LoadingScreen />;
return (
<>
<IonPage>
@@ -128,9 +119,8 @@ const LessonWordPage: React.FC = () => {
shape="round"
fill="clear"
color={'dark'}
// href={`${LESSON_LINK}/a/${lesson_info.name}`}
onClick={() => {
router.push(`${LESSON_LINK}/a/${lesson_info.name}`);
router.push(`${LESSON_LINK}/a/Vocabulary`);
}}
>
<IonIcon size="large" icon={arrowBackCircleOutline} />
@@ -152,42 +142,67 @@ const LessonWordPage: React.FC = () => {
// href={getLessonVocabularyLink(lesson_idx, cat_idx, Math.max(0, parseInt(word_idx) - 1).toString())}
onClick={() => {
router.push(
getLessonVocabularyLink(lesson_idx, cat_idx, Math.max(0, parseInt(word_idx) - 1).toString()),
getLessonVocabularyLinkString(
lesson_idx,
cat_idx,
Math.max(0, parseInt(word_idx) - 1).toString(),
),
);
}}
>
<IonIcon slot="icon-only" size="large" icon={chevronBack}></IonIcon>
</IonButton>
</div>
<div
style={{
width: '66vw',
height: '66vw',
backgroundImage: `url(${word_info.image_url})`,
backgroundPosition: 'center',
backgroundSize: 'cover',
borderRadius: '0.5rem',
margin: '.5rem',
}}
></div>
{word_info && word_info?.image != '' ? (
<div
style={{
width: '66vw',
height: '66vw',
backgroundImage: `url(${getFile(word_info.id, word_info.image)})`,
backgroundPosition: 'center',
backgroundSize: 'cover',
borderRadius: '0.5rem',
margin: '.5rem',
}}
></div>
) : (
<div
style={{
width: '66vw',
height: '66vw',
backgroundPosition: 'center',
backgroundSize: 'cover',
borderRadius: '0.5rem',
margin: '.5rem',
//
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
fontSize: '1.5rem',
}}
>
empty pic
</div>
)}
<div>
<IonButton
size="large"
shape="round"
fill="clear"
color={parseInt(word_idx) === cat_info.content.length - 1 ? 'medium' : 'dark'}
disabled={parseInt(word_idx) === cat_info.content.length - 1}
// href={getLessonVocabularyLink(
color={lastWord ? 'medium' : 'dark'}
disabled={lastWord}
// href={getLessonVocabularyLinkString(
// lesson_idx,
// cat_idx,
// Math.min(cat_info.content.length - 1, parseInt(word_idx) + 1).toString()
// Math.min(tempResult.items.length - 1, parseInt(word_idx) + 1).toString(),
// )}
onClick={() => {
router.push(
getLessonVocabularyLink(
getLessonVocabularyLinkString(
lesson_idx,
cat_idx,
Math.min(cat_info.content.length - 1, parseInt(word_idx) + 1).toString(),
Math.min(tempResult.items.length - 1, parseInt(word_idx) + 1).toString(),
),
);
}}
@@ -216,10 +231,20 @@ const LessonWordPage: React.FC = () => {
<div>
<div
style={{ display: 'flex', flexDirection: 'row', gap: '1rem', alignItems: 'center', marginTop: '1rem' }}
style={{
display: 'flex',
flexDirection: 'row',
gap: '1rem',
alignItems: 'center',
marginTop: '1rem',
}}
>
<div>
<AudioControls audio_src={word_info.sound_url} />
{word_info && word_info?.sound != '' ? (
<AudioControls audio_src={getFile(word_info.id, word_info.sound)} />
) : (
<></>
)}
<IonButton
size="large"
color="dark"
@@ -236,6 +261,7 @@ const LessonWordPage: React.FC = () => {
></IonIcon>
</IonButton>
</div>
<div style={{ fontWeight: 'bold', fontSize: '1.5rem' }}>{word_info.word}</div>
<div>
<IonButton
@@ -244,21 +270,30 @@ const LessonWordPage: React.FC = () => {
size="large"
fill="clear"
// id='open-modal'
onClick={() => {
in_fav ? handleUserRemoveFavorite() : addToFavorite(favorite_address);
}}
// onClick={() => {
// in_fav ? handleUserRemoveFavorite() : addToFavorite(favorite_address);
// }}
>
<IonIcon
size="large"
color="danger"
slot="icon-only"
icon={in_fav ? heart : heartOutline}
icon={heartOutline}
// icon={in_fav ? heart : heartOutline}
></IonIcon>
</IonButton>
</div>
</div>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', marginTop: '1rem' }}>
<div
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
marginTop: '1rem',
//
}}
>
<div style={{ fontWeight: 'bold', fontSize: '1.3rem' }}>{word_info.word_c}</div>
</div>
</div>
@@ -272,55 +307,21 @@ const LessonWordPage: React.FC = () => {
marginTop: '2rem',
}}
>
<Markdown remarkPlugins={[remarkGfm]}>{word_info.sample_e}</Markdown>
<Markdown remarkPlugins={[remarkGfm]}>{word_info.sample_c}</Markdown>
<Markdown remarkPlugins={[remarkGfm]}>
{word_info.sample_e}
{/* */}
</Markdown>
<Markdown remarkPlugins={[remarkGfm]}>
{word_info.sample_c}
{/* */}
</Markdown>
</div>
</div>
</IonContent>
</IonPage>
{/* */}
<IonModal isOpen={open_remove_modal} id="example-modal" ref={modal}>
<IonContent>
<IonToolbar>
<IonButtons slot="end">
<IonButton onClick={() => dismiss()} shape="round" fill="clear">
<IonIcon size="large" slot="icon-only" icon={close}></IonIcon>
</IonButton>
</IonButtons>
</IonToolbar>
<div style={{ marginTop: '2rem' }}>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
<div>Are you sure to remove favorite ?</div>
<div style={{ display: 'flex', flexDirection: 'row', gap: '1rem', marginTop: '1rem' }}>
<div>
<IonButton color="dark" onClick={() => dismiss()} fill="outline">
Cancel
</IonButton>
</div>
<div>
<IonButton
onClick={() => {
removeFromFavorite(getFavLessonVocabularyLink(lesson_idx, cat_idx, word_idx));
setIsOpen(true);
dismiss();
}}
fill="solid"
color="danger"
>
Remove
</IonButton>
</div>
</div>
</div>
</div>
</IonContent>
</IonModal>
<RemoveFavoritePrompt open={isOpen} setIsOpen={setIsOpen} />
</>
);
};
}
export default LessonWordPage;
export default WordPage;

View File

@@ -1,5 +1,13 @@
import { FAVORITE_LINK } from '../../constants';
export function getLessonVocabularyLinkString(
s_active_lesson_idx: string,
s_cat_idx: string,
s_word_idx: string,
): string {
return `/lesson_word_page/v/${s_active_lesson_idx}/${s_cat_idx}/${s_word_idx}`;
}
export function getLessonVocabularyLink(i_active_lesson_idx: string, i_cat_idx: string, i_word_idx: string): string {
let s_active_lesson_idx = parseInt(i_active_lesson_idx);
let s_cat_idx = parseInt(i_cat_idx);

View File

@@ -25,6 +25,9 @@ import { LESSON_LINK } from '../../constants';
import { useMyIonStore } from '../../contexts/MyIonStore';
import { listLessonCategories } from '../../public_data/listLessonCategories';
import LessonContainer from './LessonContainer';
import useHelloworld from '../../hooks/useHelloworld';
import useListAllLessonTypes from '../../hooks/useListAllLessonTypes';
import LessonsTypes, { LessonsType } from '../../types/LessonsTypes';
const Lesson: React.FC = () => {
const { act_category } = useParams<{ act_category: string }>();
@@ -35,14 +38,14 @@ const Lesson: React.FC = () => {
let [active_lesson_idx, setActiveLessonIdx] = useState<number>(0);
let [selected_content, setSelectedContent] = useState<any>([]);
useEffect(() => {
listLessonCategories().then((cats: any) => {
console.log({ cats });
setLessonContent(cats);
setActiveLessonIdx(0);
setLoading(false);
});
}, []);
// useEffect(() => {
// listLessonCategories().then((cats: any) => {
// console.log({ cats });
// setLessonContent(cats);
// setActiveLessonIdx(0);
// setLoading(false);
// });
// }, []);
useEffect(() => {
if (loading) return;
@@ -65,7 +68,24 @@ const Lesson: React.FC = () => {
}
}, [act_category, lesson_content]);
const lessonTypesQuery = useListAllLessonTypes();
const [lessonTypes, setLessonTypes] = useState<LessonsType[]>([]);
useEffect(() => {
if (lessonTypesQuery.status === 'success') {
const { data: tempLessonTypes } = lessonTypesQuery;
console.log({ tempLessonTypes });
if (tempLessonTypes) {
setLessonTypes(tempLessonTypes);
}
setLoading(false);
}
}, [lessonTypesQuery]);
if (loading) return <LoadingScreen />;
if (lessonTypes.length === 0) return <LoadingScreen />;
// return <pre>{JSON.stringify({ t: lessonTypes }, null, 2)}</pre>;
return (
<IonPage>
@@ -98,6 +118,7 @@ const Lesson: React.FC = () => {
</IonTitle>
</IonToolbar>
</IonHeader>
{/* */}
<div
style={{
width: '100%',
@@ -109,9 +130,9 @@ const Lesson: React.FC = () => {
<IonList lines="none">
<IonItem>
<IonSelect value={active_lesson_idx} onIonChange={(e) => setActiveLessonIdx(e.detail.value)}>
{lesson_content.map((category: any, idx: number) => (
{lessonTypes.map((lessonType: any, idx: number) => (
<IonSelectOption key={idx} value={idx}>
{category.name}
{lessonType.name}
</IonSelectOption>
))}
</IonSelect>
@@ -119,7 +140,10 @@ const Lesson: React.FC = () => {
</IonList>
</div>
{/* */}
<LessonContainer test_active_lesson_idx={active_lesson_idx} />
<LessonContainer
test_active_lesson_idx={active_lesson_idx}
lesson_type_id={lessonTypes[active_lesson_idx].id}
/>
{/* */}
<CongratGenius />
<CongratHardworker />