diff --git a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts index 9ef3c2423..d52969253 100644 --- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts @@ -121,6 +121,7 @@ import { SaveAsSketch } from './contributions/save-as-sketch'; import { SaveSketch } from './contributions/save-sketch'; import { VerifySketch } from './contributions/verify-sketch'; import { UploadSketch } from './contributions/upload-sketch'; +import { SurveyNotification } from './contributions/survey-notification'; import { CommonFrontendContribution } from './theia/core/common-frontend-contribution'; import { EditContributions } from './contributions/edit-contributions'; import { OpenSketchExternal } from './contributions/open-sketch-external'; @@ -291,6 +292,10 @@ import { PreferenceTreeGenerator } from './theia/preferences/preference-tree-gen import { PreferenceTreeGenerator as TheiaPreferenceTreeGenerator } from '@theia/preferences/lib/browser/util/preference-tree-generator'; import { AboutDialog } from './theia/core/about-dialog'; import { AboutDialog as TheiaAboutDialog } from '@theia/core/lib/browser/about-dialog'; +import { + SurveyNotificationService, + SurveyNotificationServicePath, +} from '../common/protocol/survey-service'; MonacoThemingService.register({ id: 'arduino-theme', @@ -475,6 +480,19 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(EditorMode).toSelf().inSingletonScope(); bind(FrontendApplicationContribution).toService(EditorMode); + // Survey notification + bind(SurveyNotification).toSelf().inSingletonScope(); + bind(FrontendApplicationContribution).toService(SurveyNotification); + + bind(SurveyNotificationService) + .toDynamicValue((context) => { + return ElectronIpcConnectionProvider.createProxy( + context.container, + SurveyNotificationServicePath + ); + }) + .inSingletonScope(); + // Layout and shell customizations. rebind(TheiaOutlineViewContribution) .to(OutlineViewContribution) diff --git a/arduino-ide-extension/src/browser/arduino-preferences.ts b/arduino-ide-extension/src/browser/arduino-preferences.ts index 0aec020d5..8450012a6 100644 --- a/arduino-ide-extension/src/browser/arduino-preferences.ts +++ b/arduino-ide-extension/src/browser/arduino-preferences.ts @@ -174,6 +174,14 @@ export const ArduinoConfigSchema: PreferenceSchema = { ), default: 'https://auth.arduino.cc/login#/register', }, + 'arduino.survey.notification': { + type: 'boolean', + description: nls.localize( + 'arduino/preferences/survey.notification', + 'True if users should be notified if a survey is available. True by default.' + ), + default: true, + }, }, }; @@ -198,6 +206,7 @@ export interface ArduinoConfiguration { 'arduino.auth.domain': string; 'arduino.auth.audience': string; 'arduino.auth.registerUri': string; + 'arduino.survey.notification': boolean; } export const ArduinoPreferences = Symbol('ArduinoPreferences'); diff --git a/arduino-ide-extension/src/browser/contributions/survey-notification.ts b/arduino-ide-extension/src/browser/contributions/survey-notification.ts new file mode 100644 index 000000000..e1a4817a6 --- /dev/null +++ b/arduino-ide-extension/src/browser/contributions/survey-notification.ts @@ -0,0 +1,78 @@ +import { MessageService } from '@theia/core'; +import { FrontendApplicationContribution } from '@theia/core/lib/browser'; +import { inject, injectable } from '@theia/core/shared/inversify'; +import { LocalStorageService } from '@theia/core/lib/browser'; +import { nls } from '@theia/core/lib/common'; +import { WindowService } from '@theia/core/lib/browser/window/window-service'; +import { ArduinoPreferences } from '../arduino-preferences'; +import { SurveyNotificationService } from '../../common/protocol/survey-service'; + +const SURVEY_MESSAGE = nls.localize( + 'arduino/survey/surveyMessage', + 'Please help us improve by answering this super short survey. We value our community and would like to get to know our supporters a little better.' +); +const DO_NOT_SHOW_AGAIN = nls.localize( + 'arduino/survey/dismissSurvey', + "Don't show again" +); +const GO_TO_SURVEY = nls.localize( + 'arduino/survey/answerSurvey', + 'Answer survey' +); + +const SURVEY_BASE_URL = 'https://surveys.hotjar.com/'; +const surveyId = '17887b40-e1f0-4bd6-b9f0-a37f229ccd8b'; + +@injectable() +export class SurveyNotification implements FrontendApplicationContribution { + @inject(MessageService) + private readonly messageService: MessageService; + + @inject(LocalStorageService) + private readonly localStorageService: LocalStorageService; + + @inject(WindowService) + private readonly windowService: WindowService; + + @inject(ArduinoPreferences) + private readonly arduinoPreferences: ArduinoPreferences; + + @inject(SurveyNotificationService) + private readonly surveyNotificationService: SurveyNotificationService; + + onStart(): void { + this.arduinoPreferences.ready.then(async () => { + if ( + (await this.surveyNotificationService.isFirstInstance()) && + this.arduinoPreferences.get('arduino.survey.notification') + ) { + const surveyAnswered = await this.localStorageService.getData( + this.surveyKey(surveyId) + ); + if (surveyAnswered !== undefined) { + return; + } + const answer = await this.messageService.info( + SURVEY_MESSAGE, + DO_NOT_SHOW_AGAIN, + GO_TO_SURVEY + ); + switch (answer) { + case GO_TO_SURVEY: + this.windowService.openNewWindow(SURVEY_BASE_URL + surveyId, { + external: true, + }); + this.localStorageService.setData(this.surveyKey(surveyId), true); + break; + case DO_NOT_SHOW_AGAIN: + this.localStorageService.setData(this.surveyKey(surveyId), false); + break; + } + } + }); + } + + private surveyKey(id: string): string { + return `answered_survey:${id}`; + } +} diff --git a/arduino-ide-extension/src/common/protocol/survey-service.ts b/arduino-ide-extension/src/common/protocol/survey-service.ts new file mode 100644 index 000000000..3ab53b230 --- /dev/null +++ b/arduino-ide-extension/src/common/protocol/survey-service.ts @@ -0,0 +1,7 @@ +export const SurveyNotificationServicePath = + '/services/survey-notification-service'; +export const SurveyNotificationService = Symbol('SurveyNotificationService'); + +export interface SurveyNotificationService { + isFirstInstance(): Promise; +} diff --git a/arduino-ide-extension/src/electron-main/arduino-electron-main-module.ts b/arduino-ide-extension/src/electron-main/arduino-electron-main-module.ts index 7df38d649..7da0c7314 100644 --- a/arduino-ide-extension/src/electron-main/arduino-electron-main-module.ts +++ b/arduino-ide-extension/src/electron-main/arduino-electron-main-module.ts @@ -21,6 +21,11 @@ import { import { IDEUpdaterImpl } from './ide-updater/ide-updater-impl'; import { TheiaElectronWindow } from './theia/theia-electron-window'; import { TheiaElectronWindow as DefaultTheiaElectronWindow } from '@theia/core/lib/electron-main/theia-electron-window'; +import { SurveyNotificationServiceImpl } from '../node/survey-service-impl'; +import { + SurveyNotificationService, + SurveyNotificationServicePath, +} from '../common/protocol/survey-service'; export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(ElectronMainApplication).toSelf().inSingletonScope(); @@ -61,4 +66,21 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(TheiaElectronWindow).toSelf(); rebind(DefaultTheiaElectronWindow).toService(TheiaElectronWindow); + + // Survey notification bindings + bind(SurveyNotificationServiceImpl).toSelf().inSingletonScope(); + bind(SurveyNotificationService).toService(SurveyNotificationServiceImpl); + bind(ElectronMainApplicationContribution).toService( + SurveyNotificationService + ); + bind(ElectronConnectionHandler) + .toDynamicValue( + (context) => + new JsonRpcConnectionHandler(SurveyNotificationServicePath, () => + context.container.get( + SurveyNotificationService + ) + ) + ) + .inSingletonScope(); }); diff --git a/arduino-ide-extension/src/node/survey-service-impl.ts b/arduino-ide-extension/src/node/survey-service-impl.ts new file mode 100644 index 000000000..aea8c0472 --- /dev/null +++ b/arduino-ide-extension/src/node/survey-service-impl.ts @@ -0,0 +1,20 @@ +import { injectable } from '@theia/core/shared/inversify'; +import { SurveyNotificationService } from '../common/protocol/survey-service'; + +/** + * Service for checking if it is the first instance of the IDE, in this case it sets a flag to true. + * This flag is used to prevent the survey notification from being visible in every open window. It must only be shown on one window. + */ +@injectable() +export class SurveyNotificationServiceImpl + implements SurveyNotificationService +{ + private surveyDidShow = false; + async isFirstInstance(): Promise { + if (this.surveyDidShow) { + return false; + } + this.surveyDidShow = true; + return this.surveyDidShow; + } +} diff --git a/i18n/en.json b/i18n/en.json index 959f0e989..08cafdf72 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -256,6 +256,7 @@ "showVerbose": "Show verbose output during", "sketchbook.location": "Sketchbook location", "sketchbook.showAllFiles": "True to show all sketch files inside the sketch. It is false by default.", + "survey.notification": "True if users should be notified if a survey is available. True by default.", "unofficialBoardSupport": "Click for a list of unofficial board support URLs", "upload": "upload", "upload.verbose": "True for verbose upload output. False by default.", @@ -305,6 +306,11 @@ "verify": "Verify", "verifyOrCompile": "Verify/Compile" }, + "survey": { + "answerSurvey": "Answer survey", + "dismissSurvey": "Don't show again", + "surveyMessage": "Please help us improve by answering this super short survey. We value our community and would like to get to know our supporters a little better." + }, "upload": { "error": "{0} error: {1}" },