mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-17 16:37:43 +00:00
fix(tasks): remove empty subtask drop-target for childless parents
The dashed empty sub-task drop zone that appeared under childless parent tasks during a drag was unwanted UI. Remove it along with its supporting machinery (the subTaskDropCandidate signal in ScheduleExternalDragService, the pointerdown candidate-arming in TaskListComponent, the isEmptySubTaskDropTargetMounted computed, and the related template/SCSS). Consequence: a task can now be nested by dragging only onto a parent that already has a subtask list. Dragging a subtask back out to a main list (convertToMainTask) and nesting into an existing subtask list (convertToSubTask) are unchanged.
This commit is contained in:
parent
96b4a2aa69
commit
ed43940e09
6 changed files with 24 additions and 106 deletions
|
|
@ -5,7 +5,6 @@ import { DragRef } from '@angular/cdk/drag-drop';
|
|||
@Injectable({ providedIn: 'root' })
|
||||
export class ScheduleExternalDragService {
|
||||
private readonly _activeTask = signal<TaskWithSubTasks | null>(null);
|
||||
private readonly _subTaskDropCandidate = signal<TaskWithSubTasks | null>(null);
|
||||
private _activeDragRef: DragRef | null = null;
|
||||
private _cancelNextDrop: boolean = false;
|
||||
|
||||
|
|
@ -25,14 +24,6 @@ export class ScheduleExternalDragService {
|
|||
return this._activeDragRef;
|
||||
}
|
||||
|
||||
subTaskDropCandidate(): TaskWithSubTasks | null {
|
||||
return this._subTaskDropCandidate();
|
||||
}
|
||||
|
||||
setSubTaskDropCandidate(task: TaskWithSubTasks | null): void {
|
||||
this._subTaskDropCandidate.set(task);
|
||||
}
|
||||
|
||||
setActiveTask(task: TaskWithSubTasks | null, dragRef: DragRef | null = null): void {
|
||||
this._activeTask.set(task);
|
||||
this._activeDragRef = dragRef;
|
||||
|
|
|
|||
|
|
@ -87,8 +87,6 @@ describe('TaskListComponent', () => {
|
|||
useValue: {
|
||||
activeTask: () => null,
|
||||
setActiveTask: () => {},
|
||||
subTaskDropCandidate: () => null,
|
||||
setSubTaskDropCandidate: () => {},
|
||||
isCancelNextDrop: () => false,
|
||||
setCancelNextDrop: () => {},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -154,7 +154,6 @@ export class TaskListComponent implements OnDestroy, AfterViewInit {
|
|||
allTasksLength = computed(() => this.tasks()?.length ?? 0);
|
||||
|
||||
readonly dropList = viewChild(CdkDropList);
|
||||
private _clearSubTaskDropCandidate: (() => void) | undefined;
|
||||
private _clearDragPointerTracking: (() => void) | undefined;
|
||||
|
||||
T: typeof T = T;
|
||||
|
|
@ -164,7 +163,6 @@ export class TaskListComponent implements OnDestroy, AfterViewInit {
|
|||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this._clearSubTaskDropCandidate?.();
|
||||
this._clearDragPointerTracking?.();
|
||||
this.dropListService.unregisterDropList(this.dropList()!);
|
||||
this._scheduleExternalDragService.setActiveTask(null);
|
||||
|
|
@ -176,28 +174,15 @@ export class TaskListComponent implements OnDestroy, AfterViewInit {
|
|||
}
|
||||
|
||||
onDragPointerDown(task: TaskWithSubTasks, event: PointerEvent): void {
|
||||
this._clearSubTaskDropCandidate?.();
|
||||
// Seed the pointer position so subtask -> parent-list drags can hit-test
|
||||
// the source subtask list before the first pointermove (see
|
||||
// _isPointerOverSubTaskList).
|
||||
if (task.parentId) {
|
||||
this.dropListService.setActiveDragPointer({ x: event.clientX, y: event.clientY });
|
||||
return;
|
||||
}
|
||||
if (!canConvertTaskToSubTask(task)) {
|
||||
return;
|
||||
}
|
||||
this._scheduleExternalDragService.setSubTaskDropCandidate(task);
|
||||
const clearCandidate = (): void => {
|
||||
this._scheduleExternalDragService.setSubTaskDropCandidate(null);
|
||||
window.removeEventListener('pointerup', clearCandidate);
|
||||
window.removeEventListener('pointercancel', clearCandidate);
|
||||
this._clearSubTaskDropCandidate = undefined;
|
||||
};
|
||||
window.addEventListener('pointerup', clearCandidate);
|
||||
window.addEventListener('pointercancel', clearCandidate);
|
||||
this._clearSubTaskDropCandidate = clearCandidate;
|
||||
}
|
||||
|
||||
onDragStarted(task: TaskWithSubTasks, event: CdkDragStart): void {
|
||||
this._clearSubTaskDropCandidate?.();
|
||||
this._scheduleExternalDragService.setActiveTask(task, event.source._dragRef);
|
||||
if (task.parentId) {
|
||||
this._startDragPointerTracking();
|
||||
|
|
@ -205,7 +190,6 @@ export class TaskListComponent implements OnDestroy, AfterViewInit {
|
|||
}
|
||||
|
||||
onDragEnded(): void {
|
||||
this._clearSubTaskDropCandidate?.();
|
||||
this._clearDragPointerTracking?.();
|
||||
this._scheduleExternalDragService.setActiveTask(null);
|
||||
this.dropListService.setActiveDragPointer(null);
|
||||
|
|
|
|||
|
|
@ -253,42 +253,32 @@
|
|||
[progress]="progress()"
|
||||
></progress-bar>
|
||||
}
|
||||
@if (t.subTasks?.length || isEmptySubTaskDropTargetMounted()) {
|
||||
<div
|
||||
class="sub-tasks"
|
||||
[class.is-empty-sub-task-drop-target]="!t.subTasks?.length"
|
||||
>
|
||||
@if (t.subTasks?.length) {
|
||||
<button
|
||||
(click)="toggleSubTaskMode()"
|
||||
[title]="T.F.TASK.CMP.TOGGLE_SUB_TASK_VISIBILITY | translate"
|
||||
class="toggle-sub-tasks-btn ico-btn mat-elevation-z2"
|
||||
color=""
|
||||
mat-mini-fab
|
||||
>
|
||||
@if (t._hideSubTasksMode === ShowSubTasksMode.HideAll) {
|
||||
<mat-icon>add</mat-icon>
|
||||
} @else {
|
||||
<mat-icon
|
||||
[class.isHideDoneTasks]="
|
||||
t._hideSubTasksMode === ShowSubTasksMode.HideDone
|
||||
"
|
||||
>remove
|
||||
</mat-icon>
|
||||
}
|
||||
</button>
|
||||
}
|
||||
@if (t.subTasks?.length) {
|
||||
<div class="sub-tasks">
|
||||
<button
|
||||
(click)="toggleSubTaskMode()"
|
||||
[title]="T.F.TASK.CMP.TOGGLE_SUB_TASK_VISIBILITY | translate"
|
||||
class="toggle-sub-tasks-btn ico-btn mat-elevation-z2"
|
||||
color=""
|
||||
mat-mini-fab
|
||||
>
|
||||
@if (t._hideSubTasksMode === ShowSubTasksMode.HideAll) {
|
||||
<mat-icon>add</mat-icon>
|
||||
} @else {
|
||||
<mat-icon
|
||||
[class.isHideDoneTasks]="t._hideSubTasksMode === ShowSubTasksMode.HideDone"
|
||||
>remove
|
||||
</mat-icon>
|
||||
}
|
||||
</button>
|
||||
<task-list
|
||||
[@expandInOnly]
|
||||
[class.empty-sub-task-list]="!t.subTasks?.length"
|
||||
[isHideAll]="!!t.subTasks?.length && !!t._hideSubTasksMode"
|
||||
[isHideDone]="
|
||||
!!t.subTasks?.length && t._hideSubTasksMode === ShowSubTasksMode.HideDone
|
||||
"
|
||||
[isHideAll]="!!t._hideSubTasksMode"
|
||||
[isHideDone]="t._hideSubTasksMode === ShowSubTasksMode.HideDone"
|
||||
[isSubTaskList]="true"
|
||||
[listModelId]="t.id"
|
||||
[parentId]="t.id"
|
||||
[tasks]="t.subTasks || []"
|
||||
[tasks]="t.subTasks"
|
||||
listId="SUB"
|
||||
></task-list>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -14,30 +14,3 @@
|
|||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.sub-tasks.is-empty-sub-task-drop-target {
|
||||
position: absolute;
|
||||
left: var(--s3);
|
||||
right: var(--s);
|
||||
bottom: -7px;
|
||||
z-index: 1;
|
||||
height: 14px;
|
||||
margin: 0;
|
||||
opacity: 0.85;
|
||||
pointer-events: auto;
|
||||
|
||||
task-list.empty-sub-task-list {
|
||||
height: 14px;
|
||||
min-height: 14px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
task-list.empty-sub-task-list ::ng-deep .task-list-inner {
|
||||
height: 14px;
|
||||
min-height: 14px;
|
||||
border: 1px dashed var(--palette-primary-400);
|
||||
border-radius: var(--task-border-radius);
|
||||
background: color-mix(in srgb, var(--palette-primary-400) 18%, transparent);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,8 +89,6 @@ import { LayoutService } from '../../../core-ui/layout/layout.service';
|
|||
import { TaskFocusService } from '../task-focus.service';
|
||||
import { selectTimeConflictTaskIds } from '../store/task.selectors';
|
||||
import { MatTooltip } from '@angular/material/tooltip';
|
||||
import { ScheduleExternalDragService } from '../../schedule/schedule-week/schedule-external-drag.service';
|
||||
import { canConvertTaskToSubTask } from '../util/can-convert-task-to-sub-task';
|
||||
|
||||
@Component({
|
||||
selector: 'task',
|
||||
|
|
@ -148,7 +146,6 @@ export class TaskComponent implements OnDestroy, AfterViewInit {
|
|||
private readonly _taskFocusService = inject(TaskFocusService);
|
||||
private readonly _dateService = inject(DateService);
|
||||
private readonly _destroyRef = inject(DestroyRef);
|
||||
private readonly _scheduleExternalDragService = inject(ScheduleExternalDragService);
|
||||
|
||||
readonly workContextService = inject(WorkContextService);
|
||||
readonly layoutService = inject(LayoutService);
|
||||
|
|
@ -276,21 +273,6 @@ export class TaskComponent implements OnDestroy, AfterViewInit {
|
|||
),
|
||||
);
|
||||
|
||||
isEmptySubTaskDropTargetMounted = computed(() => {
|
||||
const t = this.task();
|
||||
const dragTask =
|
||||
this._scheduleExternalDragService.activeTask() ??
|
||||
this._scheduleExternalDragService.subTaskDropCandidate();
|
||||
return (
|
||||
!this.isInSubTaskList() &&
|
||||
!t.parentId &&
|
||||
!t.subTasks?.length &&
|
||||
!!dragTask &&
|
||||
dragTask.id !== t.id &&
|
||||
canConvertTaskToSubTask(dragTask)
|
||||
);
|
||||
});
|
||||
|
||||
hasDeadline = computed(() => {
|
||||
const t = this.task();
|
||||
return !!(t.deadlineDay || t.deadlineWithTime);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue