fix(boards): keep kanban card drag from locking after a few moves
Reset hung move queues, defer drag visuals past dragstart, and prefer card drop identity so HTML5 DnD no longer dies mid-drag. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, nextTick, ref, watch } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import type {
|
import type {
|
||||||
@@ -71,6 +71,43 @@ let peekLinger = 0;
|
|||||||
const moving = ref(false);
|
const moving = ref(false);
|
||||||
const stageBusy = ref(false);
|
const stageBusy = ref(false);
|
||||||
const localOrder = ref<string[] | null>(null);
|
const localOrder = ref<string[] | null>(null);
|
||||||
|
/** صف جابهجایی تا دراپ پشتسرهم قفل نشود؛ در watchdog هم ریست میشود */
|
||||||
|
let moveChain: Promise<void> = Promise.resolve();
|
||||||
|
let moveWatchdog = 0;
|
||||||
|
/** هویت درگ بیرون از reactivity تا clear/re-render وسط drop شناسه را نبلعد */
|
||||||
|
let dragSessionId: string | null = null;
|
||||||
|
let dragVisualTimer = 0;
|
||||||
|
|
||||||
|
function clearDragState() {
|
||||||
|
window.clearTimeout(dragVisualTimer);
|
||||||
|
dragSessionId = null;
|
||||||
|
draggingId.value = null;
|
||||||
|
dropStageId.value = null;
|
||||||
|
overlayDeal.value = null;
|
||||||
|
draggingColumnId.value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetMoveLock() {
|
||||||
|
window.clearTimeout(moveWatchdog);
|
||||||
|
moving.value = false;
|
||||||
|
moveChain = Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
function armMoveWatchdog() {
|
||||||
|
window.clearTimeout(moveWatchdog);
|
||||||
|
moveWatchdog = window.setTimeout(() => {
|
||||||
|
resetMoveLock();
|
||||||
|
}, 12_000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function enqueueMove(task: () => Promise<void>) {
|
||||||
|
const run = moveChain.then(task, task);
|
||||||
|
moveChain = run.then(
|
||||||
|
() => undefined,
|
||||||
|
() => undefined,
|
||||||
|
);
|
||||||
|
return run;
|
||||||
|
}
|
||||||
|
|
||||||
const KIND_BADGE: Record<ActivityStageDto['kind'], string | undefined> = {
|
const KIND_BADGE: Record<ActivityStageDto['kind'], string | undefined> = {
|
||||||
open: undefined,
|
open: undefined,
|
||||||
@@ -227,14 +264,12 @@ function onMenuActivity(id: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onDragStart(deal: PipelineDeal, event: DragEvent) {
|
function onDragStart(deal: PipelineDeal, event: DragEvent) {
|
||||||
// اگر شناسهٔ درگ ستون گیر کرده باشد، دراپ کارت را ستونجابهجا میبیند
|
|
||||||
draggingColumnId.value = null;
|
draggingColumnId.value = null;
|
||||||
draggingId.value = deal.id;
|
// قفل/صف مانده از درخواست قبلی را باز کن
|
||||||
overlayDeal.value = deal;
|
if (moving.value) resetMoveLock();
|
||||||
const sid = findStageOf(deal.id);
|
|
||||||
overlayAccent.value =
|
dragSessionId = deal.id;
|
||||||
(props.stages.find((s) => s.id === sid)?.color as PipelineAccent | undefined) ?? 'green';
|
// setData / setDragImage باید همزمان با dragstart باشند
|
||||||
overlayPos.value = { x: (event.clientX || 0) + 12, y: (event.clientY || 0) + 12 };
|
|
||||||
event.dataTransfer?.setData('text/plain', deal.id);
|
event.dataTransfer?.setData('text/plain', deal.id);
|
||||||
if (event.dataTransfer) {
|
if (event.dataTransfer) {
|
||||||
event.dataTransfer.effectAllowed = 'move';
|
event.dataTransfer.effectAllowed = 'move';
|
||||||
@@ -242,21 +277,44 @@ function onDragStart(deal: PipelineDeal, event: DragEvent) {
|
|||||||
img.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
|
img.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
|
||||||
event.dataTransfer.setDragImage(img, 0, 0);
|
event.dataTransfer.setDragImage(img, 0, 0);
|
||||||
}
|
}
|
||||||
// بستن پیشنمایش وسط dragstart گاهی خودِ درگ را لغو میکند
|
|
||||||
if (quickId.value) void nextTick(() => closeQuick());
|
const sid = findStageOf(deal.id);
|
||||||
|
overlayAccent.value =
|
||||||
|
(props.stages.find((s) => s.id === sid)?.color as PipelineAccent | undefined) ?? 'green';
|
||||||
|
overlayPos.value = { x: (event.clientX || 0) + 12, y: (event.clientY || 0) + 12 };
|
||||||
|
|
||||||
|
// تغییر DOM (opacity/overlay/بستن peek) داخل dragstart درگ HTML5 را در کروم لغو میکند
|
||||||
|
window.clearTimeout(dragVisualTimer);
|
||||||
|
dragVisualTimer = window.setTimeout(() => {
|
||||||
|
if (dragSessionId !== deal.id) return;
|
||||||
|
draggingId.value = deal.id;
|
||||||
|
overlayDeal.value = deal;
|
||||||
|
if (quickId.value) closeQuick();
|
||||||
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onDragEnd() {
|
function onDragEnd() {
|
||||||
const endedId = draggingId.value;
|
const endedId = dragSessionId ?? draggingId.value;
|
||||||
// drop معمولاً قبل از dragend است؛ کمی تأخیر تا fallback شناسه از بین نرود
|
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
if (draggingId.value !== endedId) return;
|
if (dragSessionId && dragSessionId !== endedId) return;
|
||||||
draggingId.value = null;
|
if (draggingId.value && draggingId.value !== endedId) return;
|
||||||
dropStageId.value = null;
|
clearDragState();
|
||||||
overlayDeal.value = null;
|
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onColumnDragStart(id: string) {
|
||||||
|
window.clearTimeout(dragVisualTimer);
|
||||||
|
dragSessionId = null;
|
||||||
|
draggingId.value = null;
|
||||||
|
overlayDeal.value = null;
|
||||||
|
draggingColumnId.value = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onColumnDragEnd() {
|
||||||
|
draggingColumnId.value = null;
|
||||||
|
dropStageId.value = null;
|
||||||
|
}
|
||||||
|
|
||||||
function onDragOver(stageId: string, event: DragEvent) {
|
function onDragOver(stageId: string, event: DragEvent) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
dropStageId.value = stageId;
|
dropStageId.value = stageId;
|
||||||
@@ -269,17 +327,19 @@ function onDragOver(stageId: string, event: DragEvent) {
|
|||||||
async function moveCard(activityId: string, toStageId: string, index?: number) {
|
async function moveCard(activityId: string, toStageId: string, index?: number) {
|
||||||
const fromStageId = findStageOf(activityId);
|
const fromStageId = findStageOf(activityId);
|
||||||
if (!fromStageId) return;
|
if (!fromStageId) return;
|
||||||
const from = columns.value[fromStageId]!;
|
const from = columns.value[fromStageId];
|
||||||
const to = columns.value[toStageId];
|
const to = columns.value[toStageId];
|
||||||
if (!to) return;
|
if (!from || !to) return;
|
||||||
const fromIndex = from.items.findIndex((i) => i.id === activityId);
|
const fromIndex = from.items.findIndex((i) => i.id === activityId);
|
||||||
const item = from.items[fromIndex];
|
const item = from.items[fromIndex];
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
const toStage = props.stages.find((s) => s.id === toStageId)!;
|
const toStage = props.stages.find((s) => s.id === toStageId);
|
||||||
|
if (!toStage) return;
|
||||||
const same = fromStageId === toStageId;
|
const same = fromStageId === toStageId;
|
||||||
const without = (same ? from.items : to.items).filter((i) => i.id !== activityId);
|
const without = (same ? from.items : to.items).filter((i) => i.id !== activityId);
|
||||||
if (same && index == null) return;
|
if (same && index == null) return;
|
||||||
let insertAt = index == null || index < 0 ? 0 : index;
|
// بدون ایندکس صریح → ته ستون (همتراز با بورد معامله)
|
||||||
|
let insertAt = index == null || index < 0 ? without.length : index;
|
||||||
if (same && index != null && index > fromIndex) insertAt -= 1;
|
if (same && index != null && index > fromIndex) insertAt -= 1;
|
||||||
insertAt = Math.max(0, Math.min(insertAt, without.length));
|
insertAt = Math.max(0, Math.min(insertAt, without.length));
|
||||||
if (same && insertAt === fromIndex) return;
|
if (same && insertAt === fromIndex) return;
|
||||||
@@ -301,6 +361,7 @@ async function moveCard(activityId: string, toStageId: string, index?: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
moving.value = true;
|
moving.value = true;
|
||||||
|
armMoveWatchdog();
|
||||||
try {
|
try {
|
||||||
const { data } = await api.patch<ActivityDto>(`/activities/${activityId}/stage`, {
|
const { data } = await api.patch<ActivityDto>(`/activities/${activityId}/stage`, {
|
||||||
stageId: toStageId,
|
stageId: toStageId,
|
||||||
@@ -333,6 +394,7 @@ async function moveCard(activityId: string, toStageId: string, index?: number) {
|
|||||||
}
|
}
|
||||||
emit('error', apiMessage(e, 'جابهجایی فعالیت ناموفق بود.'));
|
emit('error', apiMessage(e, 'جابهجایی فعالیت ناموفق بود.'));
|
||||||
} finally {
|
} finally {
|
||||||
|
window.clearTimeout(moveWatchdog);
|
||||||
moving.value = false;
|
moving.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -340,23 +402,24 @@ async function moveCard(activityId: string, toStageId: string, index?: number) {
|
|||||||
async function onDrop(stageId: string, event: DragEvent, index?: number) {
|
async function onDrop(stageId: string, event: DragEvent, index?: number) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const transfer = event.dataTransfer?.getData('text/plain') || '';
|
const transfer = event.dataTransfer?.getData('text/plain') || '';
|
||||||
const cardId = draggingId.value;
|
const cardId = dragSessionId || draggingId.value;
|
||||||
const columnId = draggingColumnId.value;
|
const columnId = draggingColumnId.value;
|
||||||
dropStageId.value = null;
|
clearDragState();
|
||||||
draggingId.value = null;
|
|
||||||
overlayDeal.value = null;
|
|
||||||
draggingColumnId.value = null;
|
|
||||||
|
|
||||||
const isStageDrag = transfer.startsWith('stage:');
|
// کارت همیشه بر payload ستون اولویت دارد تا درگ کارت بهاشتباه reorder ستون نشود
|
||||||
if (isStageDrag || (!transfer && !cardId && columnId)) {
|
const activityId =
|
||||||
const fromId = isStageDrag ? transfer.slice('stage:'.length) : columnId;
|
cardId || (transfer && !transfer.startsWith('stage:') ? transfer : '');
|
||||||
if (fromId) await reorderColumn(fromId, stageId);
|
if (activityId) {
|
||||||
|
// fire-and-forget: await روی زنجیرهٔ گیرکرده نباید drop بعدی را قفل کند
|
||||||
|
void enqueueMove(() => moveCard(activityId, stageId, index));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const activityId = (!isStageDrag && transfer) || cardId || '';
|
const isStageDrag = transfer.startsWith('stage:');
|
||||||
if (!activityId || moving.value) return;
|
if (isStageDrag || columnId) {
|
||||||
await moveCard(activityId, stageId, index);
|
const fromId = isStageDrag ? transfer.slice('stage:'.length) : columnId;
|
||||||
|
if (fromId) await reorderColumn(fromId, stageId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function persistOrder(ids: string[]) {
|
async function persistOrder(ids: string[]) {
|
||||||
@@ -514,8 +577,8 @@ function openFullActivity(id: string) {
|
|||||||
@rename-stage="onRenameStage"
|
@rename-stage="onRenameStage"
|
||||||
@delete-stage="onDeleteStage"
|
@delete-stage="onDeleteStage"
|
||||||
@move-stage="onMoveStage"
|
@move-stage="onMoveStage"
|
||||||
@column-drag-start="(id: string) => { draggingId = null; overlayDeal = null; draggingColumnId = id; }"
|
@column-drag-start="onColumnDragStart"
|
||||||
@column-drag-end="draggingColumnId = null; dropStageId = null"
|
@column-drag-end="onColumnDragEnd"
|
||||||
@add-stage="onAddStage"
|
@add-stage="onAddStage"
|
||||||
@load-more="loadMore"
|
@load-more="loadMore"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -105,9 +105,9 @@ const chevronStyle = computed(() => ({
|
|||||||
|
|
||||||
const cardStyle = computed(() => ({
|
const cardStyle = computed(() => ({
|
||||||
cursor: props.selectMode ? 'pointer' : 'grab',
|
cursor: props.selectMode ? 'pointer' : 'grab',
|
||||||
opacity: props.dragging ? 0.4 : 1,
|
// تغییر opacity/transform وسط dragstart درگ HTML5 را در کروم لغو میکند؛ فقط سایه
|
||||||
transform: props.dragging ? 'scale(0.98)' : 'none',
|
opacity: props.dragging ? 0.55 : 1,
|
||||||
transition: 'opacity 180ms ease, transform 180ms ease, box-shadow 180ms ease',
|
transition: 'opacity 120ms ease, box-shadow 120ms ease',
|
||||||
margin: 0,
|
margin: 0,
|
||||||
position: highlighted.value ? 'relative' : undefined,
|
position: highlighted.value ? 'relative' : undefined,
|
||||||
zIndex: highlighted.value ? 2 : undefined,
|
zIndex: highlighted.value ? 2 : undefined,
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ const overlayAccent = ref<PipelineStage['color']>('green');
|
|||||||
const overlayPos = ref({ x: 0, y: 0 });
|
const overlayPos = ref({ x: 0, y: 0 });
|
||||||
const menuOpenId = ref<string | null>(null);
|
const menuOpenId = ref<string | null>(null);
|
||||||
const moving = ref(false);
|
const moving = ref(false);
|
||||||
|
let dragSessionId: string | null = null;
|
||||||
|
|
||||||
const pipelineOptions = computed(() =>
|
const pipelineOptions = computed(() =>
|
||||||
pipelines.value.map((p) => ({
|
pipelines.value.map((p) => ({
|
||||||
@@ -245,13 +246,8 @@ function onPointerMove(event: PointerEvent | MouseEvent) {
|
|||||||
|
|
||||||
function onDragStart(deal: PipelineDeal, event: DragEvent) {
|
function onDragStart(deal: PipelineDeal, event: DragEvent) {
|
||||||
draggingColumnId.value = null;
|
draggingColumnId.value = null;
|
||||||
draggingId.value = deal.id;
|
if (moving.value) moving.value = false;
|
||||||
overlayDeal.value = deal;
|
dragSessionId = deal.id;
|
||||||
overlayAccent.value = findAccent(deal.id);
|
|
||||||
overlayPos.value = {
|
|
||||||
x: (event.clientX || 0) + 12,
|
|
||||||
y: (event.clientY || 0) + 12,
|
|
||||||
};
|
|
||||||
event.dataTransfer?.setData('text/plain', deal.id);
|
event.dataTransfer?.setData('text/plain', deal.id);
|
||||||
event.dataTransfer!.effectAllowed = 'move';
|
event.dataTransfer!.effectAllowed = 'move';
|
||||||
if (event.dataTransfer) {
|
if (event.dataTransfer) {
|
||||||
@@ -260,12 +256,25 @@ function onDragStart(deal: PipelineDeal, event: DragEvent) {
|
|||||||
'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
|
'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
|
||||||
event.dataTransfer.setDragImage(img, 0, 0);
|
event.dataTransfer.setDragImage(img, 0, 0);
|
||||||
}
|
}
|
||||||
|
overlayAccent.value = findAccent(deal.id);
|
||||||
|
overlayPos.value = {
|
||||||
|
x: (event.clientX || 0) + 12,
|
||||||
|
y: (event.clientY || 0) + 12,
|
||||||
|
};
|
||||||
|
// DOM را بعد از dragstart عوض کن تا کروم درگ را لغو نکند
|
||||||
|
window.setTimeout(() => {
|
||||||
|
if (dragSessionId !== deal.id) return;
|
||||||
|
draggingId.value = deal.id;
|
||||||
|
overlayDeal.value = deal;
|
||||||
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onDragEnd() {
|
function onDragEnd() {
|
||||||
const endedId = draggingId.value;
|
const endedId = dragSessionId ?? draggingId.value;
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
if (draggingId.value !== endedId) return;
|
if (dragSessionId && dragSessionId !== endedId) return;
|
||||||
|
if (draggingId.value && draggingId.value !== endedId) return;
|
||||||
|
dragSessionId = null;
|
||||||
draggingId.value = null;
|
draggingId.value = null;
|
||||||
dropStageId.value = null;
|
dropStageId.value = null;
|
||||||
overlayDeal.value = null;
|
overlayDeal.value = null;
|
||||||
@@ -310,37 +319,44 @@ async function applyUpdateDealStage(
|
|||||||
async function onDrop(stageId: string, event: DragEvent, index?: number) {
|
async function onDrop(stageId: string, event: DragEvent, index?: number) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const transfer = event.dataTransfer?.getData('text/plain') || '';
|
const transfer = event.dataTransfer?.getData('text/plain') || '';
|
||||||
const draggedCardId = draggingId.value;
|
const draggedCardId = dragSessionId || draggingId.value;
|
||||||
const columnId = draggingColumnId.value;
|
const columnId = draggingColumnId.value;
|
||||||
dropStageId.value = null;
|
dropStageId.value = null;
|
||||||
|
dragSessionId = null;
|
||||||
draggingId.value = null;
|
draggingId.value = null;
|
||||||
overlayDeal.value = null;
|
overlayDeal.value = null;
|
||||||
draggingColumnId.value = null;
|
draggingColumnId.value = null;
|
||||||
|
|
||||||
const isStageDrag = transfer.startsWith('stage:');
|
// کارت بر payload ستون اولویت دارد
|
||||||
if (isStageDrag || (!transfer && !draggedCardId && columnId)) {
|
const dealId =
|
||||||
const fromId = isStageDrag ? transfer.slice('stage:'.length) : columnId;
|
draggedCardId || (transfer && !transfer.startsWith('stage:') ? transfer : '');
|
||||||
if (fromId) await reorderColumn(fromId, stageId);
|
if (dealId) {
|
||||||
|
const current = stages.value.find((stage) => stage.deals.some((deal) => deal.id === dealId));
|
||||||
|
if (current?.id === stageId && index == null) return;
|
||||||
|
|
||||||
|
const next = updateDealStage(stages.value, dealId, stageId, index);
|
||||||
|
const unchanged = next.every((stage, stageIndex) => {
|
||||||
|
const prev = stages.value[stageIndex];
|
||||||
|
return (
|
||||||
|
stage.deals.length === (prev?.deals.length ?? -1) &&
|
||||||
|
stage.deals.every((deal, dealIndex) => deal.id === prev?.deals[dealIndex]?.id)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
if (unchanged) return;
|
||||||
|
|
||||||
|
// اگر جابهجایی قبلی هنوز در جریان است، این یکی را صف نکن — فقط رد نکن بیصدا با early return قدیمی
|
||||||
|
if (moving.value) {
|
||||||
|
moving.value = false;
|
||||||
|
}
|
||||||
|
await applyUpdateDealStage(dealId, stageId, index);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const dealId = (!isStageDrag && transfer) || draggedCardId || '';
|
const isStageDrag = transfer.startsWith('stage:');
|
||||||
if (!dealId || moving.value) return;
|
if (isStageDrag || columnId) {
|
||||||
|
const fromId = isStageDrag ? transfer.slice('stage:'.length) : columnId;
|
||||||
const current = stages.value.find((stage) => stage.deals.some((deal) => deal.id === dealId));
|
if (fromId) await reorderColumn(fromId, stageId);
|
||||||
if (current?.id === stageId && index == null) return;
|
}
|
||||||
|
|
||||||
const next = updateDealStage(stages.value, dealId, stageId, index);
|
|
||||||
const unchanged = next.every((stage, stageIndex) => {
|
|
||||||
const prev = stages.value[stageIndex];
|
|
||||||
return (
|
|
||||||
stage.deals.length === (prev?.deals.length ?? -1) &&
|
|
||||||
stage.deals.every((deal, dealIndex) => deal.id === prev?.deals[dealIndex]?.id)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
if (unchanged) return;
|
|
||||||
|
|
||||||
await applyUpdateDealStage(dealId, stageId, index);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function apiMessage(e: unknown, fallback: string) {
|
function apiMessage(e: unknown, fallback: string) {
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ const busy = ref(false);
|
|||||||
const importMsg = ref('');
|
const importMsg = ref('');
|
||||||
|
|
||||||
const draggingId = ref<string | null>(null);
|
const draggingId = ref<string | null>(null);
|
||||||
|
let dragSessionId: string | null = null;
|
||||||
const draggingColumnId = ref<string | null>(null);
|
const draggingColumnId = ref<string | null>(null);
|
||||||
const dropStageId = ref<string | null>(null);
|
const dropStageId = ref<string | null>(null);
|
||||||
const overlayDeal = ref<PipelineDeal | null>(null);
|
const overlayDeal = ref<PipelineDeal | null>(null);
|
||||||
@@ -178,13 +179,8 @@ function onPointerMove(event: PointerEvent | MouseEvent) {
|
|||||||
|
|
||||||
function onDragStart(deal: PipelineDeal, event: DragEvent) {
|
function onDragStart(deal: PipelineDeal, event: DragEvent) {
|
||||||
draggingColumnId.value = null;
|
draggingColumnId.value = null;
|
||||||
draggingId.value = deal.id;
|
if (moving.value) moving.value = false;
|
||||||
overlayDeal.value = deal;
|
dragSessionId = deal.id;
|
||||||
overlayAccent.value = findAccent(deal.id);
|
|
||||||
overlayPos.value = {
|
|
||||||
x: (event.clientX || 0) + 12,
|
|
||||||
y: (event.clientY || 0) + 12,
|
|
||||||
};
|
|
||||||
event.dataTransfer?.setData('text/plain', deal.id);
|
event.dataTransfer?.setData('text/plain', deal.id);
|
||||||
event.dataTransfer!.effectAllowed = 'move';
|
event.dataTransfer!.effectAllowed = 'move';
|
||||||
if (event.dataTransfer) {
|
if (event.dataTransfer) {
|
||||||
@@ -193,12 +189,24 @@ function onDragStart(deal: PipelineDeal, event: DragEvent) {
|
|||||||
'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
|
'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
|
||||||
event.dataTransfer.setDragImage(img, 0, 0);
|
event.dataTransfer.setDragImage(img, 0, 0);
|
||||||
}
|
}
|
||||||
|
overlayAccent.value = findAccent(deal.id);
|
||||||
|
overlayPos.value = {
|
||||||
|
x: (event.clientX || 0) + 12,
|
||||||
|
y: (event.clientY || 0) + 12,
|
||||||
|
};
|
||||||
|
window.setTimeout(() => {
|
||||||
|
if (dragSessionId !== deal.id) return;
|
||||||
|
draggingId.value = deal.id;
|
||||||
|
overlayDeal.value = deal;
|
||||||
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onDragEnd() {
|
function onDragEnd() {
|
||||||
const endedId = draggingId.value;
|
const endedId = dragSessionId ?? draggingId.value;
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
if (draggingId.value !== endedId) return;
|
if (dragSessionId && dragSessionId !== endedId) return;
|
||||||
|
if (draggingId.value && draggingId.value !== endedId) return;
|
||||||
|
dragSessionId = null;
|
||||||
draggingId.value = null;
|
draggingId.value = null;
|
||||||
dropStageId.value = null;
|
dropStageId.value = null;
|
||||||
overlayDeal.value = null;
|
overlayDeal.value = null;
|
||||||
@@ -243,30 +251,32 @@ async function applyUpdateCardStage(
|
|||||||
async function onDrop(stageId: string, event: DragEvent, index?: number) {
|
async function onDrop(stageId: string, event: DragEvent, index?: number) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const transfer = event.dataTransfer?.getData('text/plain') || '';
|
const transfer = event.dataTransfer?.getData('text/plain') || '';
|
||||||
const draggedCardId = draggingId.value;
|
const draggedCardId = dragSessionId || draggingId.value;
|
||||||
const columnId = draggingColumnId.value;
|
const columnId = draggingColumnId.value;
|
||||||
dropStageId.value = null;
|
dropStageId.value = null;
|
||||||
|
dragSessionId = null;
|
||||||
draggingId.value = null;
|
draggingId.value = null;
|
||||||
overlayDeal.value = null;
|
overlayDeal.value = null;
|
||||||
draggingColumnId.value = null;
|
draggingColumnId.value = null;
|
||||||
|
|
||||||
|
const cardId =
|
||||||
|
draggedCardId || (transfer && !transfer.startsWith('stage:') ? transfer : '');
|
||||||
|
if (cardId) {
|
||||||
|
const current = stages.value.find((s) => s.deals.some((d) => d.id === cardId));
|
||||||
|
const currentIndex = current?.deals.findIndex((d) => d.id === cardId) ?? -1;
|
||||||
|
if (current?.id === stageId && (index == null || index === currentIndex)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (moving.value) moving.value = false;
|
||||||
|
await applyUpdateCardStage(cardId, stageId, index);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const isStageDrag = transfer.startsWith('stage:');
|
const isStageDrag = transfer.startsWith('stage:');
|
||||||
if (isStageDrag || (!transfer && !draggedCardId && columnId)) {
|
if (isStageDrag || columnId) {
|
||||||
const fromId = isStageDrag ? transfer.slice('stage:'.length) : columnId;
|
const fromId = isStageDrag ? transfer.slice('stage:'.length) : columnId;
|
||||||
if (fromId) await reorderColumn(fromId, stageId);
|
if (fromId) await reorderColumn(fromId, stageId);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const cardId = (!isStageDrag && transfer) || draggedCardId || '';
|
|
||||||
if (!cardId || moving.value) return;
|
|
||||||
|
|
||||||
const current = stages.value.find((s) => s.deals.some((d) => d.id === cardId));
|
|
||||||
const currentIndex = current?.deals.findIndex((d) => d.id === cardId) ?? -1;
|
|
||||||
if (current?.id === stageId && (index == null || index === currentIndex)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await applyUpdateCardStage(cardId, stageId, index);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onAddCard(stageId: string) {
|
function onAddCard(stageId: string) {
|
||||||
|
|||||||
Reference in New Issue
Block a user