Various components cleanups
- coding style (let -> const, etc.) - avoid double typing of component props, it's not necessary - consistent non-default exports everywhere (except for SquigglePlayground since I'm not 100% sure it's not used somewhere else) - extract common components in SquigglePlayground to avoid copy-paste - other minor improvements
This commit is contained in:
parent
9b0def16ef
commit
958c187e82
|
@ -10,23 +10,32 @@ export const Alert: React.FC<{
|
|||
backgroundColor: string;
|
||||
headingColor: string;
|
||||
bodyColor: string;
|
||||
icon: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
icon: (props: React.ComponentProps<"svg">) => JSX.Element;
|
||||
iconColor: string;
|
||||
children?: React.ReactNode;
|
||||
}> = ({
|
||||
heading = "Error",
|
||||
backgroundColor,
|
||||
headingColor,
|
||||
bodyColor,
|
||||
icon,
|
||||
icon: Icon,
|
||||
iconColor,
|
||||
children,
|
||||
}) => {
|
||||
return (
|
||||
<div className={`rounded-md p-4 ${backgroundColor}`}>
|
||||
<div className="flex">
|
||||
<div className="flex-shrink-0">{icon}</div>
|
||||
<Icon
|
||||
className={`h-5 w-5 flex-shrink-0 ${iconColor}`}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div className="ml-3">
|
||||
<h3 className={`text-sm font-medium ${headingColor}`}>{heading}</h3>
|
||||
<header className={`text-sm font-medium ${headingColor}`}>
|
||||
{heading}
|
||||
</header>
|
||||
{children && React.Children.count(children) ? (
|
||||
<div className={`mt-2 text-sm ${bodyColor}`}>{children}</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -35,49 +44,42 @@ export const Alert: React.FC<{
|
|||
|
||||
export const ErrorAlert: React.FC<{
|
||||
heading: string;
|
||||
children: React.ReactNode;
|
||||
}> = ({ heading = "Error", children }) => (
|
||||
children?: React.ReactNode;
|
||||
}> = (props) => (
|
||||
<Alert
|
||||
heading={heading}
|
||||
children={children}
|
||||
{...props}
|
||||
backgroundColor="bg-red-100"
|
||||
headingColor="text-red-800"
|
||||
bodyColor="text-red-700"
|
||||
icon={<XCircleIcon className="h-5 w-5 text-red-400" aria-hidden="true" />}
|
||||
icon={XCircleIcon}
|
||||
iconColor="text-red-400"
|
||||
/>
|
||||
);
|
||||
|
||||
export const MessageAlert: React.FC<{
|
||||
heading: string;
|
||||
children: React.ReactNode;
|
||||
}> = ({ heading = "Error", children }) => (
|
||||
children?: React.ReactNode;
|
||||
}> = (props) => (
|
||||
<Alert
|
||||
heading={heading}
|
||||
children={children}
|
||||
{...props}
|
||||
backgroundColor="bg-slate-100"
|
||||
headingColor="text-slate-700"
|
||||
bodyColor="text-slate-700"
|
||||
icon={
|
||||
<InformationCircleIcon
|
||||
className="h-5 w-5 text-slate-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
}
|
||||
icon={InformationCircleIcon}
|
||||
iconColor="text-slate-400"
|
||||
/>
|
||||
);
|
||||
|
||||
export const SuccessAlert: React.FC<{
|
||||
heading: string;
|
||||
children: React.ReactNode;
|
||||
}> = ({ heading = "Error", children }) => (
|
||||
children?: React.ReactNode;
|
||||
}> = (props) => (
|
||||
<Alert
|
||||
heading={heading}
|
||||
children={children}
|
||||
{...props}
|
||||
backgroundColor="bg-green-50"
|
||||
headingColor="text-green-800"
|
||||
bodyColor="text-green-700"
|
||||
icon={
|
||||
<CheckCircleIcon className="h-5 w-5 text-green-400" aria-hidden="true" />
|
||||
}
|
||||
icon={CheckCircleIcon}
|
||||
iconColor="text-green-400"
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -14,13 +14,13 @@ interface CodeEditorProps {
|
|||
showGutter?: boolean;
|
||||
}
|
||||
|
||||
export let CodeEditor: FC<CodeEditorProps> = ({
|
||||
export const CodeEditor: FC<CodeEditorProps> = ({
|
||||
value,
|
||||
onChange,
|
||||
oneLine = false,
|
||||
showGutter = false,
|
||||
height,
|
||||
}: CodeEditorProps) => {
|
||||
}) => {
|
||||
let lineCount = value.split("\n").length;
|
||||
let id = _.uniqueId();
|
||||
return (
|
||||
|
@ -48,4 +48,3 @@ export let CodeEditor: FC<CodeEditorProps> = ({
|
|||
/>
|
||||
);
|
||||
};
|
||||
export default CodeEditor;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import * as React from "react";
|
||||
import _ from "lodash";
|
||||
import {
|
||||
Distribution,
|
||||
result,
|
||||
|
@ -34,16 +33,24 @@ export const DistributionChart: React.FC<DistributionChartProps> = ({
|
|||
showSummary,
|
||||
width,
|
||||
showControls = false,
|
||||
}: DistributionChartProps) => {
|
||||
let [isLogX, setLogX] = React.useState(false);
|
||||
let [isExpY, setExpY] = React.useState(false);
|
||||
let shape = distribution.pointSet();
|
||||
const [sized, _] = useSize((size) => {
|
||||
if (shape.tag === "Ok") {
|
||||
let massBelow0 =
|
||||
}) => {
|
||||
const [isLogX, setLogX] = React.useState(false);
|
||||
const [isExpY, setExpY] = React.useState(false);
|
||||
const shape = distribution.pointSet();
|
||||
const [sized] = useSize((size) => {
|
||||
if (shape.tag === "Error") {
|
||||
return (
|
||||
<ErrorAlert heading="Distribution Error">
|
||||
{distributionErrorToString(shape.value)}
|
||||
</ErrorAlert>
|
||||
);
|
||||
}
|
||||
|
||||
const massBelow0 =
|
||||
shape.value.continuous.some((x) => x.x <= 0) ||
|
||||
shape.value.discrete.some((x) => x.x <= 0);
|
||||
let spec = buildVegaSpec(isLogX, isExpY);
|
||||
const spec = buildVegaSpec(isLogX, isExpY);
|
||||
|
||||
let widthProp = width ? width : size.width;
|
||||
if (widthProp < 20) {
|
||||
console.warn(
|
||||
|
@ -52,26 +59,8 @@ export const DistributionChart: React.FC<DistributionChartProps> = ({
|
|||
widthProp = 20;
|
||||
}
|
||||
|
||||
// Check whether we should disable the checkbox
|
||||
var logCheckbox = (
|
||||
<CheckBox label="Log X scale" value={isLogX} onChange={setLogX} />
|
||||
);
|
||||
if (massBelow0) {
|
||||
logCheckbox = (
|
||||
<CheckBox
|
||||
label="Log X scale"
|
||||
value={isLogX}
|
||||
onChange={setLogX}
|
||||
disabled={true}
|
||||
tooltip={
|
||||
"Your distribution has mass lower than or equal to 0. Log only works on strictly positive values."
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
var result = (
|
||||
<div style={{ width: widthProp + "px" }}>
|
||||
return (
|
||||
<div style={{ width: widthProp }}>
|
||||
<Vega
|
||||
spec={spec}
|
||||
data={{ con: shape.value.continuous, dis: shape.value.discrete }}
|
||||
|
@ -84,21 +73,24 @@ export const DistributionChart: React.FC<DistributionChartProps> = ({
|
|||
</div>
|
||||
{showControls && (
|
||||
<div>
|
||||
{logCheckbox}
|
||||
<CheckBox
|
||||
label="Log X scale"
|
||||
value={isLogX}
|
||||
onChange={setLogX}
|
||||
// Check whether we should disable the checkbox
|
||||
{...(massBelow0
|
||||
? {
|
||||
disabled: true,
|
||||
tooltip:
|
||||
"Your distribution has mass lower than or equal to 0. Log only works on strictly positive values.",
|
||||
}
|
||||
: {})}
|
||||
/>
|
||||
<CheckBox label="Exp Y scale" value={isExpY} onChange={setExpY} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
var result = (
|
||||
<ErrorAlert heading="Distribution Error">
|
||||
{distributionErrorToString(shape.value)}
|
||||
</ErrorAlert>
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
return sized;
|
||||
};
|
||||
|
@ -121,13 +113,13 @@ interface CheckBoxProps {
|
|||
tooltip?: string;
|
||||
}
|
||||
|
||||
export const CheckBox = ({
|
||||
export const CheckBox: React.FC<CheckBoxProps> = ({
|
||||
label,
|
||||
onChange,
|
||||
value,
|
||||
disabled = false,
|
||||
tooltip,
|
||||
}: CheckBoxProps) => {
|
||||
}) => {
|
||||
return (
|
||||
<span title={tooltip}>
|
||||
<input
|
||||
|
@ -141,22 +133,35 @@ export const CheckBox = ({
|
|||
);
|
||||
};
|
||||
|
||||
const TableHeadCell: React.FC<{ children: React.ReactNode }> = ({
|
||||
children,
|
||||
}) => (
|
||||
<th className="border border-slate-200 bg-slate-50 py-1 px-2 text-slate-500 font-semibold">
|
||||
{children}
|
||||
</th>
|
||||
);
|
||||
|
||||
const Cell: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
||||
<td className="border border-slate-200 py-1 px-2 text-slate-900">
|
||||
{children}
|
||||
</td>
|
||||
);
|
||||
|
||||
type SummaryTableProps = {
|
||||
distribution: Distribution;
|
||||
};
|
||||
|
||||
const SummaryTable: React.FC<SummaryTableProps> = ({
|
||||
distribution,
|
||||
}: SummaryTableProps) => {
|
||||
let mean = distribution.mean();
|
||||
let p5 = distribution.inv(0.05);
|
||||
let p10 = distribution.inv(0.1);
|
||||
let p25 = distribution.inv(0.25);
|
||||
let p50 = distribution.inv(0.5);
|
||||
let p75 = distribution.inv(0.75);
|
||||
let p90 = distribution.inv(0.9);
|
||||
let p95 = distribution.inv(0.95);
|
||||
let unwrapResult = (
|
||||
const SummaryTable: React.FC<SummaryTableProps> = ({ distribution }) => {
|
||||
const mean = distribution.mean();
|
||||
const p5 = distribution.inv(0.05);
|
||||
const p10 = distribution.inv(0.1);
|
||||
const p25 = distribution.inv(0.25);
|
||||
const p50 = distribution.inv(0.5);
|
||||
const p75 = distribution.inv(0.75);
|
||||
const p90 = distribution.inv(0.9);
|
||||
const p95 = distribution.inv(0.95);
|
||||
|
||||
const unwrapResult = (
|
||||
x: result<number, distributionError>
|
||||
): React.ReactNode => {
|
||||
if (x.tag === "Ok") {
|
||||
|
@ -170,19 +175,6 @@ const SummaryTable: React.FC<SummaryTableProps> = ({
|
|||
}
|
||||
};
|
||||
|
||||
let TableHeadCell: React.FC<{ children: React.ReactNode }> = ({
|
||||
children,
|
||||
}) => (
|
||||
<th className="border border-slate-200 bg-slate-50 py-1 px-2 text-slate-500 font-semibold">
|
||||
{children}
|
||||
</th>
|
||||
);
|
||||
let Cell: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
||||
<td className="border border-slate-200 py-1 px-2 text-slate-900 ">
|
||||
{children}
|
||||
</td>
|
||||
);
|
||||
|
||||
return (
|
||||
<table className="border border-collapse border-slate-400">
|
||||
<thead className="bg-slate-50">
|
||||
|
|
|
@ -22,7 +22,7 @@ export const FunctionChart: React.FC<FunctionChartProps> = ({
|
|||
chartSettings,
|
||||
environment,
|
||||
height,
|
||||
}: FunctionChartProps) => {
|
||||
}) => {
|
||||
if (fn.parameters.length > 1) {
|
||||
return (
|
||||
<MessageAlert heading="Function Display Not Supported">
|
||||
|
|
|
@ -151,7 +151,7 @@ export const FunctionChart1Dist: React.FC<FunctionChart1DistProps> = ({
|
|||
chartSettings,
|
||||
environment,
|
||||
height,
|
||||
}: FunctionChart1DistProps) => {
|
||||
}) => {
|
||||
let [mouseOverlay, setMouseOverlay] = React.useState(0);
|
||||
function handleHover(_name: string, value: unknown) {
|
||||
setMouseOverlay(value as number);
|
||||
|
@ -170,16 +170,14 @@ export const FunctionChart1Dist: React.FC<FunctionChart1DistProps> = ({
|
|||
},
|
||||
};
|
||||
let showChart =
|
||||
mouseItem.tag === "Ok" && mouseItem.value.tag == "distribution" ? (
|
||||
mouseItem.tag === "Ok" && mouseItem.value.tag === "distribution" ? (
|
||||
<DistributionChart
|
||||
distribution={mouseItem.value.value}
|
||||
width={400}
|
||||
height={50}
|
||||
showSummary={false}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
) : null;
|
||||
|
||||
let getPercentilesMemoized = React.useMemo(
|
||||
() => getPercentiles({ chartSettings, fn, environment }),
|
||||
|
|
|
@ -14,15 +14,15 @@ interface CodeEditorProps {
|
|||
showGutter?: boolean;
|
||||
}
|
||||
|
||||
export let JsonEditor: FC<CodeEditorProps> = ({
|
||||
export const JsonEditor: FC<CodeEditorProps> = ({
|
||||
value,
|
||||
onChange,
|
||||
oneLine = false,
|
||||
showGutter = false,
|
||||
height,
|
||||
}: CodeEditorProps) => {
|
||||
let lineCount = value.split("\n").length;
|
||||
let id = _.uniqueId();
|
||||
}) => {
|
||||
const lineCount = value.split("\n").length;
|
||||
const id = _.uniqueId();
|
||||
return (
|
||||
<AceEditor
|
||||
value={value}
|
||||
|
@ -47,5 +47,3 @@ export let JsonEditor: FC<CodeEditorProps> = ({
|
|||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default JsonEditor;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import * as React from "react";
|
||||
import _ from "lodash";
|
||||
|
||||
const orderOfMagnitudeNum = (n: number) => {
|
||||
return Math.pow(10, n);
|
||||
|
@ -74,25 +73,23 @@ export interface NumberShowerProps {
|
|||
precision?: number;
|
||||
}
|
||||
|
||||
export let NumberShower: React.FC<NumberShowerProps> = ({
|
||||
export const NumberShower: React.FC<NumberShowerProps> = ({
|
||||
number,
|
||||
precision = 2,
|
||||
}: NumberShowerProps) => {
|
||||
let numberWithPresentation = numberShow(number, precision);
|
||||
}) => {
|
||||
const numberWithPresentation = numberShow(number, precision);
|
||||
return (
|
||||
<span>
|
||||
{numberWithPresentation.value}
|
||||
{numberWithPresentation.symbol}
|
||||
{numberWithPresentation.power ? (
|
||||
<span>
|
||||
{"\u00b710"}
|
||||
{"\u00b7" /* dot symbol */}10
|
||||
<span style={{ fontSize: "0.6em", verticalAlign: "super" }}>
|
||||
{numberWithPresentation.power}
|
||||
</span>
|
||||
</span>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
) : null}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import * as React from "react";
|
||||
import _ from "lodash";
|
||||
import {
|
||||
run,
|
||||
errorValueToString,
|
||||
|
@ -28,6 +27,7 @@ function getRange<a>(x: declaration<a>) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getChartSettings<a>(x: declaration<a>): FunctionChartSettings {
|
||||
let range = getRange(x);
|
||||
let min = range.floats ? range.floats.min : 0;
|
||||
|
@ -49,12 +49,12 @@ export const VariableBox: React.FC<VariableBoxProps> = ({
|
|||
heading = "Error",
|
||||
children,
|
||||
showTypes = false,
|
||||
}: VariableBoxProps) => {
|
||||
}) => {
|
||||
if (showTypes) {
|
||||
return (
|
||||
<div className="bg-white border border-grey-200 m-2">
|
||||
<div className="border-b border-grey-200 p-3">
|
||||
<h3 className="font-mono">{heading}</h3>
|
||||
<header className="font-mono">{heading}</header>
|
||||
</div>
|
||||
<div className="p-3">{children}</div>
|
||||
</div>
|
||||
|
@ -90,7 +90,7 @@ const SquiggleItem: React.FC<SquiggleItemProps> = ({
|
|||
showControls = false,
|
||||
chartSettings,
|
||||
environment,
|
||||
}: SquiggleItemProps) => {
|
||||
}) => {
|
||||
switch (expression.tag) {
|
||||
case "number":
|
||||
return (
|
||||
|
@ -108,12 +108,8 @@ const SquiggleItem: React.FC<SquiggleItemProps> = ({
|
|||
showTypes={showTypes}
|
||||
>
|
||||
{distType === "Symbolic" && showTypes ? (
|
||||
<>
|
||||
<div>{expression.value.toString()}</div>
|
||||
</>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
) : null}
|
||||
<DistributionChart
|
||||
distribution={expression.value}
|
||||
height={height}
|
||||
|
@ -157,11 +153,11 @@ const SquiggleItem: React.FC<SquiggleItemProps> = ({
|
|||
return (
|
||||
<VariableBox heading="Array" showTypes={showTypes}>
|
||||
{expression.value.map((r, i) => (
|
||||
<div key={i} className="flex flex-row pt-1">
|
||||
<div key={i} className="flex pt-1">
|
||||
<div className="flex-none bg-slate-100 rounded-sm px-1">
|
||||
<h3 className="text-slate-400 font-mono">{i}</h3>
|
||||
<header className="text-slate-400 font-mono">{i}</header>
|
||||
</div>
|
||||
<div className="px-2 mb-2 grow ">
|
||||
<div className="px-2 mb-2 grow">
|
||||
<SquiggleItem
|
||||
key={i}
|
||||
expression={r}
|
||||
|
@ -181,12 +177,13 @@ const SquiggleItem: React.FC<SquiggleItemProps> = ({
|
|||
case "record":
|
||||
return (
|
||||
<VariableBox heading="Record" showTypes={showTypes}>
|
||||
<div className="space-y-3">
|
||||
{Object.entries(expression.value).map(([key, r]) => (
|
||||
<div key={key} className="flex flex-row pt-1">
|
||||
<div className="flex-none pr-2">
|
||||
<h3 className="text-slate-500 font-mono">{key}:</h3>
|
||||
<div key={key} className="flex space-x-2">
|
||||
<div className="flex-none">
|
||||
<header className="text-slate-500 font-mono">{key}:</header>
|
||||
</div>
|
||||
<div className="pl-2 pr-2 mb-2 grow bg-gray-50 border border-gray-100 rounded-sm">
|
||||
<div className="px-2 grow bg-gray-50 border border-gray-100 rounded-sm">
|
||||
<SquiggleItem
|
||||
expression={r}
|
||||
width={width !== undefined ? width - 20 : width}
|
||||
|
@ -200,6 +197,7 @@ const SquiggleItem: React.FC<SquiggleItemProps> = ({
|
|||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</VariableBox>
|
||||
);
|
||||
case "arraystring":
|
||||
|
@ -285,7 +283,7 @@ export interface SquiggleChartProps {
|
|||
showControls?: boolean;
|
||||
}
|
||||
|
||||
let defaultChartSettings = { start: 0, stop: 10, count: 20 };
|
||||
const defaultChartSettings = { start: 0, stop: 10, count: 20 };
|
||||
|
||||
export const SquiggleChart: React.FC<SquiggleChartProps> = ({
|
||||
squiggleString = "",
|
||||
|
@ -299,14 +297,20 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = ({
|
|||
showTypes = false,
|
||||
showControls = false,
|
||||
chartSettings = defaultChartSettings,
|
||||
}: SquiggleChartProps) => {
|
||||
}) => {
|
||||
let expressionResult = run(squiggleString, bindings, environment, jsImports);
|
||||
let e = environment ? environment : defaultEnvironment;
|
||||
let internal: JSX.Element;
|
||||
if (expressionResult.tag === "Ok") {
|
||||
if (expressionResult.tag !== "Ok") {
|
||||
return (
|
||||
<ErrorAlert heading={"Parse Error"}>
|
||||
{errorValueToString(expressionResult.value)}
|
||||
</ErrorAlert>
|
||||
);
|
||||
}
|
||||
|
||||
let e = environment ?? defaultEnvironment;
|
||||
let expression = expressionResult.value;
|
||||
onChange(expression);
|
||||
internal = (
|
||||
return (
|
||||
<SquiggleItem
|
||||
expression={expression}
|
||||
width={width}
|
||||
|
@ -318,12 +322,4 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = ({
|
|||
environment={e}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
internal = (
|
||||
<ErrorAlert heading={"Parse Error"}>
|
||||
{errorValueToString(expressionResult.value)}
|
||||
</ErrorAlert>
|
||||
);
|
||||
}
|
||||
return internal;
|
||||
};
|
||||
|
|
|
@ -57,8 +57,8 @@ export let SquiggleEditor: React.FC<SquiggleEditorProps> = ({
|
|||
showControls = false,
|
||||
showSummary = false,
|
||||
}: SquiggleEditorProps) => {
|
||||
let [expression, setExpression] = React.useState(initialSquiggleString);
|
||||
let chartSettings = {
|
||||
const [expression, setExpression] = React.useState(initialSquiggleString);
|
||||
const chartSettings = {
|
||||
start: diagramStart,
|
||||
stop: diagramStop,
|
||||
count: diagramCount,
|
||||
|
@ -150,17 +150,17 @@ export let SquigglePartial: React.FC<SquigglePartialProps> = ({
|
|||
environment,
|
||||
jsImports = defaultImports,
|
||||
}: SquigglePartialProps) => {
|
||||
let [expression, setExpression] = React.useState(initialSquiggleString);
|
||||
let [error, setError] = React.useState<string | null>(null);
|
||||
const [expression, setExpression] = React.useState(initialSquiggleString);
|
||||
const [error, setError] = React.useState<string | null>(null);
|
||||
|
||||
let runSquiggleAndUpdateBindings = () => {
|
||||
let squiggleResult = runPartial(
|
||||
const runSquiggleAndUpdateBindings = () => {
|
||||
const squiggleResult = runPartial(
|
||||
expression,
|
||||
bindings,
|
||||
environment,
|
||||
jsImports
|
||||
);
|
||||
if (squiggleResult.tag == "Ok") {
|
||||
if (squiggleResult.tag === "Ok") {
|
||||
if (onChange) onChange(squiggleResult.value);
|
||||
setError(null);
|
||||
} else {
|
||||
|
@ -181,11 +181,7 @@ export let SquigglePartial: React.FC<SquigglePartialProps> = ({
|
|||
height={20}
|
||||
/>
|
||||
</div>
|
||||
{error !== null ? (
|
||||
<ErrorAlert heading="Error">{error}</ErrorAlert>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
{error !== null ? <ErrorAlert heading="Error">{error}</ErrorAlert> : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
import _ from "lodash";
|
||||
import React, { FC, ReactElement, useState } from "react";
|
||||
import React, { FC, Fragment, useState } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { SquiggleChart } from "./SquiggleChart";
|
||||
import CodeEditor from "./CodeEditor";
|
||||
import JsonEditor from "./JsonEditor";
|
||||
import { useForm, useWatch } from "react-hook-form";
|
||||
import { Path, useForm, UseFormRegister, useWatch } from "react-hook-form";
|
||||
import * as yup from "yup";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { defaultBindings, environment } from "@quri/squiggle-lang";
|
||||
import { Tab } from "@headlessui/react";
|
||||
import { CodeIcon } from "@heroicons/react/solid";
|
||||
import { CogIcon } from "@heroicons/react/solid";
|
||||
import { ChartSquareBarIcon } from "@heroicons/react/solid";
|
||||
import { CurrencyDollarIcon } from "@heroicons/react/solid";
|
||||
import { Fragment } from "react";
|
||||
import {
|
||||
ChartSquareBarIcon,
|
||||
CodeIcon,
|
||||
CogIcon,
|
||||
CurrencyDollarIcon,
|
||||
} from "@heroicons/react/solid";
|
||||
|
||||
import { defaultBindings, environment } from "@quri/squiggle-lang";
|
||||
|
||||
import { SquiggleChart } from "./SquiggleChart";
|
||||
import { CodeEditor } from "./CodeEditor";
|
||||
import { JsonEditor } from "./JsonEditor";
|
||||
import { ErrorAlert, SuccessAlert } from "./Alert";
|
||||
|
||||
interface PlaygroundProps {
|
||||
|
@ -85,49 +87,20 @@ const schema = yup
|
|||
})
|
||||
.required();
|
||||
|
||||
type InputProps = {
|
||||
label: string;
|
||||
children: ReactElement;
|
||||
};
|
||||
|
||||
const InputItem: React.FC<InputProps> = ({ label, children }) => (
|
||||
<div className="col-span-4">
|
||||
<label className="block text-sm font-medium text-gray-600">{label}</label>
|
||||
<div className="mt-1">{children}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
let numberStyle =
|
||||
"max-w-lg block w-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:max-w-xs sm:text-sm border-gray-300 rounded-md";
|
||||
|
||||
function classNames(...classes: string[]) {
|
||||
return classes.filter(Boolean).join(" ");
|
||||
}
|
||||
|
||||
type StyledTabProps = {
|
||||
name: string;
|
||||
iconName: string;
|
||||
icon: (props: React.ComponentProps<"svg">) => JSX.Element;
|
||||
};
|
||||
|
||||
const StyledTab: React.FC<StyledTabProps> = ({ name, iconName }) => {
|
||||
let iconStyle = (isSelected: boolean) =>
|
||||
classNames(
|
||||
"-ml-0.5 mr-2 h-4 w-4 ",
|
||||
isSelected ? "text-slate-500" : "text-gray-400 group-hover:text-gray-900"
|
||||
);
|
||||
|
||||
let icon = (selected: boolean) =>
|
||||
({
|
||||
code: <CodeIcon className={iconStyle(selected)} />,
|
||||
cog: <CogIcon className={iconStyle(selected)} />,
|
||||
squareBar: <ChartSquareBarIcon className={iconStyle(selected)} />,
|
||||
dollar: <CurrencyDollarIcon className={iconStyle(selected)} />,
|
||||
}[iconName]);
|
||||
|
||||
const StyledTab: React.FC<StyledTabProps> = ({ name, icon: Icon }) => {
|
||||
return (
|
||||
<Tab key={name} as={Fragment}>
|
||||
{({ selected }) => (
|
||||
<button className="flex rounded-md focus:outline-none focus-visible:ring-offset-gray-100 ">
|
||||
<button className="group flex rounded-md focus:outline-none focus-visible:ring-offset-gray-100">
|
||||
<span
|
||||
className={classNames(
|
||||
"p-1 pl-2.5 pr-3.5 rounded-md flex items-center text-sm font-medium",
|
||||
|
@ -136,7 +109,14 @@ const StyledTab: React.FC<StyledTabProps> = ({ name, iconName }) => {
|
|||
: ""
|
||||
)}
|
||||
>
|
||||
{icon(selected)}
|
||||
<Icon
|
||||
className={classNames(
|
||||
"-ml-0.5 mr-2 h-4 w-4",
|
||||
selected
|
||||
? "text-slate-500"
|
||||
: "text-gray-400 group-hover:text-gray-900"
|
||||
)}
|
||||
/>
|
||||
<span
|
||||
className={
|
||||
selected
|
||||
|
@ -153,17 +133,77 @@ const StyledTab: React.FC<StyledTabProps> = ({ name, iconName }) => {
|
|||
);
|
||||
};
|
||||
|
||||
let SquigglePlayground: FC<PlaygroundProps> = ({
|
||||
const HeadedSection: FC<{ title: string; children: React.ReactNode }> = ({
|
||||
title,
|
||||
children,
|
||||
}) => (
|
||||
<div>
|
||||
<header className="text-lg leading-6 font-medium text-gray-900">
|
||||
{title}
|
||||
</header>
|
||||
<div className="mt-4">{children}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const Text: FC<{ children: React.ReactNode }> = ({ children }) => (
|
||||
<p className="text-sm text-gray-500">{children}</p>
|
||||
);
|
||||
|
||||
function InputItem<T>({
|
||||
name,
|
||||
label,
|
||||
type,
|
||||
register,
|
||||
}: {
|
||||
name: Path<T>;
|
||||
label: string;
|
||||
type: "number";
|
||||
register: UseFormRegister<T>;
|
||||
}) {
|
||||
const numberStyle =
|
||||
"max-w-lg block w-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:max-w-xs sm:text-sm border-gray-300 rounded-md";
|
||||
|
||||
return (
|
||||
<label className="block">
|
||||
<div className="text-sm font-medium text-gray-600 mb-1">{label}</div>
|
||||
<input type={type} {...register(name)} className={numberStyle} />
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
||||
function Checkbox<T>({
|
||||
name,
|
||||
label,
|
||||
register,
|
||||
}: {
|
||||
name: Path<T>;
|
||||
label: string;
|
||||
register: UseFormRegister<T>;
|
||||
}) {
|
||||
return (
|
||||
<label className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
{...register(name)}
|
||||
className="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
|
||||
/>
|
||||
{/* Clicking on the div makes the checkbox lose focus while mouse button is pressed, leading to annoying blinking; I couldn't figure out how to fix this. */}
|
||||
<div className="ml-3 text-sm font-medium text-gray-700">{label}</div>
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
||||
const SquigglePlayground: FC<PlaygroundProps> = ({
|
||||
initialSquiggleString = "",
|
||||
height = 500,
|
||||
showTypes = false,
|
||||
showControls = false,
|
||||
showSummary = false,
|
||||
}: PlaygroundProps) => {
|
||||
let [squiggleString, setSquiggleString] = useState(initialSquiggleString);
|
||||
let [importString, setImportString] = useState("{}");
|
||||
let [imports, setImports] = useState({});
|
||||
let [importsAreValid, setImportsAreValid] = useState(true);
|
||||
}) => {
|
||||
const [squiggleString, setSquiggleString] = useState(initialSquiggleString);
|
||||
const [importString, setImportString] = useState("{}");
|
||||
const [imports, setImports] = useState({});
|
||||
const [importsAreValid, setImportsAreValid] = useState(true);
|
||||
const { register, control } = useForm({
|
||||
resolver: yupResolver(schema),
|
||||
defaultValues: {
|
||||
|
@ -183,16 +223,16 @@ let SquigglePlayground: FC<PlaygroundProps> = ({
|
|||
const vars = useWatch({
|
||||
control,
|
||||
});
|
||||
let chartSettings = {
|
||||
const chartSettings = {
|
||||
start: Number(vars.diagramStart),
|
||||
stop: Number(vars.diagramStop),
|
||||
count: Number(vars.diagramCount),
|
||||
};
|
||||
let env: environment = {
|
||||
const env: environment = {
|
||||
sampleCount: Number(vars.sampleCount),
|
||||
xyPointLength: Number(vars.xyPointLength),
|
||||
};
|
||||
let getChangeJson = (r: string) => {
|
||||
const getChangeJson = (r: string) => {
|
||||
setImportString(r);
|
||||
try {
|
||||
setImports(JSON.parse(r));
|
||||
|
@ -202,149 +242,118 @@ let SquigglePlayground: FC<PlaygroundProps> = ({
|
|||
}
|
||||
};
|
||||
|
||||
let samplingSettings = (
|
||||
const samplingSettings = (
|
||||
<div className="space-y-6 p-3 max-w-xl">
|
||||
<InputItem label="Sample Count">
|
||||
<>
|
||||
<input
|
||||
<div>
|
||||
<InputItem
|
||||
name="sampleCount"
|
||||
type="number"
|
||||
{...register("sampleCount")}
|
||||
className={numberStyle}
|
||||
label="Sample Count"
|
||||
register={register}
|
||||
/>
|
||||
<p className="mt-2 text-sm text-gray-500">
|
||||
<div className="mt-2">
|
||||
<Text>
|
||||
How many samples to use for Monte Carlo simulations. This can
|
||||
occasionally be overridden by specific Squiggle programs.
|
||||
</p>
|
||||
</>
|
||||
</InputItem>
|
||||
<InputItem label="Coordinate Count (For PointSet Shapes)">
|
||||
<>
|
||||
<input
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<InputItem
|
||||
name="xyPointLength"
|
||||
type="number"
|
||||
{...register("xyPointLength")}
|
||||
className={numberStyle}
|
||||
register={register}
|
||||
label="Coordinate Count (For PointSet Shapes)"
|
||||
/>
|
||||
<p className="mt-2 text-sm text-gray-500">
|
||||
<div className="mt-2">
|
||||
<Text>
|
||||
When distributions are converted into PointSet shapes, we need to
|
||||
know how many coordinates to use.
|
||||
</p>
|
||||
</>
|
||||
</InputItem>
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
let viewSettings = (
|
||||
const viewSettings = (
|
||||
<div className="space-y-6 p-3 divide-y divide-gray-200 max-w-xl">
|
||||
<HeadedSection title="General Display Settings">
|
||||
<div className="space-y-4">
|
||||
<InputItem
|
||||
name="chartHeight"
|
||||
type="number"
|
||||
register={register}
|
||||
label="Chart Height (in pixels)"
|
||||
/>
|
||||
<Checkbox
|
||||
name="showTypes"
|
||||
register={register}
|
||||
label="Show information about displayed types"
|
||||
/>
|
||||
</div>
|
||||
</HeadedSection>
|
||||
|
||||
<div className="pt-8">
|
||||
<HeadedSection title="Distribution Display Settings">
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900 pb-2">
|
||||
General Display Settings
|
||||
</h3>
|
||||
<InputItem label="Chart Height (in pixels)">
|
||||
<input
|
||||
type="number"
|
||||
{...register("chartHeight")}
|
||||
className={numberStyle}
|
||||
<Checkbox
|
||||
register={register}
|
||||
name="showControls"
|
||||
label="Show toggles to adjust scale of x and y axes"
|
||||
/>
|
||||
</InputItem>
|
||||
<div className="relative flex items-start pt-3">
|
||||
<div className="flex items-center h-5">
|
||||
<input
|
||||
type="checkbox"
|
||||
{...register("showTypes")}
|
||||
className="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
|
||||
<Checkbox
|
||||
register={register}
|
||||
name="showSummary"
|
||||
label="Show summary statistics"
|
||||
/>
|
||||
</div>
|
||||
<div className="ml-3 text-sm">
|
||||
<label className="font-medium text-gray-700">
|
||||
Show information about displayed types.
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</HeadedSection>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 pt-8">
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900 pb-2">
|
||||
Distribution Display Settings
|
||||
</h3>
|
||||
|
||||
<div className="relative flex items-start">
|
||||
<div className="flex items-center h-5">
|
||||
<input
|
||||
type="checkbox"
|
||||
{...register("showControls")}
|
||||
className="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
|
||||
/>
|
||||
</div>
|
||||
<div className="ml-3 text-sm">
|
||||
<label className="font-medium text-gray-700">
|
||||
Show toggles to adjust scale of x and y axes
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative flex items-start">
|
||||
<div className="flex items-center h-5">
|
||||
<input
|
||||
type="checkbox"
|
||||
{...register("showSummary")}
|
||||
className="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
|
||||
/>
|
||||
</div>
|
||||
<div className="ml-3 text-sm">
|
||||
<label className="font-medium text-gray-700">
|
||||
Show summary statistics
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 pt-8">
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900 pb-2">
|
||||
Function Display Settings
|
||||
</h3>
|
||||
|
||||
<p className="mt-2 text-sm text-gray-500">
|
||||
When displaying functions of single variables that return numbers or
|
||||
distributions, we need to use defaults for the x-axis. We need to
|
||||
select a minimum and maximum value of x to sample, and a number n of
|
||||
the number of points to sample.
|
||||
</p>
|
||||
<div className="pt-4 grid grid-cols-1 gap-y-4 gap-x-4">
|
||||
<InputItem label="Min X Value">
|
||||
<input
|
||||
<div className="pt-8">
|
||||
<HeadedSection title="Function Display Settings">
|
||||
<div className="space-y-6">
|
||||
<Text>
|
||||
When displaying functions of single variables that return numbers
|
||||
or distributions, we need to use defaults for the x-axis. We need
|
||||
to select a minimum and maximum value of x to sample, and a number
|
||||
n of the number of points to sample.
|
||||
</Text>
|
||||
<div className="space-y-4">
|
||||
<InputItem
|
||||
type="number"
|
||||
{...register("diagramStart")}
|
||||
className={numberStyle}
|
||||
name="diagramStart"
|
||||
register={register}
|
||||
label="Min X Value"
|
||||
/>
|
||||
</InputItem>
|
||||
<InputItem label="Max X Value">
|
||||
<input
|
||||
<InputItem
|
||||
type="number"
|
||||
{...register("diagramStop")}
|
||||
className={numberStyle}
|
||||
name="diagramStop"
|
||||
register={register}
|
||||
label="Max X Value"
|
||||
/>
|
||||
</InputItem>
|
||||
<InputItem label="Points between X min and X max to sample">
|
||||
<input
|
||||
<InputItem
|
||||
type="number"
|
||||
{...register("diagramCount")}
|
||||
className={numberStyle}
|
||||
name="diagramCount"
|
||||
register={register}
|
||||
label="Points between X min and X max to sample"
|
||||
/>
|
||||
</InputItem>
|
||||
</div>
|
||||
</div>
|
||||
</HeadedSection>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
let inputVariableSettings = (
|
||||
<div className="space-y-6 p-3 max-w-3xl">
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900">
|
||||
Import Variables from JSON
|
||||
</h3>
|
||||
<p className="mt-2 text-sm text-gray-500">
|
||||
You can import variables from JSON into your Squiggle code. Variables
|
||||
are accessed with dollar signs. For example, "timeNow" would be accessed
|
||||
as "$timeNow".
|
||||
</p>
|
||||
const inputVariableSettings = (
|
||||
<div className="p-3 max-w-3xl">
|
||||
<HeadedSection title="Import Variables from JSON">
|
||||
<div className="space-y-6">
|
||||
<Text>
|
||||
You can import variables from JSON into your Squiggle code.
|
||||
Variables are accessed with dollar signs. For example, "timeNow"
|
||||
would be accessed as "$timeNow".
|
||||
</Text>
|
||||
<div className="border border-slate-200 mt-6 mb-2">
|
||||
<JsonEditor
|
||||
value={importString}
|
||||
|
@ -356,30 +365,29 @@ let SquigglePlayground: FC<PlaygroundProps> = ({
|
|||
</div>
|
||||
<div className="p-1 pt-2">
|
||||
{importsAreValid ? (
|
||||
<SuccessAlert heading="Valid Json">
|
||||
<></>
|
||||
</SuccessAlert>
|
||||
<SuccessAlert heading="Valid JSON" />
|
||||
) : (
|
||||
<ErrorAlert heading="Invalid JSON">
|
||||
You must use valid json in this editor.
|
||||
You must use valid JSON in this editor.
|
||||
</ErrorAlert>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</HeadedSection>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Tab.Group>
|
||||
<div className=" flex-col flex">
|
||||
<div className="pb-4">
|
||||
<Tab.List className="p-0.5 rounded-md bg-slate-100 hover:bg-slate-200 inline-flex">
|
||||
<StyledTab name="Code" iconName="code" />
|
||||
<StyledTab name="Sampling Settings" iconName="cog" />
|
||||
<StyledTab name="View Settings" iconName="squareBar" />
|
||||
<StyledTab name="Input Variables" iconName="dollar" />
|
||||
<Tab.List className="flex w-fit p-0.5 rounded-md bg-slate-100 hover:bg-slate-200">
|
||||
<StyledTab name="Code" icon={CodeIcon} />
|
||||
<StyledTab name="Sampling Settings" icon={CogIcon} />
|
||||
<StyledTab name="View Settings" icon={ChartSquareBarIcon} />
|
||||
<StyledTab name="Input Variables" icon={CurrencyDollarIcon} />
|
||||
</Tab.List>
|
||||
</div>
|
||||
<div className="flex" style={{ height: height + "px" }}>
|
||||
<div className="flex" style={{ height }}>
|
||||
<div className="w-1/2">
|
||||
<Tab.Panels>
|
||||
<Tab.Panel>
|
||||
|
@ -400,7 +408,7 @@ let SquigglePlayground: FC<PlaygroundProps> = ({
|
|||
</div>
|
||||
|
||||
<div className="w-1/2 p-2 pl-4">
|
||||
<div style={{ maxHeight: height + "px" }}>
|
||||
<div style={{ maxHeight: height }}>
|
||||
<SquiggleChart
|
||||
squiggleString={squiggleString}
|
||||
environment={env}
|
||||
|
@ -415,13 +423,12 @@ let SquigglePlayground: FC<PlaygroundProps> = ({
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Tab.Group>
|
||||
);
|
||||
};
|
||||
export default SquigglePlayground;
|
||||
export function renderSquigglePlaygroundToDom(props: PlaygroundProps) {
|
||||
let parent = document.createElement("div");
|
||||
const parent = document.createElement("div");
|
||||
ReactDOM.render(<SquigglePlayground {...props} />, parent);
|
||||
return parent;
|
||||
}
|
||||
|
|
|
@ -5,9 +5,9 @@ export {
|
|||
renderSquiggleEditorToDom,
|
||||
renderSquigglePartialToDom,
|
||||
} from "./components/SquiggleEditor";
|
||||
import SquigglePlayground, {
|
||||
export {
|
||||
default as SquigglePlayground,
|
||||
renderSquigglePlaygroundToDom,
|
||||
} from "./components/SquigglePlayground";
|
||||
export { SquigglePlayground, renderSquigglePlaygroundToDom };
|
||||
|
||||
export { mergeBindings } from "@quri/squiggle-lang";
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
{"version":3,"file":"SquiggleChart.stories.js","sourceRoot":"","sources":["SquiggleChart.stories.tsx"],"names":[],"mappings":";;;AAAA,6BAA8B;AAC9B,iDAA+C;AAG/C,qBAAe;IACb,KAAK,EAAE,uBAAuB;IAC9B,SAAS,EAAE,6BAAa;CACzB,CAAA;AAED,IAAM,QAAQ,GAAG,UAAC,EAAgB;QAAf,cAAc,oBAAA;IAAM,OAAA,oBAAC,6BAAa,IAAC,cAAc,EAAE,cAAc,GAAI;AAAjD,CAAiD,CAAA;AAE3E,QAAA,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;AACxC,eAAO,CAAC,IAAI,GAAG;IACb,cAAc,EAAE,cAAc;CAC/B,CAAC"}
|
Loading…
Reference in New Issue
Block a user