From f077a6f719d72d86dcd3e7c722aab166ec6cccdd Mon Sep 17 00:00:00 2001 From: Johannes Millan Date: Wed, 13 Aug 2025 19:20:31 +0200 Subject: [PATCH] refactor: improve typing --- README.md | 2 ++ electron/debug.ts | 7 ++++- .../main-header/main-header.component.ts | 6 ++-- .../mobile-side-panel-menu.component.ts | 11 +++++-- src/app/core/persistence/database.service.ts | 2 +- .../is-equal-simple-counter-cfg.util.ts | 7 +++-- .../simple-counter/simple-counter.service.ts | 11 ++++--- .../pipes/sub-task-total-time-spent.pipe.ts | 2 +- .../tasks/store/task-related-model.effects.ts | 6 ++-- src/app/features/worklog/worklog.component.ts | 6 ++-- src/app/imex/file-imex/file-imex.component.ts | 6 ++-- .../task-batch-update.reducer.ts | 23 ++++++++------ .../input-duration-slider.component.ts | 30 +++++++++++-------- src/app/ui/duration/ms-to-string$.pipe.ts | 17 ----------- src/app/ui/swipe-gesture/pan.directive.ts | 2 +- 15 files changed, 73 insertions(+), 65 deletions(-) delete mode 100644 src/app/ui/duration/ms-to-string$.pipe.ts diff --git a/README.md b/README.md index 4d6e9ab0f..9eb783535 100644 --- a/README.md +++ b/README.md @@ -350,6 +350,8 @@ git clone https://github.com/johannesjo/super-productivity.git cd super-productivity npm i -g @angular/cli npm i +# prepare the env file once +npm run env ``` **Run the dev server** diff --git a/electron/debug.ts b/electron/debug.ts index c559be284..e789f032c 100644 --- a/electron/debug.ts +++ b/electron/debug.ts @@ -97,7 +97,12 @@ export const initDebug = ( } if (opts.devToolsMode !== 'previous' && opts.devToolsMode) { - devToolsOptions.mode = opts.devToolsMode as 'bottom' | 'left' | 'right' | 'undocked' | 'detach'; + devToolsOptions.mode = opts.devToolsMode as + | 'bottom' + | 'left' + | 'right' + | 'undocked' + | 'detach'; } app.on('browser-window-created', (event, win) => { diff --git a/src/app/core-ui/main-header/main-header.component.ts b/src/app/core-ui/main-header/main-header.component.ts index abe3c2ca2..62160234e 100644 --- a/src/app/core-ui/main-header/main-header.component.ts +++ b/src/app/core-ui/main-header/main-header.component.ts @@ -121,21 +121,21 @@ export class MainHeaderComponent implements OnDestroy { currentTaskContext = toSignal(this._currentTaskContext$); private _isRouteWithSidePanel$ = this._router.events.pipe( - filter((event: any) => event instanceof NavigationEnd), + filter((event): event is NavigationEnd => event instanceof NavigationEnd), map((event) => true), // Always true since right-panel is now global startWith(true), // Always true since right-panel is now global ); isRouteWithSidePanel = toSignal(this._isRouteWithSidePanel$, { initialValue: true }); private _isScheduleSection$ = this._router.events.pipe( - filter((event: any) => event instanceof NavigationEnd), + filter((event): event is NavigationEnd => event instanceof NavigationEnd), map((event) => !!event.urlAfterRedirects.match(/(schedule)$/)), startWith(!!this._router.url.match(/(schedule)$/)), ); isScheduleSection = toSignal(this._isScheduleSection$, { initialValue: false }); private _isWorkViewPage$ = this._router.events.pipe( - filter((event: any) => event instanceof NavigationEnd), + filter((event): event is NavigationEnd => event instanceof NavigationEnd), map((event) => !!event.urlAfterRedirects.match(/tasks$/)), startWith(!!this._router.url.match(/tasks$/)), ); diff --git a/src/app/core-ui/main-header/mobile-side-panel-menu/mobile-side-panel-menu.component.ts b/src/app/core-ui/main-header/mobile-side-panel-menu/mobile-side-panel-menu.component.ts index 733407598..6514b9bef 100644 --- a/src/app/core-ui/main-header/mobile-side-panel-menu/mobile-side-panel-menu.component.ts +++ b/src/app/core-ui/main-header/mobile-side-panel-menu/mobile-side-panel-menu.component.ts @@ -125,7 +125,7 @@ export class MobileSidePanelMenuComponent { // Convert observables to signals readonly isRouteWithSidePanel = toSignal( this._router.events.pipe( - filter((event: any) => event instanceof NavigationEnd), + filter((event): event is NavigationEnd => event instanceof NavigationEnd), map((event) => true), // Always true since right-panel is now global startWith(true), // Always true since right-panel is now global ), @@ -134,7 +134,7 @@ export class MobileSidePanelMenuComponent { readonly isWorkViewPage = toSignal( this._router.events.pipe( - filter((event: any) => event instanceof NavigationEnd), + filter((event): event is NavigationEnd => event instanceof NavigationEnd), map((event) => !!event.urlAfterRedirects.match(/tasks$/)), startWith(!!this._router.url.match(/tasks$/)), ), @@ -185,7 +185,12 @@ export class MobileSidePanelMenuComponent { this.isShowMobileMenu.update((v) => !v); } - onPluginButtonClick(button: any): void { + onPluginButtonClick(button: { + pluginId: string; + onClick?: () => void; + label?: string; + icon?: string; + }): void { this._store.dispatch(togglePluginPanel(button.pluginId)); if (button.onClick) { diff --git a/src/app/core/persistence/database.service.ts b/src/app/core/persistence/database.service.ts index 374c71d71..c57051a85 100644 --- a/src/app/core/persistence/database.service.ts +++ b/src/app/core/persistence/database.service.ts @@ -86,7 +86,7 @@ export class DatabaseService { e: Error | unknown, // eslint-disable-next-line @typescript-eslint/ban-types fn: Function, - args: any[], + args: unknown[], ): Promise { devError(e); if (confirm(this._translateService.instant(T.CONFIRM.RELOAD_AFTER_IDB_ERROR))) { diff --git a/src/app/features/simple-counter/is-equal-simple-counter-cfg.util.ts b/src/app/features/simple-counter/is-equal-simple-counter-cfg.util.ts index 171cc72b9..375cd7e03 100644 --- a/src/app/features/simple-counter/is-equal-simple-counter-cfg.util.ts +++ b/src/app/features/simple-counter/is-equal-simple-counter-cfg.util.ts @@ -9,7 +9,10 @@ const FIELDS_TO_COMPARE: (keyof SimpleCounterCfgFields)[] = [ 'countdownDuration', ]; -export const isEqualSimpleCounterCfg = (a: any, b: any): boolean => { +export const isEqualSimpleCounterCfg = ( + a: SimpleCounterCfgFields[] | unknown, + b: SimpleCounterCfgFields[] | unknown, +): boolean => { if (Array.isArray(a) && Array.isArray(b)) { if (a.length !== b.length) { return false; @@ -18,7 +21,7 @@ export const isEqualSimpleCounterCfg = (a: any, b: any): boolean => { if (a[i] !== b[i]) { // eslint-disable-next-line @typescript-eslint/prefer-for-of for (let j = 0; j < FIELDS_TO_COMPARE.length; j++) { - const field: any = FIELDS_TO_COMPARE[j]; + const field = FIELDS_TO_COMPARE[j]; if (a[field] !== b[field]) { return false; } diff --git a/src/app/features/simple-counter/simple-counter.service.ts b/src/app/features/simple-counter/simple-counter.service.ts index 46b038ca2..674f1187a 100644 --- a/src/app/features/simple-counter/simple-counter.service.ts +++ b/src/app/features/simple-counter/simple-counter.service.ts @@ -1,4 +1,4 @@ -import { Injectable, inject } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { select, Store } from '@ngrx/store'; import { selectAllSimpleCounters, @@ -12,8 +12,8 @@ import { deleteSimpleCounter, deleteSimpleCounters, increaseSimpleCounterCounterToday, - setSimpleCounterCounterToday, setSimpleCounterCounterForDate, + setSimpleCounterCounterToday, toggleSimpleCounterCounter, turnOffAllSimpleCounterCounters, updateAllSimpleCounters, @@ -38,7 +38,9 @@ export class SimpleCounterService { select(selectAllSimpleCounters), ); simpleCountersUpdatedOnCfgChange$: Observable = - this.simpleCounters$.pipe(distinctUntilChanged(isEqualSimpleCounterCfg)); + this.simpleCounters$.pipe( + distinctUntilChanged((a, b) => isEqualSimpleCounterCfg(a, b)), + ); enabledSimpleCounters$: Observable = this._store$.select( selectEnabledSimpleCounters, @@ -47,9 +49,6 @@ export class SimpleCounterService { selectEnabledSimpleStopWatchCounters, ); - enabledSimpleCountersUpdatedOnCfgChange$: Observable = - this.enabledSimpleCounters$.pipe(distinctUntilChanged(isEqualSimpleCounterCfg)); - enabledAndToggledSimpleCounters$: Observable = this._store$.select( selectEnabledAndToggledSimpleCounters, ); diff --git a/src/app/features/tasks/pipes/sub-task-total-time-spent.pipe.ts b/src/app/features/tasks/pipes/sub-task-total-time-spent.pipe.ts index 3ebda7e88..a2aee83d1 100644 --- a/src/app/features/tasks/pipes/sub-task-total-time-spent.pipe.ts +++ b/src/app/features/tasks/pipes/sub-task-total-time-spent.pipe.ts @@ -3,7 +3,7 @@ import { Task } from '../task.model'; @Pipe({ name: 'subTaskTotalTimeSpent' }) export class SubTaskTotalTimeSpentPipe implements PipeTransform { - transform: (value: any, ...args: any[]) => any = getSubTasksTotalTimeSpent; + transform: (value: Task[]) => number = getSubTasksTotalTimeSpent; } export const getSubTasksTotalTimeSpent = (subTasks: Task[]): number => { diff --git a/src/app/features/tasks/store/task-related-model.effects.ts b/src/app/features/tasks/store/task-related-model.effects.ts index 3aad25a2c..8d64f8285 100644 --- a/src/app/features/tasks/store/task-related-model.effects.ts +++ b/src/app/features/tasks/store/task-related-model.effects.ts @@ -57,7 +57,7 @@ export class TaskRelatedModelEffects { ), ); - autoAddTodayTagOnMarkAsDone: any = createEffect(() => + autoAddTodayTagOnMarkAsDone = createEffect(() => this.ifAutoAddTodayEnabled$( this._actions$.pipe( ofType(TaskSharedActions.updateTask), @@ -76,7 +76,7 @@ export class TaskRelatedModelEffects { // EXTERNAL ===> TASKS // ------------------- - moveTaskToUnDone$: any = createEffect(() => + moveTaskToUnDone$ = createEffect(() => this._actions$.pipe( ofType(moveTaskInTodayList, moveProjectTaskToRegularList), filter( @@ -95,7 +95,7 @@ export class TaskRelatedModelEffects { ), ); - moveTaskToDone$: any = createEffect(() => + moveTaskToDone$ = createEffect(() => this._actions$.pipe( ofType(moveTaskInTodayList, moveProjectTaskToRegularList), filter( diff --git a/src/app/features/worklog/worklog.component.ts b/src/app/features/worklog/worklog.component.ts index 0ba8b5d8d..fd6b3f9f1 100644 --- a/src/app/features/worklog/worklog.component.ts +++ b/src/app/features/worklog/worklog.component.ts @@ -149,15 +149,15 @@ export class WorklogComponent implements AfterViewInit, OnDestroy { }); } - sortWorklogItems(a: any, b: any): number { + sortWorklogItems(a: { key: number }, b: { key: number }): number { return b.key - a.key; } - sortWorklogItemsReverse(a: any, b: any): number { + sortWorklogItemsReverse(a: { key: number }, b: { key: number }): number { return a.key - b.key; } - trackByKey(i: number, val: { key: any; val: any }): number { + trackByKey(i: number, val: { key: number; val: unknown }): number { return val.key; } diff --git a/src/app/imex/file-imex/file-imex.component.ts b/src/app/imex/file-imex/file-imex.component.ts index adcb1ad1c..796904c52 100644 --- a/src/app/imex/file-imex/file-imex.component.ts +++ b/src/app/imex/file-imex/file-imex.component.ts @@ -89,9 +89,9 @@ export class FileImexComponent implements OnInit { } // NOTE: after promise done the file is NOT yet read - async handleFileInput(ev: any): Promise { - const files = ev.target.files; - const file = files.item(0); + async handleFileInput(ev: Event): Promise { + const files = (ev.target as HTMLInputElement).files; + const file = files?.item(0); if (!file) { // No file selected or selection cancelled diff --git a/src/app/root-store/meta/task-shared-meta-reducers/task-batch-update.reducer.ts b/src/app/root-store/meta/task-shared-meta-reducers/task-batch-update.reducer.ts index b441f6e95..178c357a8 100644 --- a/src/app/root-store/meta/task-shared-meta-reducers/task-batch-update.reducer.ts +++ b/src/app/root-store/meta/task-shared-meta-reducers/task-batch-update.reducer.ts @@ -1,4 +1,4 @@ -import { Action } from '@ngrx/store'; +import { Action, ActionReducer } from '@ngrx/store'; import { DEFAULT_TASK, Task, TaskCopy } from '../../../features/tasks/task.model'; import { TaskSharedActions } from '../task-shared.actions'; @@ -21,28 +21,33 @@ import { validateAndFixDataConsistencyAfterBatchUpdate } from './validate-and-fi * Meta reducer for handling batch updates to tasks within a project * This reducer processes all operations in a single state update for efficiency */ -export const taskBatchUpdateMetaReducer = ( - reducer: any, -): ((state: any, action: any) => any) => { - return (state: any, action: Action) => { +export const taskBatchUpdateMetaReducer = = RootState>( + reducer: ActionReducer, +): ActionReducer => { + return (state: T | undefined, action: Action) => { if (action.type === TaskSharedActions.batchUpdateForProject.type) { const { projectId, operations, createdTaskIds } = action as ReturnType< typeof TaskSharedActions.batchUpdateForProject >; // Ensure state has required properties - if (!state[TASK_FEATURE_NAME] || !state[PROJECT_FEATURE_NAME]) { + const rootState = state as unknown as RootState; + if ( + !rootState || + !rootState[TASK_FEATURE_NAME] || + !rootState[PROJECT_FEATURE_NAME] + ) { Log.error('taskBatchUpdateMetaReducer: Missing required state properties'); return reducer(state, action); } // Validate project exists - if (!state[PROJECT_FEATURE_NAME].entities[projectId]) { + if (!rootState[PROJECT_FEATURE_NAME].entities[projectId]) { Log.error(`taskBatchUpdateMetaReducer: Project ${projectId} not found`); return reducer(state, action); } - let newState = { ...state } as RootState; + let newState = { ...rootState } as RootState; const tasksToAdd: Task[] = []; const tasksToUpdate: { id: string; changes: Partial }[] = []; const taskIdsToDelete: string[] = []; @@ -346,7 +351,7 @@ export const taskBatchUpdateMetaReducer = ( newTaskOrder, ); - return reducer(newState, action); + return reducer(newState as T, action); } return reducer(state, action); diff --git a/src/app/ui/duration/input-duration-slider/input-duration-slider.component.ts b/src/app/ui/duration/input-duration-slider/input-duration-slider.component.ts index 000b2269f..0e8e32d33 100644 --- a/src/app/ui/duration/input-duration-slider/input-duration-slider.component.ts +++ b/src/app/ui/duration/input-duration-slider/input-duration-slider.component.ts @@ -33,7 +33,7 @@ export class InputDurationSliderComponent implements OnInit, OnDestroy { // Convert to signals readonly minutesBefore = signal(0); - readonly dots = signal([]); + readonly dots = signal([]); readonly uid = 'duration-input-slider' + nanoid(); readonly el: HTMLElement; @@ -47,9 +47,9 @@ export class InputDurationSliderComponent implements OnInit, OnDestroy { // Internal model signal readonly _model = signal(0); - startHandler?: (ev: any) => void; + startHandler?: (ev: MouseEvent | TouchEvent) => void; endHandler?: () => void; - moveHandler?: (ev: any) => void; + moveHandler?: (ev: MouseEvent | TouchEvent) => void; readonly circleEl = viewChild('circleEl'); @@ -74,7 +74,10 @@ export class InputDurationSliderComponent implements OnInit, OnDestroy { } // don't execute when clicked on label or input - if (ev.target.tagName === 'LABEL' || ev.target.tagName === 'INPUT') { + if ( + (ev.target as HTMLElement)?.tagName === 'LABEL' || + (ev.target as HTMLElement)?.tagName === 'INPUT' + ) { this.endHandler(); return; } @@ -91,7 +94,8 @@ export class InputDurationSliderComponent implements OnInit, OnDestroy { this.moveHandler = (ev) => { if ( ev.type === 'click' && - (ev.target.tagName === 'LABEL' || ev.target.tagName === 'INPUT') + ((ev.target as HTMLElement)?.tagName === 'LABEL' || + (ev.target as HTMLElement)?.tagName === 'INPUT') ) { return; } @@ -108,16 +112,18 @@ export class InputDurationSliderComponent implements OnInit, OnDestroy { const centerX = circleEl.nativeElement.offsetWidth / 2; const centerY = circleEl.nativeElement.offsetHeight / 2; - let offsetX; + let offsetX: number; + let offsetY: number; - let offsetY; if (ev.type === 'touchmove') { - const rect = ev.target.getBoundingClientRect(); - offsetX = ev.targetTouches[0].pageX - rect.left; - offsetY = ev.targetTouches[0].pageY - rect.top; + const touchEv = ev as TouchEvent; + const rect = (ev.target as Element).getBoundingClientRect(); + offsetX = touchEv.targetTouches[0].pageX - rect.left; + offsetY = touchEv.targetTouches[0].pageY - rect.top; } else { - offsetX = ev.offsetX; - offsetY = ev.offsetY; + const mouseEv = ev as MouseEvent; + offsetX = mouseEv.offsetX; + offsetY = mouseEv.offsetY; } const x = offsetX - centerX; diff --git a/src/app/ui/duration/ms-to-string$.pipe.ts b/src/app/ui/duration/ms-to-string$.pipe.ts deleted file mode 100644 index 64f185e54..000000000 --- a/src/app/ui/duration/ms-to-string$.pipe.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core'; -import { Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; -import { msToString } from './ms-to-string.pipe'; - -@Pipe({ name: 'msToString$' }) -export class MsToStringPipe$ implements PipeTransform { - transform(value$: Observable | undefined, showSeconds?: boolean): any { - if (value$) { - value$.pipe( - map((value) => { - return msToString(value, showSeconds); - }), - ); - } - } -} diff --git a/src/app/ui/swipe-gesture/pan.directive.ts b/src/app/ui/swipe-gesture/pan.directive.ts index fd1672083..332cf0805 100644 --- a/src/app/ui/swipe-gesture/pan.directive.ts +++ b/src/app/ui/swipe-gesture/pan.directive.ts @@ -41,7 +41,7 @@ export class PanDirective implements OnDestroy { private _isPanning = false; private _touchIdentifier: number | null = null; private _isScrolling = false; - private _scrollTimeout: any = null; + private _scrollTimeout: ReturnType | null = null; private _scrollListener: (() => void) | null = null; private _scrollableParent: HTMLElement | null = null;