diff --git a/content/src/components/schedule-visualizer/ScheduleVisualizer.css b/content/src/components/schedule-visualizer/ScheduleVisualizer.css new file mode 100644 index 000000000..f5332306a --- /dev/null +++ b/content/src/components/schedule-visualizer/ScheduleVisualizer.css @@ -0,0 +1,40 @@ +.arrow { + position: relative; + display: inline-block; + height: 2px; + margin: 12px 0; + border-radius: 9999px; + background-color: #000000; +} + +.arrow::before, +.arrow::after { + content: ""; + position: absolute; + top: calc(50% - 1px); + right: 0; + width: 19px; + height: 2px; + border-radius: 9999px; + background-color: #000000; + transform-origin: calc(100% - 1px) 50%; +} + +.arrow::before { + transform: rotate(45deg); +} + +.arrow::after { + transform: rotate(-45deg); +} + +@media (prefers-color-scheme: dark) { + .arrow { + background-color: #ffffff; + } + + .arrow::before, + .arrow::after { + background-color: #ffffff; + } +} \ No newline at end of file diff --git a/content/src/components/schedule-visualizer/ScheduleVisualizer.tsx b/content/src/components/schedule-visualizer/ScheduleVisualizer.tsx new file mode 100644 index 000000000..494a31b5a --- /dev/null +++ b/content/src/components/schedule-visualizer/ScheduleVisualizer.tsx @@ -0,0 +1,112 @@ +import React from "react" + +import "./ScheduleVisualizer.css" +import { Duration, Effect, Schedule } from "effect" +import { sample } from "./run-schedule" + +export type ScheduleVisualizerProps = { + items: ReadonlyArray +} + +type ScheduleItem = { + value: string + interval: Duration.Duration | undefined + clock: Duration.Duration + hasNewValue: boolean +} + +export const ScheduleVisualizer: React.FC = ({ + items +}) => { + return ( +
+
+
+
+
time
+
+ output +
+
+
+ {items.map((item, index) => ( + + ))} +
+
+
+ ) +} + +const ScheduleItem: React.FC = ({ + value, + interval, + clock, + hasNewValue +}) => { + return ( +
+
{value}
+
+
+
+
+ {interval && Duration.toMillis(interval) > 0 + ? Duration.format(interval) + : "0"} +
+
+
+ {Duration.toMillis(clock) === 0 ? "0s" : Duration.format(clock)} +
+
+ ) +} + +const durationToPixels = (duration: Duration.Duration) => { + return Math.max(20, Math.round(Duration.toSeconds(duration) * 60)) +} + +export const scheduleToItems = async ( + schedule: Schedule.Schedule, + limit: number +) => { + let accDuration = Duration.millis(0) + return sample(schedule, limit).pipe( + Effect.map((_) => + _.map((duration) => { + const clock = accDuration + accDuration = Duration.sum(accDuration, duration) + return { + interval: duration, + clock, + hasNewValue: true, + value: "" + } satisfies ScheduleItem + }) + ), + Effect.tapErrorCause(Effect.logError), + Effect.runPromise + ) +} diff --git a/content/src/components/schedule-visualizer/ScheduleVisualizerAstro.astro b/content/src/components/schedule-visualizer/ScheduleVisualizerAstro.astro new file mode 100644 index 000000000..761b75165 --- /dev/null +++ b/content/src/components/schedule-visualizer/ScheduleVisualizerAstro.astro @@ -0,0 +1,19 @@ +--- +import { Schedule } from "effect" +import { ScheduleVisualizer, scheduleToItems } from "./ScheduleVisualizer" +// import { Code } from "astro:components" + +type Props = { + code: string + schedule: Schedule.Schedule +} + +const limit = 10 +const items = await scheduleToItems(Astro.props.schedule, limit) +--- + + + + diff --git a/content/src/components/schedule-visualizer/run-schedule.ts b/content/src/components/schedule-visualizer/run-schedule.ts new file mode 100644 index 000000000..8632b6c50 --- /dev/null +++ b/content/src/components/schedule-visualizer/run-schedule.ts @@ -0,0 +1,35 @@ +import { + Array, + Duration, + Effect, + Fiber, + Schedule, + TestClock, + TestContext +} from "effect" + +export const sample = ( + schedule: Schedule.Schedule, + limit: number +) => { + return Effect.gen(function* () { + const output: Array = [] + + const fiber = yield* Effect.gen(function* () { + const now = yield* TestClock.currentTimeMillis + const previous = Array.reduce(output, Duration.zero, Duration.sum) + + output.push(Duration.subtract(now, previous)) + }).pipe( + Effect.schedule( + schedule.pipe(Schedule.intersect(Schedule.recurs(limit))) + ), + Effect.fork + ) + + yield* TestClock.adjust(Infinity) + yield* Fiber.join(fiber) + + return output + }).pipe(Effect.provide(TestContext.TestContext)) +} diff --git a/content/src/content/docs/docs/scheduling/schedule-visualizer.mdx b/content/src/content/docs/docs/scheduling/schedule-visualizer.mdx new file mode 100644 index 000000000..5fadcfe08 --- /dev/null +++ b/content/src/content/docs/docs/scheduling/schedule-visualizer.mdx @@ -0,0 +1,48 @@ +--- +title: Schedule Visualizer +--- + +import ScheduleVisualizer from "@/components/schedule-visualizer/ScheduleVisualizerAstro.astro" +import { Duration, Schedule } from "effect" + + + + ```ts + Schedule.fixed(Duration.seconds(1)) + ``` + + + + + + ```ts + Schedule.recurs(2) + ``` + + + + + + ```ts + Schedule.fibonacci(Duration.seconds(1)) + ``` + + + + + + ```ts + Schedule.exponential(Duration.millis(10), 4).pipe( + Schedule.andThenEither(Schedule.spaced(Duration.seconds(1))), + Schedule.compose(Schedule.elapsed), + Schedule.whileOutput(Duration.lessThanOrEqualTo(Duration.seconds(10))) + ) + ``` + + \ No newline at end of file