mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-01-23 02:36:05 +00:00
refactor: observables to signals
This commit is contained in:
parent
9447a97237
commit
be991a4192
20 changed files with 248 additions and 236 deletions
|
|
@ -1,5 +1,5 @@
|
|||
@if (isShowUi$ | async) {
|
||||
@if (globalThemeService.backgroundImg$ | async; as bgImage) {
|
||||
@if (globalThemeService.backgroundImg(); as bgImage) {
|
||||
<div
|
||||
[style.background]="'url(' + bgImage + ')'"
|
||||
class="bg-image"
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
></div>
|
||||
}
|
||||
<!-- -->
|
||||
@if (isShowFocusOverlay$ | async) {
|
||||
@if (isShowFocusOverlay()) {
|
||||
<focus-mode-overlay @warp></focus-mode-overlay>
|
||||
} @else {
|
||||
<mat-sidenav-container [dir]="isRTL ? 'rtl' : 'ltr'">
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
OnDestroy,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
import { ChromeExtensionInterfaceService } from './core/chrome-extension-interface/chrome-extension-interface.service';
|
||||
import { ShortcutService } from './core-ui/shortcut/shortcut.service';
|
||||
import { GlobalConfigService } from './features/config/global-config.service';
|
||||
|
|
@ -189,9 +190,9 @@ export class AppComponent implements OnDestroy, AfterViewInit {
|
|||
),
|
||||
);
|
||||
|
||||
isShowFocusOverlay$: Observable<boolean> = this._store.select(
|
||||
selectIsFocusOverlayShown,
|
||||
);
|
||||
isShowFocusOverlay = toSignal(this._store.select(selectIsFocusOverlayShown), {
|
||||
initialValue: false,
|
||||
});
|
||||
|
||||
private _subs: Subscription = new Subscription();
|
||||
private _intervalTimer?: NodeJS.Timeout;
|
||||
|
|
@ -432,53 +433,52 @@ export class AppComponent implements OnDestroy, AfterViewInit {
|
|||
}
|
||||
|
||||
changeBackgroundFromUnsplash(): void {
|
||||
this.globalThemeService.isDarkTheme$.pipe(take(1)).subscribe((isDarkMode) => {
|
||||
const contextKey = isDarkMode ? 'backgroundImageDark' : 'backgroundImageLight';
|
||||
const isDarkMode = this.globalThemeService.isDarkTheme();
|
||||
const contextKey = isDarkMode ? 'backgroundImageDark' : 'backgroundImageLight';
|
||||
|
||||
const dialogRef = this._matDialog.open(DialogUnsplashPickerComponent, {
|
||||
width: '900px',
|
||||
maxWidth: '95vw',
|
||||
data: {
|
||||
context: contextKey,
|
||||
},
|
||||
});
|
||||
const dialogRef = this._matDialog.open(DialogUnsplashPickerComponent, {
|
||||
width: '900px',
|
||||
maxWidth: '95vw',
|
||||
data: {
|
||||
context: contextKey,
|
||||
},
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((result) => {
|
||||
if (result) {
|
||||
// Get current work context
|
||||
this.workContextService.activeWorkContext$
|
||||
.pipe(take(1))
|
||||
.subscribe((activeContext) => {
|
||||
if (!activeContext) {
|
||||
this._snackService.open({
|
||||
type: 'ERROR',
|
||||
msg: 'No active work context',
|
||||
});
|
||||
return;
|
||||
}
|
||||
dialogRef.afterClosed().subscribe((result) => {
|
||||
if (result) {
|
||||
// Get current work context
|
||||
this.workContextService.activeWorkContext$
|
||||
.pipe(take(1))
|
||||
.subscribe((activeContext) => {
|
||||
if (!activeContext) {
|
||||
this._snackService.open({
|
||||
type: 'ERROR',
|
||||
msg: 'No active work context',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract the URL from the result object
|
||||
const backgroundUrl = result.url || result;
|
||||
// Extract the URL from the result object
|
||||
const backgroundUrl = result.url || result;
|
||||
|
||||
// Update the theme based on context type
|
||||
if (activeContext.type === 'PROJECT') {
|
||||
this._projectService.update(activeContext.id, {
|
||||
theme: {
|
||||
...activeContext.theme,
|
||||
[contextKey]: backgroundUrl,
|
||||
},
|
||||
});
|
||||
} else if (activeContext.type === 'TAG') {
|
||||
this._tagService.updateTag(activeContext.id, {
|
||||
theme: {
|
||||
...activeContext.theme,
|
||||
[contextKey]: backgroundUrl,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
// Update the theme based on context type
|
||||
if (activeContext.type === 'PROJECT') {
|
||||
this._projectService.update(activeContext.id, {
|
||||
theme: {
|
||||
...activeContext.theme,
|
||||
[contextKey]: backgroundUrl,
|
||||
},
|
||||
});
|
||||
} else if (activeContext.type === 'TAG') {
|
||||
this._tagService.updateTag(activeContext.id, {
|
||||
theme: {
|
||||
...activeContext.theme,
|
||||
[contextKey]: backgroundUrl,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@
|
|||
}
|
||||
</section>
|
||||
|
||||
@if (nonHiddenProjects$ | async; as projectList) {
|
||||
@if (nonHiddenProjects(); as projectList) {
|
||||
<section class="projects tour-projects">
|
||||
<div class="g-multi-btn-wrapper e2e-projects-btn">
|
||||
<button
|
||||
|
|
@ -62,7 +62,7 @@
|
|||
#projectExpandBtn
|
||||
(click)="toggleExpandProjects()"
|
||||
(keydown)="toggleExpandProjectsLeftRight($event)"
|
||||
[class.isExpanded]="isProjectsExpanded"
|
||||
[class.isExpanded]="isProjectsExpanded()"
|
||||
class="expand-btn"
|
||||
mat-menu-item
|
||||
>
|
||||
|
|
@ -134,19 +134,19 @@
|
|||
></side-nav-item>
|
||||
}
|
||||
</div>
|
||||
@if (!projectList.length && isProjectsExpanded) {
|
||||
@if (!projectList.length && isProjectsExpanded()) {
|
||||
<div class="no-tags-info">{{ T.MH.NO_PROJECT_INFO | translate }}</div>
|
||||
}
|
||||
</section>
|
||||
}
|
||||
@if (tagList$ | async; as tagList) {
|
||||
@if (tagList(); as tagList) {
|
||||
<section class="tags">
|
||||
<button
|
||||
#menuEntry
|
||||
#tagExpandBtn
|
||||
(click)="toggleExpandTags()"
|
||||
(keydown)="toggleExpandTagsLeftRight($event)"
|
||||
[class.isExpanded]="isTagsExpanded"
|
||||
[class.isExpanded]="isTagsExpanded()"
|
||||
class="expand-btn"
|
||||
mat-menu-item
|
||||
>
|
||||
|
|
@ -172,7 +172,7 @@
|
|||
></side-nav-item>
|
||||
}
|
||||
</div>
|
||||
@if (!tagList.length && isTagsExpanded) {
|
||||
@if (!tagList.length && isTagsExpanded()) {
|
||||
<div class="no-tags-info">{{ T.MH.NO_TAG_INFO | translate }}</div>
|
||||
}
|
||||
<!-- <button (click)="addTag()"-->
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
computed,
|
||||
effect,
|
||||
ElementRef,
|
||||
HostBinding,
|
||||
HostListener,
|
||||
inject,
|
||||
OnDestroy,
|
||||
signal,
|
||||
viewChild,
|
||||
viewChildren,
|
||||
} from '@angular/core';
|
||||
|
|
@ -16,10 +18,10 @@ import { DialogCreateProjectComponent } from '../../features/project/dialogs/cre
|
|||
import { Project } from '../../features/project/project.model';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { DRAG_DELAY_FOR_TOUCH_LONGER } from '../../app.constants';
|
||||
import { BehaviorSubject, combineLatest, Observable, Subscription } from 'rxjs';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { WorkContextService } from '../../features/work-context/work-context.service';
|
||||
import { standardListAnimation } from '../../ui/animations/standard-list.ani';
|
||||
import { first, map, switchMap } from 'rxjs/operators';
|
||||
import { first } from 'rxjs/operators';
|
||||
import { TagService } from '../../features/tag/tag.service';
|
||||
import { Tag } from '../../features/tag/tag.model';
|
||||
import { WorkContextType } from '../../features/work-context/work-context.model';
|
||||
|
|
@ -96,50 +98,52 @@ export class SideNavComponent implements OnDestroy {
|
|||
private readonly _globalConfigService = inject(GlobalConfigService);
|
||||
private readonly _store = inject(Store);
|
||||
|
||||
readonly navEntries = viewChildren<MatMenuItem>('menuEntry');
|
||||
navEntries = viewChildren<MatMenuItem>('menuEntry');
|
||||
IS_MOUSE_PRIMARY = IS_MOUSE_PRIMARY;
|
||||
IS_TOUCH_PRIMARY = IS_TOUCH_PRIMARY;
|
||||
DRAG_DELAY_FOR_TOUCH_LONGER = DRAG_DELAY_FOR_TOUCH_LONGER;
|
||||
|
||||
keyboardFocusTimeout?: number;
|
||||
readonly projectExpandBtn = viewChild('projectExpandBtn', { read: ElementRef });
|
||||
isProjectsExpanded: boolean = this.fetchProjectListState();
|
||||
isProjectsExpanded$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(
|
||||
this.isProjectsExpanded,
|
||||
);
|
||||
projectExpandBtn = viewChild('projectExpandBtn', { read: ElementRef });
|
||||
isProjectsExpanded = signal(this.fetchProjectListState());
|
||||
|
||||
allNonInboxProjects = toSignal(this._store.select(selectAllProjectsExceptInbox), {
|
||||
initialValue: [],
|
||||
});
|
||||
nonHiddenProjects$: Observable<Project[]> = this.isProjectsExpanded$.pipe(
|
||||
switchMap((isExpanded) =>
|
||||
isExpanded
|
||||
? this._store.select(selectUnarchivedVisibleProjects)
|
||||
: combineLatest([
|
||||
this._store.select(selectUnarchivedVisibleProjects),
|
||||
this.workContextService.activeWorkContextId$,
|
||||
]).pipe(map(([projects, id]) => projects.filter((p) => p.id === id))),
|
||||
),
|
||||
private _allVisibleProjects = toSignal(
|
||||
this._store.select(selectUnarchivedVisibleProjects),
|
||||
{ initialValue: [] },
|
||||
);
|
||||
private _activeWorkContextId = toSignal(this.workContextService.activeWorkContextId$, {
|
||||
initialValue: null,
|
||||
});
|
||||
|
||||
readonly tagExpandBtn = viewChild('tagExpandBtn', { read: ElementRef });
|
||||
isTagsExpanded: boolean = this.fetchTagListState();
|
||||
isTagsExpanded$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(
|
||||
this.isTagsExpanded,
|
||||
);
|
||||
nonHiddenProjects = computed(() => {
|
||||
const isExpanded = this.isProjectsExpanded();
|
||||
const projects = this._allVisibleProjects();
|
||||
if (isExpanded) {
|
||||
return projects;
|
||||
}
|
||||
const activeId = this._activeWorkContextId();
|
||||
return projects.filter((p) => p.id === activeId);
|
||||
});
|
||||
|
||||
tagListToDisplay$: Observable<Tag[]> = this.tagService.tagsNoMyDayAndNoList$;
|
||||
tagExpandBtn = viewChild('tagExpandBtn', { read: ElementRef });
|
||||
isTagsExpanded = signal(this.fetchTagListState());
|
||||
|
||||
tagList$: Observable<Tag[]> = this.isTagsExpanded$.pipe(
|
||||
switchMap((isExpanded) =>
|
||||
isExpanded
|
||||
? this.tagListToDisplay$
|
||||
: combineLatest([
|
||||
this.tagListToDisplay$,
|
||||
this.workContextService.activeWorkContextId$,
|
||||
]).pipe(map(([tags, id]) => tags.filter((t) => t.id === id))),
|
||||
),
|
||||
);
|
||||
private _tagListToDisplay = toSignal(this.tagService.tagsNoMyDayAndNoList$, {
|
||||
initialValue: [],
|
||||
});
|
||||
|
||||
tagList = computed(() => {
|
||||
const isExpanded = this.isTagsExpanded();
|
||||
const tags = this._tagListToDisplay();
|
||||
if (isExpanded) {
|
||||
return tags;
|
||||
}
|
||||
const activeId = this._activeWorkContextId();
|
||||
return tags.filter((t) => t.id === activeId);
|
||||
});
|
||||
T: typeof T = T;
|
||||
activeWorkContextId?: string | null;
|
||||
WorkContextType: typeof WorkContextType = WorkContextType;
|
||||
|
|
@ -198,8 +202,8 @@ export class SideNavComponent implements OnDestroy {
|
|||
return localStorage.getItem(LS.IS_PROJECT_LIST_EXPANDED) === 'true';
|
||||
}
|
||||
|
||||
storeProjectListState(isExpanded: boolean): void {
|
||||
this.isProjectsExpanded = isExpanded;
|
||||
private _storeProjectListState(isExpanded: boolean): void {
|
||||
this.isProjectsExpanded.set(isExpanded);
|
||||
localStorage.setItem(LS.IS_PROJECT_LIST_EXPANDED, isExpanded.toString());
|
||||
}
|
||||
|
||||
|
|
@ -208,23 +212,20 @@ export class SideNavComponent implements OnDestroy {
|
|||
}
|
||||
|
||||
storeTagListState(isExpanded: boolean): void {
|
||||
this.isTagsExpanded = isExpanded;
|
||||
this.isTagsExpanded.set(isExpanded);
|
||||
localStorage.setItem(LS.IS_TAG_LIST_EXPANDED, isExpanded.toString());
|
||||
}
|
||||
|
||||
toggleExpandProjects(): void {
|
||||
const newState: boolean = !this.isProjectsExpanded;
|
||||
this.storeProjectListState(newState);
|
||||
this.isProjectsExpanded$.next(newState);
|
||||
const newState: boolean = !this.isProjectsExpanded();
|
||||
this._storeProjectListState(newState);
|
||||
}
|
||||
|
||||
toggleExpandProjectsLeftRight(ev: KeyboardEvent): void {
|
||||
if (ev.key === 'ArrowLeft' && this.isProjectsExpanded) {
|
||||
this.storeProjectListState(false);
|
||||
this.isProjectsExpanded$.next(this.isProjectsExpanded);
|
||||
} else if (ev.key === 'ArrowRight' && !this.isProjectsExpanded) {
|
||||
this.storeProjectListState(true);
|
||||
this.isProjectsExpanded$.next(this.isProjectsExpanded);
|
||||
if (ev.key === 'ArrowLeft' && this.isProjectsExpanded()) {
|
||||
this._storeProjectListState(false);
|
||||
} else if (ev.key === 'ArrowRight' && !this.isProjectsExpanded()) {
|
||||
this._storeProjectListState(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -244,18 +245,15 @@ export class SideNavComponent implements OnDestroy {
|
|||
}
|
||||
|
||||
toggleExpandTags(): void {
|
||||
const newState: boolean = !this.isTagsExpanded;
|
||||
const newState: boolean = !this.isTagsExpanded();
|
||||
this.storeTagListState(newState);
|
||||
this.isTagsExpanded$.next(newState);
|
||||
}
|
||||
|
||||
toggleExpandTagsLeftRight(ev: KeyboardEvent): void {
|
||||
if (ev.key === 'ArrowLeft' && this.isTagsExpanded) {
|
||||
if (ev.key === 'ArrowLeft' && this.isTagsExpanded()) {
|
||||
this.storeTagListState(false);
|
||||
this.isTagsExpanded$.next(this.isTagsExpanded);
|
||||
} else if (ev.key === 'ArrowRight' && !this.isTagsExpanded) {
|
||||
} else if (ev.key === 'ArrowRight' && !this.isTagsExpanded()) {
|
||||
this.storeTagListState(true);
|
||||
this.isTagsExpanded$.next(this.isTagsExpanded);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { effect, inject, Injectable } from '@angular/core';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
import { BodyClass, IS_ELECTRON } from '../../app.constants';
|
||||
import { IS_MAC } from '../../util/is-mac';
|
||||
import {
|
||||
|
|
@ -51,7 +52,7 @@ export class GlobalThemeService {
|
|||
(localStorage.getItem(LS.DARK_MODE) as DarkModeCfg) || 'system',
|
||||
);
|
||||
|
||||
isDarkTheme$: Observable<boolean> = this.darkMode$.pipe(
|
||||
private _isDarkThemeObs$: Observable<boolean> = this.darkMode$.pipe(
|
||||
switchMap((darkMode) => {
|
||||
switch (darkMode) {
|
||||
case 'dark':
|
||||
|
|
@ -69,9 +70,11 @@ export class GlobalThemeService {
|
|||
distinctUntilChanged(),
|
||||
);
|
||||
|
||||
backgroundImg$: Observable<string | null | undefined> = combineLatest([
|
||||
isDarkTheme = toSignal(this._isDarkThemeObs$, { initialValue: false });
|
||||
|
||||
private _backgroundImgObs$: Observable<string | null | undefined> = combineLatest([
|
||||
this._workContextService.currentTheme$,
|
||||
this.isDarkTheme$,
|
||||
this._isDarkThemeObs$,
|
||||
]).pipe(
|
||||
map(([theme, isDarkMode]) =>
|
||||
isDarkMode ? theme.backgroundImageDark : theme.backgroundImageLight,
|
||||
|
|
@ -79,6 +82,8 @@ export class GlobalThemeService {
|
|||
distinctUntilChanged(),
|
||||
);
|
||||
|
||||
backgroundImg = toSignal(this._backgroundImgObs$);
|
||||
|
||||
init(): void {
|
||||
// This is here to make web page reloads on non-work-context pages at least usable
|
||||
this._setBackgroundGradient(true);
|
||||
|
|
@ -190,7 +195,7 @@ export class GlobalThemeService {
|
|||
this._workContextService.currentTheme$.subscribe((theme: WorkContextThemeCfg) =>
|
||||
this._setColorTheme(theme),
|
||||
);
|
||||
this.isDarkTheme$.subscribe((isDarkTheme) => this._setDarkTheme(isDarkTheme));
|
||||
this._isDarkThemeObs$.subscribe((isDarkTheme) => this._setDarkTheme(isDarkTheme));
|
||||
}
|
||||
|
||||
private _initHandlersForInitialBodyClasses(): void {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<div class="mac-os-drag-bar"></div>
|
||||
|
||||
@if (!(isPomodoroEnabled$ | async)) {
|
||||
@if (!isPomodoroEnabled()) {
|
||||
<header>
|
||||
<banner></banner>
|
||||
</header>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { ChangeDetectionStrategy, Component, inject, OnDestroy } from '@angular/core';
|
||||
import { TaskService } from '../../tasks/task.service';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { Subject } from 'rxjs';
|
||||
import { first, takeUntil } from 'rxjs/operators';
|
||||
import { GlobalConfigService } from '../../config/global-config.service';
|
||||
import { expandAnimation } from '../../../ui/animations/expand.ani';
|
||||
|
|
@ -31,7 +31,7 @@ import { FocusModeDurationSelectionComponent } from '../focus-mode-duration-sele
|
|||
import { FocusModePreparationComponent } from '../focus-mode-preparation/focus-mode-preparation.component';
|
||||
import { FocusModeMainComponent } from '../focus-mode-main/focus-mode-main.component';
|
||||
import { FocusModeTaskDoneComponent } from '../focus-mode-task-done/focus-mode-task-done.component';
|
||||
import { AsyncPipe, NgTemplateOutlet } from '@angular/common';
|
||||
import { NgTemplateOutlet } from '@angular/common';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
import { BannerService } from '../../../core/banner/banner.service';
|
||||
import { BannerId } from '../../../core/banner/banner.model';
|
||||
|
|
@ -55,7 +55,6 @@ import { FocusModeService } from '../focus-mode.service';
|
|||
FocusModeMainComponent,
|
||||
FocusModeTaskDoneComponent,
|
||||
MatButton,
|
||||
AsyncPipe,
|
||||
TranslatePipe,
|
||||
MatButtonToggleGroup,
|
||||
MatButtonToggle,
|
||||
|
|
@ -83,7 +82,9 @@ export class FocusModeOverlayComponent implements OnDestroy {
|
|||
initialValue: undefined,
|
||||
});
|
||||
|
||||
isPomodoroEnabled$: Observable<boolean> = this._store.select(selectIsPomodoroEnabled);
|
||||
isPomodoroEnabled = toSignal(this._store.select(selectIsPomodoroEnabled), {
|
||||
initialValue: false,
|
||||
});
|
||||
|
||||
T: typeof T = T;
|
||||
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@
|
|||
<div class="msg">
|
||||
<div class="worked-for-label">{{ T.F.FOCUS_MODE.WORKED_FOR | translate }}</div>
|
||||
<div class="worked-for-value">
|
||||
{{ lastSessionTotalDuration$ | async | msToString: true }}
|
||||
{{ lastSessionTotalDuration() | msToString: true }}
|
||||
</div>
|
||||
|
||||
<div class="task-title-label">{{ T.F.FOCUS_MODE.ON | translate }}</div>
|
||||
<div class="task-title">{{ taskTitle$ | async }}</div>
|
||||
<div class="task-title">{{ taskTitle() }}</div>
|
||||
</div>
|
||||
|
||||
<div class="btn-wrapper">
|
||||
|
|
@ -24,7 +24,7 @@
|
|||
{{ T.F.FOCUS_MODE.BACK_TO_PLANNING | translate }}
|
||||
</button>
|
||||
|
||||
@if (!(currentTask$ | async)) {
|
||||
@if (!currentTask()) {
|
||||
<button
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
|
|
@ -33,7 +33,7 @@
|
|||
{{ T.F.FOCUS_MODE.START_NEXT_FOCUS_SESSION | translate }}
|
||||
</button>
|
||||
}
|
||||
@if (currentTask$ | async) {
|
||||
@if (currentTask()) {
|
||||
<button
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { AsyncPipe } from '@angular/common';
|
||||
import { AfterViewInit, ChangeDetectionStrategy, Component, inject } from '@angular/core';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
import { MatButton } from '@angular/material/button';
|
||||
|
||||
import { of } from 'rxjs';
|
||||
|
|
@ -31,25 +31,26 @@ import {
|
|||
templateUrl: './focus-mode-task-done.component.html',
|
||||
styleUrls: ['./focus-mode-task-done.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [MatButton, AsyncPipe, MsToStringPipe, TranslatePipe],
|
||||
imports: [MatButton, MsToStringPipe, TranslatePipe],
|
||||
})
|
||||
export class FocusModeTaskDoneComponent implements AfterViewInit {
|
||||
private _store = inject(Store);
|
||||
private readonly _confettiService = inject(ConfettiService);
|
||||
|
||||
mode$ = this._store.select(selectFocusModeMode);
|
||||
currentTask$ = this._store.select(selectCurrentTask);
|
||||
lastCurrentTask$ = this._store.select(selectLastCurrentTask);
|
||||
taskTitle$ = this.lastCurrentTask$.pipe(
|
||||
switchMap((lastCurrentTask) =>
|
||||
lastCurrentTask
|
||||
? of(lastCurrentTask.title)
|
||||
: this.currentTask$.pipe(map((task) => task?.title)),
|
||||
mode = toSignal(this._store.select(selectFocusModeMode));
|
||||
currentTask = toSignal(this._store.select(selectCurrentTask));
|
||||
taskTitle = toSignal(
|
||||
this._store.select(selectLastCurrentTask).pipe(
|
||||
switchMap((lastCurrentTask) =>
|
||||
lastCurrentTask
|
||||
? of(lastCurrentTask.title)
|
||||
: this._store.select(selectCurrentTask).pipe(map((task) => task?.title)),
|
||||
),
|
||||
take(1),
|
||||
),
|
||||
take(1),
|
||||
);
|
||||
lastSessionTotalDuration$ = this._store.select(
|
||||
selectLastSessionTotalDurationOrTimeElapsedFallback,
|
||||
lastSessionTotalDuration = toSignal(
|
||||
this._store.select(selectLastSessionTotalDurationOrTimeElapsedFallback),
|
||||
);
|
||||
T: typeof T = T;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<div class="wrapper">
|
||||
@if (projectMetricsService.simpleMetrics$ | async; as sm) {
|
||||
@if (projectMetricsService.simpleMetrics(); as sm) {
|
||||
<section
|
||||
class="basic-stats"
|
||||
[@fade]
|
||||
|
|
@ -63,16 +63,16 @@
|
|||
</div>
|
||||
</section>
|
||||
}
|
||||
@if (!(metricService.hasData$ | async)) {
|
||||
@if (!metricService.hasData()) {
|
||||
<p style="margin-top: 32px">
|
||||
{{ T.F.METRIC.CMP.NO_ADDITIONAL_DATA_YET | translate }}
|
||||
</p>
|
||||
}
|
||||
@if (metricService.hasData$ | async) {
|
||||
@if (metricService.hasData()) {
|
||||
<section class="metric-metrics">
|
||||
<h1>{{ T.F.METRIC.CMP.GLOBAL_METRICS | translate }}</h1>
|
||||
<section class="pie-charts">
|
||||
@if (metricService.improvementCountsPieChartData$ | async; as improvementCounts) {
|
||||
@if (metricService.improvementCountsPieChartData(); as improvementCounts) {
|
||||
<section>
|
||||
<h3>{{ T.F.METRIC.CMP.IMPROVEMENT_SELECTION_COUNT | translate }}</h3>
|
||||
<lazy-chart
|
||||
|
|
@ -86,7 +86,7 @@
|
|||
</lazy-chart>
|
||||
</section>
|
||||
}
|
||||
@if (metricService.obstructionCountsPieChartData$ | async; as obstructionCounts) {
|
||||
@if (metricService.obstructionCountsPieChartData(); as obstructionCounts) {
|
||||
<section>
|
||||
<h3>{{ T.F.METRIC.CMP.OBSTRUCTION_SELECTION_COUNT | translate }}</h3>
|
||||
<lazy-chart
|
||||
|
|
@ -102,7 +102,7 @@
|
|||
}
|
||||
</section>
|
||||
<section class="line-charts">
|
||||
@if (productivityHappiness$ | async; as productivityHappiness) {
|
||||
@if (productivityHappiness(); as productivityHappiness) {
|
||||
<section>
|
||||
<h3>{{ T.F.METRIC.CMP.MOOD_PRODUCTIVITY_OVER_TIME | translate }}</h3>
|
||||
<lazy-chart
|
||||
|
|
@ -118,11 +118,11 @@
|
|||
</section>
|
||||
</section>
|
||||
}
|
||||
@if (metricService.hasData$ | async) {
|
||||
@if (metricService.hasData()) {
|
||||
<section class="metric-metrics">
|
||||
<h2>{{ T.F.METRIC.CMP.SIMPLE_COUNTERS | translate }}</h2>
|
||||
<section class="line-charts">
|
||||
@if (simpleClickCounterData$ | async; as simpleCounterClickData) {
|
||||
@if (simpleClickCounterData(); as simpleCounterClickData) {
|
||||
<section>
|
||||
<h3>{{ T.F.METRIC.CMP.SIMPLE_CLICK_COUNTERS_OVER_TIME | translate }}</h3>
|
||||
<lazy-chart
|
||||
|
|
@ -136,7 +136,7 @@
|
|||
</lazy-chart>
|
||||
</section>
|
||||
}
|
||||
@if (simpleCounterStopWatchData$ | async; as simpleCounterStopWatchData) {
|
||||
@if (simpleCounterStopWatchData(); as simpleCounterStopWatchData) {
|
||||
<section>
|
||||
<h3>{{ T.F.METRIC.CMP.SIMPLE_STOPWATCH_COUNTERS_OVER_TIME | translate }}</h3>
|
||||
<lazy-chart
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
|
||||
import { ChartConfiguration, ChartType } from 'chart.js';
|
||||
import { MetricService } from './metric.service';
|
||||
import { Observable } from 'rxjs';
|
||||
import { LineChartData } from './metric.model';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
import { fadeAnimation } from '../../ui/animations/fade.ani';
|
||||
import { T } from '../../t.const';
|
||||
import { ProjectMetricsService } from './project-metrics.service';
|
||||
|
|
@ -27,14 +26,15 @@ export class MetricComponent {
|
|||
|
||||
T: typeof T = T;
|
||||
|
||||
productivityHappiness$: Observable<LineChartData> =
|
||||
this.metricService.getProductivityHappinessChartData$();
|
||||
productivityHappiness = toSignal(
|
||||
this.metricService.getProductivityHappinessChartData$(),
|
||||
);
|
||||
|
||||
simpleClickCounterData$: Observable<LineChartData> =
|
||||
this.metricService.getSimpleClickCounterMetrics$();
|
||||
simpleClickCounterData = toSignal(this.metricService.getSimpleClickCounterMetrics$());
|
||||
|
||||
simpleCounterStopWatchData$: Observable<LineChartData> =
|
||||
this.metricService.getSimpleCounterStopwatchMetrics$();
|
||||
simpleCounterStopWatchData = toSignal(
|
||||
this.metricService.getSimpleCounterStopwatchMetrics$(),
|
||||
);
|
||||
|
||||
pieChartOptions: ChartConfiguration<'pie', number[], string>['options'] = {
|
||||
scales: {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { Injectable, inject } from '@angular/core';
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
import { select, Store } from '@ngrx/store';
|
||||
import {
|
||||
addMetric,
|
||||
|
|
@ -7,11 +8,10 @@ import {
|
|||
upsertMetric,
|
||||
} from './store/metric.actions';
|
||||
import { combineLatest, Observable, of } from 'rxjs';
|
||||
import { LineChartData, Metric, MetricState, PieChartData } from './metric.model';
|
||||
import { LineChartData, Metric, MetricState } from './metric.model';
|
||||
import {
|
||||
selectImprovementCountsPieChartData,
|
||||
selectMetricById,
|
||||
selectMetricFeatureState,
|
||||
selectMetricHasData,
|
||||
selectObstructionCountsPieChartData,
|
||||
selectProductivityHappinessLineChartData,
|
||||
|
|
@ -24,7 +24,6 @@ import {
|
|||
selectCheckedImprovementIdsForDay,
|
||||
selectRepeatedImprovementIds,
|
||||
} from './improvement/store/improvement.reducer';
|
||||
import { selectHasSimpleCounterData } from '../simple-counter/store/simple-counter.reducer';
|
||||
import { DateService } from 'src/app/core/date/date.service';
|
||||
|
||||
@Injectable({
|
||||
|
|
@ -34,18 +33,17 @@ export class MetricService {
|
|||
private _store$ = inject<Store<MetricState>>(Store);
|
||||
private _dateService = inject(DateService);
|
||||
|
||||
// metrics$: Observable<Metric[]> = this._store$.pipe(select(selectAllMetrics));
|
||||
hasData$: Observable<boolean> = this._store$.pipe(select(selectMetricHasData));
|
||||
hasSimpleCounterData$: Observable<boolean> = this._store$.pipe(
|
||||
select(selectHasSimpleCounterData),
|
||||
hasData = toSignal(this._store$.pipe(select(selectMetricHasData)), {
|
||||
initialValue: false,
|
||||
});
|
||||
improvementCountsPieChartData = toSignal(
|
||||
this._store$.pipe(select(selectImprovementCountsPieChartData)),
|
||||
{ initialValue: null },
|
||||
);
|
||||
state$: Observable<MetricState> = this._store$.pipe(select(selectMetricFeatureState));
|
||||
// lastTrackedMetric$: Observable<Metric> = this._store$.pipe(select(selectLastTrackedMetric));
|
||||
improvementCountsPieChartData$: Observable<PieChartData | null> = this._store$.pipe(
|
||||
select(selectImprovementCountsPieChartData),
|
||||
);
|
||||
obstructionCountsPieChartData$: Observable<PieChartData | null> = this._store$.pipe(
|
||||
select(selectObstructionCountsPieChartData),
|
||||
|
||||
obstructionCountsPieChartData = toSignal(
|
||||
this._store$.pipe(select(selectObstructionCountsPieChartData)),
|
||||
{ initialValue: null },
|
||||
);
|
||||
|
||||
// getMetricForDay$(id: string = getWorklogStr()): Observable<Metric> {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { Injectable, inject } from '@angular/core';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
import { combineLatest, EMPTY, from, Observable } from 'rxjs';
|
||||
import { SimpleMetrics } from './metric.model';
|
||||
import { delay, map, switchMap, take } from 'rxjs/operators';
|
||||
|
|
@ -18,7 +19,7 @@ export class ProjectMetricsService {
|
|||
private _worklogService = inject(WorklogService);
|
||||
private _workContextService = inject(WorkContextService);
|
||||
|
||||
simpleMetrics$: Observable<SimpleMetrics> =
|
||||
private _simpleMetricsObs$: Observable<SimpleMetrics> =
|
||||
this._workContextService.activeWorkContextTypeAndId$.pipe(
|
||||
// wait for current projectId to settle in :(
|
||||
delay(100),
|
||||
|
|
@ -38,4 +39,6 @@ export class ProjectMetricsService {
|
|||
: EMPTY;
|
||||
}),
|
||||
);
|
||||
|
||||
simpleMetrics = toSignal(this._simpleMetricsObs$);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@
|
|||
}
|
||||
</button>
|
||||
|
||||
@if (isSearchIssueProvidersAvailable$ | async) {
|
||||
@if (isSearchIssueProvidersAvailable()) {
|
||||
<button
|
||||
(click)="isSearchIssueProviders.set(!isSearchIssueProviders())"
|
||||
[matTooltip]="'Toggle searching issue providers'"
|
||||
|
|
@ -92,7 +92,7 @@
|
|||
}
|
||||
</button>
|
||||
}
|
||||
@if (isAddToBacklogAvailable$ | async) {
|
||||
@if (isAddToBacklogAvailable()) {
|
||||
<button
|
||||
(click)="isAddToBacklog.set(!isAddToBacklog())"
|
||||
[matTooltip]="T.F.TASK.ADD_TASK_BAR.TOGGLE_ADD_TO_BACKLOG_TODAY | translate"
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ import { MentionConfig } from 'angular-mentions/lib/mention-config';
|
|||
import { AddTaskBarService } from './add-task-bar.service';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { selectEnabledIssueProviders } from '../../issue/store/issue-provider.selectors';
|
||||
import { toObservable } from '@angular/core/rxjs-interop';
|
||||
import { toObservable, toSignal } from '@angular/core/rxjs-interop';
|
||||
import {
|
||||
MatAutocomplete,
|
||||
MatAutocompleteOrigin,
|
||||
|
|
@ -138,12 +138,17 @@ export class AddTaskBarComponent implements AfterViewInit, OnDestroy {
|
|||
|
||||
mentionConfig$: Observable<MentionConfig> = this._addTaskBarService.getMentionConfig$();
|
||||
|
||||
isAddToBacklogAvailable$: Observable<boolean> =
|
||||
this._workContextService.activeWorkContext$.pipe(map((ctx) => !!ctx.isEnableBacklog));
|
||||
isAddToBacklogAvailable = toSignal(
|
||||
this._workContextService.activeWorkContext$.pipe(map((ctx) => !!ctx.isEnableBacklog)),
|
||||
{ initialValue: false },
|
||||
);
|
||||
|
||||
isSearchIssueProvidersAvailable$: Observable<boolean> = this._store
|
||||
.select(selectEnabledIssueProviders)
|
||||
.pipe(map((issueProviders) => issueProviders.length > 0));
|
||||
isSearchIssueProvidersAvailable = toSignal(
|
||||
this._store
|
||||
.select(selectEnabledIssueProviders)
|
||||
.pipe(map((issueProviders) => issueProviders.length > 0)),
|
||||
{ initialValue: false },
|
||||
);
|
||||
|
||||
private _isAddInProgress?: boolean;
|
||||
private _lastAddedTaskId?: string;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<work-view
|
||||
[backlogTasks]="workContextService.backlogTasks$ | async"
|
||||
[doneTasks]="workContextService.doneTasks$ | async"
|
||||
[isShowBacklog]="isShowBacklog$ | async"
|
||||
[undoneTasks]="workContextService.undoneTasks$ | async"
|
||||
[backlogTasks]="backlogTasks()"
|
||||
[doneTasks]="doneTasks()"
|
||||
[isShowBacklog]="isShowBacklog()"
|
||||
[undoneTasks]="undoneTasks()"
|
||||
></work-view>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
|
||||
import { WorkContextService } from '../../features/work-context/work-context.service';
|
||||
import { Observable } from 'rxjs';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { AsyncPipe } from '@angular/common';
|
||||
import { WorkViewComponent } from '../../features/work-view/work-view.component';
|
||||
|
||||
@Component({
|
||||
|
|
@ -10,12 +9,19 @@ import { WorkViewComponent } from '../../features/work-view/work-view.component'
|
|||
templateUrl: './project-task-page.component.html',
|
||||
styleUrls: ['./project-task-page.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [AsyncPipe, WorkViewComponent],
|
||||
imports: [WorkViewComponent],
|
||||
})
|
||||
export class ProjectTaskPageComponent {
|
||||
workContextService = inject(WorkContextService);
|
||||
|
||||
isShowBacklog$: Observable<boolean> = this.workContextService.activeWorkContext$.pipe(
|
||||
map((workContext) => !!workContext.isEnableBacklog),
|
||||
isShowBacklog = toSignal(
|
||||
this.workContextService.activeWorkContext$.pipe(
|
||||
map((workContext) => !!workContext.isEnableBacklog),
|
||||
),
|
||||
{ initialValue: false },
|
||||
);
|
||||
|
||||
backlogTasks = toSignal(this.workContextService.backlogTasks$, { initialValue: [] });
|
||||
doneTasks = toSignal(this.workContextService.doneTasks$, { initialValue: [] });
|
||||
undoneTasks = toSignal(this.workContextService.undoneTasks$, { initialValue: [] });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,13 +19,7 @@ import { MatProgressSpinner } from '@angular/material/progress-spinner';
|
|||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { Subject } from 'rxjs';
|
||||
import {
|
||||
debounceTime,
|
||||
distinctUntilChanged,
|
||||
switchMap,
|
||||
takeUntil,
|
||||
take,
|
||||
} from 'rxjs/operators';
|
||||
import { debounceTime, distinctUntilChanged, switchMap, takeUntil } from 'rxjs/operators';
|
||||
import { UnsplashService, UnsplashPhoto } from '../../core/unsplash/unsplash.service';
|
||||
import { GlobalThemeService } from '../../core/theme/global-theme.service';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
|
@ -108,13 +102,9 @@ export class DialogUnsplashPickerComponent implements OnInit, OnDestroy {
|
|||
this.isLoading.set(true);
|
||||
// If empty query, use context-aware default
|
||||
if (!query.trim()) {
|
||||
return this._globalThemeService.isDarkTheme$.pipe(
|
||||
take(1),
|
||||
switchMap((isDark) => {
|
||||
const defaultQuery = this.getDefaultSearchQuery(isDark);
|
||||
return this._unsplashService.searchPhotos(defaultQuery);
|
||||
}),
|
||||
);
|
||||
const isDark = this._globalThemeService.isDarkTheme();
|
||||
const defaultQuery = this.getDefaultSearchQuery(isDark);
|
||||
return this._unsplashService.searchPhotos(defaultQuery);
|
||||
}
|
||||
return this._unsplashService.searchPhotos(query);
|
||||
}),
|
||||
|
|
@ -132,9 +122,8 @@ export class DialogUnsplashPickerComponent implements OnInit, OnDestroy {
|
|||
});
|
||||
|
||||
// Initial load with context-aware defaults
|
||||
this._globalThemeService.isDarkTheme$.pipe(take(1)).subscribe((isDark) => {
|
||||
this.onSearchChange(this.getDefaultSearchQuery(isDark));
|
||||
});
|
||||
const isDark = this._globalThemeService.isDarkTheme();
|
||||
this.onSearchChange(this.getDefaultSearchQuery(isDark));
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<div
|
||||
#wrapperEl
|
||||
[class.isHideOverflow]="isHideOverflow"
|
||||
[class.isHideOverflow]="isHideOverflow()"
|
||||
class="markdown-wrapper"
|
||||
>
|
||||
@if (isShowEdit || isTurnOffMarkdownParsing()) {
|
||||
@if (isShowEdit() || isTurnOffMarkdownParsing()) {
|
||||
<textarea
|
||||
#textareaEl
|
||||
(blur)="untoggleShowEdit(); setBlur($event)"
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
(keydown)="keypressHandler($event)"
|
||||
(keypress)="keypressHandler($event)"
|
||||
[@fadeIn]
|
||||
[ngModel]="modelCopy"
|
||||
[ngModel]="modelCopy()"
|
||||
class="mat-body-2 markdown-unparsed"
|
||||
rows="1"
|
||||
></textarea>
|
||||
|
|
@ -24,7 +24,7 @@
|
|||
#previewEl
|
||||
(click)="clickPreview($event)"
|
||||
[data]="model"
|
||||
[hidden]="isShowEdit"
|
||||
[hidden]="isShowEdit()"
|
||||
class="mat-body-2 markdown-parsed markdown"
|
||||
markdown
|
||||
tabindex="0"
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {
|
|||
OnDestroy,
|
||||
OnInit,
|
||||
output,
|
||||
signal,
|
||||
viewChild,
|
||||
} from '@angular/core';
|
||||
import { fadeInAnimation } from '../animations/fade.ani';
|
||||
|
|
@ -53,10 +54,10 @@ export class InlineMarkdownComponent implements OnInit, OnDestroy {
|
|||
readonly textareaEl = viewChild<ElementRef>('textareaEl');
|
||||
readonly previewEl = viewChild<MarkdownComponent>('previewEl');
|
||||
|
||||
isHideOverflow: boolean = false;
|
||||
isChecklistMode: boolean = false;
|
||||
isShowEdit: boolean = false;
|
||||
modelCopy: string | undefined;
|
||||
isHideOverflow = signal(false);
|
||||
isChecklistMode = signal(false);
|
||||
isShowEdit = signal(false);
|
||||
modelCopy = signal<string | undefined>(undefined);
|
||||
|
||||
isTurnOffMarkdownParsing = computed(() => {
|
||||
const misc = this._globalConfigService.misc();
|
||||
|
|
@ -69,7 +70,7 @@ export class InlineMarkdownComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
@HostBinding('class.isFocused') get isFocused(): boolean {
|
||||
return this.isShowEdit;
|
||||
return this.isShowEdit();
|
||||
}
|
||||
|
||||
private _model: string | undefined;
|
||||
|
|
@ -82,25 +83,26 @@ export class InlineMarkdownComponent implements OnInit, OnDestroy {
|
|||
// Accessor inputs cannot be migrated as they are too complex.
|
||||
@Input() set model(v: string | undefined) {
|
||||
this._model = v;
|
||||
this.modelCopy = v;
|
||||
this.modelCopy.set(v);
|
||||
|
||||
if (!this.isShowEdit) {
|
||||
if (!this.isShowEdit()) {
|
||||
window.setTimeout(() => {
|
||||
this.resizeParsedToFit();
|
||||
});
|
||||
}
|
||||
|
||||
this.isChecklistMode =
|
||||
this.isChecklistMode &&
|
||||
this.isShowChecklistToggle() &&
|
||||
!!v &&
|
||||
isMarkdownChecklist(v);
|
||||
this.isChecklistMode.set(
|
||||
this.isChecklistMode() &&
|
||||
this.isShowChecklistToggle() &&
|
||||
!!v &&
|
||||
isMarkdownChecklist(v),
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: Skipped for migration because:
|
||||
// Accessor inputs cannot be migrated as they are too complex.
|
||||
@Input() set isFocus(val: boolean) {
|
||||
if (!this.isShowEdit && val) {
|
||||
if (!this.isShowEdit() && val) {
|
||||
this._toggleShowEdit();
|
||||
}
|
||||
}
|
||||
|
|
@ -123,7 +125,7 @@ export class InlineMarkdownComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
checklistToggle(): void {
|
||||
this.isChecklistMode = !this.isChecklistMode;
|
||||
this.isChecklistMode.set(!this.isChecklistMode());
|
||||
}
|
||||
|
||||
keypressHandler(ev: KeyboardEvent): void {
|
||||
|
|
@ -154,17 +156,17 @@ export class InlineMarkdownComponent implements OnInit, OnDestroy {
|
|||
untoggleShowEdit(): void {
|
||||
if (!this.isLock()) {
|
||||
this.resizeParsedToFit();
|
||||
this.isShowEdit = false;
|
||||
this.isShowEdit.set(false);
|
||||
}
|
||||
const textareaEl = this.textareaEl();
|
||||
if (!textareaEl) {
|
||||
throw new Error('Textarea not visible');
|
||||
}
|
||||
this.modelCopy = textareaEl.nativeElement.value;
|
||||
this.modelCopy.set(textareaEl.nativeElement.value);
|
||||
|
||||
if (this.modelCopy !== this.model) {
|
||||
this.model = this.modelCopy;
|
||||
this.changed.emit(this.modelCopy as string);
|
||||
if (this.modelCopy() !== this.model) {
|
||||
this.model = this.modelCopy();
|
||||
this.changed.emit(this.modelCopy() as string);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -190,13 +192,13 @@ export class InlineMarkdownComponent implements OnInit, OnDestroy {
|
|||
height: '100vh',
|
||||
restoreFocus: true,
|
||||
data: {
|
||||
content: this.modelCopy,
|
||||
content: this.modelCopy(),
|
||||
},
|
||||
})
|
||||
.afterClosed()
|
||||
.subscribe((res) => {
|
||||
if (typeof res === 'string') {
|
||||
this.modelCopy = res;
|
||||
this.modelCopy.set(res);
|
||||
this.changed.emit(res);
|
||||
}
|
||||
});
|
||||
|
|
@ -236,40 +238,44 @@ export class InlineMarkdownComponent implements OnInit, OnDestroy {
|
|||
toggleChecklistMode(ev: Event): void {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
this.isChecklistMode = true;
|
||||
this.isChecklistMode.set(true);
|
||||
this._toggleShowEdit();
|
||||
|
||||
if (this.isDefaultText()) {
|
||||
this.modelCopy = '- [ ] ';
|
||||
this.modelCopy.set('- [ ] ');
|
||||
} else {
|
||||
this.modelCopy += '\n- [ ] ';
|
||||
this.modelCopy.set(this.modelCopy() + '\n- [ ] ');
|
||||
// cleanup string on add
|
||||
this.modelCopy = this.modelCopy?.replace(/\n\n- \[/g, '\n- [').replace(/^\n/g, '');
|
||||
this.modelCopy.set(
|
||||
this.modelCopy()
|
||||
?.replace(/\n\n- \[/g, '\n- [')
|
||||
.replace(/^\n/g, ''),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private _toggleShowEdit(): void {
|
||||
this.isShowEdit = true;
|
||||
this.modelCopy = this.model || '';
|
||||
this.isShowEdit.set(true);
|
||||
this.modelCopy.set(this.model || '');
|
||||
setTimeout(() => {
|
||||
const textareaEl = this.textareaEl();
|
||||
if (!textareaEl) {
|
||||
throw new Error('Textarea not visible');
|
||||
}
|
||||
textareaEl.nativeElement.value = this.modelCopy;
|
||||
textareaEl.nativeElement.value = this.modelCopy();
|
||||
textareaEl.nativeElement.focus();
|
||||
this.resizeTextareaToFit();
|
||||
});
|
||||
}
|
||||
|
||||
private _hideOverflow(): void {
|
||||
this.isHideOverflow = true;
|
||||
this.isHideOverflow.set(true);
|
||||
if (this._hideOverFlowTimeout) {
|
||||
window.clearTimeout(this._hideOverFlowTimeout);
|
||||
}
|
||||
|
||||
this._hideOverFlowTimeout = window.setTimeout(() => {
|
||||
this.isHideOverflow = false;
|
||||
this.isHideOverflow.set(false);
|
||||
this._cd.detectChanges();
|
||||
}, HIDE_OVERFLOW_TIMEOUT_DURATION);
|
||||
}
|
||||
|
|
@ -311,12 +317,12 @@ export class InlineMarkdownComponent implements OnInit, OnDestroy {
|
|||
allLines[itemIndex] = item.includes('[ ]')
|
||||
? item.replace('[ ]', '[x]').replace('[]', '[x]')
|
||||
: item.replace('[x]', '[ ]');
|
||||
this.modelCopy = allLines.join('\n');
|
||||
this.modelCopy.set(allLines.join('\n'));
|
||||
|
||||
// Update the markdown string
|
||||
if (this.modelCopy !== this.model) {
|
||||
this.model = this.modelCopy;
|
||||
this.changed.emit(this.modelCopy);
|
||||
if (this.modelCopy() !== this.model) {
|
||||
this.model = this.modelCopy();
|
||||
this.changed.emit(this.modelCopy() as string);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue