119 lines
3.4 KiB
TypeScript
119 lines
3.4 KiB
TypeScript
import {useI18n} from 'vue-i18n'
|
|
|
|
import type {ITask} from '@/modelTypes/ITask'
|
|
import {useTaskStore} from '@/stores/tasks'
|
|
import {useProjectStore} from '@/stores/projects'
|
|
import {success, error} from '@/message'
|
|
|
|
/**
|
|
* Finds a project ID from elements at a given mouse position.
|
|
* Searches through elements under the mouse and their parents for data-project-id attribute.
|
|
*/
|
|
function findProjectIdAtPosition(mouseX: number, mouseY: number): number | null {
|
|
// Validate coordinates are finite numbers (required by elementsFromPoint)
|
|
// This prevents errors on Firefox mobile where touch events may provide invalid coordinates
|
|
if (!Number.isFinite(mouseX) || !Number.isFinite(mouseY)) {
|
|
return null
|
|
}
|
|
|
|
const elementsUnderMouse = document.elementsFromPoint(mouseX, mouseY)
|
|
|
|
for (const el of elementsUnderMouse) {
|
|
if (!(el instanceof HTMLElement)) {
|
|
continue
|
|
}
|
|
|
|
const withProjectId =
|
|
el.dataset?.projectId != null
|
|
? el
|
|
: el.closest('[data-project-id]') as HTMLElement | null
|
|
|
|
const projectId = withProjectId?.dataset.projectId
|
|
if (projectId) {
|
|
const parsed = parseInt(projectId, 10)
|
|
if (!Number.isNaN(parsed)) {
|
|
return parsed
|
|
}
|
|
}
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
export interface TaskDragToProjectResult {
|
|
moved: boolean
|
|
targetProjectId: number | null
|
|
}
|
|
|
|
/**
|
|
* Composable for handling task drag-and-drop to sidebar projects.
|
|
*
|
|
* Provides functionality to:
|
|
* - Detect when a task is dropped over a sidebar project
|
|
* - Move the task to the target project
|
|
* - Show success/error notifications
|
|
*
|
|
* @returns Functions for handling drag start and checking for project drops
|
|
*/
|
|
export function useTaskDragToProject() {
|
|
const {t} = useI18n({useScope: 'global'})
|
|
const taskStore = useTaskStore()
|
|
const projectStore = useProjectStore()
|
|
|
|
/**
|
|
* Attempts to move a dragged task to a project at the given mouse position.
|
|
* Should be called in the drag end handler.
|
|
*
|
|
* @param e - The drag event with originalEvent containing mouse coordinates
|
|
* @param onSuccess - Optional callback called after successful move (e.g., to update local state)
|
|
* @returns Result indicating if task was moved and to which project
|
|
*/
|
|
async function handleTaskDropToProject(
|
|
e: { originalEvent?: MouseEvent },
|
|
onSuccess?: (task: ITask, targetProjectId: number) => void,
|
|
): Promise<TaskDragToProjectResult> {
|
|
const draggedTask = taskStore.draggedTask
|
|
|
|
if (!draggedTask || !e.originalEvent) {
|
|
taskStore.setDraggedTask(null)
|
|
return {moved: false, targetProjectId: null}
|
|
}
|
|
|
|
const mouseX = e.originalEvent.clientX
|
|
const mouseY = e.originalEvent.clientY
|
|
const targetProjectId = findProjectIdAtPosition(mouseX, mouseY)
|
|
|
|
if (!targetProjectId || targetProjectId <= 0 || targetProjectId === draggedTask.projectId) {
|
|
taskStore.setDraggedTask(null)
|
|
return {moved: false, targetProjectId}
|
|
}
|
|
|
|
const targetProject = projectStore.projects[targetProjectId]
|
|
|
|
try {
|
|
await taskStore.update({
|
|
...draggedTask,
|
|
projectId: targetProjectId,
|
|
})
|
|
|
|
if (onSuccess) {
|
|
onSuccess(draggedTask, targetProjectId)
|
|
}
|
|
|
|
success({message: t('task.movedToProject', {project: targetProject?.title || t('project.title')})})
|
|
|
|
return {moved: true, targetProjectId}
|
|
} catch (e) {
|
|
error(e)
|
|
return {moved: false, targetProjectId}
|
|
} finally {
|
|
// Always clears drag state - callers should not clear again
|
|
taskStore.setDraggedTask(null)
|
|
}
|
|
}
|
|
|
|
return {
|
|
handleTaskDropToProject,
|
|
}
|
|
}
|