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