Files
momentry_studio/src/composables/useDragSelect.ts
Momentry Studio f3d75ebce8 feat: Library 批量注册 + PeopleView 修复 + 国际化
Library:
- 添加 checkbox 和鼠标框选功能
- Unregistered 卡片添加快速注册按钮
- 状态标签移至左下方,人脸图标移至左侧中间
- Deep Scan 改为 unregister -> register -> process
- 完整国际化(日语、繁体中文、英语)

PeopleView:
- 修复卡片不显示问题(使用 get_file_traces API)
- 移除对 identity_id 字段的依赖

其他:
- 添加日语语言包
- 添加 useDragSelect composable
- 新增文档:Face Bbox Null Bug Report
2026-10-01 00:42:13 +08:00

150 lines
3.8 KiB
TypeScript

import { ref, type Ref } from 'vue'
export interface DragSelectBox {
x1: number
y1: number
x2: number
y2: number
}
export interface DragSelectOptions {
getSelected: () => string[]
setSelected: (keys: string[]) => void
keyOf?: (el: HTMLElement) => string
onBox?: (box: DragSelectBox | null) => void
}
const DRAG_THRESHOLD = 4
function overlaps(ax1: number, ay1: number, ax2: number, ay2: number, bx1: number, by1: number, bx2: number, by2: number) {
return ax1 < bx2 && ax2 > bx1 && ay1 < by2 && ay2 > by1
}
export function useDragSelect(containerRef: Ref<HTMLElement | null>, opts: DragSelectOptions) {
const box = ref<DragSelectBox | null>(null)
function setBox(b: DragSelectBox | null) {
box.value = b
opts.onBox?.(b)
}
let startX = 0
let startY = 0
let curX = 0
let curY = 0
let moved = false
let lastWasDrag = false
function onMousedown(e: MouseEvent) {
if (e.button !== 0) return
if (e.ctrlKey || e.metaKey || e.shiftKey) return
const target = e.target as HTMLElement
if (target.closest('button, a, input, textarea, select, .ms-ppl-card-checkbox, .ms-fm-play-btn')) return
startX = curX = e.clientX
startY = curY = e.clientY
moved = false
lastWasDrag = false
setBox({ x1: startX, y1: startY, x2: startX, y2: startY })
window.addEventListener('mousemove', onMousemove)
window.addEventListener('mouseup', onMouseup)
}
function onMousemove(e: MouseEvent) {
curX = e.clientX
curY = e.clientY
if (!moved && Math.abs(curX - startX) < DRAG_THRESHOLD && Math.abs(curY - startY) < DRAG_THRESHOLD) {
setBox({ x1: startX, y1: startY, x2: startX, y2: startY })
return
}
if (!moved) {
moved = true
document.body.style.userSelect = 'none'
}
setBox({
x1: Math.min(startX, curX),
y1: Math.min(startY, curY),
x2: Math.max(startX, curX),
y2: Math.max(startY, curY),
})
}
function onMouseup(e: MouseEvent) {
window.removeEventListener('mousemove', onMousemove)
window.removeEventListener('mouseup', onMouseup)
if (!moved) {
setBox(null)
return
}
lastWasDrag = true
const rect = box.value
setBox(null)
if (document.body.style.userSelect === 'none') {
document.body.style.userSelect = ''
}
const container = containerRef.value
if (!container || !rect) {
return
}
const bx1 = rect.x1
const by1 = rect.y1
const bx2 = rect.x2
const by2 = rect.y2
const keyOf = opts.keyOf ?? ((el: HTMLElement) => el.dataset.dragKey || '')
const intersecting = new Set<string>()
const cards = container.querySelectorAll<HTMLElement>('[data-drag-key]')
cards.forEach((card) => {
const r = card.getBoundingClientRect()
if (overlaps(r.left, r.top, r.right, r.bottom, bx1, by1, bx2, by2)) {
const key = keyOf(card)
if (key) intersecting.add(key)
}
})
const prev = opts.getSelected()
if (e.shiftKey) {
const next = new Set(prev)
intersecting.forEach((k) => next.add(k))
opts.setSelected(Array.from(next))
} else if (e.ctrlKey || e.metaKey) {
const next = new Set(prev)
intersecting.forEach((k) => {
if (next.has(k)) next.delete(k)
else next.add(k)
})
opts.setSelected(Array.from(next))
} else {
opts.setSelected(Array.from(intersecting))
}
}
function onClickCapture(e: MouseEvent) {
if (lastWasDrag) {
e.preventDefault()
e.stopPropagation()
lastWasDrag = false
}
}
return {
box,
onMousedown,
onClickCapture,
}
}
export function dragSelectRectStyle(b: DragSelectBox) {
return {
left: b.x1 + 'px',
top: b.y1 + 'px',
width: Math.max(0, b.x2 - b.x1) + 'px',
height: Math.max(0, b.y2 - b.y1) + 'px',
}
}