Skip to content

Commit a230988

Browse files
b3008claude
andcommitted
Add Regenerate button to ScheduleExplorer calendar view
Replaces the hidden _reshuffle Storybook control with a visible Regenerate button in the calendar UI. Uses window-exposed build functions to avoid duplicating rendering logic in inline scripts. Only shown for random/stratified-random schedule types. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 0a5c12b commit a230988

16 files changed

Lines changed: 629 additions & 422 deletions

src/stories/SignalProtocol.stories.ts

Lines changed: 119 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -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 &le; 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+
&#x21bb; Regenerate<span style="font-size:11px;opacity:.7;">&thinsp;#<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
},

storybook-static/assets/Color-6VNJS4EI-C-Gl9Xy4.js renamed to storybook-static/assets/Color-6VNJS4EI-BtZEsT7W.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

storybook-static/assets/DocsRenderer-NNNQARDV-Dm2nNqUy.js renamed to storybook-static/assets/DocsRenderer-NNNQARDV-BcPLI9W8.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

storybook-static/assets/Introduction-D3F0CC_k.js renamed to storybook-static/assets/Introduction-BxD8NXuJ.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)