From 2619ba906c87f34347077c17333379550459e820 Mon Sep 17 00:00:00 2001 From: Sebastian Richner Date: Mon, 19 Feb 2024 13:44:24 +0100 Subject: [PATCH] Fix build issue, improve experience sampling --- src/electron/electron/ipc/Api.ts | 50 ++++++++++++++++--- src/electron/electron/main/index.ts | 1 - .../electron/main/services/SettingsService.ts | 2 +- .../electron/main/services/WindowService.ts | 25 +++++++--- src/electron/electron/shared/StudyConfig.ts | 35 ------------- src/electron/shared/StudyConfiguration.ts | 36 +++++++++++++ .../config => shared}/study.config.ts | 5 +- .../shared => src/utils}/Commands.ts | 1 + .../{electron/shared => src/utils}/Events.ts | 0 .../ipc => src/utils}/TypedIpcMain.ts | 0 src/electron/src/utils/typedIpcRenderer.ts | 6 +-- src/electron/src/views/ExperienceSampling.vue | 45 +++++++++++++---- 12 files changed, 139 insertions(+), 67 deletions(-) delete mode 100644 src/electron/electron/shared/StudyConfig.ts create mode 100644 src/electron/shared/StudyConfiguration.ts rename src/electron/{electron/config => shared}/study.config.ts (87%) rename src/electron/{electron/shared => src/utils}/Commands.ts (73%) rename src/electron/{electron/shared => src/utils}/Events.ts (100%) rename src/electron/{electron/ipc => src/utils}/TypedIpcMain.ts (100%) diff --git a/src/electron/electron/ipc/Api.ts b/src/electron/electron/ipc/Api.ts index 61eeda7d..f1b3cb7d 100644 --- a/src/electron/electron/ipc/Api.ts +++ b/src/electron/electron/ipc/Api.ts @@ -2,13 +2,47 @@ import { TypedIpcMain } from './TypedIpcMain'; import Events from '../shared/Events'; import Commands from '../shared/Commands'; import { ExperienceSamplingService } from '../main/services/ExperienceSamplingService'; -import { ipcMain } from 'electron'; +import { ipcMain, IpcMainInvokeEvent } from 'electron'; +import { WindowService } from '../main/services/WindowService'; +import { getLogger } from '../shared/Logger'; -const typedIpcMain: TypedIpcMain = ipcMain as TypedIpcMain; -const experienceSamplingService: ExperienceSamplingService = new ExperienceSamplingService(); -typedIpcMain.handle( - 'createExperienceSample', - async (e, promptedAt: number, question: string, response: number): Promise => { - await experienceSamplingService.createExperienceSample(promptedAt, question, response); + +const LOG = getLogger('IpcHandler') +export class IpcHandler { + private readonly actions: any; + private readonly windowService: WindowService; + + private readonly experienceSamplingService: ExperienceSamplingService; + private typedIpcMain: TypedIpcMain = ipcMain as TypedIpcMain; + + constructor(windowService: WindowService, experienceSamplingService: ExperienceSamplingService) { + this.windowService = windowService; + this.experienceSamplingService = experienceSamplingService; + this.actions = { + createExperienceSample: this.createExperienceSample, + closeExperienceSamplingWindow: this.closeExperienceSamplingWindow + }; + } + + public init(): void { + Object.keys(this.actions).forEach((action: string): void => { + LOG.info(`ipcMain.handle setup: ${action}`); + ipcMain.handle(action, async (_event: IpcMainInvokeEvent, ...args): Promise => { + try { + return await this.actions[action].apply(this, args); + } catch (error) { + LOG.error(error); + return error; + } + }); + }); + } + + private async createExperienceSample(promptedAt: number, question: string, response: number) { + await this.experienceSamplingService.createExperienceSample(promptedAt, question, response); + } + + private async closeExperienceSamplingWindow(): Promise { + await this.windowService.closeExperienceSamplingWindow(); } -); +} diff --git a/src/electron/electron/main/index.ts b/src/electron/electron/main/index.ts index 36cd92e5..e85ec172 100644 --- a/src/electron/electron/main/index.ts +++ b/src/electron/electron/main/index.ts @@ -8,7 +8,6 @@ import log from 'electron-log/main'; import { getLogger } from '../shared/Logger'; import { DatabaseService } from './services/DatabaseService'; import { SettingsService } from './services/SettingsService'; -import studyConfig from '../config/study.config'; import { TrackerType } from '../enums/TrackerType.enum'; import { WindowActivityTrackerService } from './services/trackers/WindowActivityTrackerService'; import { UserInputTrackerService } from './services/trackers/UserInputTrackerService'; diff --git a/src/electron/electron/main/services/SettingsService.ts b/src/electron/electron/main/services/SettingsService.ts index 42ad5ff4..f1197ffa 100644 --- a/src/electron/electron/main/services/SettingsService.ts +++ b/src/electron/electron/main/services/SettingsService.ts @@ -1,6 +1,6 @@ import { Settings } from '../entities/Settings'; -import studyConfig from '../../config/study.config'; import { generateAlphaNumericString } from './utils/helpers'; +import studyConfig from '../../../shared/study.config'; export class SettingsService { public async init(): Promise { diff --git a/src/electron/electron/main/services/WindowService.ts b/src/electron/electron/main/services/WindowService.ts index 769512ae..d1f33c90 100644 --- a/src/electron/electron/main/services/WindowService.ts +++ b/src/electron/electron/main/services/WindowService.ts @@ -3,10 +3,10 @@ import { getLogger } from '../../shared/Logger'; import AppUpdaterService from './AppUpdaterService'; import { is } from './utils/helpers'; import path from 'path'; -import studyConfig from '../../config/study.config'; import MenuItemConstructorOptions = Electron.MenuItemConstructorOptions; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; +import studyConfig from '../../../shared/study.config'; const LOG = getLogger('WindowService'); @@ -14,6 +14,7 @@ export class WindowService { private readonly appUpdaterService: AppUpdaterService; private tray: Tray; private readonly isDevelopment: boolean = is.dev; + private experienceSamplingWindow: BrowserWindow; constructor(appUpdaterService: AppUpdaterService) { this.appUpdaterService = appUpdaterService; @@ -43,7 +44,7 @@ export class WindowService { const windowWidth = 500; const windowHeight = 130; - const win = new BrowserWindow({ + this.experienceSamplingWindow = new BrowserWindow({ width: windowWidth, height: windowHeight, x: width - windowWidth - windowPadding, @@ -65,21 +66,31 @@ export class WindowService { }); if (process.env.VITE_DEV_SERVER_URL) { - await win.loadURL(process.env.VITE_DEV_SERVER_URL + '#experience-sampling'); + await this.experienceSamplingWindow.loadURL( + process.env.VITE_DEV_SERVER_URL + '#experience-sampling' + ); } else { - await win.loadFile(path.join(process.env.DIST, 'index.html'), { + await this.experienceSamplingWindow.loadFile(path.join(process.env.DIST, 'index.html'), { hash: 'experience-sampling' }); } - win.setVisibleOnAllWorkspaces(true); + this.experienceSamplingWindow.setVisibleOnAllWorkspaces(true); let opacity = 0; const interval = setInterval(() => { if (opacity >= 1) clearInterval(interval); - win?.setOpacity(opacity); + this.experienceSamplingWindow?.setOpacity(opacity); opacity += 0.1; }, 10); - win.show(); + this.experienceSamplingWindow.show(); + } + + public async closeExperienceSamplingWindow() { + if (this.experienceSamplingWindow) { + this.experienceSamplingWindow.close(); + this.experienceSamplingWindow.setOpacity(0); + this.experienceSamplingWindow = null; + } } public updateTray( diff --git a/src/electron/electron/shared/StudyConfig.ts b/src/electron/electron/shared/StudyConfig.ts deleted file mode 100644 index ee8470e6..00000000 --- a/src/electron/electron/shared/StudyConfig.ts +++ /dev/null @@ -1,35 +0,0 @@ -export interface UserInputTrackerConfig { - enabled: boolean; - intervalInMs: number; -} - -export interface WindowActivityTrackerConfig { - enabled: boolean; - intervalInMs: number; -} - -export interface ExperienceSamplingConfig { - enabled: boolean; - questions: string[]; - responseOptions: string[][]; - samplingIntervalInMinutes: number; - samplingRandomization: boolean; -} - -export interface TrackerConfig { - windowActivityTracker: WindowActivityTrackerConfig; - userInputTracker: UserInputTrackerConfig; - experienceSampling: ExperienceSamplingConfig; -} - -export interface StudyConfig { - name: string; - shortDescription: string; - infoUrl: string; - privacyPolicyUrl: string; - uploadUrl: string; - contactName: string; - contactEmail: string; - subjectIdLength: number; - trackers: TrackerConfig; -} diff --git a/src/electron/shared/StudyConfiguration.ts b/src/electron/shared/StudyConfiguration.ts new file mode 100644 index 00000000..5d8d752a --- /dev/null +++ b/src/electron/shared/StudyConfiguration.ts @@ -0,0 +1,36 @@ +export interface UserInputTrackerConfiguration { + enabled: boolean; + intervalInMs: number; +} + +export interface WindowActivityTrackerConfiguration { + enabled: boolean; + intervalInMs: number; +} + +export interface ExperienceSamplingConfiguration { + enabled: boolean; + scale: number; + questions: string[]; + responseOptions: string[][]; + samplingIntervalInMinutes: number; + samplingRandomization: boolean; +} + +export interface TrackerConfiguration { + windowActivityTracker: WindowActivityTrackerConfiguration; + userInputTracker: UserInputTrackerConfiguration; + experienceSampling: ExperienceSamplingConfiguration; +} + +export interface StudyConfiguration { + name: string; + shortDescription: string; + infoUrl: string; + privacyPolicyUrl: string; + uploadUrl: string; + contactName: string; + contactEmail: string; + subjectIdLength: number; + trackers: TrackerConfiguration; +} diff --git a/src/electron/electron/config/study.config.ts b/src/electron/shared/study.config.ts similarity index 87% rename from src/electron/electron/config/study.config.ts rename to src/electron/shared/study.config.ts index 01b1c4dc..8bff8ece 100644 --- a/src/electron/electron/config/study.config.ts +++ b/src/electron/shared/study.config.ts @@ -1,6 +1,6 @@ -import { StudyConfig } from '../shared/StudyConfig'; +import { StudyConfiguration } from './StudyConfiguration'; -const studyConfig: StudyConfig = { +const studyConfig: StudyConfiguration = { name: 'Personal Analytics', shortDescription: 'A study to understand how people...', infoUrl: 'https://hasel.dev', @@ -20,6 +20,7 @@ const studyConfig: StudyConfig = { }, experienceSampling: { enabled: true, + scale: 5, questions: ['How are you feeling right now?', 'How is your day going?'], responseOptions: [ ['Bad', 'Good'], diff --git a/src/electron/electron/shared/Commands.ts b/src/electron/src/utils/Commands.ts similarity index 73% rename from src/electron/electron/shared/Commands.ts rename to src/electron/src/utils/Commands.ts index cd611c47..5e54e06a 100644 --- a/src/electron/electron/shared/Commands.ts +++ b/src/electron/src/utils/Commands.ts @@ -1,4 +1,5 @@ type Commands = { createExperienceSample: (promptedAt: number, question: string, response: number) => Promise; + closeExperienceSamplingWindow: () => Promise; }; export default Commands; diff --git a/src/electron/electron/shared/Events.ts b/src/electron/src/utils/Events.ts similarity index 100% rename from src/electron/electron/shared/Events.ts rename to src/electron/src/utils/Events.ts diff --git a/src/electron/electron/ipc/TypedIpcMain.ts b/src/electron/src/utils/TypedIpcMain.ts similarity index 100% rename from src/electron/electron/ipc/TypedIpcMain.ts rename to src/electron/src/utils/TypedIpcMain.ts diff --git a/src/electron/src/utils/typedIpcRenderer.ts b/src/electron/src/utils/typedIpcRenderer.ts index a81d7d14..0db2b4eb 100644 --- a/src/electron/src/utils/typedIpcRenderer.ts +++ b/src/electron/src/utils/typedIpcRenderer.ts @@ -1,6 +1,6 @@ -import { TypedIpcRenderer } from '../../electron/ipc/TypedIpcMain'; -import Events from '../../electron/shared/Events'; -import Commands from '../../electron/shared/Commands'; +import Events from './Events'; +import Commands from './Commands'; +import { TypedIpcRenderer } from './TypedIpcMain'; const typedIpcRenderer = window.ipcRenderer as TypedIpcRenderer; diff --git a/src/electron/src/views/ExperienceSampling.vue b/src/electron/src/views/ExperienceSampling.vue index 6c508728..b83313f8 100644 --- a/src/electron/src/views/ExperienceSampling.vue +++ b/src/electron/src/views/ExperienceSampling.vue @@ -1,22 +1,45 @@ - +