@@ -3,66 +3,117 @@ const vscode = require("vscode");
3
3
/**
4
4
* @typedef {string } btnText selecting this option will not hide the notification in the future
5
5
* @typedef {string } saveAsDefaultBtnTxt selecting this option will hide the notification in the future
6
+ * @typedef {Object.<string, btnText|[btnText, saveAsDefaultBtnTxt]> } Options
6
7
*/
7
8
9
+ /**
10
+ * The Notifier class handles notifications, warnings and errors sent to the user.
11
+ * Use this.createNotification() for sending a notification.
12
+ */
8
13
class Notifier {
9
14
/** @param {PyMakr } pymakr */
10
15
constructor ( pymakr ) {
11
16
this . pymakr = pymakr ;
17
+ this . DONT_ASK_AGAIN = "No and don't ask again" ;
18
+ this . DONT_SHOW_AGAIN = "Don't show again" ;
19
+ this . messagers = {
20
+ info : vscode . window . showInformationMessage ,
21
+ warning : vscode . window . showWarningMessage ,
22
+ error : vscode . window . showErrorMessage ,
23
+ } ;
12
24
}
13
25
14
26
/**
27
+ * Creates VSCode notification, warning or error.
15
28
* @example
16
29
* createNotification(
17
30
* 'warning', // info, warning or error
18
31
* 'something happened', // the message
19
32
* // buttons, the key is the return value
20
33
* {
21
34
* optA: 'Use Option A',
22
- * // to make an option defaultable , use an array and provide the default as the second entry
35
+ * // to make an option persistable , use an array and provide the persisted value as the second entry
23
36
* optB: ['Use option B', 'Always use option B']
24
37
* }
25
38
* )
26
- * @template {Object.<string, btnText|[btnText, saveAsDefaultBtnTxt]> } Buttons
39
+ * @template {Options } Buttons
27
40
* @param {'info'|'warning'|'error' } type
28
- * @param {string } message
29
- * @param {Buttons= } options
30
- * @param {Boolean= } disableable
31
- * @param {string= } id
32
- * @returns {Promise<keyof Buttons|'disabled' > }
41
+ * @param {string } message // the message shown to the user
42
+ * @param {Buttons= } options // map of {key: choice} or {key: [choice, persistent choice]}
43
+ * @param {Boolean= } rememberable // if true, a subsequent notification will ask if the choice should be saved for future prompts
44
+ * @param {string= } id // if not set, the notification message will be used
45
+ * @returns {Promise<keyof Buttons> }
33
46
*/
34
- async createNotification ( type , message , options , disableable , id ) {
35
- id = id || message ;
36
- const storedValue = this . pymakr . config . get ( ) . get ( `notifications.${ id } ` ) ;
37
- if ( storedValue ) return storedValue ;
47
+ async createNotification ( type , message , options , rememberable , id ) {
48
+ // Stale devices have question marks after stale values. We don't want these to cause duplicates
49
+ id = id || message . replace ( / \? / g, "" ) ;
50
+ const storedValue = this . pymakr . config . get ( ) . get ( `misc.notifications` ) [ id ] ;
51
+ const messager = this . messagers [ type ] ;
52
+
53
+ const textToKeyMap = Object . entries ( options ) . reduce ( ( acc , [ key , arr ] ) => {
54
+ [ arr ] . flat ( ) . forEach ( ( val ) => ( acc [ val ] = key ) ) ;
55
+ return acc ;
56
+ } , { } ) ;
38
57
39
- const nativeOptions = disableable ? { disabled : [ false , "Don't show again" ] } : { } ;
40
- const allOptions = { ... options , ... nativeOptions } ;
58
+ // if we have a stored choice, return it - except for a DONT_ASK_AGAIN value
59
+ if ( storedValue && storedValue !== this . DONT_ASK_AGAIN ) return textToKeyMap [ storedValue ] ;
41
60
61
+ const choice = await messager ( message , ...Object . values ( options ) . flat ( ) . filter ( Boolean ) ) ;
62
+
63
+ const result = textToKeyMap [ choice ] ;
64
+
65
+ // store user's choice if wanted
66
+ this . handlePersistables ( id , options , choice , rememberable ) ;
67
+
68
+ return result ;
69
+ }
70
+
71
+ /**
72
+ * Handles persistable logic. If a choice is a persistable it will be saved.
73
+ * If it is not persistable, but the prompt is rememberable and the user makes a selection the choice will be saved.
74
+ * @param {string } id
75
+ * @param {Options } options
76
+ * @param {string } choice
77
+ * @param {Boolean } rememberable
78
+ */
79
+ async handlePersistables ( id , options , choice , rememberable ) {
42
80
// array of button texts whose values should be saved
43
- const persistables = Object . values ( allOptions )
81
+ const persistables = Object . values ( options )
44
82
. map ( ( text ) => Array . isArray ( text ) && text [ 1 ] )
45
83
. filter ( Boolean ) ;
46
84
47
- const messagers = {
48
- info : vscode . window . showInformationMessage ,
49
- warning : vscode . window . showWarningMessage ,
50
- error : vscode . window . showErrorMessage ,
51
- } ;
85
+ const shouldStoreChoice = persistables . includes ( choice ) || ( rememberable && ( await this . askToStore ( choice ) ) ) ;
86
+ if ( shouldStoreChoice === this . DONT_ASK_AGAIN ) choice = this . DONT_ASK_AGAIN ;
87
+ if ( shouldStoreChoice ) {
88
+ console . log ( "setting" , `misc.notifications.${ id } ` , choice ) ;
89
+ const config = this . pymakr . config . get ( ) . get ( "misc.notifications" ) ;
90
+ this . pymakr . config . get ( ) . update ( `misc.notifications` , { ...config , [ id ] : choice } ) ;
91
+ }
92
+ }
52
93
53
- const messager = messagers [ type ] ;
54
- const result = await messager ( message , ...Object . values ( allOptions ) . flat ( ) . filter ( Boolean ) ) ;
94
+ /**
95
+ * Prompts user if they want their choice to be remembered
96
+ * @param {string } choice
97
+ */
98
+ async askToStore ( choice ) {
99
+ if ( choice === undefined ) return false ;
55
100
56
- if ( persistables . includes ( result ) ) console . log ( "should store this thing" , result ) ;
101
+ const options = {
102
+ Yes : true ,
103
+ [ this . DONT_ASK_AGAIN ] : this . DONT_ASK_AGAIN ,
104
+ } ;
57
105
58
- const textToKeyMap = Object . entries ( allOptions ) . reduce ( ( acc , [ key , arr ] ) => {
59
- [ arr ] . flat ( ) . forEach ( val => acc [ val ] = key )
60
- return acc ;
61
- } , { } ) ;
106
+ const shouldSaveChoice = await vscode . window . showInformationMessage (
107
+ `Do you wish to save your choice: " ${ choice } "` ,
108
+ ... Object . keys ( options )
109
+ ) ;
62
110
63
- return textToKeyMap [ result ]
111
+ return options [ shouldSaveChoice ] ;
64
112
}
65
113
114
+ /**
115
+ * All available notifications in Pymakr
116
+ */
66
117
notifications = {
67
118
showSharedTerminalInfo : ( ) =>
68
119
this . createNotification (
@@ -77,16 +128,21 @@ class Notifier {
77
128
this . createNotification (
78
129
"info" ,
79
130
`${ device . displayName } seems to be busy. Do you wish restart it in safe mode?` ,
80
- { restart : "Restart in safe mode" } ,
131
+ { restart : "Restart in safe mode" , [ this . DONT_SHOW_AGAIN ] : [ null , this . DONT_SHOW_AGAIN ] } ,
81
132
true
82
133
) ,
83
134
84
135
/** @param {Device } device */
85
136
terminalAlreadyExists : ( device ) =>
86
- this . createNotification ( "info" , `A terminal for ${ device . displayName } already exists.` , {
87
- sharedTerm : [ "Create new shared terminal" , "Always create a shared terminal" ] ,
88
- openExistingTerm : [ "Open existing terminal" , "Always Open existing terminal" ] ,
89
- } ) ,
137
+ this . createNotification (
138
+ "info" ,
139
+ `A terminal for ${ device . displayName } already exists.` ,
140
+ {
141
+ sharedTerm : "Create new shared terminal" ,
142
+ openExistingTerm : "Open existing terminal" ,
143
+ } ,
144
+ true
145
+ ) ,
90
146
91
147
/** @param {Project } project */
92
148
couldNotParsePymakrConfig : ( project ) =>
@@ -118,7 +174,8 @@ class Notifier {
118
174
couldNotSafeboot : ( device ) =>
119
175
this . createNotification (
120
176
"warning" ,
121
- "Could not safeboot device. Please hard reset the device and verify that a shield is installed."
177
+ "Could not safeboot device. Please hard reset the device and verify that a shield is installed." ,
178
+ { [ this . DONT_SHOW_AGAIN ] : [ null , this . DONT_SHOW_AGAIN ] }
122
179
) ,
123
180
} ;
124
181
}
0 commit comments