fix: increase z-index of face detail navigation arrows

- Navigation arrows z-index increased from 10010 to 10100
- Fixes issue where modal overlay was covering the arrows
- Left/right buttons now clickable in face detail modal
This commit is contained in:
2026-10-30 14:33:12 +08:00
parent 81f11d4294
commit 8382590c58

View File

@@ -1097,7 +1097,14 @@ const faceMoveTarget = ref('')
const faceMoveNewGroupName = ref('') const faceMoveNewGroupName = ref('')
// Cluster results (synced with store for cross-navigation persistence) // Cluster results (synced with store for cross-navigation persistence)
const clusterResults = computed(() => clusterResultsCache.value[selectedFileUuid.value] || []) const clusterResults = computed(() => {
const results = clusterResultsCache.value[selectedFileUuid.value] || []
console.log('[clusterResults] cache for', selectedFileUuid.value, ':', results.length, 'groups')
if (results.length > 0) {
console.log('[clusterResults] first group:', JSON.stringify(results[0], null, 2))
}
return results
})
const clusterTracesMap = ref<Record<string, any>>({}) const clusterTracesMap = ref<Record<string, any>>({})
const clusterRunning = computed(() => isClusterRunning(selectedFileUuid.value)) const clusterRunning = computed(() => isClusterRunning(selectedFileUuid.value))
@@ -1131,6 +1138,10 @@ const faceGroups = computed(() => {
} }
const defaultName = `group ${String(idx + 1).padStart(2, '0')}` const defaultName = `group ${String(idx + 1).padStart(2, '0')}`
if (!c.name) c.name = defaultName if (!c.name) c.name = defaultName
// Convert Person_X to Group_X for display
if (c.name && c.name.startsWith('Person_')) {
c.name = 'Group_' + c.name.substring(7)
}
const visibleTraceIds = ffFilterActive.value const visibleTraceIds = ffFilterActive.value
? traceIds.filter(tid => !ffHiddenTraces.value.has(tid)) ? traceIds.filter(tid => !ffHiddenTraces.value.has(tid))
: traceIds : traceIds
@@ -1176,11 +1187,19 @@ const clusterDetailCluster = computed(() => {
// Map trace_id → group name for Faces section // Map trace_id → group name for Faces section
const traceGroupMap = computed(() => { const traceGroupMap = computed(() => {
const map: Record<string, string> = {} const map: Record<string, string> = {}
console.log('[traceGroupMap] clusterResults:', clusterResults.value.length, 'groups')
for (const c of clusterResults.value) { for (const c of clusterResults.value) {
let name = c.name || ''
// Convert Person_X to Group_X for display
if (name.startsWith('Person_')) {
name = 'Group_' + name.substring(7)
}
console.log('[traceGroupMap] cluster:', c.cluster_id, 'name:', name, 'trace_ids:', c.trace_ids)
for (const tid of c.trace_ids || []) { for (const tid of c.trace_ids || []) {
map[String(tid)] = c.name map[String(tid)] = name
} }
} }
console.log('[traceGroupMap] final map:', map)
return map return map
}) })
@@ -1218,7 +1237,25 @@ const facesTotalPages = computed(() => {
}) })
function traceLabel(tr: any): string { function traceLabel(tr: any): string {
const n = tr?.name || traceGroupMap.value[String(tr.trace_id)] // Prefer cluster name from clusterResultsCache
const clusters = clusterResultsCache.value[selectedFileUuid.value] || []
console.log('[traceLabel] trace_id:', tr.trace_id, 'clusters in cache:', clusters.length)
if (clusters.length > 0) {
console.log('[traceLabel] first cluster:', clusters[0])
}
for (const c of clusters) {
if (c.trace_ids && c.trace_ids.includes(tr.trace_id)) {
let name = c.name || ''
// Convert Person_X to Group_X for display
if (name.startsWith('Person_')) {
name = 'Group_' + name.substring(7)
}
console.log('[traceLabel] Found cluster name:', name)
return name
}
}
const n = tr?.name
console.log('[traceLabel] Fallback to:', n || t('people.section.unassigned'))
return n || t('people.section.unassigned') return n || t('people.section.unassigned')
} }
@@ -2028,6 +2065,11 @@ async function loadClusterResults() {
} }
const groups = results?.face_groups || results?.clusters || [] const groups = results?.face_groups || results?.clusters || []
const frames = results?.frames || [] const frames = results?.frames || []
console.log('[loadClusterResults] groups:', groups.length, 'frames:', frames.length)
if (frames.length > 0) {
console.log('[loadClusterResults] first frame faces:', frames[0].faces)
console.log('[loadClusterResults] first group:', groups[0])
}
// 統一 group_id → cluster_id, 並添加 group_uuid // 統一 group_id → cluster_id, 並添加 group_uuid
for (const g of groups) { for (const g of groups) {
if (g.group_id !== undefined && g.cluster_id === undefined) { if (g.group_id !== undefined && g.cluster_id === undefined) {
@@ -2069,6 +2111,7 @@ async function loadClusterResults() {
const cid = c.cluster_id || c.group_id const cid = c.cluster_id || c.group_id
c.member_faces = clusterFacesMap[cid] || [] c.member_faces = clusterFacesMap[cid] || []
c.face_count = c.member_faces.length c.face_count = c.member_faces.length
console.log(`[loadClusterResults] group ${c.name} (cid=${cid}): member_faces=${c.member_faces.length}`)
c.trace_ids = Array.from(clusterTraceIdsMap[cid] || []) c.trace_ids = Array.from(clusterTraceIdsMap[cid] || [])
// Set representative_trace from representative_face // Set representative_trace from representative_face
if (c.representative_face?.face_id) { if (c.representative_face?.face_id) {
@@ -2083,7 +2126,10 @@ async function loadClusterResults() {
} }
} }
if (groups.length) { if (groups.length) {
console.log('[loadClusterResults] Calling setClusterResults with', groups.length, 'groups')
console.log('[loadClusterResults] First group trace_ids:', groups[0]?.trace_ids)
setClusterResults(fileUuid, groups) setClusterResults(fileUuid, groups)
console.log('[loadClusterResults] After setClusterResults, cache:', clusterResultsCache.value[fileUuid]?.length)
// Load trace profiles for names in background (non-blocking) // Load trace profiles for names in background (non-blocking)
loadClusterNames(fileUuid, groups).catch(() => {}) loadClusterNames(fileUuid, groups).catch(() => {})
@@ -2749,7 +2795,7 @@ function closeClusterCtxMenu(e?: MouseEvent) {
clusterCtxMenu.value.show = false clusterCtxMenu.value.show = false
} }
function clusterCtxAction(action: string) { async function clusterCtxAction(action: string) {
const c = clusterCtxMenu.value.cluster const c = clusterCtxMenu.value.cluster
if (!c) return if (!c) return
clusterCtxMenu.value.show = false clusterCtxMenu.value.show = false
@@ -2766,7 +2812,7 @@ function clusterCtxAction(action: string) {
// Update trace profiles via API // Update trace profiles via API
const traceIds = clusters[idx]?.trace_ids || [] const traceIds = clusters[idx]?.trace_ids || []
if (traceIds.length && selectedFileUuid.value) { if (traceIds.length && selectedFileUuid.value) {
updateTraceProfileGroup(selectedFileUuid.value, traceIds, { name }) await updateTraceProfileGroup(selectedFileUuid.value, traceIds, { name })
} }
} }
} else if (action === 'skip') { } else if (action === 'skip') {
@@ -3297,24 +3343,75 @@ async function executeMerge(targetGroupId: string) {
const sourceGroupIds = groupIds.filter(id => id !== targetGroupId) const sourceGroupIds = groupIds.filter(id => id !== targetGroupId)
const sourceGroups = faceGroups.value.filter((c: any) => sourceGroupIds.includes(c.group_uuid)) const sourceGroups = faceGroups.value.filter((c: any) => sourceGroupIds.includes(c.group_uuid))
const sourceGroupNames = sourceGroups.map(g => g.name).filter(Boolean) // Collect all trace_ids from source groups
const allTraceIds: number[] = []
for (const g of sourceGroups) {
if (g.trace_ids) {
allTraceIds.push(...g.trace_ids)
}
}
if (sourceGroupNames.length === 0) { if (allTraceIds.length === 0) {
alert('No valid source groups to merge') alert('No traces to merge')
return return
} }
if (!confirm(`Merge ${sourceGroupNames.length} group(s) into "${targetGroupName}"?`)) return if (!confirm(`Merge ${sourceGroups.length} group(s) with ${allTraceIds.length} trace(s) into "${targetGroupName}"?`)) return
operationLoading.value = true operationLoading.value = true
operationMessage.value = `Merging ${sourceGroupNames.length} group(s)...` operationMessage.value = `Merging ${sourceGroups.length} group(s)...`
try { try {
const result = await apiCall('merge_groups', { // Update all source traces to target group name
fileUuid: selectedFileUuid.value, console.log('[executeMerge] Updating trace_ids:', allTraceIds, 'to name:', targetGroupName)
sourceGroups: sourceGroupNames,
targetGroupName: targetGroupName // 1. Update trace_profiles via API
}) const result = await updateTraceProfileGroup(selectedFileUuid.value, allTraceIds, { name: targetGroupName })
console.log('[executeMerge] API Result:', result)
// 2. Update clusterResultsCache locally
const clusters = getClusterResults(selectedFileUuid.value)
// Find target cluster
const targetCluster = clusters.find((c: any) => c.name === targetGroupName || c.cluster_id === targetGroup.cluster_id)
if (targetCluster) {
// Add source trace_ids to target cluster (avoid duplicates)
if (!targetCluster.trace_ids) targetCluster.trace_ids = []
for (const tid of allTraceIds) {
if (!targetCluster.trace_ids.includes(tid)) {
targetCluster.trace_ids.push(tid)
}
}
targetCluster.trace_count = targetCluster.trace_ids.length
}
// Remove trace_ids from source clusters
for (const sourceGroup of sourceGroups) {
const sourceCluster = clusters.find((c: any) => c.cluster_id === sourceGroup.cluster_id || c.group_id === sourceGroup.group_id)
if (sourceCluster && sourceCluster.trace_ids) {
sourceCluster.trace_ids = sourceCluster.trace_ids.filter((tid: number) => !allTraceIds.includes(tid))
sourceCluster.trace_count = sourceCluster.trace_ids.length
}
}
// Remove empty source clusters
const nonEmptyClusters = clusters.filter((c: any) => !sourceGroups.some(sg =>
(c.cluster_id === sg.cluster_id || c.group_id === sg.group_id) && (!c.trace_ids || c.trace_ids.length === 0)
))
setClusterResults(selectedFileUuid.value, nonEmptyClusters)
console.log('[executeMerge] Updated cache')
// Reload thumbnails for updated clusters
for (const c of nonEmptyClusters) {
if (c.trace_ids && c.trace_ids.length > 0) {
const repId = c.representative_trace || c.trace_ids[0]
if (repId) {
loadTraceThumb({ trace_id: repId, file_uuid: selectedFileUuid.value, best_face_id: 0 })
}
}
}
if (result?.success) { if (result?.success) {
@@ -4121,7 +4218,7 @@ h1 { margin: 0; }
/* Face detail side navigation arrows */ /* Face detail side navigation arrows */
.ms-fs-nav-arrow { .ms-fs-nav-arrow {
position: fixed; top: 50%; transform: translateY(-50%); z-index: 10010; position: fixed; top: 50%; transform: translateY(-50%); z-index: 10100;
width: 44px; height: 44px; border-radius: 50%; border: none; width: 44px; height: 44px; border-radius: 50%; border: none;
background: rgba(0,0,0,0.5); color: #fff; cursor: pointer; background: rgba(0,0,0,0.5); color: #fff; cursor: pointer;
display: flex; align-items: center; justify-content: center; display: flex; align-items: center; justify-content: center;