import { Img, Rect, Node, Video, makeScene2D, Txt, Line, LineSegment, NodeProps, } from "@motion-canvas/2d"; import { Direction, beginSlide, createRef, map, slideTransition, tween, all, waitFor, range, makeRef, } from "@motion-canvas/core"; import { CodeBlock } from "@motion-canvas/2d/lib/components/CodeBlock"; import { theme } from "../theme"; import * as ts from "typescript"; export interface FunctionBoxProps extends NodeProps { source?: string; fn?: Function; padding?: number; delta?: number; workingText?: string; idlingText?: string; arity?: number; isChild?: boolean; } type FunctionArgs = { node?: Node; val: any }[]; /* */ export class FunctionBox extends Node { private readonly source: string; private readonly workingText: string; private readonly idlingText: string; private readonly function: any; private readonly delta: number; private readonly arity: number; private readonly padding: number; private readonly block = createRef(); private readonly node = createRef(); private readonly rect = createRef(); private readonly boxMoji = createRef(); private readonly inputSegments: Line[] = []; private readonly inputs: Rect[] = []; private readonly outputSegment = createRef(); private readonly output = createRef(); private readonly child = createRef(); private readonly isChild: boolean; private currentArgs: FunctionArgs = []; public constructor(props?: FunctionBoxProps) { super({ ...props, }); this.arity = props?.arity ?? 1; if (props.fn) { this.source = props.fn.toString(); this.function = props.fn; } else { this.source = props?.source ?? `(x: number): number => x + 2`; const functionCode = ts.transpile(this.source); this.function = eval(functionCode); } this.delta = props?.delta ?? 20; this.padding = props?.padding ?? 100; this.workingText = props?.workingText ?? "👷‍♀️⚙️"; this.idlingText = props?.idlingText ?? "😴"; this.isChild = props?.isChild ?? false; this.add( {range(this.arity).map((i) => ( ))} {this.idlingText} , ); } public *resetInput(duration: number) { yield* all( ...this.inputs.map((x) => all( x.opacity(0, duration), x.height(0, duration), x.width(0, duration), ), ), ...this.inputSegments.map((segment) => segment.points([], duration)), ); } public *resetOutput(duration: number) { yield* all( this.output().opacity(0, duration), this.outputSegment().points([], duration), ); yield this.output().removeChildren(); } public *reset(duration: number) { yield* all(this.resetInput(duration), this.resetOutput(duration)); } public *setInputs(args: FunctionArgs, duration: number) { if (args.length != this.arity) throw new Error("input length must equal function arity"); this.currentArgs = args; this.inputs.forEach((input, i) => { input.removeChildren(); input.add( args[i].node ?? ( {args[i].val.toString()} ), ); }); yield* all( ...this.inputSegments.map((segment) => segment.points( [ { x: -this.padding, y: 0 }, { x: -this.delta, y: 0 }, ], duration, ), ), ...this.inputs.map((input) => all( input.height(40, duration), input.width(40, duration), input.opacity(1, duration), ), ), ); } public *propogateInput(duration: number) { const opacityChangeDuration = 0.1; yield* all( ...this.inputSegments.map((segment) => segment.opacity(0.2, opacityChangeDuration), ), ); yield* all( ...this.inputSegments.map((segment) => segment.points([], duration)), ); yield* all( ...this.inputSegments.map((segment) => segment.opacity(1, opacityChangeDuration), ), ...this.inputs.map((input) => input.opacity(0, opacityChangeDuration)), this.boxMoji().text(this.workingText, duration), ); } public *propogateOutput(duration: number) { const opacityChangeDuration = 0.1; const output = this.function(...this.currentArgs.map((input) => input.val)); if (typeof output === "function") { yield this.output().add( , ); } else { yield this.output().add( {output.toString()} , ); } yield* all( this.boxMoji().text(this.idlingText, duration), this.outputSegment().points( [ { x: -this.delta, y: 0 }, { x: this.padding, y: 0 }, ], duration, ), this.child()?.opacity(1, duration), this.output().opacity(1, duration), this.outputSegment().opacity(1, duration), ); } }