Init Repo

This commit is contained in:
2026-02-28 03:22:04 +08:00
commit de59b57ee7
883 changed files with 156857 additions and 0 deletions
@@ -0,0 +1,281 @@
<template>
<div class="space-y-2 px-4">
<div class="rounded-md border">
<Table>
<TableHeader>
<TableRow v-for="headerGroup in table.getHeaderGroups()" :key="headerGroup.id">
<TableHead
v-for="header in headerGroup.headers"
:key="header.id"
v-show="header.column.getIsVisible()"
:class="[
header.column.getCanSort() ? 'cursor-pointer select-none hover:bg-muted/50' : '',
header.column.id === 'select' ? 'w-12' : '',
header.column.id === 'actions' ? 'w-12' : '',
allTaskTypes.includes(header.column.id) ? 'w-[140px]' : '',
]"
@click="header.column.getCanSort() ? header.column.toggleSorting() : null"
>
<FlexRender
v-if="!header.isPlaceholder"
:render="header.column.columnDef.header"
:props="header.getContext()"
/>
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<template v-if="table.getRowModel().rows?.length">
<TableRow
v-for="row in table.getRowModel().rows"
:key="row.id"
:data-state="row.getIsSelected() ? 'selected' : undefined"
class="cursor-pointer hover:bg-muted/50"
:class="{
'bg-muted/30': row.getIsSelected(),
'table-row-selectable': true,
'selecting': isRangeSelecting
}"
@click="handleRowClick(row.original, $event, row)"
@mousedown="handleMouseDown"
@mouseup="handleMouseUp"
>
<TableCell
v-for="cell in row.getAllCells()"
:key="cell.id"
v-show="cell.column.getIsVisible()"
v-memo="[cell.getValue(), cell.column.getIsVisible()]"
>
<FlexRender
:render="cell.column.columnDef.cell"
:props="cell.getContext()"
/>
</TableCell>
</TableRow>
</template>
<template v-else>
<TableRow>
<TableCell :colspan="columns.length" class="h-24 text-center">
No results.
</TableCell>
</TableRow>
</template>
</TableBody>
</Table>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import {
FlexRender,
getCoreRowModel,
getSortedRowModel,
useVueTable,
type ColumnDef,
type SortingState,
type VisibilityState,
} from '@tanstack/vue-table'
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table'
import { type Shot } from '@/services/shot'
interface Props {
columns: ColumnDef<Shot>[]
data: Shot[]
sorting: SortingState
columnVisibility: VisibilityState
allTaskTypes: string[]
}
const props = defineProps<Props>()
const emit = defineEmits<{
'update:sorting': [sorting: SortingState]
'update:columnVisibility': [visibility: VisibilityState]
'update:rowSelection': [selection: Record<string, boolean>]
'row-click': [shot: Shot, event: MouseEvent]
'selection-cleared': []
}>()
// Track the last selected row index for range selection
const lastSelectedIndex = ref<number | null>(null)
const isRangeSelecting = ref(false)
const rowSelection = ref<Record<string, boolean>>({})
const table = useVueTable({
get data() {
return props.data
},
get columns() {
return props.columns
},
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
enableRowSelection: true,
enableMultiRowSelection: true,
getRowId: (row) => String(row.id),
onSortingChange: (updaterOrValue) => {
const newSorting =
typeof updaterOrValue === 'function'
? updaterOrValue(props.sorting)
: updaterOrValue
emit('update:sorting', newSorting)
},
onColumnVisibilityChange: (updaterOrValue) => {
const newVisibility =
typeof updaterOrValue === 'function'
? updaterOrValue(props.columnVisibility)
: updaterOrValue
emit('update:columnVisibility', newVisibility)
},
// Re-add the onRowSelectionChange callback but make it work with our custom logic
onRowSelectionChange: (updaterOrValue) => {
const newSelection =
typeof updaterOrValue === 'function'
? updaterOrValue(rowSelection.value)
: updaterOrValue
rowSelection.value = newSelection
},
state: {
get sorting() {
return props.sorting
},
get columnVisibility() {
return props.columnVisibility
},
get rowSelection() {
return rowSelection.value
},
},
})
const handleRowClick = (shot: Shot, event: MouseEvent, row: any) => {
// If double-click handler will handle it, skip selection logic
if (event.detail === 2) {
return
}
// Check if we clicked on an interactive element (simplified check)
const target = event.target as HTMLElement
if (target) {
// Check if we clicked on a button, checkbox, or other interactive element
const interactiveElement = target.closest('button, input, select, textarea, a[href], [role="button"], [role="menuitem"]')
if (interactiveElement) {
return
}
}
// Handle selection based on modifier keys
handleRowSelection(row, event)
emit('row-click', shot, event)
}
const handleRowSelection = (row: any, event: MouseEvent) => {
const currentIndex = row.index
const allRows = table.getRowModel().rows
const shotId = String(row.id)
if (event.shiftKey && lastSelectedIndex.value !== null) {
// Prevent text selection when shift-clicking
event.preventDefault()
// Range selection
const startIndex = Math.min(lastSelectedIndex.value, currentIndex)
const endIndex = Math.max(lastSelectedIndex.value, currentIndex)
// Create new selection object
const newSelection: Record<string, boolean> = {}
// Select all rows in the range
for (let i = startIndex; i <= endIndex; i++) {
if (allRows[i]) {
newSelection[allRows[i].id] = true
}
}
rowSelection.value = newSelection
lastSelectedIndex.value = currentIndex
} else if (event.ctrlKey || event.metaKey) {
// Ctrl/Cmd + Click: Toggle individual selection (additional selection)
const newSelection: Record<string, boolean> = { ...rowSelection.value }
if (newSelection[shotId]) {
// Row is selected, deselect it
delete newSelection[shotId]
} else {
// Row is not selected, select it
newSelection[shotId] = true
}
rowSelection.value = newSelection
lastSelectedIndex.value = currentIndex
} else {
// Default: Single selection (clear others and select this one)
// This applies even if the row is already selected - it becomes the only selection
rowSelection.value = { [shotId]: true }
lastSelectedIndex.value = currentIndex
}
}
const handleMouseDown = (event: MouseEvent) => {
// Detect if this is a range selection operation
if (event.shiftKey) {
isRangeSelecting.value = true
// Prevent text selection immediately
event.preventDefault()
}
}
const handleMouseUp = () => {
// Reset range selecting state
isRangeSelecting.value = false
}
// Watch rowSelection changes and emit selection-change events
watch(
rowSelection,
(newSelection) => {
emit('update:rowSelection', newSelection)
},
{ deep: true }
)
</script>
<style scoped>
/* Prevent text selection during range selection */
.table-row-selectable.selecting {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
/* Prevent text selection on shift key operations */
.table-row-selectable {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
/* Allow text selection for specific elements that should be selectable */
.table-row-selectable input,
.table-row-selectable textarea,
.table-row-selectable [contenteditable] {
-webkit-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
user-select: text;
}
</style>