From 68c1706e4a480c0bc5259c529d67cd795516ffdb Mon Sep 17 00:00:00 2001 From: kura Date: Fri, 1 May 2026 20:30:17 +0800 Subject: [PATCH] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E8=99=9A=E6=8B=9F=E6=BB=9A?= =?UTF-8?q?=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/SubtitleEditor.vue | 185 ++++-------------------------- src/style.css | 8 -- 2 files changed, 25 insertions(+), 168 deletions(-) diff --git a/src/components/SubtitleEditor.vue b/src/components/SubtitleEditor.vue index c181309..e4d2f17 100644 --- a/src/components/SubtitleEditor.vue +++ b/src/components/SubtitleEditor.vue @@ -2,21 +2,6 @@ import { computed, ref, nextTick, watch } from 'vue' import type { SubtitleSegment, SubtitleTask } from '../lib/types' -const LOG_ROW_HEIGHT = 20 -const LOG_OVERSCAN = 10 - -const SEGMENT_TIMESTAMP_HEIGHT = 20 -const SEGMENT_TEXTAREA_HEIGHT = 60 -const SEGMENT_PADDING_GAP = 34 -const SEGMENT_CHARS_PER_LINE = 40 -const SEGMENT_LINE_HEIGHT = 20 -const SEGMENT_OVERSCAN = 5 - -function estimateSegmentHeight(seg: SubtitleSegment): number { - const textLines = Math.max(1, Math.ceil((seg.sourceText?.length || 1) / SEGMENT_CHARS_PER_LINE)) - return SEGMENT_TIMESTAMP_HEIGHT + textLines * SEGMENT_LINE_HEIGHT + SEGMENT_TEXTAREA_HEIGHT + SEGMENT_PADDING_GAP -} - const props = defineProps<{ task: SubtitleTask | null logs: string[] @@ -56,129 +41,14 @@ const sortedSegments = computed(() => const logsExpanded = ref(false) const logContainerRef = ref(null) -const logScrollTop = ref(0) -const isNearBottom = ref(true) - -const visibleLogs = computed(() => { - const total = props.logs.length - if (total === 0) return { items: [] as string[], start: 0, paddingTop: 0, paddingBottom: 0 } - - const containerHeight = logContainerRef.value?.clientHeight ?? 220 - const startIndex = Math.max(0, Math.floor(logScrollTop.value / LOG_ROW_HEIGHT) - LOG_OVERSCAN) - const visibleCount = Math.ceil(containerHeight / LOG_ROW_HEIGHT) + LOG_OVERSCAN * 2 - const endIndex = Math.min(total, startIndex + visibleCount) - - return { - items: props.logs.slice(startIndex, endIndex), - start: startIndex, - paddingTop: startIndex * LOG_ROW_HEIGHT, - paddingBottom: Math.max(0, total * LOG_ROW_HEIGHT - endIndex * LOG_ROW_HEIGHT), - } -}) - -function handleLogScroll(event: Event) { - const el = event.target as HTMLElement - logScrollTop.value = el.scrollTop - isNearBottom.value = el.scrollTop + el.clientHeight >= el.scrollHeight - 40 -} watch(() => props.logs.length, () => { - if (isNearBottom.value) { - nextTick(() => { - if (logContainerRef.value) { - logContainerRef.value.scrollTop = logContainerRef.value.scrollHeight - logScrollTop.value = logContainerRef.value.scrollTop - } - }) - } -}) - -const segContainerRef = ref(null) -const segScrollTop = ref(0) -const segHeights = ref(new Map()) -const segObservers = new Map() - -const segOffsetCache = computed(() => { - const segments = sortedSegments.value - const cache = new Map() - let offset = 0 - for (const seg of segments) { - const height = segHeights.value.get(seg.id) ?? estimateSegmentHeight(seg) - cache.set(seg.id, { height, offset }) - offset += height + 8 - } - return { cache, totalHeight: Math.max(0, offset - 8) } -}) - -const visibleSegments = computed(() => { - const segments = sortedSegments.value - if (segments.length === 0) return { items: [] as SubtitleSegment[], paddingTop: 0, paddingBottom: 0 } - - const containerHeight = segContainerRef.value?.clientHeight ?? 600 - const { cache, totalHeight } = segOffsetCache.value - - let startIndex = segments.length - 1 - for (let i = 0; i < segments.length; i++) { - const info = cache.get(segments[i].id)! - if (info.offset + info.height > segScrollTop.value) { - startIndex = Math.max(0, i - SEGMENT_OVERSCAN) - break - } - } - - let endIndex = segments.length - for (let i = startIndex; i < segments.length; i++) { - const info = cache.get(segments[i].id)! - if (info.offset > segScrollTop.value + containerHeight) { - endIndex = Math.min(segments.length, i + SEGMENT_OVERSCAN) - break - } - } - - const startOffset = cache.get(segments[startIndex].id)?.offset ?? 0 - const endInfo = cache.get(segments[Math.min(endIndex - 1, segments.length - 1)].id) - const endOffset = endInfo ? endInfo.offset + endInfo.height : totalHeight - const paddingBottom = Math.max(0, totalHeight - endOffset) - - return { - items: segments.slice(startIndex, endIndex), - paddingTop: startOffset, - paddingBottom, - } -}) - -function handleSegScroll(event: Event) { - const el = event.target as HTMLElement - segScrollTop.value = el.scrollTop -} - -function onSegMount(el: HTMLElement, segmentId: string) { - const existing = segObservers.get(segmentId) - if (existing) existing.disconnect() - - const ro = new ResizeObserver((entries) => { - for (const entry of entries) { - const actualHeight = entry.contentBoxSize - ? entry.contentBoxSize[0].blockSize - : entry.borderBoxSize - ? entry.borderBoxSize[0].blockSize - : (entry.target as HTMLElement).offsetHeight - if (actualHeight > 0) { - segHeights.value = new Map(segHeights.value).set(segmentId, actualHeight) - } + nextTick(() => { + if (logContainerRef.value) { + logContainerRef.value.scrollTop = logContainerRef.value.scrollHeight } }) - ro.observe(el) - segObservers.set(segmentId, ro) -} - -function onSegUnmount(segmentId: string) { - const ro = segObservers.get(segmentId) - if (ro) { - ro.disconnect() - segObservers.delete(segmentId) - } -} +}) function formatTime(seconds: number) { const ms = Math.round(seconds * 1000) @@ -227,28 +97,25 @@ function updateTranslatedText(segment: SubtitleSegment, value: string) { -
-
-
-
- {{ formatTime(segment.start) }} - {{ formatTime(segment.end) }} - {{ segment.id }} -
-

{{ segment.sourceText || $t('editor.waiting') }}

-