summaryrefslogtreecommitdiff
path: root/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'src/components')
-rw-r--r--src/components/function_box.tsx290
1 files changed, 290 insertions, 0 deletions
diff --git a/src/components/function_box.tsx b/src/components/function_box.tsx
new file mode 100644
index 0000000..3c65062
--- /dev/null
+++ b/src/components/function_box.tsx
@@ -0,0 +1,290 @@
+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 }[];
+
+/*
+<Node ref={this.node} opacity={0}>
+ <CodeBlock
+ fontFamily={theme.font}
+ language="typescript"
+ ref={this.block}
+ fontSize={1}
+ code={this.source}
+ ></CodeBlock>
+ </Node>
+ */
+
+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<CodeBlock>();
+ private readonly node = createRef<Node>();
+ private readonly rect = createRef<Rect>();
+
+ private readonly boxMoji = createRef<Txt>();
+ private readonly inputSegments: Line[] = [];
+ private readonly inputs: Rect[] = [];
+ private readonly outputSegment = createRef<Line>();
+ private readonly output = createRef<Node>();
+ private readonly child = createRef<FunctionBox>();
+
+ 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(
+ <Rect
+ opacity={this.opacity}
+ direction={"row"}
+ alignItems={"center"}
+ layout
+ >
+ <Rect direction={"column"} alignItems={"end"}>
+ {range(this.arity).map((i) => (
+ <Rect direction={"row"} alignItems={"center"} gap={10}>
+ <Rect
+ direction={"row"}
+ ref={makeRef(this.inputs, i)}
+ justifyContent={"end"}
+ opacity={1}
+ ></Rect>
+
+ <Line
+ points={[]}
+ stroke={theme.green.hex}
+ ref={makeRef(this.inputSegments, i)}
+ lineWidth={5}
+ arrowSize={10}
+ endArrow
+ ></Line>
+ </Rect>
+ ))}
+ </Rect>
+
+ <Rect
+ ref={this.rect}
+ radius={4}
+ stroke={theme.overlay0.hex}
+ fill={theme.crust.hex}
+ lineWidth={4}
+ padding={60}
+ direction={"row"}
+ height={"100%"}
+ gap={40}
+ >
+ <Txt fontFamily={theme.font} fill={theme.text.hex} ref={this.boxMoji}>
+ {this.idlingText}
+ </Txt>
+ </Rect>
+
+ <Rect direction={"column"} height={"100%"} alignItems={"end"}>
+ <Rect direction={"row"} alignItems={"center"} gap={10}>
+ <Line
+ points={[]}
+ stroke={theme.red.hex}
+ lineWidth={5}
+ arrowSize={10}
+ ref={this.outputSegment}
+ endArrow
+ ></Line>
+ <Rect
+ direction={"row"}
+ ref={this.output}
+ justifyContent={"end"}
+ opacity={1}
+ ></Rect>
+ </Rect>
+ </Rect>
+ </Rect>,
+ );
+ }
+
+ 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 ?? (
+ <Txt fontFamily={theme.font} fontSize={30} fill={theme.text.hex}>
+ {args[i].val.toString()}
+ </Txt>
+ ),
+ );
+ });
+ 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(
+ <FunctionBox
+ opacity={0}
+ isChild={true}
+ ref={this.child}
+ fn={output}
+ ></FunctionBox>,
+ );
+ } else {
+ yield this.output().add(
+ <Txt fontFamily={theme.font} fontSize={30} fill={theme.text.hex}>
+ {output.toString()}
+ </Txt>,
+ );
+ }
+
+ 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),
+ );
+ }
+}