1+ class PassedState {
2+ _key = 'python-type-challenges' ;
3+
4+ /**
5+ * Initializing when there is no state in the local storage. If there is no state in the local storage, the initial state is required.
6+ * this function will check the new state and the old state whether is undefined or not and updated the old state based on new state.
7+ *
8+ * @param {object } newState - the initial state of the challenges which grouped by the level.
9+ * @returns void
10+ */
11+ init ( newState ) {
12+ const oldState = this . get ( ) ;
13+ // initialize the state when there is no state in the local storage.
14+ if ( ! oldState && ! newState ) {
15+ throw new Error ( 'initial state is required when there is no state in the local storage.' ) ;
16+ }
17+
18+ // check new state and old state whether is undefined or not. and merge the new state to the old state.
19+ const state = this . _checkAndMerge ( oldState , newState ) ;
20+ this . _save ( state ) ;
21+ }
22+
23+ get ( ) {
24+ const currentState = localStorage . getItem ( this . _key ) ;
25+ return JSON . parse ( currentState ) ;
26+ }
27+
28+ /**
29+ * Save the state to the local storage with JSON format.
30+ * @param {object } state - the state contains the challenge name and whether the challenge is passed.
31+ */
32+ _save ( state ) {
33+ localStorage . setItem ( this . _key , JSON . stringify ( state ) ) ;
34+ }
35+
36+ /**
37+ * Set the target challenge as passed in the state.
38+ *
39+ * @param {'basic' | 'intermediate' | 'advanced' | 'extreme' } level - the level of the challenge.
40+ * @param {string } challengeName - the name of the challenge.
41+ * @returns void
42+ */
43+ setPassed ( level , challengeName ) {
44+ let state = this . get ( ) ;
45+
46+ const challenges = state [ level ] ;
47+ for ( const challenge of challenges ) {
48+ if ( challenge . name === challengeName ) {
49+ challenge . passed = true ;
50+ break ;
51+ }
52+ }
53+
54+ this . _save ( state ) ;
55+ }
56+
57+ /**
58+ * Merge the new state and the current state.
59+ * this function will compare the new state with the current state and finally overwrite the current state based on the new state:
60+ * - If the old key in the current state isn't in the new state, the old key will be removed from the current state.
61+ * - If the new key in the new state isn't in the current state, the new key will be added to the current state.
62+ *
63+ * @param {object } oldState - the current state stored in the local storage.
64+ * @param {object } newState - the latest state from the server.
65+ * @returns mergedState - the merged state.
66+ */
67+ _checkAndMerge ( oldState , newState ) {
68+ if ( ! newState && ! oldState ) {
69+ throw new Error ( 'one of the new state and the old state is required.' ) ;
70+ }
71+
72+ if ( ! newState && oldState ) {
73+ return oldState ;
74+ }
75+
76+ const state = { } ;
77+ for ( const level in newState ) {
78+ const challenges = [ ] ;
79+ for ( const challengeName of newState [ level ] ) {
80+ challenges . push ( {
81+ name : challengeName ,
82+ passed : false
83+ } ) ;
84+ }
85+ state [ level ] = challenges ;
86+ }
87+
88+ if ( ! oldState && newState ) {
89+ return state ;
90+ }
91+
92+ let mergedState = { } ;
93+ const levels = [ 'basic' , 'intermediate' , 'advanced' , 'extreme' ] ;
94+
95+ for ( const level of levels ) {
96+ // Initialize an empty array for merged challenges
97+ let mergedChallenges = [ ] ;
98+
99+ // Create a map for quick lookup of challenges by name
100+ const oldChallengesMap = new Map ( oldState [ level ] . map ( challenge => [ challenge . name , challenge ] ) ) ;
101+ const newChallengesMap = new Map ( state [ level ] . map ( challenge => [ challenge . name , challenge ] ) ) ;
102+
103+ // Add or update challenges from the newState
104+ for ( const [ name , newChallenge ] of newChallengesMap . entries ( ) ) {
105+ let hasPassed = oldChallengesMap . get ( name ) ?. passed || newChallenge . passed ;
106+ mergedChallenges . push ( { ...newChallenge , passed : hasPassed } ) ;
107+ }
108+
109+ // Set the merged challenges for the current level in the mergedState
110+ mergedState [ level ] = mergedChallenges ;
111+ }
112+
113+ return mergedState ;
114+ }
115+ }
116+
117+ const passedState = new PassedState ( ) ;
118+ export default passedState ;
0 commit comments