update,
This commit is contained in:
@@ -22,7 +22,10 @@ import FavVocabularyPage from './pages/Favorite/Vocabulary';
|
||||
import FavoriteVocabularyPage from './pages/Favorite/WordPage';
|
||||
import ConnectivesPage from './pages/Lesson/ConnectivesPage';
|
||||
import Lesson from './pages/Lesson/index';
|
||||
import LessonWordPage from './pages/Lesson/WordPage';
|
||||
|
||||
// NOTES: old version using json file
|
||||
import LessonWordPageByDb from './pages/Lesson/LessonWordPageByDb';
|
||||
import WordPage from './pages/Lesson/WordPage';
|
||||
//
|
||||
import ListeningPractice from './pages/ListeningPractice';
|
||||
import PracticeFinish from './pages/ListeningPractice/Finish';
|
||||
@@ -58,7 +61,7 @@ function RouteConfig() {
|
||||
<Route exact path={`${LESSON_LINK}/a/:act_category`}>
|
||||
<Lesson />
|
||||
</Route>
|
||||
|
||||
{/* */}
|
||||
<Route exact path={LESSON_LINK}>
|
||||
<Lesson />
|
||||
</Route>
|
||||
@@ -122,9 +125,17 @@ function RouteConfig() {
|
||||
<FavConnectivesPage />
|
||||
</Route>
|
||||
|
||||
{/* TODO: change lesson_idx to lesson_type_idx, need to modify LessonWordPage as well */}
|
||||
{/* http://localhost:5173/lesson_word_page/v/000000000000001/000000000000003/0 */}
|
||||
<Route exact path={`${LESSON_WORD_PAGE_LINK}/v/:lesson_idx/:cat_idx/:word_idx`}>
|
||||
<WordPage />
|
||||
</Route>
|
||||
|
||||
{/*
|
||||
<Route exact path={`${LESSON_WORD_PAGE_LINK}/v/:lesson_idx/:cat_idx/:word_idx`}>
|
||||
<LessonWordPage />
|
||||
</Route>
|
||||
*/}
|
||||
<Route exact path={`${LESSON_WORD_PAGE_LINK}/c/:lesson_idx/:cat_idx/:word_idx`}>
|
||||
<ConnectivesPage />
|
||||
</Route>
|
||||
|
@@ -1,3 +1,4 @@
|
||||
// CHANGELOG:
|
||||
// 0.0.1 - implement screen
|
||||
// 0.0.2 - implement logic
|
||||
// 0.0.3 - first demo
|
||||
|
@@ -1,8 +1,12 @@
|
||||
import { PocketBaseProvider } from '../hooks/usePocketBase';
|
||||
import { AppStateProvider } from './AppState';
|
||||
import { MyIonFavoriteProvider } from './MyIonFavorite';
|
||||
import { MyIonMetricProvider } from './MyIonMetric';
|
||||
import { MyIonQuizProvider } from './MyIonQuiz';
|
||||
import { MyIonStoreProvider } from './MyIonStore';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
const ContextMeta = ({ children }: { children: React.ReactNode }) => {
|
||||
return (
|
||||
@@ -12,8 +16,12 @@ const ContextMeta = ({ children }: { children: React.ReactNode }) => {
|
||||
<MyIonFavoriteProvider>
|
||||
<MyIonQuizProvider>
|
||||
<MyIonMetricProvider>
|
||||
{/* */}
|
||||
{children}
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<PocketBaseProvider>
|
||||
{children}
|
||||
{/* */}
|
||||
</PocketBaseProvider>
|
||||
</QueryClientProvider>
|
||||
</MyIonMetricProvider>
|
||||
</MyIonQuizProvider>
|
||||
</MyIonFavoriteProvider>
|
||||
|
29
002_source/ionic_mobile/src/hooks/useGetVocabularyRoute.tsx
Normal file
29
002_source/ionic_mobile/src/hooks/useGetVocabularyRoute.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { usePocketBase } from './usePocketBase.tsx';
|
||||
import type LessonsTypes from '../types/LessonsTypes';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import Vocabularies from '../types/Vocabularies.tsx';
|
||||
|
||||
const useGetVocabularyRoute = (lessonType: string, catId: string) => {
|
||||
const { user, pb } = usePocketBase();
|
||||
return useQuery({
|
||||
queryKey: ['useGetVocabularyRoute', lessonType, catId, 'feeds', 'all', user?.id || ''],
|
||||
staleTime: 60 * 1000,
|
||||
queryFn: async ({
|
||||
queryKey,
|
||||
}: {
|
||||
queryKey: ['useGetVocabularyRoute', string, string, 'feeds', 'all', string | null];
|
||||
}) => {
|
||||
console.log('calling useGetLessonCategoriesRoute');
|
||||
return await pb.collection('Vocabularies').getList<Vocabularies[]>(1, 9999, {
|
||||
// TODO: sort by field -> pos
|
||||
sort: 'id',
|
||||
filter: `lesson_type_id = "${lessonType}" && cat_id = "${catId}"`,
|
||||
$autoCancel: false,
|
||||
expand: 'lesson_type_id,cat_id',
|
||||
});
|
||||
},
|
||||
// enabled: !!user?.id,
|
||||
});
|
||||
};
|
||||
|
||||
export default useGetVocabularyRoute;
|
15
002_source/ionic_mobile/src/hooks/useHelloworld.tsx
Normal file
15
002_source/ionic_mobile/src/hooks/useHelloworld.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import React from 'react'
|
||||
|
||||
export default function useHelloworld() {
|
||||
const [count, setCount] = React.useState(0)
|
||||
|
||||
const handleIncrement = () => {
|
||||
// setCount(count + 1)
|
||||
console.log("helloworld")
|
||||
}
|
||||
|
||||
return {
|
||||
count,
|
||||
handleIncrement,
|
||||
}
|
||||
}
|
38
002_source/ionic_mobile/src/hooks/useListAllLessonTypes.tsx
Normal file
38
002_source/ionic_mobile/src/hooks/useListAllLessonTypes.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import { usePocketBase } from './usePocketBase.tsx';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { LessonsType } from '../types/LessonsTypes';
|
||||
|
||||
const useListAllLessonTypes = () => {
|
||||
const { user, pb } = usePocketBase();
|
||||
return useQuery({
|
||||
queryKey: [
|
||||
'useListAllLessonTypes',
|
||||
'feeds',
|
||||
'all',
|
||||
user?.id || '',
|
||||
//
|
||||
],
|
||||
staleTime: 60 * 1000,
|
||||
queryFn: async ({
|
||||
queryKey,
|
||||
}: {
|
||||
queryKey: [
|
||||
'useListAllLessonTypes',
|
||||
'feeds',
|
||||
'all',
|
||||
string | null,
|
||||
//
|
||||
];
|
||||
}) => {
|
||||
console.log('calling useListAllLessonTypes');
|
||||
return await pb.collection('LessonsTypes').getFullList<LessonsType>({
|
||||
// TODO: sort by field -> pos
|
||||
sort: 'id',
|
||||
$autoCancel: false,
|
||||
});
|
||||
},
|
||||
// enabled: !!user?.id,
|
||||
});
|
||||
};
|
||||
|
||||
export default useListAllLessonTypes;
|
49
002_source/ionic_mobile/src/hooks/usePocketBase.tsx
Normal file
49
002_source/ionic_mobile/src/hooks/usePocketBase.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import { useCallback, useState, useEffect, createContext, useContext } from 'react';
|
||||
import PocketBase, { AuthRecord } from 'pocketbase';
|
||||
type PocketBaseContextValue = {
|
||||
pb: PocketBase;
|
||||
user: AuthRecord;
|
||||
logout: () => void;
|
||||
};
|
||||
const PocketBaseContext = createContext<PocketBaseContextValue | null>(null);
|
||||
|
||||
const POCKETBASE_URL = import.meta.env.VITE_POCKETBASE_URL;
|
||||
|
||||
export const PocketBaseProvider = ({ children }: { children: any }) => {
|
||||
const [pb, _] = useState(new PocketBase(POCKETBASE_URL));
|
||||
const [user, setUser] = useState(pb.authStore.record);
|
||||
|
||||
useEffect(() => {
|
||||
// Update user state when auth store changes
|
||||
const unsubscribe = pb.authStore.onChange((_, model) => {
|
||||
setUser(model);
|
||||
});
|
||||
|
||||
return unsubscribe;
|
||||
}, [pb.authStore]);
|
||||
|
||||
const logout = useCallback(() => pb.authStore.clear(), [pb.authStore]);
|
||||
|
||||
return <PocketBaseContext.Provider value={{ pb, user, logout }}>{children}</PocketBaseContext.Provider>;
|
||||
};
|
||||
|
||||
export const usePocketBase = () => {
|
||||
const context = useContext(PocketBaseContext);
|
||||
if (context === null) {
|
||||
throw new Error('usePocketBase must be used within a PocketBaseProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
export const useRequireAuth = () => {
|
||||
const { pb, user } = usePocketBase();
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
if (!pb.authStore.isValid) {
|
||||
navigate(URLS.LOGIN);
|
||||
}
|
||||
}, [pb.authStore.isValid, navigate]);
|
||||
|
||||
return user;
|
||||
};
|
@@ -9,6 +9,8 @@ interface IWordCard {
|
||||
sample_c: string;
|
||||
image_url: string;
|
||||
sound_url: string;
|
||||
//
|
||||
id: string;
|
||||
}
|
||||
|
||||
export default IWordCard;
|
||||
|
@@ -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;
|
@@ -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;
|
@@ -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 <></>;
|
||||
};
|
@@ -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;
|
@@ -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;
|
||||
}
|
@@ -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;
|
||||
|
@@ -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);
|
||||
|
@@ -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 />
|
||||
|
23
002_source/ionic_mobile/src/types/LessonCategory.d.ts
vendored
Normal file
23
002_source/ionic_mobile/src/types/LessonCategory.d.ts
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
type LessonCategory = {
|
||||
isEmpty?: boolean;
|
||||
//
|
||||
id: string;
|
||||
collectionId: string;
|
||||
//
|
||||
cat_name: string;
|
||||
cat_image_url?: string;
|
||||
cat_image?: string;
|
||||
pos: number;
|
||||
visible: string;
|
||||
lesson_id: string;
|
||||
description: string;
|
||||
remarks: string;
|
||||
createdAt: Date;
|
||||
//
|
||||
name: string;
|
||||
avatar: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
quota: number;
|
||||
status: 'pending' | 'active' | 'blocked' | 'NA';
|
||||
};
|
20
002_source/ionic_mobile/src/types/LessonsTypes.tsx
Normal file
20
002_source/ionic_mobile/src/types/LessonsTypes.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
export type LessonsType = {
|
||||
id: string;
|
||||
isEmpty?: boolean;
|
||||
name: string;
|
||||
type: string;
|
||||
pos: number;
|
||||
visible: 'visible' | 'hidden';
|
||||
createdAt: Date;
|
||||
//
|
||||
// original
|
||||
// id: string;
|
||||
// name: string;
|
||||
//
|
||||
avatar?: string;
|
||||
email: string;
|
||||
phone?: string;
|
||||
quota: number;
|
||||
status: 'pending' | 'active' | 'blocked';
|
||||
// createdAt: Date;
|
||||
};
|
24
002_source/ionic_mobile/src/types/Vocabularies.tsx
Normal file
24
002_source/ionic_mobile/src/types/Vocabularies.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
// RULES: interface for handling vocabulary record
|
||||
type Vocabulary = {
|
||||
//
|
||||
id: string;
|
||||
collectionId: string;
|
||||
//
|
||||
word: string;
|
||||
word_c: string;
|
||||
sample_e: string;
|
||||
sample_c: string;
|
||||
cat_id: string;
|
||||
category: string;
|
||||
lesson_type_id: string;
|
||||
image: File[];
|
||||
sound: File[];
|
||||
//
|
||||
expand?: {
|
||||
cat_id: LessonCategory;
|
||||
};
|
||||
};
|
||||
|
||||
type Vocabularies = Vocabulary[];
|
||||
|
||||
export default Vocabularies;
|
Reference in New Issue
Block a user