新增拖入功能
This commit is contained in:
parent
7e9abf5f07
commit
75dfb67754
57
src/App.vue
57
src/App.vue
@ -4,6 +4,7 @@ import { useI18n } from 'vue-i18n'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { open } from '@tauri-apps/plugin-dialog'
|
||||
import { listen, type UnlistenFn } from '@tauri-apps/api/event'
|
||||
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'
|
||||
import TaskQueue from './components/TaskQueue.vue'
|
||||
import SubtitleEditor from './components/SubtitleEditor.vue'
|
||||
import { useTaskStore } from './stores/tasks'
|
||||
@ -71,7 +72,9 @@ const translationConfig = ref<TranslationConfig>({
|
||||
const pending = ref(false)
|
||||
const feedback = ref('')
|
||||
const showAdvanced = ref(false)
|
||||
const isDragging = ref(false)
|
||||
let unlistenMenuAction: UnlistenFn | null = null
|
||||
let dragDropUnlistenFn: UnlistenFn | null = null
|
||||
|
||||
const selectedTask = computed(() => taskStore.selectedTask)
|
||||
const hasTranslationKey = computed(() => translationConfig.value.apiKey.trim().length > 0)
|
||||
@ -80,6 +83,7 @@ onMounted(() => {
|
||||
taskStore.initialize()
|
||||
void loadDefaultModelPaths()
|
||||
void bindMenuActions()
|
||||
void initDragDrop()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
@ -87,6 +91,10 @@ onUnmounted(() => {
|
||||
unlistenMenuAction()
|
||||
unlistenMenuAction = null
|
||||
}
|
||||
if (dragDropUnlistenFn) {
|
||||
dragDropUnlistenFn()
|
||||
dragDropUnlistenFn = null
|
||||
}
|
||||
})
|
||||
|
||||
watch(locale, (newLocale) => {
|
||||
@ -219,6 +227,43 @@ async function handlePickFiles() {
|
||||
}
|
||||
}
|
||||
|
||||
function getFileExtension(filePath: string): string {
|
||||
const lastDot = filePath.lastIndexOf('.')
|
||||
if (lastDot === -1) return ''
|
||||
return filePath.slice(lastDot + 1).toLowerCase()
|
||||
}
|
||||
|
||||
function isMediaFile(filePath: string): boolean {
|
||||
const ext = getFileExtension(filePath)
|
||||
return MEDIA_EXTENSIONS.includes(ext as typeof MEDIA_EXTENSIONS[number])
|
||||
}
|
||||
|
||||
async function initDragDrop() {
|
||||
try {
|
||||
dragDropUnlistenFn = await getCurrentWebviewWindow().onDragDropEvent((event) => {
|
||||
if (event.payload.type === 'enter' || event.payload.type === 'over') {
|
||||
isDragging.value = true
|
||||
} else if (event.payload.type === 'leave') {
|
||||
isDragging.value = false
|
||||
} else if (event.payload.type === 'drop') {
|
||||
isDragging.value = false
|
||||
const mediaFiles = event.payload.paths.filter(isMediaFile)
|
||||
if (mediaFiles.length === 0) {
|
||||
feedback.value = t('app.feedback.noMediaFiles')
|
||||
return
|
||||
}
|
||||
if (mediaFiles.length !== event.payload.paths.length) {
|
||||
const skipped = event.payload.paths.length - mediaFiles.length
|
||||
feedback.value = t('app.feedback.someFilesSkipped', { count: skipped })
|
||||
}
|
||||
void submitFiles(mediaFiles)
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize drag-drop:', error)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleFiles(event: Event) {
|
||||
const input = event.target as HTMLInputElement
|
||||
const files = Array.from(input.files ?? [])
|
||||
@ -264,6 +309,18 @@ async function handleTranslateFromEditor() {
|
||||
|
||||
<template>
|
||||
<main class="app-shell">
|
||||
<Transition name="drag">
|
||||
<div v-if="isDragging" class="drag-overlay">
|
||||
<div class="drag-overlay-content">
|
||||
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
|
||||
<polyline points="17 8 12 3 7 8"/>
|
||||
<line x1="12" y1="3" x2="12" y2="15"/>
|
||||
</svg>
|
||||
<span>{{ $t('app.feedback.dropHint') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
<section class="topbar panel">
|
||||
<div class="toolbar-main">
|
||||
<div class="toolbar-title">
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
import { computed, ref, nextTick, watch } from 'vue'
|
||||
import type { SubtitleSegment, SubtitleTask } from '../lib/types'
|
||||
|
||||
const PAGE_SIZE = 200
|
||||
const PAGE_SIZE = 100
|
||||
|
||||
const props = defineProps<{
|
||||
task: SubtitleTask | null
|
||||
|
||||
@ -30,6 +30,9 @@ export default {
|
||||
noApiKey: 'Please configure LLM API Key first',
|
||||
translationStarted: 'Translation task started',
|
||||
translationFailed: 'Translation failed',
|
||||
noMediaFiles: 'No supported media files found',
|
||||
someFilesSkipped: '{count} file(s) skipped (unsupported format)',
|
||||
dropHint: 'Drop to add files',
|
||||
},
|
||||
llm: {
|
||||
apiBase: 'LLM API Base',
|
||||
|
||||
@ -30,6 +30,9 @@ export default {
|
||||
noApiKey: '请先配置 LLM API Key',
|
||||
translationStarted: '翻译任务已开始',
|
||||
translationFailed: '翻译失败',
|
||||
noMediaFiles: '拖入的文件中没有支持的音视频文件',
|
||||
someFilesSkipped: '{count} 个文件被跳过(不支持的格式)',
|
||||
dropHint: '松开以添加文件',
|
||||
},
|
||||
llm: {
|
||||
apiBase: 'LLM API Base',
|
||||
|
||||
@ -885,3 +885,52 @@ textarea {
|
||||
grid-column: span 1;
|
||||
}
|
||||
}
|
||||
|
||||
.drag-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 9999;
|
||||
background: rgba(245, 245, 247, 0.85);
|
||||
backdrop-filter: blur(8px);
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 3px dashed var(--c-accent);
|
||||
margin: 12px;
|
||||
border-radius: calc(var(--radius-lg) + 4px);
|
||||
}
|
||||
|
||||
.drag-overlay-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
color: var(--c-accent);
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.drag-overlay-content svg {
|
||||
animation: drag-bounce 1.2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes drag-bounce {
|
||||
0%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-8px);
|
||||
}
|
||||
}
|
||||
|
||||
.drag-enter-active,
|
||||
.drag-leave-active {
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.drag-enter-from,
|
||||
.drag-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user