@@ -465,10 +465,9 @@ export const ScheduleExplorer: Story = {
465465 description : "End of date range (YYYY-MM-DD)" ,
466466 table : { category : "Date Range" } ,
467467 } ,
468- _reshuffle : {
469- control : { type : "number" , min : 1 } ,
470- description : "Change to regenerate random schedules" ,
471- table : { category : "Date Range" } ,
468+ _seed : {
469+ control : false ,
470+ table : { disable : true } ,
472471 } ,
473472 } ,
474473 args : {
@@ -490,7 +489,7 @@ export const ScheduleExplorer: Story = {
490489 weekendFixedTimes : "" ,
491490 rangeStart : "2025-01-06" ,
492491 rangeEnd : "2025-01-19" ,
493- _reshuffle : 1 ,
492+ _seed : 1 ,
494493 } ,
495494 render : ( args ) => {
496495 const el = createProtocolElement ( args ) ;
@@ -501,53 +500,132 @@ export const ScheduleExplorer: Story = {
501500 return html `< p style ="color:#c62828;font-family:sans-serif; "> Invalid date range. Use YYYY-MM-DD format with start ≤ end.</ p > ` ;
502501 }
503502
504- let times : Date [ ] = [ ] ;
503+ function computeHourRange ( times : Date [ ] ) {
504+ let minHour = 6 , maxHour = 23 ;
505+ if ( times . length > 0 ) {
506+ const hours = times . map ( ( s : Date ) => s . getHours ( ) ) ;
507+ minHour = Math . max ( 0 , Math . min ( ...hours ) - 1 ) ;
508+ maxHour = Math . min ( 24 , Math . max ( ...hours ) + 2 ) ;
509+ }
510+ if ( args . windowStart ) {
511+ const h = parseInt ( args . windowStart , 10 ) ;
512+ if ( ! isNaN ( h ) ) minHour = Math . min ( minHour , Math . max ( 0 , h - 1 ) ) ;
513+ }
514+ if ( args . windowEnd ) {
515+ const h = parseInt ( args . windowEnd , 10 ) ;
516+ if ( ! isNaN ( h ) ) maxHour = Math . max ( maxHour , Math . min ( 24 , h + 1 ) ) ;
517+ }
518+ if ( maxHour - minHour < 4 ) maxHour = Math . min ( 24 , minHour + 4 ) ;
519+ return { minHour, maxHour } ;
520+ }
521+
505522 let warnings : string [ ] = [ ] ;
523+ let times : Date [ ] = [ ] ;
506524 try {
507525 warnings = el . validate ( ) ;
508526 times = el . getSignallingTimes ( start , end ) ;
509527 } catch ( e : any ) {
510528 warnings . push ( "Error generating schedule: " + e . message ) ;
511529 }
512530
513- // Determine visible hour range from signals + configured windows
514- let minHour = 6 , maxHour = 23 ;
515- if ( times . length > 0 ) {
516- const hours = times . map ( ( s : Date ) => s . getHours ( ) ) ;
517- minHour = Math . max ( 0 , Math . min ( ...hours ) - 1 ) ;
518- maxHour = Math . min ( 24 , Math . max ( ...hours ) + 2 ) ;
519- }
520- if ( args . windowStart ) {
521- const h = parseInt ( args . windowStart , 10 ) ;
522- if ( ! isNaN ( h ) ) minHour = Math . min ( minHour , Math . max ( 0 , h - 1 ) ) ;
523- }
524- if ( args . windowEnd ) {
525- const h = parseInt ( args . windowEnd , 10 ) ;
526- if ( ! isNaN ( h ) ) maxHour = Math . max ( maxHour , Math . min ( 24 , h + 1 ) ) ;
527- }
528- if ( maxHour - minHour < 4 ) maxHour = Math . min ( 24 , minHour + 4 ) ;
531+ const { minHour, maxHour } = computeHourRange ( times ) ;
532+ const isRandom = args . scheduleType !== "fixed" ;
533+ const containerId = "schedule-explorer-" + Date . now ( ) ;
534+
535+ // Expose build functions on window so the refresh button can reuse them
536+ // without duplicating rendering logic in an inline script.
537+ const w = window as any ;
538+ w . __scheduleExplorer = {
539+ buildWarningsHtml,
540+ buildStatsHtml,
541+ buildWeekCalendarHtml,
542+ buildTextListHtml,
543+ computeHourRange,
544+ createProtocolElement,
545+ args,
546+ start,
547+ end,
548+ containerId,
549+ } ;
550+
551+ const refreshScript = isRandom
552+ ? `<script type="module">
553+ (function() {
554+ let generation = 1;
555+ document.getElementById('${ containerId } -btn').addEventListener('click', function() {
556+ const ctx = window.__scheduleExplorer;
557+ const protocol = ctx.createProtocolElement(ctx.args);
558+ const times = protocol.getSignallingTimes(ctx.start, ctx.end);
559+ const { minHour, maxHour } = ctx.computeHourRange(times);
560+ generation++;
561+
562+ // Update counter
563+ document.getElementById('${ containerId } -counter').textContent = generation;
564+
565+ // Flash button green briefly
566+ const btn = this;
567+ btn.style.background = '#0b8043';
568+ setTimeout(() => { btn.style.background = '#1a73e8'; }, 200);
569+
570+ // Update stats
571+ document.getElementById('${ containerId } -stats').innerHTML =
572+ ctx.buildStatsHtml(times, ctx.start, ctx.end);
573+
574+ // Update calendar
575+ document.getElementById('${ containerId } -calendar').innerHTML =
576+ ctx.buildWeekCalendarHtml(times, ctx.start, ctx.end, minHour, maxHour);
577+
578+ // Update text list
579+ const listHtml = ctx.buildTextListHtml(times);
580+ document.getElementById('${ containerId } -list').innerHTML =
581+ '<details open style="margin-top:24px;">' +
582+ '<summary style="font-size:16px;font-weight:700;cursor:pointer;margin-bottom:8px;">Signal List (' + times.length + ' total)</summary>' +
583+ '<div style="max-height:500px;overflow-y:auto;border:1px solid #eee;border-radius:4px;">' + listHtml + '</div></details>';
584+ });
585+ })();
586+ <\/script>`
587+ : "" ;
529588
530589 return html `
531- < div style ="font-family:sans-serif; ">
590+ < div id =" ${ containerId } " style ="font-family:sans-serif; ">
532591 ${ unsafeHTML ( buildWarningsHtml ( warnings ) ) }
533- ${ unsafeHTML ( buildStatsHtml ( times , start , end ) ) }
534-
535- < p style ="font-size:12px;color:#70757a;margin:0 0 10px; ">
536- Weekday signals in < span style ="background:#1a73e8;color:#fff;padding:1px 6px;border-radius:3px;font-size:11px; "> blue</ span > ,
537- weekend in < span style ="background:#0b8043;color:#fff;padding:1px 6px;border-radius:3px;font-size:11px; "> green</ span > .
538- Hover blocks for details. Thicker border marks Monday (week boundary).
539- </ p >
540-
541- ${ unsafeHTML ( buildWeekCalendarHtml ( times , start , end , minHour , maxHour ) ) }
542-
543- < details open style ="margin-top:24px; ">
544- < summary style ="font-size:16px;font-weight:700;cursor:pointer;margin-bottom:8px; ">
545- Signal List (${ times . length } total)
546- </ summary >
547- < div style ="max-height:500px;overflow-y:auto;border:1px solid #eee;border-radius:4px; ">
548- ${ unsafeHTML ( buildTextListHtml ( times ) ) }
549- </ div >
550- </ details >
592+
593+ < div id ="${ containerId } -stats ">
594+ ${ unsafeHTML ( buildStatsHtml ( times , start , end ) ) }
595+ </ div >
596+
597+ < div style ="display:flex;align-items:center;gap:12px;margin-bottom:10px; ">
598+ < p style ="font-size:12px;color:#70757a;margin:0;flex:1; ">
599+ Weekday signals in < span style ="background:#1a73e8;color:#fff;padding:1px 6px;border-radius:3px;font-size:11px; "> blue</ span > ,
600+ weekend in < span style ="background:#0b8043;color:#fff;padding:1px 6px;border-radius:3px;font-size:11px; "> green</ span > .
601+ Hover blocks for details. Thicker border marks Monday (week boundary).
602+ </ p >
603+ ${ isRandom ? unsafeHTML ( `<button id="${ containerId } -btn" style="
604+ display:inline-flex;align-items:center;gap:6px;padding:6px 14px;
605+ background:#1a73e8;color:#fff;border:none;border-radius:6px;
606+ font-size:13px;font-weight:500;font-family:sans-serif;
607+ cursor:pointer;white-space:nowrap;
608+ " onmouseenter="this.style.background='#1557b0'" onmouseleave="this.style.background='#1a73e8'">
609+ ↻ Regenerate<span style="font-size:11px;opacity:.7;"> #<span id="${ containerId } -counter">1</span></span>
610+ </button>` ) : "" }
611+ </ div >
612+
613+ < div id ="${ containerId } -calendar ">
614+ ${ unsafeHTML ( buildWeekCalendarHtml ( times , start , end , minHour , maxHour ) ) }
615+ </ div >
616+
617+ < div id ="${ containerId } -list ">
618+ < details open style ="margin-top:24px; ">
619+ < summary style ="font-size:16px;font-weight:700;cursor:pointer;margin-bottom:8px; ">
620+ Signal List (${ times . length } total)
621+ </ summary >
622+ < div style ="max-height:500px;overflow-y:auto;border:1px solid #eee;border-radius:4px; ">
623+ ${ unsafeHTML ( buildTextListHtml ( times ) ) }
624+ </ div >
625+ </ details >
626+ </ div >
627+
628+ ${ unsafeHTML ( refreshScript ) }
551629 </ div >
552630 ` ;
553631 } ,
0 commit comments