Skip to content

Commit

Permalink
ability to have separate rendered props in a graph (#220)
Browse files Browse the repository at this point in the history
* prop types that can become components via refs also expose profiles
* change `for_render` macros  for two render outputs
* separate rendered props for point in text
  • Loading branch information
dqnykamp authored Jul 31, 2024
1 parent 3333926 commit b1d95c7
Show file tree
Hide file tree
Showing 51 changed files with 1,007 additions and 251 deletions.
4 changes: 2 additions & 2 deletions packages/doenetml-prototype/src/renderers/doenet/boolean.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React from "react";
import { BasicComponent } from "../types";
import type { BooleanProps } from "@doenet/doenetml-worker-rust";
import type { BooleanPropsInText } from "@doenet/doenetml-worker-rust";

type BooleanData = { props: BooleanProps };
type BooleanData = { props: BooleanPropsInText };

export const Boolean: BasicComponent<BooleanData> = ({ node }) => {
return <span>{node.data.props.value.toString()}</span>;
Expand Down
4 changes: 2 additions & 2 deletions packages/doenetml-prototype/src/renderers/doenet/division.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React from "react";
import { BasicComponentWithPassthroughChildren } from "../types";
import { Element } from "../element";
import type { DivisionProps } from "@doenet/doenetml-worker-rust";
import type { DivisionPropsInText } from "@doenet/doenetml-worker-rust";
import { generateHtmlId } from "../utils";

export const Division: BasicComponentWithPassthroughChildren<{
props: DivisionProps;
props: DivisionPropsInText;
}> = ({ children, node, visibilityRef, annotation, ancestors }) => {
const htmlId = generateHtmlId(node, annotation, ancestors);
const titleElmId = node.data.props.title;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@ import React from "react";
import { BasicComponent } from "../types";
import { GraphContext, LAYER_OFFSETS } from "./graph";
import * as JSG from "jsxgraph";
import { attachStandardGraphListeners } from "./jsxgraph/listeners";
import {
attachStandardGraphListeners,
GraphListeners,
removeStandardGraphListeners,
} from "./jsxgraph/listeners";

export const LineInGraph: BasicComponent = ({ node }) => {
const board = React.useContext(GraphContext);
const lineRef = React.useRef<JSG.Line | null>(null);
const lineListenersAttached = React.useRef<GraphListeners>({});

React.useEffect(() => {
if (!board) {
Expand Down Expand Up @@ -36,15 +41,11 @@ export const LineInGraph: BasicComponent = ({ node }) => {
return;
}

attachStandardGraphListeners(line);
// TODO: actually create the line listeners and actions
lineListenersAttached.current = attachStandardGraphListeners(line, {});

return () => {
line.off("drag");
line.off("down");
line.off("hit");
line.off("up");
line.off("keyfocusout");
line.off("keydown");
removeStandardGraphListeners(line, lineListenersAttached.current);
board.removeObject(line);
};
}, [board, lineRef]);
Expand Down
172 changes: 172 additions & 0 deletions packages/doenetml-prototype/src/renderers/doenet/graph-point.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import React from "react";
import { BasicComponent } from "../types";
import { GraphContext, LAYER_OFFSETS } from "./graph";
import * as JSG from "jsxgraph";
import {
attachStandardGraphListeners,
GraphListenerActions,
GraphListeners,
removeStandardGraphListeners,
} from "./jsxgraph/listeners";
import { Action, PointPropsInGraph } from "@doenet/doenetml-worker-rust";
import { useAppDispatch } from "../../state/hooks";
import { coreActions } from "../../state/redux-slices/core";
import { numberFromSerializedAst } from "../../utils/math/math-expression-utils";

type PointData = { props: PointPropsInGraph };

export const PointInGraph: BasicComponent<PointData> = ({ node }) => {
const board = React.useContext(GraphContext);
const pointRef = React.useRef<JSG.Point | null>(null);
const pointListenersActions = React.useRef<GraphListenerActions>({});
const pointListenersAttached = React.useRef<GraphListeners>({});
const hadNonNumericCoords = React.useRef(false);
const id = node.data.id;

const dispatch = useAppDispatch();

const x: number = numberFromSerializedAst(node.data.props.x.math_object);
const y: number = numberFromSerializedAst(node.data.props.y.math_object);

React.useEffect(() => {
if (!board) {
pointRef.current = null;
return;
}
if (pointRef.current) {
return;
}
const point = createPoint(board, {
coords: [x, y],
labelForGraph: "test",
lineColor: "var(--mainPurple)",
hidden: false,
fixed: false,
draggable: true,
fixLocation: false,
layer: 0,
selectedStyle: { lineStyle: "solid", lineOpacity: 1, lineWidth: 2 },
dashed: false,
});
pointRef.current = point;
if (!point) {
return;
}

// TODO: call actions when point moves

pointListenersActions.current.drag = function (e, interactionState) {
let action: Action = {
component: "point",
actionName: "move",
componentIdx: id,
args: { x: pointRef.current!.X(), y: pointRef.current!.Y() },
};
dispatch(coreActions.dispatchAction(action));
};

pointListenersAttached.current = attachStandardGraphListeners(
point,
pointListenersActions.current,
);

return () => {
removeStandardGraphListeners(point, pointListenersAttached.current);
board.removeObject(point);
};
}, [board, pointRef]);

if (!board || !pointRef.current) {
return null;
}

// We have a pre-existing point. Update the rendered point so that it matches values from the props.

if (pointRef.current.hasLabel) {
// the the point has a label, need to update it so that it moves if the point moves
pointRef.current.label!.needsUpdate = true;
pointRef.current.label!.update();
}

// move the point to the current location determined by the props
pointRef.current.coords.setCoordinates(JXG.COORDS_BY_USER, [1, x, y]);

// update the point and the board so the point actually moves to the specified location
pointRef.current.needsUpdate = true;
// if the point previous had non-numeric coordinates,
// it appears that the point requires a fullUpdate to get it to reappear.
if (hadNonNumericCoords.current) {
//@ts-ignore
pointRef.current.fullUpdate();
} else {
pointRef.current.update();
}

// record for next time whether or not we have non-numeric coordinates
hadNonNumericCoords.current = !Number.isFinite(x) || !Number.isFinite(y);

board.updateRenderer();

return null;
};

function createPoint(
board: JSG.Board,
props: {
coords: [number, number];
labelForGraph: string;
lineColor: string;
hidden: boolean;
fixed: boolean;
draggable: boolean;
fixLocation: boolean;
layer: number;
selectedStyle: {
lineStyle: string;
lineOpacity: number;
lineWidth: number;
};
dashed: boolean;
},
) {
const lineColor = props.lineColor;

// Things to be passed to JSXGraph as attributes
const jsxPointAttributes: JSG.PointAttributes = {
name: props.labelForGraph,
visible: !props.hidden,
fixed: props.fixed,
layer: 10 * props.layer + LAYER_OFFSETS.line,
strokeColor: lineColor,
strokeOpacity: props.selectedStyle.lineOpacity,
highlightStrokeColor: lineColor,
highlightStrokeOpacity: props.selectedStyle.lineOpacity * 0.5,
strokeWidth: props.selectedStyle.lineWidth,
highlightStrokeWidth: props.selectedStyle.lineWidth,
dash: styleToDash(props.selectedStyle.lineStyle, props.dashed),
highlight: !props.fixLocation,
};

const point: JSG.Point = board.create(
"point",
props.coords,
jsxPointAttributes,
);

return point;
}

/**
* Return the the dash length for a given style.
*/
function styleToDash(style: string, dash: boolean) {
if (style === "dashed" || dash) {
return 2;
} else if (style === "solid") {
return 0;
} else if (style === "dotted") {
return 1;
} else {
return 0;
}
}
4 changes: 2 additions & 2 deletions packages/doenetml-prototype/src/renderers/doenet/graph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { BasicComponent } from "../types";
import "./graph.css";
import { Toolbar, ToolbarItem } from "@ariakit/react";
import { Element } from "../element";
import { Action, GraphProps } from "@doenet/doenetml-worker-rust";
import { Action, GraphPropsInText } from "@doenet/doenetml-worker-rust";
import { useAppDispatch } from "../../state/hooks";
import { coreActions } from "../../state/redux-slices/core";
import { arrayEq } from "../../utils/array";
Expand All @@ -40,7 +40,7 @@ export const LAYER_OFFSETS = {
text: 6,
};

type GraphData = { props: GraphProps };
type GraphData = { props: GraphPropsInText };
type BoundingBox = [x1: number, y1: number, x2: number, y2: number];

export const Graph: BasicComponent<GraphData> = ({ node }) => {
Expand Down
4 changes: 2 additions & 2 deletions packages/doenetml-prototype/src/renderers/doenet/li.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React from "react";
import { BasicComponentWithPassthroughChildren } from "../types";
import type { LiProps } from "@doenet/doenetml-worker-rust";
import type { LiPropsInText } from "@doenet/doenetml-worker-rust";
import "./li.css";
import { generateHtmlId } from "../utils";

export const Li: BasicComponentWithPassthroughChildren<{
props: LiProps;
props: LiPropsInText;
}> = ({ children, node, annotation, ancestors }) => {
const htmlId = generateHtmlId(node, annotation, ancestors);
const label = node.data.props.label;
Expand Down
3 changes: 2 additions & 1 deletion packages/doenetml-prototype/src/renderers/doenet/math.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import { MathJax } from "better-react-mathjax";
import { BasicComponent } from "../types";
import { useAppSelector } from "../../state/hooks";
import { renderingOnServerSelector } from "../../state/redux-slices/global";
import { MathPropsInText } from "@doenet/doenetml-worker-rust";

type MathData = { props: { latex: string } };
type MathData = { props: MathPropsInText };

export const Math: BasicComponent<MathData> = ({ node }) => {
const onServer = useAppSelector(renderingOnServerSelector);
Expand Down
4 changes: 2 additions & 2 deletions packages/doenetml-prototype/src/renderers/doenet/number.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React from "react";
import { BasicComponent } from "../types";
import type { NumberProps } from "@doenet/doenetml-worker-rust";
import type { NumberPropsInText } from "@doenet/doenetml-worker-rust";

type NumberData = { props: NumberProps };
type NumberData = { props: NumberPropsInText };

export const Number: BasicComponent<NumberData> = ({ node }) => {
return <span>{node.data.props.text}</span>;
Expand Down
4 changes: 2 additions & 2 deletions packages/doenetml-prototype/src/renderers/doenet/ol.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React from "react";
import { BasicComponentWithPassthroughChildren } from "../types";
import type { OlProps } from "@doenet/doenetml-worker-rust";
import type { OlPropsInText } from "@doenet/doenetml-worker-rust";

export const Ol: BasicComponentWithPassthroughChildren<{
props: OlProps;
props: OlPropsInText;
}> = ({ children, node }) => {
return <ol>{children}</ol>;
};
8 changes: 4 additions & 4 deletions packages/doenetml-prototype/src/renderers/doenet/p.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React from "react";
import { BasicComponentWithPassthroughChildren } from "../types";
import type { PProps } from "@doenet/doenetml-worker-rust";
import type { PPropsInText } from "@doenet/doenetml-worker-rust";

export const P: BasicComponentWithPassthroughChildren<{ props: PProps }> = ({
children,
}) => {
export const P: BasicComponentWithPassthroughChildren<{
props: PPropsInText;
}> = ({ children }) => {
return <div className="para">{children}</div>;
};
4 changes: 2 additions & 2 deletions packages/doenetml-prototype/src/renderers/doenet/point.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import {
GraphListeners,
removeStandardGraphListeners,
} from "./jsxgraph/listeners";
import { Action, PointProps } from "@doenet/doenetml-worker-rust";
import { Action, PointPropsInGraph } from "@doenet/doenetml-worker-rust";
import { useAppDispatch } from "../../state/hooks";
import { coreActions } from "../../state/redux-slices/core";
import { numberFromSerializedAst } from "../../utils/math/math-expression-utils";

type PointData = { props: PointProps };
type PointData = { props: PointPropsInGraph };

export const PointInGraph: BasicComponent<PointData> = ({ node }) => {
const board = React.useContext(GraphContext);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import React from "react";
import type { Action, TextInputProps } from "@doenet/doenetml-worker-rust";
import type {
Action,
TextInputPropsInText,
} from "@doenet/doenetml-worker-rust";
import { BasicComponent } from "../types";
import { useAppDispatch, useAppSelector } from "../../state/hooks";
import { renderingOnServerSelector } from "../../state/redux-slices/global";
import "./text-input.css";
import { coreActions } from "../../state/redux-slices/core";

type TextInputData = { props: TextInputProps };
type TextInputData = { props: TextInputPropsInText };

export const TextInput: BasicComponent<TextInputData> = ({ node }) => {
const onServer = useAppSelector(renderingOnServerSelector);
Expand Down
25 changes: 25 additions & 0 deletions packages/doenetml-prototype/src/renderers/doenet/text-point.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from "react";
import { MathJax } from "better-react-mathjax";
import { BasicComponent } from "../types";
import { useAppSelector } from "../../state/hooks";
import { renderingOnServerSelector } from "../../state/redux-slices/global";
import { PointPropsInText } from "@doenet/doenetml-worker-rust";

type PointData = { props: PointPropsInText };

export const PointInText: BasicComponent<PointData> = ({ node }) => {
const onServer = useAppSelector(renderingOnServerSelector);
if (onServer) {
return (
<span className="process-math">{node.data.props.coordsLatex}</span>
);
}
// better-react-mathjax cannot handle multiple children (it will not update when they change)
// so create a single string.
const latexString = `\\(${node.data.props.coordsLatex}\\)`;
return (
<MathJax inline dynamic>
{latexString}
</MathJax>
);
};
4 changes: 2 additions & 2 deletions packages/doenetml-prototype/src/renderers/doenet/text.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React from "react";
import { BasicComponent } from "../types";
import type { TextProps } from "@doenet/doenetml-worker-rust";
import type { TextPropsInText } from "@doenet/doenetml-worker-rust";

type TextData = { props: TextProps };
type TextData = { props: TextPropsInText };

export const Text: BasicComponent<TextData> = ({ node }) => {
return <span>{node.data.props.value}</span>;
Expand Down
4 changes: 2 additions & 2 deletions packages/doenetml-prototype/src/renderers/doenet/ul.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React from "react";
import { BasicComponentWithPassthroughChildren } from "../types";
import type { UlProps } from "@doenet/doenetml-worker-rust";
import type { UlPropsInText } from "@doenet/doenetml-worker-rust";

export const Ul: BasicComponentWithPassthroughChildren<{
props: UlProps;
props: UlPropsInText;
}> = ({ children, node }) => {
return <ul>{children}</ul>;
};
Loading

0 comments on commit b1d95c7

Please sign in to comment.