-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add startup notifications defined by remote GH source (#502)
- Loading branch information
Showing
11 changed files
with
247 additions
and
46 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
# Extension metadata | ||
|
||
**DO NOT DELETE THIS FOLDER UNLESS YOU KNOW WHAT YOU ARE DOING** | ||
|
||
This folder contains remotely-updated metadata to provide updates to the Cadence VSCode Extension without requiring a new release of the extension itself. When consuming this metadata, the latest commit to the default repository branch should be assumed to be the latest version of the extension metadata. | ||
|
||
Currently, it is only used by the Cadence VSCode Extension to fetch any notifications that should be displayed to the user. | ||
|
||
## Notfications schema | ||
|
||
```ts | ||
interface Notification { | ||
_type: 'Notification' | ||
id: string | ||
type: 'error' | 'info' | 'warning' | ||
text: string | ||
buttons?: Array<{ | ||
label: string | ||
link: string | ||
}> | ||
suppressable?: boolean | ||
} | ||
``` | ||
|
||
### Fields | ||
|
||
- `_type`: The type of the object. Should always be `"Notification"`. | ||
- `id`: A unique identifier for the notification. This is used to determine if the notification has already been displayed to the user. | ||
- `type`: The type of notification. Can be `"info"`, `"warning"`, or `"error"`. | ||
- `text`: The text to display to the user. | ||
- `buttons`: An array of buttons to display to the user. Each button should have a `label` field and a `link` field. The `link` field should be a URL to open when the button is clicked. | ||
- `suppressable`: Whether or not the user should be able to suppress the notification. (defaults to `true`) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
[ | ||
{ | ||
"_type": "Notification", | ||
"id": "1", | ||
"type": "info", | ||
"text": "Cadence 1.0 pre-release builds are now available! Developers should begin upgrading their projects - see the Cadence 1.0 Migration Guide for more details.", | ||
"buttons": [ | ||
{ | ||
"label": "Learn More", | ||
"link": "https://cadence-lang.org/docs/cadence-migration-guide" | ||
} | ||
], | ||
"suppressable": false | ||
} | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { Memento } from 'vscode' | ||
|
||
interface State { | ||
dismissedNotifications: string[] | ||
} | ||
|
||
export class StorageProvider { | ||
#globalState: Memento | ||
|
||
constructor (globalState: Memento) { | ||
this.#globalState = globalState | ||
} | ||
|
||
get<T extends keyof State>(key: T, fallback: State[T]): State[T] { | ||
return this.#globalState.get(key, fallback) | ||
} | ||
|
||
async set<T extends keyof State>(key: T, value: State[T]): Promise<void> { | ||
return await (this.#globalState.update(key, value) as Promise<void>) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import { StorageProvider } from '../storage/storage-provider' | ||
import { promptUserErrorMessage, promptUserInfoMessage, promptUserWarningMessage } from './prompts' | ||
import * as vscode from 'vscode' | ||
|
||
const NOTIFICATIONS_URL = 'https://raw.githubusercontent.com/onflow/vscode-cadence/master/.metadata/notifications.json' | ||
|
||
export interface Notification { | ||
_type: 'Notification' | ||
id: string | ||
type: 'error' | 'info' | 'warning' | ||
text: string | ||
buttons?: Array<{ | ||
label: string | ||
link: string | ||
}> | ||
suppressable?: boolean | ||
} | ||
|
||
export class NotificationProvider { | ||
#storageProvider: StorageProvider | ||
|
||
constructor ( | ||
storageProvider: StorageProvider | ||
) { | ||
this.#storageProvider = storageProvider | ||
} | ||
|
||
activate (): void { | ||
void this.#fetchAndDisplayNotifications() | ||
} | ||
|
||
async #fetchAndDisplayNotifications (): Promise<void> { | ||
// Fetch notifications | ||
const notifications = await this.#fetchNotifications() | ||
|
||
// Display all valid notifications | ||
notifications | ||
.filter(this.#notificationFilter.bind(this)) | ||
.forEach(this.#displayNotification.bind(this)) | ||
} | ||
|
||
#displayNotification (notification: Notification): void { | ||
const transformButton = (button: { label: string, link: string }): { label: string, callback: () => void } => { | ||
return { | ||
label: button.label, | ||
callback: () => { | ||
void vscode.env.openExternal(vscode.Uri.parse(button.link)) | ||
} | ||
} | ||
} | ||
|
||
// Transform buttons | ||
let buttons: Array<{ label: string, callback: () => void }> = [] | ||
if (notification.suppressable === true) { | ||
buttons = [{ | ||
label: 'Don\'t show again', | ||
callback: () => { | ||
this.#dismissNotification(notification) | ||
} | ||
}] | ||
} | ||
buttons = buttons?.concat(notification.buttons?.map(transformButton) ?? []) | ||
|
||
if (notification.type === 'error') { | ||
promptUserErrorMessage(notification.text, buttons) | ||
} else if (notification.type === 'info') { | ||
promptUserInfoMessage(notification.text, buttons) | ||
} else if (notification.type === 'warning') { | ||
promptUserWarningMessage(notification.text, buttons) | ||
} | ||
} | ||
|
||
#notificationFilter (notification: Notification): boolean { | ||
if (notification.suppressable === true && this.#isNotificationDismissed(notification)) { | ||
return false | ||
} | ||
|
||
return true | ||
} | ||
|
||
async #fetchNotifications (): Promise<Notification[]> { | ||
return await fetch(NOTIFICATIONS_URL).then(async res => await res.json()).then((notifications: Notification[]) => { | ||
return notifications | ||
}).catch(() => { | ||
return [] | ||
}) | ||
} | ||
|
||
#dismissNotification (notification: Notification): void { | ||
const dismissedNotifications = this.#storageProvider.get('dismissedNotifications', []) | ||
void this.#storageProvider.set('dismissedNotifications', [...dismissedNotifications, notification.id]) | ||
} | ||
|
||
#isNotificationDismissed (notification: Notification): boolean { | ||
const dismissedNotifications = this.#storageProvider.get('dismissedNotifications', []) | ||
return dismissedNotifications.includes(notification.id) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,43 @@ | ||
/* Information and error prompts */ | ||
import { window } from 'vscode' | ||
|
||
export function promptUserInfoMessage (message: string, buttonText: string, callback: Function): void { | ||
export interface PromptButton { | ||
label: string | ||
callback: Function | ||
} | ||
|
||
export function promptUserInfoMessage (message: string, buttons: PromptButton[] = []): void { | ||
window.showInformationMessage( | ||
message, | ||
buttonText | ||
...buttons.map((button) => button.label) | ||
).then((choice) => { | ||
if (choice === buttonText) { | ||
callback() | ||
const button = buttons.find((button) => button.label === choice) | ||
if (button != null) { | ||
button.callback() | ||
} | ||
}, () => {}) | ||
} | ||
|
||
export function promptUserErrorMessage (message: string, buttonText: string, callback: Function): void { | ||
export function promptUserErrorMessage (message: string, buttons: PromptButton[] = []): void { | ||
window.showErrorMessage( | ||
message, | ||
buttonText | ||
...buttons.map((button) => button.label) | ||
).then((choice) => { | ||
const button = buttons.find((button) => button.label === choice) | ||
if (button != null) { | ||
button.callback() | ||
} | ||
}, () => {}) | ||
} | ||
|
||
export function promptUserWarningMessage (message: string, buttons: PromptButton[] = []): void { | ||
window.showWarningMessage( | ||
message, | ||
...buttons.map((button) => button.label) | ||
).then((choice) => { | ||
if (choice === buttonText) { | ||
callback() | ||
const button = buttons.find((button) => button.label === choice) | ||
if (button != null) { | ||
button.callback() | ||
} | ||
}, () => {}) | ||
} |
Oops, something went wrong.