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