This commit is contained in:
louiscklaw
2025-04-29 22:55:21 +08:00
parent 9d3e832081
commit 51935d203a
9 changed files with 198 additions and 95 deletions

View File

@@ -100,6 +100,7 @@ function RouteConfig() {
<ConnectiveRevisionSelectCategory />
</Route>
{/* */}
{/* http://localhost:5173/matching_frenzy/r/000000000000001 */}
<Route exact path={`${MATCHING_FRENZY_LINK}/r/:p_route`}>
<MatchingFrenzyMatchRun />
</Route>

View File

@@ -30,7 +30,9 @@ export const AppStateProvider: React.FC<{ children: ReactNode }> = ({ children }
// user_config.json
// 012_put_matching_frenzy_count_down_time_to_config_file
const [user_config_json, setUserConfigJson] = useState<any>({});
const [MATCHING_FRENZY_COUNT_DOWN_S, setMATCHING_FRENZY_COUNT_DOWN_S] = useState<number>(120);
// TODO: resume to 120
const [MATCHING_FRENZY_COUNT_DOWN_S, setMATCHING_FRENZY_COUNT_DOWN_S] = useState<number>(300);
useEffect(() => {
fetch('/data/user_config.json')
.then((res) => res.json())

View File

@@ -0,0 +1,28 @@
import { QuizMFQuestion } from '../types/QuizMFQuestion';
import { usePocketBase } from './usePocketBase';
import { QueryClient } from '@tanstack/react-query';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: Infinity,
},
},
});
const fetchMFQuestions = async (cat_id: string, pb: any) => {
const response = await queryClient.fetchQuery({
queryKey: ['fetchData'],
staleTime: 60 * 1000,
queryFn: async () => {
return await pb.collection('QuizMFQuestions').getList<QuizMFQuestion>(1, 9999, {
filter: `cat_id = "${cat_id}"`,
$autoCancel: false,
});
},
});
return response;
};
export default fetchMFQuestions;

View File

@@ -0,0 +1,44 @@
import { QuizMFQuestion } from '../types/QuizMFQuestion';
import { usePocketBase } from './usePocketBase';
import { QueryClient } from '@tanstack/react-query';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: Infinity,
},
},
});
const useListQuizMFQuestionsByCategoryId = (cat_id: string) => {
const { user, pb } = usePocketBase();
const fetchData = async () => {
const response = await queryClient.fetchQuery({
queryKey: ['fetchData'],
staleTime: 60 * 1000,
queryFn: async () => {
return await pb.collection('QuizMFQuestions').getList<QuizMFQuestion>(1, 9999, {
filter: `cat_id = "${cat_id}"`,
$autoCancel: false,
});
},
});
return response;
};
return useQuery({
queryKey: ['useListQuizMFQuestionsByCategoryId'],
staleTime: 60 * 1000,
queryFn: async () => {
return await pb.collection('QuizMFQuestions').getList<QuizMFQuestion>(1, 9999, {
filter: `cat_id = "${cat_id}"`,
$autoCancel: false,
});
},
// enabled: !!user.id,
});
};
export default useListQuizMFQuestionsByCategoryId;

View File

@@ -1,5 +1,4 @@
import { useEffect } from 'react';
// import Helloworld from './Helloworld';
import React, { useEffect } from 'react';
import './style.css';
function CountDown({

View File

@@ -0,0 +1,20 @@
import React, { useState } from 'react';
import { alarmOutline, arrowBackCircleOutline } from 'ionicons/icons';
import { IonButton, IonContent, IonIcon, IonPage, useIonRouter } from '@ionic/react';
import { useAppStateContext } from '../../../contexts/AppState';
function CountDownDisplay() {
const { MATCHING_FRENZY_COUNT_DOWN_S } = useAppStateContext();
const [countdown_s, setCountDown_s] = useState<number>(MATCHING_FRENZY_COUNT_DOWN_S);
const [show_time_left_s, setShowTimeLeft] = useState<string>('0:00');
return (
<div>
<IonIcon icon={alarmOutline}></IonIcon>
{countdown_s > 0 ? show_time_left_s : 'times up'}
</div>
);
}
export default CountDownDisplay;

View File

@@ -3,47 +3,48 @@ import { alarmOutline, arrowBackCircleOutline } from 'ionicons/icons';
import { useEffect, useState } from 'react';
import { useParams } from 'react-router';
import { LoadingScreen } from '../../components/LoadingScreen';
import { MATCHING_FRENZY_LINK, QUIZ_MAIN_MENU_LINK } from '../../constants';
import { MATCHING_FRENZY_LINK } from '../../constants';
import { useAppStateContext } from '../../contexts/AppState';
import { useMyIonQuizContext } from '../../contexts/MyIonQuiz';
import IMatchingFrenzyQuestion from '../../interfaces/IMatchingFrenzyQuestion';
import { listMatchingFrenzyContent } from '../../public_data/listMatchingFrenzyContent';
import { shuffleArray } from '../../utils/shuffleArray';
import CountDown from './CountDown';
import MatchingFrenzyCard from './MatchingFrenzyCard';
import PressStartToBegin from './PressStartToBegin';
import './style.css';
import ConfirmUserQuitQuiz from '../../components/ConfirmUserQuitQuiz';
import formatTimeDisplay from './formatTimeDisplay';
import fetchMFQuestions from '../../hooks/fetchMFQuestions';
import { usePocketBase } from '../../hooks/usePocketBase';
function MatchingFrenzyMatchRun() {
const [loading, setLoading] = useState<boolean>(true);
const router = useIonRouter();
const { p_route } = useParams<{ p_route: string }>();
const i_p_route = parseInt(p_route);
const [question_list, setQuestionList] = useState<IMatchingFrenzyQuestion[] | []>([]);
const { p_route: cat_id } = useParams<{ p_route: string }>();
let [start_match, setStartMatch] = useState(false);
let [test_random, setTestRandom] = useState(0);
const [show_confirm_user_exit, setShowConfirmUserExit] = useState<boolean>(false);
const { setMatchingFrenzyInProgress } = useAppStateContext();
const [question_list, setQuestionList] = useState<IMatchingFrenzyQuestion[]>([]);
const [current_question_meta, setCurrentQuestionMeta] = useState<IMatchingFrenzyQuestion | undefined>(undefined);
const [current_question, setCurrentQuestion] = useState(0);
const [num_correct, setNumCorrect] = useState(0);
const { setTabActive } = useAppStateContext();
const [answer_list, setAnswerList] = useState<string[]>([]);
const { MATCHING_FRENZY_COUNT_DOWN_S } = useAppStateContext();
const [countdown_s, setCountDown_s] = useState<number>(MATCHING_FRENZY_COUNT_DOWN_S);
const [show_time_left_s, setShowTimeLeft] = useState<string>('0:00');
const { setMatchingFrenzyInProgress } = useAppStateContext();
const { setMatchingFrenzyCurrentTest, setMatchingFrenzyResult, saveMatchingFrenzyResultToScoreBoard } =
useMyIonQuizContext();
const [num_correct, setNumCorrect] = useState(0);
const [answer_list, setAnswerList] = useState<string[]>([]);
let [start_match, setStartMatch] = useState(false);
let [test_random, setTestRandom] = useState(0);
// TODO: review useless
const i_p_route = parseInt(p_route);
const formatTimeDisplay = (s: number) => {
return `${Math.floor(s / 60)}:${(s % 60).toString().padStart(2, '0')}`;
function processStartMatch() {
setMatchingFrenzyInProgress(true);
setStartMatch(true);
}
const incNumCorrect = () => {
setNumCorrect(num_correct + 1);
};
const nextQuestion = () => {
@@ -71,15 +72,8 @@ function MatchingFrenzyMatchRun() {
setCurrentQuestionMeta(question_list[next_question_num]);
}
};
const incNumCorrect = () => {
setNumCorrect(num_correct + 1);
};
function processStartMatch() {
setMatchingFrenzyInProgress(true);
setStartMatch(true);
}
const { setMatchingFrenzyCurrentTest, setMatchingFrenzyResult, saveMatchingFrenzyResultToScoreBoard } =
useMyIonQuizContext();
useEffect(() => {
setShowTimeLeft(formatTimeDisplay(countdown_s));
@@ -102,9 +96,9 @@ function MatchingFrenzyMatchRun() {
}, [countdown_s]);
const [init_ans, setInitAns] = useState<string[]>([]);
useEffect(() => {
if (!current_question_meta) return;
let init_options = [...question_list.map((q) => q.word), ...answer_list, ...init_ans];
let all_answer_list = [...new Set(init_options)];
@@ -115,83 +109,73 @@ function MatchingFrenzyMatchRun() {
setAnswerList(shuffleArray([...sliced_shuffle_array, current_question_meta.word]));
}, [current_question_meta]);
const { user, pb } = usePocketBase();
useEffect(() => {
(async () => {
const res_json = await listMatchingFrenzyContent();
const cat_json = res_json[i_p_route];
const init_answer = cat_json.init_answer;
setInitAns(cat_json.init_answer);
fetchMFQuestions(cat_id, pb)
.then((result) => {
console.log(result);
const cat_json = result.items;
const init_answer = result.items[0].init_answer;
setInitAns(init_answer);
let temp = res_json[i_p_route].content;
let shuffled_temp = shuffleArray(temp);
let temp = result.items;
let shuffled_temp = shuffleArray(temp);
setQuestionList(shuffled_temp);
setCurrentQuestionMeta(cat_json.content[current_question]);
})();
setQuestionList(shuffled_temp);
setCurrentQuestionMeta(result.items[current_question] as unknown as IMatchingFrenzyQuestion);
setTabActive(QUIZ_MAIN_MENU_LINK);
setTestRandom(Math.random());
}, []);
const [show_confirm_user_exit, setShowConfirmUserExit] = useState<boolean>(false);
setLoading(false);
})
.catch((error) => {
console.error(error);
});
}, [cat_id]);
if (!current_question_meta) return <LoadingScreen />;
if (loading) return <LoadingScreen />;
return (
<>
<IonPage>
<IonContent fullscreen>
{start_match ? (
<>
<div>
<div
style={{
marginLeft: '1rem',
marginRight: '1rem',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
}}
>
<div style={{ width: '33vw' }}>
<IonButton color="dark" fill="clear" shape="round" onClick={() => setShowConfirmUserExit(true)}>
<IonIcon slot="icon-only" size="large" icon={arrowBackCircleOutline}></IonIcon>
</IonButton>
</div>
<div>
<IonIcon icon={alarmOutline}></IonIcon>
{countdown_s > 0 ? show_time_left_s : 'times up'}
</div>
<div style={{ width: '33vw', display: 'flex', justifyContent: 'flex-end' }}>
Matches:{num_correct}
</div>
<div>
<div
style={{
marginLeft: '1rem',
marginRight: '1rem',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
}}
>
<div style={{ width: '33vw' }}>
<IonButton color="dark" fill="clear" shape="round" onClick={() => setShowConfirmUserExit(true)}>
<IonIcon slot="icon-only" size="large" icon={arrowBackCircleOutline}></IonIcon>
</IonButton>
</div>
<div style={{ margin: '1rem' }}></div>
{/* */}
{/*
<div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-between' }}>
<div style={{ display: 'flex', flexDirection: 'row', gap: '0.3rem' }}>
<IonIcon icon={alarmOutline}></IonIcon>
{countdown_s > 0 ? show_time_left_s : 'times up'}
</div>
<div>Matches:{num_correct}</div>
<div>
<IonIcon icon={alarmOutline}></IonIcon>
{countdown_s > 0 ? show_time_left_s : 'times up'}
</div>
*/}
<MatchingFrenzyCard
num_correct={num_correct}
incNumCorrect={incNumCorrect}
//
nextQuestion={nextQuestion}
question_meta={{
//
word_c: current_question_meta.word_c,
modal_answer: current_question_meta.word,
}}
shuffle_word_list={answer_list}
/>
<div style={{ width: '33vw', display: 'flex', justifyContent: 'flex-end' }}>Matches:{num_correct}</div>
</div>
</>
<div style={{ margin: '1rem' }}></div>
<MatchingFrenzyCard
num_correct={num_correct}
incNumCorrect={incNumCorrect}
//
nextQuestion={nextQuestion}
question_meta={{
//
word_c: current_question_meta.word_c,
modal_answer: current_question_meta.word,
}}
shuffle_word_list={answer_list}
/>
</div>
) : (
<PressStartToBegin processStartMatch={processStartMatch} />
)}
@@ -210,6 +194,7 @@ function MatchingFrenzyMatchRun() {
}
export default MatchingFrenzyMatchRun;
function isEndOfQuestionList(current_question: number, question_list: [] | IMatchingFrenzyQuestion[]) {
return current_question >= question_list.length - 1;
}

View File

@@ -0,0 +1,5 @@
const formatTimeDisplay = (s: number) => {
return `${Math.floor(s / 60)}:${(s % 60).toString().padStart(2, '0')}`;
};
export default formatTimeDisplay;

View File

@@ -0,0 +1,19 @@
export interface QuizMFQuestion {
id: string;
created: Date;
updated: Date;
word: string;
word_c: string;
cat_id: string;
visible?: string;
sound?: File;
cat_image?: File;
expand?: {
cat_id?: {
id: string;
cat_name: string;
cat_image?: File;
};
};
init_answer: string[];
}