From 8a1e2a53357acc8523cda319ce4bc369d6867c75 Mon Sep 17 00:00:00 2001 From: Maxim Vershinin Date: Thu, 20 Aug 2026 12:47:58 +0500 Subject: [PATCH] FIX: aligns line number with the corresponding lines in the tracklist editor --- .../src/components/TracklistEditor.tsx | 53 ++++++++++++++----- 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/web/frontend/src/components/TracklistEditor.tsx b/web/frontend/src/components/TracklistEditor.tsx index d878f45..3f49bb7 100644 --- a/web/frontend/src/components/TracklistEditor.tsx +++ b/web/frontend/src/components/TracklistEditor.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useState } from 'react' +import React, { useCallback, useEffect, useRef, useState } from 'react' import { Box, Paper, TextField, Typography, Alert } from '@mui/material' import { useDropzone } from 'react-dropzone' import { useTracklistStore } from '../stores/tracklistStore' @@ -10,6 +10,9 @@ export const TracklistEditor: React.FC = () => { const { options } = useOptionsStore() const [isDragging, setIsDragging] = useState(false) + const textAreaRef = useRef(null) + const lineNumbersRef = useRef(null) + const validate = (text: string) => { const result = parseAndValidateTracklist(text, options.tracklist_format) setEntries(result.entries) @@ -23,10 +26,29 @@ export const TracklistEditor: React.FC = () => { validate(text) } + const handleScroll = useCallback(() => { + if (lineNumbersRef.current && textAreaRef.current) { + lineNumbersRef.current.scrollTop = textAreaRef.current.scrollTop + } + }, []) + + useEffect(() => { + const textArea = textAreaRef.current + if (textArea) { + textArea.addEventListener('scroll', handleScroll) + return () => textArea.removeEventListener('scroll', handleScroll) + } + }, [handleScroll]) + + useEffect(() => { + if (rawText) { + validate(rawText) + } + }, [options.tracklist_format]) + const onDrop = useCallback( (acceptedFiles: File[]) => { if (acceptedFiles.length === 0) return - const file = acceptedFiles[0] const reader = new FileReader() reader.onload = (event) => { @@ -48,13 +70,6 @@ export const TracklistEditor: React.FC = () => { multiple: false, }) - // Re-validate when tracklist format changes - useEffect(() => { - if (rawText) { - validate(rawText) - } - }, [options.tracklist_format]) - const lineCount = rawText.split('\n').filter(line => line.trim() !== '').length return ( @@ -82,12 +97,13 @@ export const TracklistEditor: React.FC = () => { - + {/* Line numbers column */} { textAlign: 'right', userSelect: 'none', overflow: 'hidden', + paddingTop: '8.5px', + paddingBottom: '8.5px', + scrollbarWidth: 'none', + '&::-webkit-scrollbar': { display: 'none' }, + whiteSpace: 'nowrap', // Prevent wrapping of line numbers }} > {rawText.split('\n').map((_, i) => ( @@ -102,7 +123,7 @@ export const TracklistEditor: React.FC = () => { ))} - {/* Editor text area */} + {/* Editor text area – now with horizontal scroll and no wrap */} { onChange={handleTextChange} placeholder={`Enter your tracklist here...\n\nExample (format: ${options.tracklist_format}):\n00:00 Intro\n01:30 Song One - Artist A\n04:20-06:45 Another Song - Artist B`} variant="outlined" + inputRef={textAreaRef} sx={{ '& .MuiInputBase-root': { fontFamily: 'monospace', fontSize: '14px', lineHeight: 1.7, + overflowX: 'auto', // Enable horizontal scroll + }, + '& .MuiInputBase-input': { + paddingTop: '8.5px', + paddingBottom: '8.5px', + whiteSpace: 'nowrap', // Prevent wrapping + overflowX: 'auto', }, }} error={!isValid && errors.length > 0}