@@ -132,38 +132,17 @@ const BeatGrid: React.FC<BeatGridProps> = ({ beats = [], totalBeats = 16, groupS
132
132
} as Beat ) ;
133
133
}
134
134
135
- // Group beats
136
- const groups = [ ] ;
137
- for ( let i = 0 ; i < totalBeats ; i += groupSize ) {
138
- const group = beatsToRender . slice ( i , i + groupSize ) ;
139
- groups . push ( group ) ;
140
- }
141
-
142
135
return (
143
- < div className = "grid grid-cols-4 gap-0 border border-gray-200 rounded-lg" >
144
- { groups . map ( ( group , groupIndex ) => (
145
- < div key = { groupIndex } className = "border-r border-gray-200 last:border-r-0" >
146
- < div className = "grid grid-cols-4" >
147
- { group . map ( ( beat , beatIndex ) => {
148
- const renderedBeat = renderBeat ( beat ) ;
149
- const isLyrics = typeof beat !== 'number' &&
150
- beat ?. elements ?. some ( e => e ?. lyrics ) || false ;
151
- const className = isLyrics ? 'text-blue-600 font-medium' : 'text-black' ;
152
-
153
- return (
154
- < div
155
- key = { `${ groupIndex } -${ beatIndex } ` }
156
- className = "text-center p-1 border-r border-gray-100 last:border-r-0 relative group"
157
- title = { `Beat ${ groupIndex * groupSize + beatIndex + 1 } ` }
158
- >
159
- < span className = { className } > { renderedBeat } </ span >
160
- < div className = "absolute -top-8 left-1/2 transform -translate-x-1/2 bg-black text-white text-xs px-2 py-1 rounded opacity-0 group-hover:opacity-100 transition-opacity duration-200 pointer-events-none" >
161
- Beat { groupIndex * groupSize + beatIndex + 1 }
162
- </ div >
163
- </ div >
164
- ) ;
165
- } ) }
166
- </ div >
136
+ < div className = "grid grid-cols-16 gap-1" >
137
+ { beatsToRender . map ( ( beat , index ) => (
138
+ < div
139
+ key = { index }
140
+ className = { `border p-2 text-center hover:bg-gray-50 transition-colors ${
141
+ index % groupSize === 0 ? 'border-l-2' : ''
142
+ } `}
143
+ title = { `Beat ${ index + 1 } ` }
144
+ >
145
+ { typeof beat === 'number' ? beat : formatter . formatBeat ( beat ) }
167
146
</ div >
168
147
) ) }
169
148
</ div >
@@ -389,6 +368,71 @@ const SUREditor: React.FC<{ content: string; onChange: (content: string) => void
389
368
) ;
390
369
} ;
391
370
371
+ interface PreviewProps {
372
+ document : SurDocument ;
373
+ }
374
+
375
+ const formatter = new SurFormatter ( ) ;
376
+
377
+ export function Preview ( { document } : PreviewProps ) {
378
+ if ( ! document ) return null ;
379
+
380
+ // Calculate total beats
381
+ const totalBeats = document . composition . sections . reduce (
382
+ ( sum , section ) => sum + section . beats . length ,
383
+ 0
384
+ ) ;
385
+
386
+ return (
387
+ < div className = "preview-container" >
388
+ { /* Title Section */ }
389
+ < h1 className = "text-2xl font-bold mb-4" >
390
+ { document . metadata . name || 'Untitled Composition' }
391
+ </ h1 >
392
+
393
+ { /* Metadata Section */ }
394
+ < div className = "metadata-section mb-4" >
395
+ < div className = "grid grid-cols-2 gap-2" >
396
+ < div > Raag:</ div >
397
+ < div > { document . metadata . raag || 'Not specified' } </ div >
398
+ < div > Taal:</ div >
399
+ < div > { document . metadata . taal || 'Not specified' } </ div >
400
+ < div > Tempo:</ div >
401
+ < div > { document . metadata . tempo || 'Not specified' } </ div >
402
+ </ div >
403
+ </ div >
404
+
405
+ { /* Statistics */ }
406
+ < div className = "stats-section mb-4" >
407
+ < p > Total Beats: { totalBeats } </ p >
408
+ </ div >
409
+
410
+ { /* Composition Sections */ }
411
+ { document . composition . sections . map ( ( section , sectionIndex ) => (
412
+ < div key = { sectionIndex } className = "section-container mb-6" >
413
+ { /* Section Header */ }
414
+ < h2 className = "text-xl font-semibold mb-2" >
415
+ { section . title || 'Untitled Section' }
416
+ </ h2 >
417
+
418
+ { /* Beats Grid */ }
419
+ < div className = "grid grid-cols-8 gap-2" >
420
+ { section . beats . map ( ( beat , beatIndex ) => (
421
+ < div
422
+ key = { beatIndex }
423
+ className = "border p-2 text-center"
424
+ title = { `Beat ${ beatIndex + 1 } ` }
425
+ >
426
+ { formatter . formatBeat ( beat ) }
427
+ </ div >
428
+ ) ) }
429
+ </ div >
430
+ </ div >
431
+ ) ) }
432
+ </ div >
433
+ ) ;
434
+ }
435
+
392
436
const SUREditorViewer = ( ) => {
393
437
const [ content , setContent ] = useState ( DEFAULT_SUR ) ;
394
438
const [ hideControls , setHideControls ] = useState ( false ) ;
@@ -410,58 +454,155 @@ const SUREditorViewer = () => {
410
454
411
455
< TabsContent value = "preview" >
412
456
< Card className = "w-full" >
413
- < CardHeader className = "border-b border-gray-200" >
414
- < div className = "flex justify-between items-start" >
415
- < div onClick = { toggleControls } >
416
- < CardTitle className = "text-2xl font-bold mb-2" >
417
- Preview
418
- </ CardTitle >
457
+ < CardHeader className = "border-b border-gray-200 pb-6" >
458
+ { /* First Row: Title and Download Button */ }
459
+ < div className = "flex justify-between items-center mb-4" >
460
+ < CardTitle className = "text-3xl font-bold" >
461
+ { ( ( ) => {
462
+ try {
463
+ const surDoc = parseSURFile ( content ) ;
464
+ return surDoc . metadata . name || 'Untitled Composition' ;
465
+ } catch ( e ) {
466
+ return 'Preview' ;
467
+ }
468
+ } ) ( ) }
469
+ </ CardTitle >
470
+ { ( ( ) => {
471
+ try {
472
+ const surDoc = parseSURFile ( content ) ;
473
+ return (
474
+ < PDFExporter
475
+ config = { {
476
+ name : surDoc . metadata . name || 'Untitled' ,
477
+ tempo : surDoc . metadata . tempo ,
478
+ beats_per_row : surDoc . metadata . beats_per_row
479
+ } }
480
+ composition = { surDoc . composition . sections . map ( section => ( {
481
+ title : section . title ,
482
+ lines : groupBeatsIntoLines ( section . beats ) . map ( line => ( {
483
+ beats : line
484
+ } ) )
485
+ } ) ) }
486
+ />
487
+ ) ;
488
+ } catch ( e ) {
489
+ return null ;
490
+ }
491
+ } ) ( ) }
492
+ </ div >
493
+
494
+ { /* Second Row: Metadata */ }
495
+ < div className = "cursor-pointer" onClick = { toggleControls } >
496
+ < div className = { `metadata-section ${ hideControls ? 'hidden' : '' } ` } >
497
+ { ( ( ) => {
498
+ try {
499
+ const surDoc = parseSURFile ( content ) ;
500
+ return (
501
+ < div className = "bg-gray-50 rounded-lg p-4" >
502
+ < dl className = "grid grid-cols-3 gap-6" >
503
+ < div >
504
+ < dt className = "text-sm font-medium text-gray-500 mb-1" > Raag</ dt >
505
+ < dd className = "text-base font-semibold text-gray-900" >
506
+ { surDoc . metadata . raag || 'Not specified' }
507
+ </ dd >
508
+ </ div >
509
+ < div >
510
+ < dt className = "text-sm font-medium text-gray-500 mb-1" > Taal</ dt >
511
+ < dd className = "text-base font-semibold text-gray-900" >
512
+ { surDoc . metadata . taal || 'Not specified' }
513
+ </ dd >
514
+ </ div >
515
+ < div >
516
+ < dt className = "text-sm font-medium text-gray-500 mb-1" > Tempo</ dt >
517
+ < dd className = "text-base font-semibold text-gray-900" >
518
+ { surDoc . metadata . tempo || 'Not specified' }
519
+ </ dd >
520
+ </ div >
521
+ </ dl >
522
+ </ div >
523
+ ) ;
524
+ } catch ( e ) {
525
+ return null ;
526
+ }
527
+ } ) ( ) }
419
528
</ div >
420
529
</ div >
421
530
</ CardHeader >
422
-
531
+
423
532
< CardContent className = "p-6" >
424
533
< div className = "space-y-6" >
425
534
{ /* Beat numbers row */ }
426
535
< div className = "mb-3 font-mono text-sm" >
427
536
< div className = "text-gray-600 mb-0.5" > Beat:</ div >
428
- < BeatGrid
429
- beats = { Array . from ( { length : 16 } , ( _ , i ) => i + 1 ) }
430
- totalBeats = { 16 }
431
- groupSize = { 4 }
432
- />
537
+ < div className = "grid grid-cols-4 gap-0 border border-gray-200 rounded-lg" >
538
+ { [ 0 , 1 , 2 , 3 ] . map ( ( group ) => (
539
+ < div key = { group } className = "border-r border-gray-200 last:border-r-0" >
540
+ < div className = "grid grid-cols-4" >
541
+ { [ 1 , 2 , 3 , 4 ] . map ( ( num ) => {
542
+ const beatNum = group * 4 + num ;
543
+ return (
544
+ < div
545
+ key = { beatNum }
546
+ className = "text-center p-2 border-r border-gray-100 last:border-r-0"
547
+ title = { `Beat ${ beatNum } ` }
548
+ >
549
+ { beatNum }
550
+ </ div >
551
+ ) ;
552
+ } ) }
553
+ </ div >
554
+ </ div >
555
+ ) ) }
556
+ </ div >
433
557
</ div >
434
-
558
+
435
559
{ /* Composition sections */ }
436
560
{ ( ( ) => {
437
561
try {
438
562
const surDoc = parseSURFile ( content ) ;
439
- console . log ( 'Rendering document:' , surDoc ) ;
563
+ const formatter = new SurFormatter ( ) ;
440
564
441
565
return surDoc . composition . sections . map ( ( section , sectionIdx ) => {
442
- console . log ( 'Rendering section:' , section . title , 'beats:' , section . beats ) ;
443
-
444
- if ( ! Array . isArray ( section . beats ) ) {
445
- console . error ( 'Section beats is not an array:' , section . beats ) ;
446
- return null ;
447
- }
448
-
449
566
// Group beats into lines
450
567
const beatLines = groupBeatsIntoLines ( section . beats ) ;
451
568
452
569
return (
453
570
< div key = { sectionIdx } className = "space-y-1.5" >
454
- < h3 className = "text-lg font-semibold text-blue-600 mb-1 " >
571
+ < h3 className = "text-lg font-semibold text-blue-600 mb-2 " >
455
572
{ section . title }
456
573
</ h3 >
457
574
< div className = "font-mono text-sm space-y-2" >
458
575
{ beatLines . map ( ( beatLine , lineIdx ) => (
459
- < BeatGrid
460
- key = { `${ sectionIdx } -${ lineIdx } ` }
461
- beats = { beatLine }
462
- totalBeats = { 16 }
463
- groupSize = { 4 }
464
- />
576
+ < div key = { `${ sectionIdx } -${ lineIdx } ` }
577
+ className = "grid grid-cols-4 gap-0 border border-gray-200 rounded-lg" >
578
+ { [ 0 , 1 , 2 , 3 ] . map ( ( group ) => (
579
+ < div key = { group } className = "border-r border-gray-200 last:border-r-0" >
580
+ < div className = "grid grid-cols-4" >
581
+ { [ 0 , 1 , 2 , 3 ] . map ( ( num ) => {
582
+ const beatIndex = group * 4 + num ;
583
+ const beat = beatLine [ beatIndex ] || {
584
+ elements : [ { note : { pitch : NotePitch . SILENCE } } ] ,
585
+ bracketed : false
586
+ } ;
587
+ return (
588
+ < div
589
+ key = { beatIndex }
590
+ className = { `text-center p-2 border-r border-gray-100 last:border-r-0 relative group ${
591
+ beat . elements . some ( e => e . lyrics ) ? 'text-blue-600' : ''
592
+ } `}
593
+ >
594
+ { formatter . formatBeat ( beat ) }
595
+ { /* Tooltip */ }
596
+ < div className = "absolute -top-8 left-1/2 transform -translate-x-1/2 bg-black text-white text-xs px-2 py-1 rounded opacity-0 group-hover:opacity-100 transition-opacity duration-200 pointer-events-none" >
597
+ Beat { lineIdx * 16 + beatIndex + 1 }
598
+ </ div >
599
+ </ div >
600
+ ) ;
601
+ } ) }
602
+ </ div >
603
+ </ div >
604
+ ) ) }
605
+ </ div >
465
606
) ) }
466
607
</ div >
467
608
</ div >
0 commit comments