update,
This commit is contained in:
@@ -22,7 +22,10 @@ import FavVocabularyPage from './pages/Favorite/Vocabulary';
|
|||||||
import FavoriteVocabularyPage from './pages/Favorite/WordPage';
|
import FavoriteVocabularyPage from './pages/Favorite/WordPage';
|
||||||
import ConnectivesPage from './pages/Lesson/ConnectivesPage';
|
import ConnectivesPage from './pages/Lesson/ConnectivesPage';
|
||||||
import Lesson from './pages/Lesson/index';
|
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 ListeningPractice from './pages/ListeningPractice';
|
||||||
import PracticeFinish from './pages/ListeningPractice/Finish';
|
import PracticeFinish from './pages/ListeningPractice/Finish';
|
||||||
@@ -58,7 +61,7 @@ function RouteConfig() {
|
|||||||
<Route exact path={`${LESSON_LINK}/a/:act_category`}>
|
<Route exact path={`${LESSON_LINK}/a/:act_category`}>
|
||||||
<Lesson />
|
<Lesson />
|
||||||
</Route>
|
</Route>
|
||||||
|
{/* */}
|
||||||
<Route exact path={LESSON_LINK}>
|
<Route exact path={LESSON_LINK}>
|
||||||
<Lesson />
|
<Lesson />
|
||||||
</Route>
|
</Route>
|
||||||
@@ -122,9 +125,17 @@ function RouteConfig() {
|
|||||||
<FavConnectivesPage />
|
<FavConnectivesPage />
|
||||||
</Route>
|
</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`}>
|
<Route exact path={`${LESSON_WORD_PAGE_LINK}/v/:lesson_idx/:cat_idx/:word_idx`}>
|
||||||
<LessonWordPage />
|
<LessonWordPage />
|
||||||
</Route>
|
</Route>
|
||||||
|
*/}
|
||||||
<Route exact path={`${LESSON_WORD_PAGE_LINK}/c/:lesson_idx/:cat_idx/:word_idx`}>
|
<Route exact path={`${LESSON_WORD_PAGE_LINK}/c/:lesson_idx/:cat_idx/:word_idx`}>
|
||||||
<ConnectivesPage />
|
<ConnectivesPage />
|
||||||
</Route>
|
</Route>
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
// CHANGELOG:
|
||||||
// 0.0.1 - implement screen
|
// 0.0.1 - implement screen
|
||||||
// 0.0.2 - implement logic
|
// 0.0.2 - implement logic
|
||||||
// 0.0.3 - first demo
|
// 0.0.3 - first demo
|
||||||
|
@@ -1,8 +1,12 @@
|
|||||||
|
import { PocketBaseProvider } from '../hooks/usePocketBase';
|
||||||
import { AppStateProvider } from './AppState';
|
import { AppStateProvider } from './AppState';
|
||||||
import { MyIonFavoriteProvider } from './MyIonFavorite';
|
import { MyIonFavoriteProvider } from './MyIonFavorite';
|
||||||
import { MyIonMetricProvider } from './MyIonMetric';
|
import { MyIonMetricProvider } from './MyIonMetric';
|
||||||
import { MyIonQuizProvider } from './MyIonQuiz';
|
import { MyIonQuizProvider } from './MyIonQuiz';
|
||||||
import { MyIonStoreProvider } from './MyIonStore';
|
import { MyIonStoreProvider } from './MyIonStore';
|
||||||
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
const ContextMeta = ({ children }: { children: React.ReactNode }) => {
|
const ContextMeta = ({ children }: { children: React.ReactNode }) => {
|
||||||
return (
|
return (
|
||||||
@@ -12,8 +16,12 @@ const ContextMeta = ({ children }: { children: React.ReactNode }) => {
|
|||||||
<MyIonFavoriteProvider>
|
<MyIonFavoriteProvider>
|
||||||
<MyIonQuizProvider>
|
<MyIonQuizProvider>
|
||||||
<MyIonMetricProvider>
|
<MyIonMetricProvider>
|
||||||
{/* */}
|
<QueryClientProvider client={queryClient}>
|
||||||
|
<PocketBaseProvider>
|
||||||
{children}
|
{children}
|
||||||
|
{/* */}
|
||||||
|
</PocketBaseProvider>
|
||||||
|
</QueryClientProvider>
|
||||||
</MyIonMetricProvider>
|
</MyIonMetricProvider>
|
||||||
</MyIonQuizProvider>
|
</MyIonQuizProvider>
|
||||||
</MyIonFavoriteProvider>
|
</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;
|
sample_c: string;
|
||||||
image_url: string;
|
image_url: string;
|
||||||
sound_url: string;
|
sound_url: string;
|
||||||
|
//
|
||||||
|
id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default IWordCard;
|
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,
|
play,
|
||||||
volumeHighOutline,
|
volumeHighOutline,
|
||||||
} from 'ionicons/icons';
|
} from 'ionicons/icons';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
// import { StoreContext, useMyIonStore } from '../../contexts/store';
|
|
||||||
//
|
//
|
||||||
import Markdown from 'react-markdown';
|
import Markdown from 'react-markdown';
|
||||||
import { useParams } from 'react-router';
|
import { useParams } from 'react-router';
|
||||||
@@ -26,19 +25,25 @@ import { useMyIonStore } from '../../../contexts/MyIonStore';
|
|||||||
import ILesson from '../../../interfaces/ILesson';
|
import ILesson from '../../../interfaces/ILesson';
|
||||||
import ILessonCategory from '../../../interfaces/ILessonCategory';
|
import ILessonCategory from '../../../interfaces/ILessonCategory';
|
||||||
import IWordCard from '../../../interfaces/IWordCard';
|
import IWordCard from '../../../interfaces/IWordCard';
|
||||||
import { getFavLessonVocabularyLink, getLessonVocabularyLink } from '../../Lesson/getLessonWordLink';
|
import {
|
||||||
|
getFavLessonVocabularyLink,
|
||||||
|
getLessonVocabularyLink,
|
||||||
|
getLessonVocabularyLinkString,
|
||||||
|
} from '../../Lesson/getLessonWordLink';
|
||||||
import { AudioControls } from './AudioControls';
|
import { AudioControls } from './AudioControls';
|
||||||
|
import useGetVocabularyRoute from '../../../hooks/useGetVocabularyRoute';
|
||||||
|
import { UseQueryResult } from '@tanstack/react-query';
|
||||||
|
import { ListResult } from 'pocketbase';
|
||||||
|
import LessonsTypes from '../../../types/LessonsTypes';
|
||||||
|
|
||||||
//
|
function WordPage(): React.JSX.Element {
|
||||||
|
|
||||||
const LessonWordPage: React.FC = () => {
|
|
||||||
const router = useIonRouter();
|
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [open_remove_modal, setOpenRemoveModal] = useState(false);
|
|
||||||
|
|
||||||
|
const router = useIonRouter();
|
||||||
const modal = useRef<HTMLIonModalElement>(null);
|
const modal = useRef<HTMLIonModalElement>(null);
|
||||||
const { lesson_idx, cat_idx, word_idx } = useParams<{ lesson_idx: string; cat_idx: string; word_idx: string }>();
|
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 [lesson_info, setLessonInfo] = useState<ILesson | undefined>(undefined);
|
||||||
const [cat_info, setCatInfo] = useState<ILessonCategory | undefined>(undefined);
|
const [cat_info, setCatInfo] = useState<ILessonCategory | undefined>(undefined);
|
||||||
const [word_info, setWordInfo] = useState<IWordCard | 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 { play: play_word, playing } = useGlobalAudioPlayer();
|
||||||
const { myIonStoreAddFavoriteVocabulary, myIonStoreRemoveFavoriteVocabulary, myIonStoreFindInFavoriteVocabulary } =
|
const { myIonStoreAddFavoriteVocabulary, myIonStoreRemoveFavoriteVocabulary, myIonStoreFindInFavoriteVocabulary } =
|
||||||
useMyIonFavorite();
|
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));
|
let [favorite_address, setFavoriteAddress] = useState(getFavLessonVocabularyLink(lesson_idx, cat_idx, word_idx));
|
||||||
useEffect(() => {
|
let { status, data: tempResult } = useGetVocabularyRoute(lesson_idx, cat_idx);
|
||||||
setFavoriteAddress(getFavLessonVocabularyLink(lesson_idx, cat_idx, word_idx));
|
|
||||||
|
|
||||||
if (lesson_contents.length > 0) {
|
function getFile(recordId: string, fileName: string) {
|
||||||
let lesson_content: ILesson = lesson_contents[parseInt(lesson_idx)];
|
return `http://127.0.0.1:8090/api/files/Vocabularies/${recordId}/${fileName}`;
|
||||||
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]);
|
|
||||||
|
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(() => {
|
||||||
|
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);
|
||||||
function dismiss() {
|
//
|
||||||
setOpenRemoveModal(false);
|
setLastWord(parseInt(word_idx) === tempResult.items.length - 1);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
}
|
}
|
||||||
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]);
|
}, [lesson_idx, cat_idx, word_idx]);
|
||||||
|
|
||||||
// if (loading) return <>loading</>;
|
useEffect(() => {
|
||||||
// if (!word_info) return <>loading</>;
|
// console.log({ lesson_idx, cat_idx, word_idx });
|
||||||
|
if (tempResult) {
|
||||||
if (lesson_info == undefined) return <LoadingScreen />;
|
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 (!cat_info || !word_info) return <LoadingScreen />;
|
||||||
|
|
||||||
|
if (!tempResult) return <LoadingScreen />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<IonPage>
|
<IonPage>
|
||||||
@@ -128,9 +119,8 @@ const LessonWordPage: React.FC = () => {
|
|||||||
shape="round"
|
shape="round"
|
||||||
fill="clear"
|
fill="clear"
|
||||||
color={'dark'}
|
color={'dark'}
|
||||||
// href={`${LESSON_LINK}/a/${lesson_info.name}`}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
router.push(`${LESSON_LINK}/a/${lesson_info.name}`);
|
router.push(`${LESSON_LINK}/a/Vocabulary`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IonIcon size="large" icon={arrowBackCircleOutline} />
|
<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())}
|
// href={getLessonVocabularyLink(lesson_idx, cat_idx, Math.max(0, parseInt(word_idx) - 1).toString())}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
router.push(
|
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>
|
<IonIcon slot="icon-only" size="large" icon={chevronBack}></IonIcon>
|
||||||
</IonButton>
|
</IonButton>
|
||||||
</div>
|
</div>
|
||||||
|
{word_info && word_info?.image != '' ? (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
width: '66vw',
|
width: '66vw',
|
||||||
height: '66vw',
|
height: '66vw',
|
||||||
backgroundImage: `url(${word_info.image_url})`,
|
backgroundImage: `url(${getFile(word_info.id, word_info.image)})`,
|
||||||
backgroundPosition: 'center',
|
backgroundPosition: 'center',
|
||||||
backgroundSize: 'cover',
|
backgroundSize: 'cover',
|
||||||
borderRadius: '0.5rem',
|
borderRadius: '0.5rem',
|
||||||
margin: '.5rem',
|
margin: '.5rem',
|
||||||
}}
|
}}
|
||||||
></div>
|
></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>
|
<div>
|
||||||
<IonButton
|
<IonButton
|
||||||
size="large"
|
size="large"
|
||||||
shape="round"
|
shape="round"
|
||||||
fill="clear"
|
fill="clear"
|
||||||
color={parseInt(word_idx) === cat_info.content.length - 1 ? 'medium' : 'dark'}
|
color={lastWord ? 'medium' : 'dark'}
|
||||||
disabled={parseInt(word_idx) === cat_info.content.length - 1}
|
disabled={lastWord}
|
||||||
// href={getLessonVocabularyLink(
|
// href={getLessonVocabularyLinkString(
|
||||||
// lesson_idx,
|
// lesson_idx,
|
||||||
// cat_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={() => {
|
onClick={() => {
|
||||||
router.push(
|
router.push(
|
||||||
getLessonVocabularyLink(
|
getLessonVocabularyLinkString(
|
||||||
lesson_idx,
|
lesson_idx,
|
||||||
cat_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>
|
||||||
<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>
|
<div>
|
||||||
<AudioControls audio_src={word_info.sound_url} />
|
{word_info && word_info?.sound != '' ? (
|
||||||
|
<AudioControls audio_src={getFile(word_info.id, word_info.sound)} />
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
<IonButton
|
<IonButton
|
||||||
size="large"
|
size="large"
|
||||||
color="dark"
|
color="dark"
|
||||||
@@ -236,6 +261,7 @@ const LessonWordPage: React.FC = () => {
|
|||||||
></IonIcon>
|
></IonIcon>
|
||||||
</IonButton>
|
</IonButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ fontWeight: 'bold', fontSize: '1.5rem' }}>{word_info.word}</div>
|
<div style={{ fontWeight: 'bold', fontSize: '1.5rem' }}>{word_info.word}</div>
|
||||||
<div>
|
<div>
|
||||||
<IonButton
|
<IonButton
|
||||||
@@ -244,21 +270,30 @@ const LessonWordPage: React.FC = () => {
|
|||||||
size="large"
|
size="large"
|
||||||
fill="clear"
|
fill="clear"
|
||||||
// id='open-modal'
|
// id='open-modal'
|
||||||
onClick={() => {
|
// onClick={() => {
|
||||||
in_fav ? handleUserRemoveFavorite() : addToFavorite(favorite_address);
|
// in_fav ? handleUserRemoveFavorite() : addToFavorite(favorite_address);
|
||||||
}}
|
// }}
|
||||||
>
|
>
|
||||||
<IonIcon
|
<IonIcon
|
||||||
size="large"
|
size="large"
|
||||||
color="danger"
|
color="danger"
|
||||||
slot="icon-only"
|
slot="icon-only"
|
||||||
icon={in_fav ? heart : heartOutline}
|
icon={heartOutline}
|
||||||
|
// icon={in_fav ? heart : heartOutline}
|
||||||
></IonIcon>
|
></IonIcon>
|
||||||
</IonButton>
|
</IonButton>
|
||||||
</div>
|
</div>
|
||||||
</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 style={{ fontWeight: 'bold', fontSize: '1.3rem' }}>{word_info.word_c}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -272,55 +307,21 @@ const LessonWordPage: React.FC = () => {
|
|||||||
marginTop: '2rem',
|
marginTop: '2rem',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Markdown remarkPlugins={[remarkGfm]}>{word_info.sample_e}</Markdown>
|
<Markdown remarkPlugins={[remarkGfm]}>
|
||||||
<Markdown remarkPlugins={[remarkGfm]}>{word_info.sample_c}</Markdown>
|
{word_info.sample_e}
|
||||||
|
{/* */}
|
||||||
|
</Markdown>
|
||||||
|
<Markdown remarkPlugins={[remarkGfm]}>
|
||||||
|
{word_info.sample_c}
|
||||||
|
{/* */}
|
||||||
|
</Markdown>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</IonContent>
|
</IonContent>
|
||||||
</IonPage>
|
</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';
|
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 {
|
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_active_lesson_idx = parseInt(i_active_lesson_idx);
|
||||||
let s_cat_idx = parseInt(i_cat_idx);
|
let s_cat_idx = parseInt(i_cat_idx);
|
||||||
|
@@ -25,6 +25,9 @@ import { LESSON_LINK } from '../../constants';
|
|||||||
import { useMyIonStore } from '../../contexts/MyIonStore';
|
import { useMyIonStore } from '../../contexts/MyIonStore';
|
||||||
import { listLessonCategories } from '../../public_data/listLessonCategories';
|
import { listLessonCategories } from '../../public_data/listLessonCategories';
|
||||||
import LessonContainer from './LessonContainer';
|
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 Lesson: React.FC = () => {
|
||||||
const { act_category } = useParams<{ act_category: string }>();
|
const { act_category } = useParams<{ act_category: string }>();
|
||||||
@@ -35,14 +38,14 @@ const Lesson: React.FC = () => {
|
|||||||
let [active_lesson_idx, setActiveLessonIdx] = useState<number>(0);
|
let [active_lesson_idx, setActiveLessonIdx] = useState<number>(0);
|
||||||
let [selected_content, setSelectedContent] = useState<any>([]);
|
let [selected_content, setSelectedContent] = useState<any>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
listLessonCategories().then((cats: any) => {
|
// listLessonCategories().then((cats: any) => {
|
||||||
console.log({ cats });
|
// console.log({ cats });
|
||||||
setLessonContent(cats);
|
// setLessonContent(cats);
|
||||||
setActiveLessonIdx(0);
|
// setActiveLessonIdx(0);
|
||||||
setLoading(false);
|
// setLoading(false);
|
||||||
});
|
// });
|
||||||
}, []);
|
// }, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (loading) return;
|
if (loading) return;
|
||||||
@@ -65,7 +68,24 @@ const Lesson: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}, [act_category, lesson_content]);
|
}, [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 (loading) return <LoadingScreen />;
|
||||||
|
if (lessonTypes.length === 0) return <LoadingScreen />;
|
||||||
|
|
||||||
|
// return <pre>{JSON.stringify({ t: lessonTypes }, null, 2)}</pre>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IonPage>
|
<IonPage>
|
||||||
@@ -98,6 +118,7 @@ const Lesson: React.FC = () => {
|
|||||||
</IonTitle>
|
</IonTitle>
|
||||||
</IonToolbar>
|
</IonToolbar>
|
||||||
</IonHeader>
|
</IonHeader>
|
||||||
|
{/* */}
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
@@ -109,9 +130,9 @@ const Lesson: React.FC = () => {
|
|||||||
<IonList lines="none">
|
<IonList lines="none">
|
||||||
<IonItem>
|
<IonItem>
|
||||||
<IonSelect value={active_lesson_idx} onIonChange={(e) => setActiveLessonIdx(e.detail.value)}>
|
<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}>
|
<IonSelectOption key={idx} value={idx}>
|
||||||
{category.name}
|
{lessonType.name}
|
||||||
</IonSelectOption>
|
</IonSelectOption>
|
||||||
))}
|
))}
|
||||||
</IonSelect>
|
</IonSelect>
|
||||||
@@ -119,7 +140,10 @@ const Lesson: React.FC = () => {
|
|||||||
</IonList>
|
</IonList>
|
||||||
</div>
|
</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 />
|
<CongratGenius />
|
||||||
<CongratHardworker />
|
<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