@@ -14,145 +14,149 @@ import { client } from "../extension";
14
14
import _ from "lodash" ;
15
15
import { McqPanelWithLogging as McqPanel } from "../webview/components/McqPanel" ;
16
16
17
- let mcqPanel : vscode . WebviewPanel | null = null ;
17
+ /*
18
+ * MessageHandler is a singleton class that handles messages from the frontend
19
+ * and manages the state of the webview panels.
20
+ * Design choice: Active panel, mcqPanel, and activeEditor are all stored in the same class.
21
+ * Trade-off: Requires an instance of MessageHandler to be created and used instead of exporting
22
+ * the activeEditor and active panel directly.
23
+ */
24
+ export class MessageHandler {
25
+ private messageQueue : MessageType [ ] = [ ] ;
26
+ private handling = false ;
27
+ public panel : vscode . WebviewPanel | null = null ;
28
+ public mcqPanel : vscode . WebviewPanel | null = null ;
29
+ public activeEditor : Editor | null = null ;
18
30
19
- let panel : vscode . WebviewPanel | null = null ;
20
- // This needs to be a reference to active
21
- // TODO: Fix this ugly code!
22
- export let activeEditor : Editor | null = null ;
23
-
24
- const messageQueue : MessageType [ ] = [ ] ;
25
- let handling = false ;
31
+ async handleMessage ( context : vscode . ExtensionContext , message : MessageType ) {
32
+ this . messageQueue . push ( message ) ;
33
+ if ( this . handling ) {
34
+ return ;
35
+ }
36
+ this . handling = true ;
26
37
27
- export async function handleMessage (
28
- context : vscode . ExtensionContext ,
29
- message : MessageType ,
30
- ) {
31
- messageQueue . push ( message ) ;
32
- if ( handling ) {
33
- return ;
34
- }
35
- handling = true ;
38
+ while ( this . messageQueue . length > 0 ) {
39
+ const message = this . messageQueue . shift ( ) ! ;
40
+ console . log ( `${ Date . now ( ) } Beginning handleMessage: ${ message . type } ` ) ;
41
+ switch ( message . type ) {
42
+ case MessageTypeNames . ExtensionPing :
43
+ sendToFrontend ( this . panel , Messages . ExtensionPong ( null ) ) ;
44
+ break ;
45
+ case MessageTypeNames . MCQQuestion : {
46
+ if ( this . mcqPanel === null ) {
47
+ this . mcqPanel = vscode . window . createWebviewPanel (
48
+ "mcq-question-panel" ,
49
+ `Question ${ message . questionId + 1 } ` ,
50
+ vscode . ViewColumn . One ,
51
+ { enableScripts : true , retainContextWhenHidden : true } ,
52
+ ) ;
36
53
37
- while ( messageQueue . length > 0 ) {
38
- const message = messageQueue . shift ( ) ! ;
39
- console . log ( `${ Date . now ( ) } Beginning handleMessage: ${ message . type } ` ) ;
40
- switch ( message . type ) {
41
- case MessageTypeNames . ExtensionPing :
42
- sendToFrontend ( panel , Messages . ExtensionPong ( null ) ) ;
43
- break ;
44
- case MessageTypeNames . MCQQuestion : {
45
- if ( mcqPanel === null ) {
46
- mcqPanel = vscode . window . createWebviewPanel (
47
- "mcq-question-panel" ,
48
- `Question ${ message . questionId + 1 } ` ,
49
- vscode . ViewColumn . One ,
50
- { enableScripts : true , retainContextWhenHidden : true } ,
54
+ this . mcqPanel . onDidDispose ( ( ) => {
55
+ this . mcqPanel = null ;
56
+ } ) ;
57
+ }
58
+ this . mcqPanel . title = `Question ${ message . questionId + 1 } ` ;
59
+ this . mcqPanel . iconPath = vscode . Uri . joinPath (
60
+ context . extensionUri ,
61
+ "assets" ,
62
+ "icon.png" ,
51
63
) ;
52
-
53
- mcqPanel . onDidDispose ( ( ) => {
54
- mcqPanel = null ;
64
+ let activePanel = await McqPanel ( {
65
+ data : {
66
+ assessmentName : message . assessmentName ?? "" ,
67
+ questionId : message . questionId ,
68
+ question : message . question ,
69
+ choices : message . options ,
70
+ workspaceLocation : message . workspaceLocation ?? "assessment" ,
71
+ } ,
55
72
} ) ;
56
- }
57
- mcqPanel . title = `Question ${ message . questionId + 1 } ` ;
58
- mcqPanel . iconPath = vscode . Uri . joinPath (
59
- context . extensionUri ,
60
- "assets" ,
61
- "icon.png" ,
62
- ) ;
63
- let activePanel = await McqPanel ( {
64
- data : {
65
- assessmentName : message . assessmentName ?? "" ,
66
- questionId : message . questionId ,
67
- question : message . question ,
68
- choices : message . options ,
69
- workspaceLocation : message . workspaceLocation ?? "assessment" ,
70
- } ,
71
- } ) ;
72
- setWebviewContent (
73
- mcqPanel ,
74
- context ,
75
- < div
76
- // @ts -ignore: Our FakeReact doesn't modify the style attribute
77
- style = "width: 100%; height: calc(100vh - 10px)"
78
- id = "mcq-panel"
79
- >
80
- { ReactDOMServer . renderToString ( activePanel ) }
81
- </ div > ,
82
- ) ;
83
- mcqPanel . reveal ( vscode . ViewColumn . One ) ;
73
+ setWebviewContent (
74
+ this . mcqPanel ,
75
+ context ,
76
+ < div
77
+ // @ts -ignore: Our FakeReact doesn't modify the style attribute
78
+ style = "width: 100%; height: calc(100vh - 10px)"
79
+ id = "mcq-panel"
80
+ >
81
+ { ReactDOMServer . renderToString ( activePanel ) }
82
+ </ div > ,
83
+ ) ;
84
+ this . mcqPanel . reveal ( vscode . ViewColumn . One ) ;
84
85
85
- break ;
86
- }
87
- case MessageTypeNames . MCQAnswer :
88
- console . log ( `Received MCQ answer: ${ message . choice } ` ) ;
89
- sendToFrontend ( panel , message ) ;
90
- break ;
91
- case MessageTypeNames . NewEditor :
92
- // console.log(message.questionType + " questionType \n");
93
- // if (message.questionType == "mcq") {
94
- // break;
95
- // }
96
- activeEditor = await Editor . create (
97
- message . workspaceLocation ,
98
- message . assessmentName ,
99
- message . questionId ,
100
- message . prepend ,
101
- message . initialCode ,
102
- ) ;
103
- activeEditor . uri ;
104
- const info = context . globalState . get ( "info" ) ?? { } ;
105
- if ( activeEditor . uri ) {
106
- // TODO: Write our own wrapper to set nested keys easily, removing lodash
107
- // @ts -ignore
108
- _ . set ( info , `["${ activeEditor . uri } "].chapter` , message . chapter ?? 1 ) ;
109
- // TODO: message.prepend can be undefined in runtime, investigate
110
- const nPrependLines =
111
- message . prepend && message . prepend !== ""
112
- ? message . prepend . split ( "\n" ) . length + 2 // account for start/end markers
113
- : 0 ;
114
- _ . set ( info , `["${ activeEditor . uri } "].prepend` , nPrependLines ) ;
115
- context . globalState . update ( "info" , info ) ;
116
- client . sendRequest ( "source/publishInfo" , info ) ;
86
+ break ;
117
87
}
118
-
119
- panel ?. reveal ( vscode . ViewColumn . Two ) ;
120
- console . log (
121
- `EXTENSION: NewEditor: activeEditor set to ${ activeEditor . assessmentName } _${ activeEditor . questionId } ` ,
122
- ) ;
123
-
124
- activeEditor . onChange ( ( editor ) => {
125
- const workspaceLocation = editor . workspaceLocation ;
126
- const code = editor . getText ( ) ;
127
- if ( ! code ) {
128
- return ;
129
- }
130
- if ( editor !== activeEditor ) {
131
- console . log (
132
- `EXTENSION: Editor ${ editor . assessmentName } _${ editor . questionId } _${ editor . assessmentType } is no longer active, skipping onChange` ,
88
+ case MessageTypeNames . MCQAnswer :
89
+ console . log ( `Received MCQ answer: ${ message . choice } ` ) ;
90
+ sendToFrontend ( this . panel , message ) ;
91
+ break ;
92
+ case MessageTypeNames . NewEditor :
93
+ this . activeEditor = await Editor . create (
94
+ message . workspaceLocation ,
95
+ message . assessmentName ,
96
+ message . questionId ,
97
+ message . prepend ,
98
+ message . initialCode ,
99
+ ) ;
100
+ this . activeEditor . uri ;
101
+ const info = context . globalState . get ( "info" ) ?? { } ;
102
+ if ( this . activeEditor . uri ) {
103
+ // TODO: Write our own wrapper to set nested keys easily, removing lodash
104
+ // @ts -ignore
105
+ _ . set (
106
+ info ,
107
+ `["${ this . activeEditor . uri } "].chapter` ,
108
+ message . chapter ?? 1 ,
133
109
) ;
110
+ // TODO: message.prepend can be undefined in runtime, investigate
111
+ const nPrependLines =
112
+ message . prepend && message . prepend !== ""
113
+ ? message . prepend . split ( "\n" ) . length + 2 // account for start/end markers
114
+ : 0 ;
115
+ _ . set ( info , `["${ this . activeEditor . uri } "].prepend` , nPrependLines ) ;
116
+ context . globalState . update ( "info" , info ) ;
117
+ client . sendRequest ( "source/publishInfo" , info ) ;
134
118
}
135
- if ( activeEditor ) {
136
- console . log ( "activeEditor keys and values:" ) ;
137
- Object . entries ( activeEditor ) . forEach ( ( [ key , value ] ) => {
138
- console . log ( `${ key } :` , value ) ;
139
- } ) ;
140
- }
141
- const message = Messages . Text ( workspaceLocation , code ) ;
142
- console . log ( `Sending message: ${ JSON . stringify ( message ) } ` ) ;
143
- sendToFrontend ( panel , message ) ;
144
- } ) ;
145
- break ;
146
- // case MessageTypeNames.Text:
147
- // if (!activeEditor) {
148
- // console.log("ERROR: activeEditor is not set");
149
- // break;
150
- // }
151
- // activeEditor.replace(message.code, "Text");
152
- // break;
119
+
120
+ this . panel ?. reveal ( vscode . ViewColumn . Two ) ;
121
+ console . log (
122
+ `EXTENSION: NewEditor: activeEditor set to ${ this . activeEditor . assessmentName } _${ this . activeEditor . questionId } ` ,
123
+ ) ;
124
+
125
+ this . activeEditor . onChange ( ( editor : Editor ) => {
126
+ const workspaceLocation = editor . workspaceLocation ;
127
+ const code = editor . getText ( ) ;
128
+ if ( ! code ) {
129
+ return ;
130
+ }
131
+ if ( editor !== this . activeEditor ) {
132
+ console . log (
133
+ `EXTENSION: Editor ${ editor . assessmentName } _${ editor . questionId } _${ editor . assessmentType } is no longer active, skipping onChange` ,
134
+ ) ;
135
+ }
136
+ if ( this . activeEditor ) {
137
+ console . log ( "activeEditor keys and values:" ) ;
138
+ Object . entries ( this . activeEditor ) . forEach ( ( [ key , value ] ) => {
139
+ console . log ( `${ key } :` , value ) ;
140
+ } ) ;
141
+ }
142
+ const message = Messages . Text ( workspaceLocation , code ) ;
143
+ console . log ( `Sending message: ${ JSON . stringify ( message ) } ` ) ;
144
+ sendToFrontend ( this . panel , message ) ;
145
+ } ) ;
146
+ break ;
147
+ }
148
+ console . log ( `${ Date . now ( ) } Finish handleMessage: ${ message . type } ` ) ;
153
149
}
154
- console . log ( `${ Date . now ( ) } Finish handleMessage: ${ message . type } ` ) ;
150
+
151
+ this . handling = false ;
155
152
}
156
153
157
- handling = false ;
154
+ static instance : MessageHandler | null = null ;
155
+
156
+ static getInstance ( ) {
157
+ if ( ! MessageHandler . instance ) {
158
+ MessageHandler . instance = new MessageHandler ( ) ;
159
+ }
160
+ return MessageHandler . instance ;
161
+ }
158
162
}
0 commit comments