Sync source to CNB / Sync Git ref (push) Skipped
移植 Axolotl 的拖拽式实例分组界面到 Starlight,接入阶段1的规范化分组后端: - 新增 helpers/analytics.ts(trackEvent 遥测桩) - 补全 helpers/instance-groups.ts(6 个 invoke 包装) - 新增 composables/useInstanceGroups.ts(分组状态 + 事件监听) - 升级 composables/useGridGrouping.ts(排序方向 isSortAscending/toggleSortDirection) - helpers/events.js 补 instance_groups_listener(对接 instance_groups_changed 事件) - 新增 components/ui/library/DraggableInstanceCard.vue - 新增 components/ui/library/InstanceGroup.vue(并补 group-drag-handle 修复组拖拽重排) - 新增 components/ui/modal/InstanceGroupModal.vue - GridDisplay.vue 替换为 Axolotl 拖拽版(DragDropProvider + vuedraggable) - 补 en-US/zh-CN i18n 键各 39 条
92 lines
2.0 KiB
Vue
92 lines
2.0 KiB
Vue
<script setup lang="ts">
|
|
import { KeyboardSensor, PointerSensor, useDraggable } from '@dnd-kit/vue'
|
|
import { useEventListener } from '@vueuse/core'
|
|
import { computed, ref } from 'vue'
|
|
|
|
import type { GameInstance } from '@/helpers/types'
|
|
|
|
const props = defineProps<{
|
|
instance: GameInstance
|
|
instanceGroupId: string
|
|
disabled: boolean
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
(e: 'contextmenu' | 'card-click', event: MouseEvent): void
|
|
(e: 'start-long-press' | 'cancel-long-press'): void
|
|
}>()
|
|
|
|
const cardElement = ref<HTMLElement>()
|
|
|
|
const instanceCardSensors = [
|
|
PointerSensor.configure({
|
|
preventActivation: () => false,
|
|
}),
|
|
KeyboardSensor,
|
|
]
|
|
|
|
const dragDisabled = computed(() => props.disabled)
|
|
|
|
const { isDragging } = useDraggable({
|
|
id: computed(() => `instance:${props.instanceGroupId}:${props.instance.id}`),
|
|
element: cardElement,
|
|
disabled: dragDisabled,
|
|
sensors: instanceCardSensors,
|
|
data: computed(() => ({
|
|
instanceId: props.instance.id,
|
|
fromGroup: props.instanceGroupId,
|
|
})),
|
|
})
|
|
|
|
let wantsClick = false
|
|
|
|
useEventListener(window, 'pointerdown', () => {
|
|
wantsClick = true
|
|
})
|
|
useEventListener(window, 'pointerup', () => {
|
|
if (isDragging.value) {
|
|
wantsClick = false
|
|
}
|
|
})
|
|
useEventListener(window, 'blur', () => {
|
|
wantsClick = false
|
|
})
|
|
|
|
function onCardClick(event: MouseEvent) {
|
|
if (!wantsClick || isDragging.value) return
|
|
wantsClick = false
|
|
emit('card-click', event)
|
|
}
|
|
|
|
function onContextMenu(event: MouseEvent) {
|
|
emit('contextmenu', event)
|
|
}
|
|
|
|
function onMouseDown() {
|
|
if (!dragDisabled.value) {
|
|
emit('start-long-press')
|
|
}
|
|
}
|
|
|
|
function onMouseUp() {
|
|
emit('cancel-long-press')
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div
|
|
ref="cardElement"
|
|
class="group relative min-w-0 w-full"
|
|
@click="onCardClick"
|
|
@mousedown="onMouseDown"
|
|
@mouseup="onMouseUp"
|
|
@mouseleave="onMouseUp"
|
|
@touchstart="!dragDisabled && onMouseDown()"
|
|
@touchend="onMouseUp"
|
|
@touchcancel="onMouseUp"
|
|
@contextmenu.prevent.stop="onContextMenu"
|
|
>
|
|
<slot :is-dragging="isDragging" />
|
|
<slot v-if="!isDragging" name="overlay" />
|
|
</div>
|
|
</template> |