1.1.5: settings/media folder details + empty-click deselects everything

- Settings → Media tab now shows per-folder image/audio counts, skipped
  subfolders, skipped files, and a rescan button. listClipart hard-skips
  subdirectories and unsupported file extensions, returning a skip
  report the panel renders.
- Empty-click anywhere — chrome, bare stage, empty timeline tracks —
  clears the full selection (layers, clip instances, keyframes) via a
  shared useDeselectAll helper. shouldChromeClickDeselect() splits
  exclusions into interactive elements, overlays, editing panels and
  timeline selection elements so empty timeline space deselects too.
This commit is contained in:
th committed 2026-04-25 09:42:36 +02:00
1 parent 3005d9310d
commit b0813a1d50
12 files changed
+669 -43

No files matched your search

+16 -5
View File
@@ -37,6 +37,7 @@ import { BUTTON_STATE_TIMES } from '@/types/scene'
import { useButtonRuntime } from '@/composables/useButtonRuntime'
import { evalAction } from '@/lib/actions'
import { t } from '@/i18n'
import { useDeselectAll } from '@/composables/useDeselectAll'
import { useKfSelection } from '@/composables/useKfSelection'
import { showLocked } from '@/composables/useShowLocked'
import { expandedTrackOrder } from '@/lib/sceneOrder'
@@ -45,6 +46,7 @@ import { CLIP_FALLBACK_BOUNDS, computeClipBounds, type Rect } from '@/lib/clipBo
const store = useSceneStore()
const clipart = useClipartStore()
const { selectedKfs } = useKfSelection()
const deselectAll = useDeselectAll()
const container = ref<HTMLDivElement | null>(null)
const containerSize = ref({ width: 0, height: 0 })
@@ -2016,7 +2018,7 @@ function finishBoxSelect() {
const dPxX = Math.abs(b.curX - b.startX) * zoom.value
const dPxY = Math.abs(b.curY - b.startY) * zoom.value
if (indices.size === 0 && dPxX < 3 && dPxY < 3) {
store.selectLayer(null)
deselectAll()
}
return
}
@@ -2030,12 +2032,12 @@ function finishBoxSelect() {
const draggedX = Math.abs(b.curX - b.startX) * zoom.value
const draggedY = Math.abs(b.curY - b.startY) * zoom.value
if (draggedX < 3 && draggedY < 3) {
if (!b.additive) store.selectLayer(null)
if (!b.additive) deselectAll()
return
}
const stage = stageRef.value?.getStage()
if (!stage) {
if (!b.additive) store.selectLayer(null)
if (!b.additive) deselectAll()
return
}
const hits: string[] = []
@@ -2074,7 +2076,7 @@ function finishBoxSelect() {
})()
: hits
if (merged.length === 0) {
store.selectLayer(null)
deselectAll()
return
}
if (merged.length === 1) {
@@ -2615,7 +2617,7 @@ function handleStageMouseDown(e: Konva.KonvaEventObject<MouseEvent>) {
if (target.name?.() === 'path-frame-dot') return
if (vertexMenu.value) vertexMenu.value = null
if (target === target.getStage()) {
store.selectLayer(null)
deselectAll()
return
}
const id = target.id?.()
@@ -2628,11 +2630,20 @@ function handleStageMouseDown(e: Konva.KonvaEventObject<MouseEvent>) {
}
return
}
// Bubbled mousedown from a Konva node we don't recognise as a
// layer. Use selectLayer(null) — NOT deselectAll — so that any
// clip-instance selection just set by an inner @mousedown
// handler (e.g. the ci-group's own listener) survives this
// bubbled fall-through.
store.selectLayer(null)
return
}
const match = store.activeLayers.find((l) => l.id === id)
if (!match) {
// Same rationale: id-bearing nodes that aren't active layers are
// typically ci-group / ci-frame / ci-child shapes whose own
// mousedown handler has already run and set the clip-instance
// selection. Don't trample that.
store.selectLayer(null)
return
}