mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-08 17:10:07 +00:00
181 lines
5.2 KiB
TypeScript
181 lines
5.2 KiB
TypeScript
import { useKeyModifier } from '@vueuse/core'
|
|
import { computed, ref } from 'vue'
|
|
|
|
import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
|
|
import { useAssetSelectionStore } from '@/platform/assets/composables/useAssetSelectionStore'
|
|
|
|
export function useAssetSelection() {
|
|
const selectionStore = useAssetSelectionStore()
|
|
|
|
// Track whether the asset selection is active (e.g., when sidebar is open)
|
|
const isActive = ref<boolean>(true)
|
|
|
|
// Key modifiers - raw values
|
|
const shiftKeyRaw = useKeyModifier('Shift')
|
|
const ctrlKeyRaw = useKeyModifier('Control')
|
|
const metaKeyRaw = useKeyModifier('Meta')
|
|
|
|
// Only respond to key modifiers when active
|
|
const shiftKey = computed(() => isActive.value && shiftKeyRaw.value)
|
|
const ctrlKey = computed(() => isActive.value && ctrlKeyRaw.value)
|
|
const metaKey = computed(() => isActive.value && metaKeyRaw.value)
|
|
const cmdOrCtrlKey = computed(() => ctrlKey.value || metaKey.value)
|
|
|
|
/**
|
|
* Handle asset click with modifier keys for selection
|
|
* @param asset The clicked asset
|
|
* @param index The index of the clicked asset in the current list
|
|
* @param allAssets All assets in the current view for range selection
|
|
*/
|
|
function handleAssetClick(
|
|
asset: AssetItem,
|
|
index: number,
|
|
allAssets: AssetItem[]
|
|
) {
|
|
// Input validation
|
|
if (!asset?.id || index < 0 || index >= allAssets.length) {
|
|
console.warn('Invalid asset selection parameters')
|
|
return
|
|
}
|
|
|
|
const assetId = asset.id
|
|
|
|
// Shift + Click: Range selection
|
|
if (shiftKey.value && selectionStore.lastSelectedIndex >= 0) {
|
|
const start = Math.min(selectionStore.lastSelectedIndex, index)
|
|
const end = Math.max(selectionStore.lastSelectedIndex, index)
|
|
|
|
// Batch operation for better performance
|
|
const rangeIds = allAssets.slice(start, end + 1).map((a) => a.id)
|
|
const existingIds = Array.from(selectionStore.selectedAssetIds)
|
|
const combinedIds = [...new Set([...existingIds, ...rangeIds])]
|
|
|
|
// Single update instead of multiple forEach operations
|
|
selectionStore.setSelection(combinedIds)
|
|
|
|
// Don't update lastSelectedIndex for shift selection
|
|
return
|
|
}
|
|
|
|
// Ctrl/Cmd + Click: Toggle individual selection
|
|
if (cmdOrCtrlKey.value) {
|
|
selectionStore.toggleSelection(assetId)
|
|
selectionStore.setLastSelectedIndex(index)
|
|
return
|
|
}
|
|
|
|
// Normal Click: Single selection
|
|
selectionStore.clearSelection()
|
|
selectionStore.addToSelection(assetId)
|
|
selectionStore.setLastSelectedIndex(index)
|
|
}
|
|
|
|
/**
|
|
* Select all assets in the current view
|
|
*/
|
|
function selectAll(allAssets: AssetItem[]) {
|
|
const allIds = allAssets.map((a) => a.id)
|
|
selectionStore.setSelection(allIds)
|
|
if (allAssets.length > 0) {
|
|
selectionStore.setLastSelectedIndex(allAssets.length - 1)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the actual asset objects for selected IDs
|
|
*/
|
|
function getSelectedAssets(allAssets: AssetItem[]): AssetItem[] {
|
|
return allAssets.filter((asset) => selectionStore.isSelected(asset.id))
|
|
}
|
|
|
|
function reconcileSelection(assets: AssetItem[]) {
|
|
if (selectionStore.selectedAssetIds.size === 0) {
|
|
return
|
|
}
|
|
|
|
if (assets.length === 0) {
|
|
selectionStore.clearSelection()
|
|
return
|
|
}
|
|
|
|
const visibleIds = new Set(assets.map((asset) => asset.id))
|
|
const nextSelectedIds: string[] = []
|
|
|
|
for (const id of selectionStore.selectedAssetIds) {
|
|
if (visibleIds.has(id)) {
|
|
nextSelectedIds.push(id)
|
|
}
|
|
}
|
|
|
|
if (nextSelectedIds.length === selectionStore.selectedAssetIds.size) {
|
|
return
|
|
}
|
|
|
|
if (nextSelectedIds.length === 0) {
|
|
selectionStore.clearSelection()
|
|
return
|
|
}
|
|
|
|
selectionStore.setSelection(nextSelectedIds)
|
|
selectionStore.setLastSelectedIndex(-1)
|
|
}
|
|
|
|
/**
|
|
* Get the output count for a single asset
|
|
* Same logic as in AssetsSidebarTab.vue
|
|
*/
|
|
function getOutputCount(item: AssetItem): number {
|
|
const count = item.user_metadata?.outputCount
|
|
return typeof count === 'number' && count > 0 ? count : 1
|
|
}
|
|
|
|
/**
|
|
* Get the total output count for given assets
|
|
*/
|
|
function getTotalOutputCount(assets: AssetItem[]): number {
|
|
return assets.reduce((sum, asset) => sum + getOutputCount(asset), 0)
|
|
}
|
|
|
|
/**
|
|
* Activate key event listeners (when sidebar opens)
|
|
*/
|
|
function activate() {
|
|
isActive.value = true
|
|
}
|
|
|
|
/**
|
|
* Deactivate key event listeners (when sidebar closes)
|
|
*/
|
|
function deactivate() {
|
|
isActive.value = false
|
|
// Reset selection state to ensure clean state when deactivated
|
|
selectionStore.reset()
|
|
}
|
|
|
|
return {
|
|
// Selection state
|
|
selectedIds: computed(() => selectionStore.selectedAssetIds),
|
|
selectedCount: computed(() => selectionStore.selectedCount),
|
|
hasSelection: computed(() => selectionStore.hasSelection),
|
|
isSelected: (assetId: string) => selectionStore.isSelected(assetId),
|
|
|
|
// Selection actions
|
|
handleAssetClick,
|
|
selectAll,
|
|
clearSelection: () => selectionStore.clearSelection(),
|
|
getSelectedAssets,
|
|
reconcileSelection,
|
|
getOutputCount,
|
|
getTotalOutputCount,
|
|
reset: () => selectionStore.reset(),
|
|
|
|
// Lifecycle management
|
|
activate,
|
|
deactivate,
|
|
|
|
// Key states (for UI feedback)
|
|
shiftKey,
|
|
cmdOrCtrlKey
|
|
}
|
|
}
|