1
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,4 +1,4 @@
|
||||
/node_modules/
|
||||
/public/
|
||||
/dist/
|
||||
nul
|
||||
nul/
|
||||
@ -75,17 +75,21 @@ const initialText = (child: FolderChild) => {
|
||||
}
|
||||
|
||||
.folder-preview {
|
||||
--folder-chip-size: min(20px, calc(var(--icon-size, 72px) * 0.28));
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
grid-template-rows: repeat(2, 1fr);
|
||||
grid-template-columns: repeat(2, var(--folder-chip-size));
|
||||
grid-template-rows: repeat(2, var(--folder-chip-size));
|
||||
gap: 6px;
|
||||
padding-top: 6px;
|
||||
padding: 4px;
|
||||
place-content: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.folder-chip {
|
||||
width: var(--folder-chip-size);
|
||||
height: var(--folder-chip-size);
|
||||
border-radius: 8px;
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
color: #222;
|
||||
|
||||
@ -98,18 +98,21 @@ const mergeOrderSnapshot = ref<GridOrderEntry[] | null>(null);
|
||||
const mergePendingFolderId = ref<string | null>(null);
|
||||
const mergePendingInsertIndex = ref<number | null>(null);
|
||||
const OVERLAP_MERGE_THRESHOLD = 0.6;
|
||||
const OVERLAP_HOLD_MS = 100;
|
||||
const OVERLAP_HOLD_MS = 0;
|
||||
const OVERLAP_DROP_THRESHOLD = 0.35;
|
||||
const MERGE_HIT_INSET_RATIO = 0.18;
|
||||
const MERGE_HIT_INSET_PX = 6;
|
||||
const MERGE_HIT_INSET_RATIO = 0;
|
||||
const MERGE_HIT_INSET_PX = 0;
|
||||
const POINTER_ROW_TOLERANCE_RATIO = 0.6;
|
||||
const POINTER_EDGE_PADDING = 6;
|
||||
const lastPointer = ref<{ x: number; y: number } | null>(null);
|
||||
const pointerTargetEl = ref<HTMLElement | null>(null);
|
||||
const isPressing = ref(false);
|
||||
const pressTimer = ref<number | null>(null);
|
||||
const layoutAnimationMs = 240;
|
||||
const layoutEasing = 'cubic-bezier(0.4, 0, 0.2, 1)';
|
||||
const DELETE_ANIM_MS = 240;
|
||||
const DELETE_ANIM_EASING = 'cubic-bezier(0.4, 0, 0.2, 1)';
|
||||
const PRESS_DELAY_MS = 100;
|
||||
const normalizeGroupId = (groupId?: string | null) => groupId ?? DEFAULT_GROUP_ID;
|
||||
const activeGroupId = computed(() => normalizeGroupId(props.activeGroupId));
|
||||
|
||||
@ -415,6 +418,32 @@ const handleClick = (event: MouseEvent) => {
|
||||
}
|
||||
};
|
||||
|
||||
const clearPressTimer = () => {
|
||||
if (pressTimer.value !== null) {
|
||||
window.clearTimeout(pressTimer.value);
|
||||
pressTimer.value = null;
|
||||
}
|
||||
};
|
||||
|
||||
const stopPressing = () => {
|
||||
clearPressTimer();
|
||||
isPressing.value = false;
|
||||
};
|
||||
|
||||
const handlePointerDown = (event: PointerEvent) => {
|
||||
if (event.button !== 0) return;
|
||||
const target = event.target as HTMLElement | null;
|
||||
if (!target?.closest('.grid-item')) return;
|
||||
clearPressTimer();
|
||||
pressTimer.value = window.setTimeout(() => {
|
||||
isPressing.value = true;
|
||||
}, PRESS_DELAY_MS);
|
||||
};
|
||||
|
||||
const handleGlobalPointerUp = () => {
|
||||
stopPressing();
|
||||
};
|
||||
|
||||
const handleClickCapture = (event: MouseEvent) => {
|
||||
if (isDragging.value || suppressClick.value || Date.now() < clickBlockUntil.value) {
|
||||
event.preventDefault();
|
||||
@ -798,29 +827,11 @@ const handleMergeDrop = () => {
|
||||
const dragId = draggingMeta.value.id;
|
||||
const dragType = draggingMeta.value.type;
|
||||
if (!dragId || !dragType) return;
|
||||
let targetEl = mergeTargetEl.value;
|
||||
if (!mergeActive.value || !targetEl) {
|
||||
targetEl = hoverTargetEl.value;
|
||||
const dragEl = draggingEl.value;
|
||||
if (!targetEl && dragEl) {
|
||||
const stats = findTargetStats(dragEl);
|
||||
const best = stats?.best ?? null;
|
||||
if (best && best.ratio >= OVERLAP_MERGE_THRESHOLD) {
|
||||
targetEl = best.el;
|
||||
}
|
||||
}
|
||||
}
|
||||
let targetEl = mergeTargetEl.value ?? hoverTargetEl.value ?? pointerTargetEl.value;
|
||||
if (!targetEl) {
|
||||
clearHoverTarget();
|
||||
return;
|
||||
}
|
||||
if (!mergeActive.value && draggingEl.value) {
|
||||
const ratio = getOverlapRatio(draggingEl.value, targetEl);
|
||||
if (ratio < OVERLAP_DROP_THRESHOLD) {
|
||||
clearHoverTarget();
|
||||
return;
|
||||
}
|
||||
}
|
||||
const targetId = targetEl.dataset.id;
|
||||
const targetType = targetEl.dataset.type as GridItemType | undefined;
|
||||
if (!targetId || !targetType) return;
|
||||
@ -1232,6 +1243,8 @@ onMounted(async () => {
|
||||
ensureOrderConsistency();
|
||||
previousIconIds.value = new Set(layoutStore.icons.map(icon => icon.id));
|
||||
previousWidgetIds.value = new Set(widgetsStore.widgets.map(widget => widget.id));
|
||||
window.addEventListener('pointerup', handleGlobalPointerUp);
|
||||
window.addEventListener('pointercancel', handleGlobalPointerUp);
|
||||
await nextTick();
|
||||
if (!gridRef.value) return;
|
||||
grid.value = new Muuri(gridRef.value, {
|
||||
@ -1266,6 +1279,7 @@ onMounted(async () => {
|
||||
grid.value.on('dragStart', (item: any) => {
|
||||
isDragging.value = true;
|
||||
suppressClick.value = true;
|
||||
stopPressing();
|
||||
const el = item.getElement() as HTMLElement;
|
||||
draggingEl.value = el;
|
||||
draggingItemRef.value = item;
|
||||
@ -1305,42 +1319,35 @@ onMounted(async () => {
|
||||
const now = performance.now();
|
||||
const pointerTarget = pointerTargetEl.value;
|
||||
|
||||
if (mergeActive.value) {
|
||||
const locked = mergeTargetEl.value;
|
||||
if (!locked || !locked.isConnected) {
|
||||
deactivateMergeTarget(true);
|
||||
} else if (pointerTarget && pointerTarget !== locked) {
|
||||
deactivateMergeTarget(true);
|
||||
mergeCandidateEl.value = pointerTarget;
|
||||
mergeCandidateAt.value = now;
|
||||
setHoverTarget(pointerTarget);
|
||||
} else if (!pointerTarget) {
|
||||
deactivateMergeTarget(true);
|
||||
} else {
|
||||
setHoverTarget(locked);
|
||||
}
|
||||
} else {
|
||||
if (pointerTarget && pointerTarget.isConnected) {
|
||||
setHoverTarget(pointerTarget);
|
||||
if (mergeCandidateEl.value === pointerTarget) {
|
||||
if (now - mergeCandidateAt.value >= OVERLAP_HOLD_MS) {
|
||||
activateMergeTarget(pointerTarget);
|
||||
}
|
||||
} else {
|
||||
if (mergeCandidateEl.value !== pointerTarget) {
|
||||
mergeCandidateEl.value = pointerTarget;
|
||||
mergeCandidateAt.value = now;
|
||||
if (mergeActive.value && mergeTargetEl.value !== pointerTarget) {
|
||||
deactivateMergeTarget(false);
|
||||
}
|
||||
}
|
||||
const holdReady = now - mergeCandidateAt.value >= OVERLAP_HOLD_MS;
|
||||
if (holdReady) {
|
||||
if (!mergeActive.value || mergeTargetEl.value !== pointerTarget) {
|
||||
activateMergeTarget(pointerTarget);
|
||||
} else {
|
||||
setHoverTarget(pointerTarget);
|
||||
}
|
||||
} else {
|
||||
clearHoverTarget();
|
||||
}
|
||||
} else {
|
||||
mergeCandidateEl.value = null;
|
||||
mergeCandidateAt.value = 0;
|
||||
deactivateMergeTarget(true);
|
||||
clearHoverTarget();
|
||||
}
|
||||
}
|
||||
|
||||
applySortLock(mergeActive.value);
|
||||
});
|
||||
}
|
||||
});
|
||||
applySortLock(!!pointerTarget);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
grid.value.on('dragEnd', () => {
|
||||
isDragging.value = false;
|
||||
@ -1497,20 +1504,24 @@ watch(
|
||||
|
||||
onUnmounted(() => {
|
||||
pendingResizedKeys.clear();
|
||||
stopPressing();
|
||||
window.removeEventListener('pointerup', handleGlobalPointerUp);
|
||||
window.removeEventListener('pointercancel', handleGlobalPointerUp);
|
||||
grid.value?.destroy();
|
||||
grid.value = null;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
ref="gridRef"
|
||||
class="grid-canvas"
|
||||
:style="{ '--layout-anim-ms': `${layoutAnimationMs}ms` }"
|
||||
@click.capture="handleClickCapture"
|
||||
@click="handleClick"
|
||||
@contextmenu.prevent="handleContextMenu"
|
||||
>
|
||||
<div
|
||||
ref="gridRef"
|
||||
:class="['grid-canvas', { 'is-dragging': isDragging, 'is-pressing': isPressing }]"
|
||||
:style="{ '--layout-anim-ms': `${layoutAnimationMs}ms` }"
|
||||
@click.capture="handleClickCapture"
|
||||
@click="handleClick"
|
||||
@pointerdown="handlePointerDown"
|
||||
@contextmenu.prevent="handleContextMenu"
|
||||
>
|
||||
<div
|
||||
v-for="item in orderedItems"
|
||||
:key="`${item.type}-${item.id}`"
|
||||
@ -1576,6 +1587,20 @@ onUnmounted(() => {
|
||||
transition: opacity 160ms ease;
|
||||
}
|
||||
|
||||
.grid-item * {
|
||||
cursor: inherit;
|
||||
}
|
||||
|
||||
.grid-canvas.is-dragging,
|
||||
.grid-canvas.is-dragging * {
|
||||
cursor: grabbing !important;
|
||||
}
|
||||
|
||||
.grid-canvas.is-pressing,
|
||||
.grid-canvas.is-pressing * {
|
||||
cursor: grabbing !important;
|
||||
}
|
||||
|
||||
.grid-item.is-resized {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user