Skip to content

Commit 0e3ff23

Browse files
feat: added ability to save prompt choices
1 parent 190aa48 commit 0e3ff23

File tree

2 files changed

+95
-38
lines changed

2 files changed

+95
-38
lines changed

package.json

+5-5
Original file line numberDiff line numberDiff line change
@@ -264,12 +264,12 @@
264264
}
265265
},
266266
{
267-
"title": "Notifications",
267+
"title": "Misc",
268268
"properties": {
269-
"pymakr.notifications.showSharedTerminalInfo": {
270-
"type": "boolean",
271-
"default": true,
272-
"description": "Show notification when a shared terminal is created."
269+
"pymakr.misc.notifications": {
270+
"description": "Stored choices for Pymakr notifications. To undo a choice, simply delete the entry.",
271+
"type": "object",
272+
"additionalProperties": "/.+/"
273273
}
274274
}
275275
}

src/utils/Notifier.js

+90-33
Original file line numberDiff line numberDiff line change
@@ -3,66 +3,117 @@ const vscode = require("vscode");
33
/**
44
* @typedef {string} btnText selecting this option will not hide the notification in the future
55
* @typedef {string} saveAsDefaultBtnTxt selecting this option will hide the notification in the future
6+
* @typedef {Object.<string, btnText|[btnText, saveAsDefaultBtnTxt]>} Options
67
*/
78

9+
/**
10+
* The Notifier class handles notifications, warnings and errors sent to the user.
11+
* Use this.createNotification() for sending a notification.
12+
*/
813
class Notifier {
914
/** @param {PyMakr} pymakr */
1015
constructor(pymakr) {
1116
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+
};
1224
}
1325

1426
/**
27+
* Creates VSCode notification, warning or error.
1528
* @example
1629
* createNotification(
1730
* 'warning', // info, warning or error
1831
* 'something happened', // the message
1932
* // buttons, the key is the return value
2033
* {
2134
* 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
2336
* optB: ['Use option B', 'Always use option B']
2437
* }
2538
* )
26-
* @template {Object.<string, btnText|[btnText, saveAsDefaultBtnTxt]>} Buttons
39+
* @template {Options} Buttons
2740
* @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>}
3346
*/
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+
}, {});
3857

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];
4160

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) {
4280
// array of button texts whose values should be saved
43-
const persistables = Object.values(allOptions)
81+
const persistables = Object.values(options)
4482
.map((text) => Array.isArray(text) && text[1])
4583
.filter(Boolean);
4684

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+
}
5293

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;
55100

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+
};
57105

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+
);
62110

63-
return textToKeyMap[result]
111+
return options[shouldSaveChoice];
64112
}
65113

114+
/**
115+
* All available notifications in Pymakr
116+
*/
66117
notifications = {
67118
showSharedTerminalInfo: () =>
68119
this.createNotification(
@@ -77,16 +128,21 @@ class Notifier {
77128
this.createNotification(
78129
"info",
79130
`${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] },
81132
true
82133
),
83134

84135
/** @param {Device} device */
85136
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+
),
90146

91147
/** @param {Project} project */
92148
couldNotParsePymakrConfig: (project) =>
@@ -118,7 +174,8 @@ class Notifier {
118174
couldNotSafeboot: (device) =>
119175
this.createNotification(
120176
"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] }
122179
),
123180
};
124181
}

0 commit comments

Comments
 (0)