1+ // App.tsx
12import { useEffect , useRef , useState } from 'react'
23import './App.css'
34
45type CheckResult = { ok : boolean ; details : string }
56
6- const CORRECT_ANSWER = "すばらしい。"
7-
8- function checkAnswer ( actual : string ) : CheckResult {
9- if ( actual === CORRECT_ANSWER ) {
10- return { ok : true , details : `一致: "${ actual } "` }
11- } else {
12- return { ok : false , details : `不一致: あなたの入力="${ actual } "` }
13- }
7+ // 答えの定義
8+ const ANSWERS = {
9+ p1 : "すばらしい。" ,
10+ p2 : "./images/star5.png"
1411}
1512
1613function App ( ) {
14+ // 現在の問題番号
15+ const [ step , setStep ] = useState ( 1 )
16+ // 判定結果
1717 const [ result , setResult ] = useState < CheckResult | null > ( null )
1818 const [ error , setError ] = useState < string | null > ( null )
19- const baselineRef = useRef < string | null > ( null )
19+
20+ // イベントリスナー内で最新の step を参照するための Ref
21+ const stepRef = useRef ( step )
22+ // 各ステップの初期値を保持するベースライン
23+ const baselineRef = useRef < { p1 : string | null ; p2 : string | null } > ( {
24+ p1 : null ,
25+ p2 : null ,
26+ } )
27+
28+ // state が変わったら ref も更新しておく
29+ useEffect ( ( ) => {
30+ stepRef . current = step
31+ } , [ step ] )
2032
2133 useEffect ( ( ) => {
2234 let port : chrome . runtime . Port | null = null
@@ -30,27 +42,61 @@ function App() {
3042
3143 port . onMessage . addListener ( ( msg ) => {
3244 if ( msg ?. type === 'DOM_VALUE_UPDATE' ) {
33- const actual = String ( msg . actual ?? '' )
45+ const { p1, p2 } = msg . values
46+ const currentStep = stepRef . current
3447
35- // 初回はベースラインの設定のみ行う
36- if ( baselineRef . current === null ) {
37- baselineRef . current = actual
38- setResult ( null ) // 待機画面を維持
39- return
48+
49+ if ( currentStep === 1 ) {
50+ // 初回ロード時の値をベースラインとして記憶
51+ if ( baselineRef . current . p1 === null ) {
52+ baselineRef . current . p1 = p1
53+ return
54+ }
55+
56+ // ベースラインから変化があった場合のみ判定
57+ if ( p1 !== baselineRef . current . p1 ) {
58+ const isCorrect = p1 === ANSWERS . p1
59+ setResult ( {
60+ ok : isCorrect ,
61+ details : isCorrect ? `一致: "${ p1 } "` : `不一致: "${ p1 } "`
62+ } )
63+
64+ // 正解なら2秒後に次の問題へ
65+ if ( isCorrect ) {
66+ setTimeout ( ( ) => {
67+ setStep ( 2 )
68+ setResult ( null )
69+ baselineRef . current . p2 = p2
70+ } , 2000 )
71+ }
72+ }
4073 }
4174
42- // 2回目以降で、かつ値がベースラインから変更された場合のみ判定する
43- if ( actual !== baselineRef . current ) {
44- setResult ( checkAnswer ( actual ) )
75+
76+ if ( currentStep === 2 ) {
77+ if ( baselineRef . current . p2 === null ) {
78+ baselineRef . current . p2 = p2
79+ return
80+ }
81+
82+ if ( p2 !== baselineRef . current . p2 ) {
83+
84+ const isCorrect = p2 . includes ( ANSWERS . p2 )
85+ setResult ( {
86+ ok : isCorrect ,
87+ details : isCorrect ? `一致: (${ p2 } )` : `不一致: (${ p2 } )`
88+ } )
89+ // ※ここでさらに step3 へ遷移させることも可能
90+ }
4591 }
4692 }
4793 } )
4894
4995 port . onDisconnect . addListener ( ( ) => {
50- // ページ更新・遷移でリセット
51- baselineRef . current = null
96+ baselineRef . current = { p1 : null , p2 : null }
5297 port = null
53- setResult ( null ) // 待機画面に戻す
98+ setResult ( null )
99+ setStep ( 1 ) // 接続が切れたら最初に戻す
54100 } )
55101 } catch ( e : any ) {
56102 setError ( e ?. message ?? String ( e ) )
@@ -61,29 +107,45 @@ function App() {
61107
62108 return ( ) => {
63109 if ( port ) {
64- try {
65- port . disconnect ( )
66- } catch { }
110+ try { port . disconnect ( ) } catch { }
67111 }
68- baselineRef . current = null
112+ baselineRef . current = { p1 : null , p2 : null }
69113 }
70114 } , [ ] )
71115
116+ // UI描画
72117 return (
73118 < div className = "container" >
74119 { error && < p style = { { color : 'red' } } > Error: { error } </ p > }
75- { result ? (
76- < >
77- < h2 > 問題1</ h2 >
78- < p > レビューの「とにかくひどい。」を「すばらしい。」に書き換えてみよう!</ p >
79- < h2 > 判定: { result . ok ? '正解' : '不正解' } </ h2 >
80- < div > 詳細: { result . details } </ div >
81- </ >
82- ) : (
120+
121+ { /* ステップ1の表示 */ }
122+ { step === 1 && (
83123 < div >
84124 < h2 > 問題1</ h2 >
85125 < p > レビューの「とにかくひどい。」を「すばらしい。」に書き換えてみよう!</ p >
86- < p > ヒント:開発者ツールを使って、レビューに当たる要素を探してみよう。</ p >
126+ { result && (
127+ < div className = { result . ok ? "result-ok" : "result-ng" } >
128+ < h3 > 判定: { result . ok ? '正解!' : '不正解' } </ h3 >
129+ < p > { result . details } </ p >
130+ { result . ok && < p > 次の問題へ進みます...</ p > }
131+ </ div >
132+ ) }
133+ { ! result && < p className = "hint" > ヒント:開発者ツールを使って、レビューに当たる要素を探してみよう。</ p > }
134+ </ div >
135+ ) }
136+
137+ { /* ステップ2の表示 */ }
138+ { step === 2 && (
139+ < div >
140+ < h2 > 問題2</ h2 >
141+ < p > 星1の画像(star1.png)の < code > src</ code > を書き換えて、星5 (star5.png) にしてみよう!</ p >
142+ { result && (
143+ < div className = { result . ok ? "result-ok" : "result-ng" } >
144+ < h3 > 判定: { result . ok ? '正解!' : '不正解' } </ h3 >
145+ < p > { result . details } </ p >
146+ </ div >
147+ ) }
148+ { ! result && < p className = "hint" > ヒント:imgタグの src 属性を探そう。</ p > }
87149 </ div >
88150 ) }
89151 </ div >
0 commit comments