refactor: convert to signals

This commit is contained in:
Johannes Millan 2025-07-17 11:21:36 +02:00
parent 1d2e80e552
commit 6edff77bb4
2 changed files with 76 additions and 77 deletions

View file

@ -1,7 +1,7 @@
<div
#gridContainer
class="week-grid-container {{ containerExtraClass }}"
[class.isCtrlKeyPressed]="isCtrlPressed"
class="week-grid-container {{ containerExtraClass() }}"
[class.isCtrlKeyPressed]="isCtrlPressed()"
(click)="onGridClick($event)"
(mousemove)="onMoveOverGrid($event)"
>
@ -24,12 +24,12 @@
}
<!-- Grid Cols -->
@for (day of daysToShow; track $index) {
@for (day of daysToShow(); track $index) {
<div
class="col"
[attr.data-day]="day"
style="grid-column: {{ $index + 2 }}; grid-row: 1 / span {{
($index === 0 ? currentTimeRow : endOfDayColRowStart) - 1
($index === 0 ? currentTimeRow() : endOfDayColRowStart()) - 1
}}"
>
<div class="drop-label">{{ T.F.SCHEDULE.PLAN_START_DAY | translate }}</div>
@ -38,24 +38,26 @@
class="col end-of-day"
[attr.data-day]="day"
style="grid-column: {{ $index + 2 }}; grid-row: {{
$index === 0 ? currentTimeRow : endOfDayColRowStart
}} / span {{ totalRows - ($index === 0 ? currentTimeRow : endOfDayColRowStart) }}"
$index === 0 ? currentTimeRow() : endOfDayColRowStart()
}} / span {{
totalRows - ($index === 0 ? currentTimeRow() : endOfDayColRowStart())
}}"
>
<div class="drop-label">{{ T.F.SCHEDULE.PLAN_END_DAY | translate }}</div>
</div>
}
<!-- Work Start and End -->
@if (workStartEnd) {
@if (workStartEnd()) {
<div
class="work-start"
style="grid-row: {{ workStartEnd.workStartRow }}"
style="grid-row: {{ workStartEnd()!.workStartRow }}"
>
<div>{{ T.F.SCHEDULE.START | translate }}</div>
</div>
<div
class="work-end"
style="grid-row: {{ workStartEnd.workEndRow }}"
style="grid-row: {{ workStartEnd()!.workEndRow }}"
>
<div>{{ T.F.SCHEDULE.END | translate }}</div>
</div>
@ -63,13 +65,13 @@
<div
class="current-time"
style="grid-column: 2; grid-row: {{ currentTimeRow }} / span 1"
style="grid-column: 2; grid-row: {{ currentTimeRow() }} / span 1"
>
<div class="circle"></div>
</div>
<!-- Events -->
@for (ev of safeEvents; track ev.id) {
@for (ev of safeEvents(); track ev.id) {
@if (
ev.type === SVEType.Task ||
ev.type === SVEType.SplitTask ||
@ -92,7 +94,7 @@
}
<!-- Excess tasks planned for day -->
@for (beyondBudgetDay of safeBeyondBudget; track i; let i = $index) {
@for (beyondBudgetDay of safeBeyondBudget(); track i; let i = $index) {
@if (beyondBudgetDay.length > 0) {
<div
class="excess-entries"
@ -124,12 +126,12 @@
}
}
@if (newTaskPlaceholder$ | async; as newTaskPlaceholder) {
@if (newTaskPlaceholder(); as newTaskPlaceholder) {
<create-task-placeholder
[isEditMode]="isCreateTaskActive"
[isEditMode]="isCreateTaskActive()"
[time]="newTaskPlaceholder.time"
[date]="newTaskPlaceholder.date"
(editEnd)="isCreateTaskActive = false; this.newTaskPlaceholder$.next(null)"
(editEnd)="isCreateTaskActive.set(false); this.newTaskPlaceholder.set(null)"
[style]="newTaskPlaceholder.style"
>
</create-task-placeholder>

View file

@ -1,16 +1,16 @@
import {
ChangeDetectionStrategy,
Component,
DestroyRef,
ElementRef,
inject,
Input,
input,
LOCALE_ID,
OnDestroy,
OnInit,
viewChild,
signal,
computed,
} from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { ScheduleEvent } from '../schedule.model';
import {
CdkDrag,
@ -24,14 +24,12 @@ import { FH, SVEType, T_ID_PREFIX } from '../schedule.const';
import { throttle } from 'helpful-decorators';
import { CreateTaskPlaceholderComponent } from '../create-task-placeholder/create-task-placeholder.component';
import { ScheduleEventComponent } from '../schedule-event/schedule-event.component';
import { AsyncPipe } from '@angular/common';
import { TranslatePipe } from '@ngx-translate/core';
import { MatIcon } from '@angular/material/icon';
import { T } from '../../../t.const';
import { IS_TOUCH_PRIMARY } from '../../../util/is-mouse-primary';
import { DRAG_DELAY_FOR_TOUCH } from '../../../app.constants';
import { MatTooltip } from '@angular/material/tooltip';
import { ShortcutService } from '../../../core-ui/shortcut/shortcut.service';
import { Log } from '../../../core/log';
const D_HOURS = 24;
@ -43,7 +41,6 @@ const IS_NOT_DRAGGING_CLASS = 'is-not-dragging';
@Component({
selector: 'schedule-week',
imports: [
AsyncPipe,
ScheduleEventComponent,
CdkDrag,
CreateTaskPlaceholderComponent,
@ -58,16 +55,14 @@ const IS_NOT_DRAGGING_CLASS = 'is-not-dragging';
})
export class ScheduleWeekComponent implements OnInit, OnDestroy {
private _store = inject(Store);
shortcutService = inject(ShortcutService);
private locale = inject(LOCALE_ID);
destroyRef = inject(DestroyRef);
@Input() events: ScheduleEvent[] | null = [];
@Input() beyondBudget: ScheduleEvent[][] | null = [];
@Input() daysToShow: string[] = [];
@Input() workStartEnd: { workStartRow: number; workEndRow: number } | null = null;
@Input() currentTimeRow: number = 0;
@Input() isCtrlPressed: boolean = false;
events = input<ScheduleEvent[] | null>([]);
beyondBudget = input<ScheduleEvent[][] | null>([]);
daysToShow = input<string[]>([]);
workStartEnd = input<{ workStartRow: number; workEndRow: number } | null>(null);
currentTimeRow = input<number>(0);
isCtrlPressed = input<boolean>(false);
FH = FH;
IS_TOUCH_PRIMARY = IS_TOUCH_PRIMARY;
@ -76,13 +71,13 @@ export class ScheduleWeekComponent implements OnInit, OnDestroy {
T: typeof T = T;
rowsByNr = Array.from({ length: D_HOURS * FH }, (_, index) => index).filter(
(v, index) => index % FH === 0,
(_, index) => index % FH === 0,
);
is12HourFormat = Intl.DateTimeFormat(this.locale, { hour: 'numeric' }).resolvedOptions()
.hour12;
times: string[] = this.rowsByNr.map((rowVal, index) => {
times: string[] = this.rowsByNr.map((_, index) => {
return this.is12HourFormat
? index >= 13
? (index - 12).toString() + ':00 PM'
@ -90,36 +85,32 @@ export class ScheduleWeekComponent implements OnInit, OnDestroy {
: index.toString() + ':00';
});
endOfDayColRowStart: number = D_HOURS * 0.5 * FH;
endOfDayColRowStart = signal<number>(D_HOURS * 0.5 * FH);
totalRows: number = D_HOURS * FH;
get safeEvents(): ScheduleEvent[] {
return this.events || [];
}
safeEvents = computed(() => this.events() || []);
safeBeyondBudget = computed(() => this.beyondBudget() || []);
get safeBeyondBudget(): ScheduleEvent[][] {
return this.beyondBudget || [];
}
newTaskPlaceholder$ = new BehaviorSubject<{
newTaskPlaceholder = signal<{
style: string;
time: string;
date: string;
} | null>(null);
isDragging = false;
isDraggingDelayed = false;
isCreateTaskActive = false;
containerExtraClass = IS_NOT_DRAGGING_CLASS;
prevDragOverEl: HTMLElement | null = null;
dragCloneEl: HTMLElement | null = null;
isDragging = signal(false);
isDraggingDelayed = signal(false);
isCreateTaskActive = signal(false);
containerExtraClass = signal(IS_NOT_DRAGGING_CLASS);
prevDragOverEl = signal<HTMLElement | null>(null);
dragCloneEl = signal<HTMLElement | null>(null);
readonly gridContainer = viewChild.required<ElementRef>('gridContainer');
private _currentAniTimeout: number | undefined;
ngOnInit(): void {
this.endOfDayColRowStart = this.workStartEnd?.workStartRow || D_HOURS * 0.5 * FH;
const workStartEnd = this.workStartEnd();
this.endOfDayColRowStart.set(workStartEnd?.workStartRow || D_HOURS * 0.5 * FH);
}
ngOnDestroy(): void {
@ -129,17 +120,17 @@ export class ScheduleWeekComponent implements OnInit, OnDestroy {
onGridClick(ev: MouseEvent): void {
if (ev.target instanceof HTMLElement) {
if (ev.target.classList.contains('col')) {
this.isCreateTaskActive = true;
this.isCreateTaskActive.set(true);
}
}
}
@throttle(30)
onMoveOverGrid(ev: MouseEvent): void {
if (this.isDragging || this.isDraggingDelayed) {
if (this.isDragging() || this.isDraggingDelayed()) {
return;
}
if (this.isCreateTaskActive) {
if (this.isCreateTaskActive()) {
return;
}
@ -173,19 +164,19 @@ export class ScheduleWeekComponent implements OnInit, OnDestroy {
const minutes = Math.floor(((row - 1) % FH) * (60 / FH));
const time = `${hours}:${minutes.toString().padStart(2, '0')}`;
this.newTaskPlaceholder$.next({
this.newTaskPlaceholder.set({
style: `grid-row: ${row} / span 6; grid-column: ${targetColColOffset} / span 1`,
time,
date: this.daysToShow[targetColColOffset - 2],
date: this.daysToShow()[targetColColOffset - 2],
});
} else {
this.newTaskPlaceholder$.next(null);
this.newTaskPlaceholder.set(null);
}
}
@throttle(30)
dragMoved(ev: CdkDragMove<ScheduleEvent>): void {
if (!this.isDragging) {
if (!this.isDragging()) {
return;
}
@ -201,13 +192,14 @@ export class ScheduleWeekComponent implements OnInit, OnDestroy {
return;
}
if (targetEl !== this.prevDragOverEl) {
const prevEl = this.prevDragOverEl();
if (targetEl !== prevEl) {
Log.log('dragMoved targetElChanged', targetEl);
if (this.prevDragOverEl) {
this.prevDragOverEl.classList.remove(DRAG_OVER_CLASS);
if (prevEl) {
prevEl.classList.remove(DRAG_OVER_CLASS);
}
this.prevDragOverEl = targetEl;
this.prevDragOverEl.set(targetEl);
if (
targetEl.classList.contains(SVEType.Task) ||
@ -224,38 +216,43 @@ export class ScheduleWeekComponent implements OnInit, OnDestroy {
dragStarted(ev: CdkDragStart<ScheduleEvent>): void {
Log.log('dragStart', ev);
this.isDragging = this.isDraggingDelayed = true;
this.containerExtraClass = IS_DRAGGING_CLASS + ' ' + ev.source.data.type;
this.isDragging.set(true);
this.isDraggingDelayed.set(true);
this.containerExtraClass.set(IS_DRAGGING_CLASS + ' ' + ev.source.data.type);
const cur = ev.source.element.nativeElement;
if (this.dragCloneEl) {
this.dragCloneEl.remove();
const cloneEl = this.dragCloneEl();
if (cloneEl) {
cloneEl.remove();
}
this.dragCloneEl = cur.cloneNode(true) as HTMLElement;
this.dragCloneEl.style.transform = 'translateY(0)';
this.dragCloneEl.style.opacity = '.1';
this.dragCloneEl.classList.add(DRAG_CLONE_CLASS);
cur.parentNode?.insertBefore(this.dragCloneEl, cur);
const newCloneEl = cur.cloneNode(true) as HTMLElement;
newCloneEl.style.transform = 'translateY(0)';
newCloneEl.style.opacity = '.1';
newCloneEl.classList.add(DRAG_CLONE_CLASS);
cur.parentNode?.insertBefore(newCloneEl, cur);
this.dragCloneEl.set(newCloneEl);
}
dragReleased(ev: CdkDragRelease): void {
const prevEl = this.prevDragOverEl();
Log.log('dragReleased', {
target: ev.event.target,
source: ev.source.element.nativeElement,
ev,
dragOverEl: this.prevDragOverEl,
dragOverEl: prevEl,
});
const target = (this.prevDragOverEl || ev.event.target) as HTMLElement;
if (this.prevDragOverEl) {
this.prevDragOverEl.classList.remove(DRAG_OVER_CLASS);
this.prevDragOverEl = null;
const target = (prevEl || ev.event.target) as HTMLElement;
if (prevEl) {
prevEl.classList.remove(DRAG_OVER_CLASS);
this.prevDragOverEl.set(null);
}
if (this.dragCloneEl) {
this.dragCloneEl.remove();
const cloneEl = this.dragCloneEl();
if (cloneEl) {
cloneEl.remove();
}
this.isDragging = false;
this.isDragging.set(false);
ev.source.element.nativeElement.style.pointerEvents = '';
ev.source.element.nativeElement.style.opacity = '0';
@ -264,10 +261,10 @@ export class ScheduleWeekComponent implements OnInit, OnDestroy {
ev.source.element.nativeElement.style.opacity = '';
ev.source.element.nativeElement.style.pointerEvents = '';
}
this.isDraggingDelayed = false;
this.isDraggingDelayed.set(false);
}, 100);
this.containerExtraClass = IS_NOT_DRAGGING_CLASS;
this.containerExtraClass.set(IS_NOT_DRAGGING_CLASS);
if (target.tagName.toLowerCase() === 'div' && target.classList.contains('col')) {
const isMoveToEndOfDay = target.classList.contains('end-of-day');