mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-01-22 18:30:09 +00:00
refactor: improve typing
This commit is contained in:
parent
fbc45de335
commit
f077a6f719
15 changed files with 73 additions and 65 deletions
|
|
@ -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**
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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$/)),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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<void> {
|
||||
devError(e);
|
||||
if (confirm(this._translateService.instant(T.CONFIRM.RELOAD_AFTER_IDB_ERROR))) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<SimpleCounter[]> =
|
||||
this.simpleCounters$.pipe(distinctUntilChanged(isEqualSimpleCounterCfg));
|
||||
this.simpleCounters$.pipe(
|
||||
distinctUntilChanged((a, b) => isEqualSimpleCounterCfg(a, b)),
|
||||
);
|
||||
|
||||
enabledSimpleCounters$: Observable<SimpleCounter[]> = this._store$.select(
|
||||
selectEnabledSimpleCounters,
|
||||
|
|
@ -47,9 +49,6 @@ export class SimpleCounterService {
|
|||
selectEnabledSimpleStopWatchCounters,
|
||||
);
|
||||
|
||||
enabledSimpleCountersUpdatedOnCfgChange$: Observable<SimpleCounter[]> =
|
||||
this.enabledSimpleCounters$.pipe(distinctUntilChanged(isEqualSimpleCounterCfg));
|
||||
|
||||
enabledAndToggledSimpleCounters$: Observable<SimpleCounter[]> = this._store$.select(
|
||||
selectEnabledAndToggledSimpleCounters,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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 => {
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -89,9 +89,9 @@ export class FileImexComponent implements OnInit {
|
|||
}
|
||||
|
||||
// NOTE: after promise done the file is NOT yet read
|
||||
async handleFileInput(ev: any): Promise<void> {
|
||||
const files = ev.target.files;
|
||||
const file = files.item(0);
|
||||
async handleFileInput(ev: Event): Promise<void> {
|
||||
const files = (ev.target as HTMLInputElement).files;
|
||||
const file = files?.item(0);
|
||||
|
||||
if (!file) {
|
||||
// No file selected or selection cancelled
|
||||
|
|
|
|||
|
|
@ -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 = <T extends Partial<RootState> = RootState>(
|
||||
reducer: ActionReducer<T>,
|
||||
): ActionReducer<T> => {
|
||||
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<Task> }[] = [];
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ export class InputDurationSliderComponent implements OnInit, OnDestroy {
|
|||
|
||||
// Convert to signals
|
||||
readonly minutesBefore = signal(0);
|
||||
readonly dots = signal<any[]>([]);
|
||||
readonly dots = signal<number[]>([]);
|
||||
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<ElementRef>('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;
|
||||
|
|
|
|||
|
|
@ -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<any> | undefined, showSeconds?: boolean): any {
|
||||
if (value$) {
|
||||
value$.pipe(
|
||||
map((value) => {
|
||||
return msToString(value, showSeconds);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<typeof setTimeout> | null = null;
|
||||
private _scrollListener: (() => void) | null = null;
|
||||
private _scrollableParent: HTMLElement | null = null;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue