Merge branch 'develop' into documentation-refactors-jul

* develop:
  delete SquiggleItem
  hotfix: version increment
  copy share link button
  revert last commit
  changed import from local docusaurus component to `@quri/squiggle-components` component
  inherit props to `runPartial`
  absolute path for `bindingsImportsFile`
  `yarn format` compels me
  fix build
  trailing slash?
  @berekuk's CR
  lazy tabs (fixes #506)
  remove SquiggleItem, unused
  fix lint
  checking in editor.jsx file
  removed comment, cleaned up a function
  imported bindings
This commit is contained in:
Ozzie Gooen 2022-07-28 16:00:13 -07:00
commit 42528eb07e
15 changed files with 192 additions and 305 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "@quri/squiggle-components", "name": "@quri/squiggle-components",
"version": "0.2.23", "version": "0.2.24",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@floating-ui/react-dom": "^0.7.2", "@floating-ui/react-dom": "^0.7.2",

View File

@ -0,0 +1,52 @@
import React from "react";
import { SquiggleEditor } from "./SquiggleEditor";
import type { SquiggleEditorProps } from "./SquiggleEditor";
import { runPartial, defaultBindings } from "@quri/squiggle-lang";
import type {
result,
errorValue,
bindings as bindingsType,
} from "@quri/squiggle-lang";
function resultDefault(x: result<bindingsType, errorValue>): bindingsType {
switch (x.tag) {
case "Ok":
return x.value;
case "Error":
return defaultBindings;
}
}
export type SquiggleEditorWithImportedBindingsProps = SquiggleEditorProps & {
bindingsImportUrl: string;
};
export const SquiggleEditorWithImportedBindings: React.FC<
SquiggleEditorWithImportedBindingsProps
> = (props) => {
const { bindingsImportUrl, ...editorProps } = props;
const [bindingsResult, setBindingsResult] = React.useState({
tag: "Ok",
value: defaultBindings,
} as result<bindingsType, errorValue>);
React.useEffect(() => {
async function retrieveBindings(fileName: string) {
let contents = await fetch(fileName).then((response) => {
return response.text();
});
setBindingsResult(
runPartial(
contents,
editorProps.bindings,
editorProps.environment,
editorProps.jsImports
)
);
}
retrieveBindings(bindingsImportUrl);
}, [bindingsImportUrl]);
const deliveredBindings = resultDefault(bindingsResult);
return (
<SquiggleEditor {...{ ...editorProps, bindings: deliveredBindings }} />
);
};

View File

@ -1,287 +0,0 @@
import * as React from "react";
import {
squiggleExpression,
environment,
declaration,
} from "@quri/squiggle-lang";
import { NumberShower } from "./NumberShower";
import {
DistributionChart,
DistributionPlottingSettings,
} from "./DistributionChart";
import { FunctionChart, FunctionChartSettings } from "./FunctionChart";
function getRange<a>(x: declaration<a>) {
const first = x.args[0];
switch (first.tag) {
case "Float": {
return { floats: { min: first.value.min, max: first.value.max } };
}
case "Date": {
return { time: { min: first.value.min, max: first.value.max } };
}
}
}
function getChartSettings<a>(x: declaration<a>): FunctionChartSettings {
const range = getRange(x);
const min = range.floats ? range.floats.min : 0;
const max = range.floats ? range.floats.max : 10;
return {
start: min,
stop: max,
count: 20,
};
}
interface VariableBoxProps {
heading: string;
children: React.ReactNode;
showTypes: boolean;
}
export const VariableBox: React.FC<VariableBoxProps> = ({
heading = "Error",
children,
showTypes = false,
}) => {
if (showTypes) {
return (
<div className="bg-white border border-grey-200 m-2">
<div className="border-b border-grey-200 p-3">
<header className="font-mono">{heading}</header>
</div>
<div className="p-3">{children}</div>
</div>
);
} else {
return <div>{children}</div>;
}
};
export interface SquiggleItemProps {
/** The input string for squiggle */
expression: squiggleExpression;
width?: number;
height: number;
distributionPlotSettings: DistributionPlottingSettings;
/** Whether to show type information */
showTypes: boolean;
/** Settings for displaying functions */
chartSettings: FunctionChartSettings;
/** Environment for further function executions */
environment: environment;
}
export const SquiggleItem: React.FC<SquiggleItemProps> = ({
expression,
width,
height,
distributionPlotSettings,
showTypes = false,
chartSettings,
environment,
}) => {
switch (expression.tag) {
case "number":
return (
<VariableBox heading="Number" showTypes={showTypes}>
<div className="font-semibold text-slate-600">
<NumberShower precision={3} number={expression.value} />
</div>
</VariableBox>
);
case "distribution": {
const distType = expression.value.type();
return (
<VariableBox
heading={`Distribution (${distType})`}
showTypes={showTypes}
>
{distType === "Symbolic" && showTypes ? (
<div>{expression.value.toString()}</div>
) : null}
<DistributionChart
distribution={expression.value}
{...distributionPlotSettings}
height={height}
width={width}
/>
</VariableBox>
);
}
case "string":
return (
<VariableBox heading="String" showTypes={showTypes}>
<span className="text-slate-400">"</span>
<span className="text-slate-600 font-semibold">
{expression.value}
</span>
<span className="text-slate-400">"</span>
</VariableBox>
);
case "boolean":
return (
<VariableBox heading="Boolean" showTypes={showTypes}>
{expression.value.toString()}
</VariableBox>
);
case "symbol":
return (
<VariableBox heading="Symbol" showTypes={showTypes}>
<span className="text-slate-500 mr-2">Undefined Symbol:</span>
<span className="text-slate-600">{expression.value}</span>
</VariableBox>
);
case "call":
return (
<VariableBox heading="Call" showTypes={showTypes}>
{expression.value}
</VariableBox>
);
case "array":
return (
<VariableBox heading="Array" showTypes={showTypes}>
{expression.value.map((r, i) => (
<div key={i} className="flex pt-1">
<div className="flex-none bg-slate-100 rounded-sm px-1">
<header className="text-slate-400 font-mono">{i}</header>
</div>
<div className="px-2 mb-2 grow">
<SquiggleItem
key={i}
expression={r}
width={width !== undefined ? width - 20 : width}
height={50}
distributionPlotSettings={distributionPlotSettings}
showTypes={showTypes}
chartSettings={chartSettings}
environment={environment}
/>
</div>
</div>
))}
</VariableBox>
);
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 space-x-2">
<div className="flex-none">
<header className="text-slate-500 font-mono">{key}:</header>
</div>
<div className="px-2 grow bg-gray-50 border border-gray-100 rounded-sm">
<SquiggleItem
expression={r}
width={width !== undefined ? width - 20 : width}
height={height / 3}
showTypes={showTypes}
distributionPlotSettings={distributionPlotSettings}
chartSettings={chartSettings}
environment={environment}
/>
</div>
</div>
))}
</div>
</VariableBox>
);
case "arraystring":
return (
<VariableBox heading="Array String" showTypes={showTypes}>
{expression.value.map((r) => `"${r}"`).join(", ")}
</VariableBox>
);
case "date":
return (
<VariableBox heading="Date" showTypes={showTypes}>
{expression.value.toDateString()}
</VariableBox>
);
case "void":
return (
<VariableBox heading="Void" showTypes={showTypes}>
{"Void"}
</VariableBox>
);
case "timeDuration": {
return (
<VariableBox heading="Time Duration" showTypes={showTypes}>
<NumberShower precision={3} number={expression.value} />
</VariableBox>
);
}
case "lambda":
return (
<VariableBox heading="Function" showTypes={showTypes}>
<div className="text-amber-700 bg-amber-100 rounded-md font-mono p-1 pl-2 mb-3 mt-1 text-sm">{`function(${expression.value.parameters.join(
","
)})`}</div>
<FunctionChart
fn={expression.value}
chartSettings={chartSettings}
distributionPlotSettings={distributionPlotSettings}
height={height}
environment={{
sampleCount: environment.sampleCount / 10,
xyPointLength: environment.xyPointLength / 10,
}}
/>
</VariableBox>
);
case "lambdaDeclaration": {
return (
<VariableBox heading="Function Declaration" showTypes={showTypes}>
<FunctionChart
fn={expression.value.fn}
chartSettings={getChartSettings(expression.value)}
distributionPlotSettings={distributionPlotSettings}
height={height}
environment={{
sampleCount: environment.sampleCount / 10,
xyPointLength: environment.xyPointLength / 10,
}}
/>
</VariableBox>
);
}
case "module": {
return (
<VariableBox heading="Module" showTypes={showTypes}>
<div className="space-y-3">
{Object.entries(expression.value)
.filter(([key, r]) => key !== "Math")
.map(([key, r]) => (
<div key={key} className="flex space-x-2">
<div className="flex-none">
<header className="text-slate-500 font-mono">{key}:</header>
</div>
<div className="px-2 grow bg-gray-50 border border-gray-100 rounded-sm">
<SquiggleItem
expression={r}
width={width !== undefined ? width - 20 : width}
height={height / 3}
showTypes={showTypes}
distributionPlotSettings={distributionPlotSettings}
chartSettings={chartSettings}
environment={environment}
/>
</div>
</div>
))}
</div>
</VariableBox>
);
}
default: {
return (
<div>
<span>No display for type: </span>{" "}
<span className="font-semibold text-slate-600">{expression.tag}</span>
</div>
);
}
}
};

View File

@ -13,6 +13,7 @@ import { yupResolver } from "@hookform/resolvers/yup";
import { import {
ChartSquareBarIcon, ChartSquareBarIcon,
CheckCircleIcon, CheckCircleIcon,
ClipboardCopyIcon,
CodeIcon, CodeIcon,
CogIcon, CogIcon,
CurrencyDollarIcon, CurrencyDollarIcon,
@ -40,6 +41,7 @@ import {
defaultColor, defaultColor,
defaultTickFormat, defaultTickFormat,
} from "../lib/distributionSpecBuilder"; } from "../lib/distributionSpecBuilder";
import { Button } from "./ui/Button";
type PlaygroundProps = SquiggleChartProps & { type PlaygroundProps = SquiggleChartProps & {
/** The initial squiggle string to put in the playground */ /** The initial squiggle string to put in the playground */
@ -49,6 +51,8 @@ type PlaygroundProps = SquiggleChartProps & {
onSettingsChange?(settings: any): void; onSettingsChange?(settings: any): void;
/** Should we show the editor? */ /** Should we show the editor? */
showEditor?: boolean; showEditor?: boolean;
/** Useful for playground on squiggle website, where we update the anchor link based on current code and settings */
showShareButton?: boolean;
}; };
const schema = yup const schema = yup
@ -197,6 +201,29 @@ const RunControls: React.FC<{
); );
}; };
const ShareButton: React.FC = () => {
const [isCopied, setIsCopied] = useState(false);
const copy = () => {
navigator.clipboard.writeText((window.top || window).location.href);
setIsCopied(true);
setTimeout(() => setIsCopied(false), 1000);
};
return (
<div className="w-36">
<Button onClick={copy} wide>
{isCopied ? (
"Copied to clipboard!"
) : (
<div className="flex items-center space-x-1">
<ClipboardCopyIcon className="w-4 h-4" />
<span>Copy share link</span>
</div>
)}
</Button>
</div>
);
};
type PlaygroundContextShape = { type PlaygroundContextShape = {
getLeftPanelElement: () => HTMLDivElement | undefined; getLeftPanelElement: () => HTMLDivElement | undefined;
}; };
@ -220,6 +247,7 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
onCodeChange, onCodeChange,
onSettingsChange, onSettingsChange,
showEditor = true, showEditor = true,
showShareButton = false,
}) => { }) => {
const [code, setCode] = useMaybeControlledValue({ const [code, setCode] = useMaybeControlledValue({
value: controlledCode, value: controlledCode,
@ -370,6 +398,7 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
<StyledTab name="View Settings" icon={ChartSquareBarIcon} /> <StyledTab name="View Settings" icon={ChartSquareBarIcon} />
<StyledTab name="Input Variables" icon={CurrencyDollarIcon} /> <StyledTab name="Input Variables" icon={CurrencyDollarIcon} />
</StyledTab.List> </StyledTab.List>
<div className="flex space-x-2 items-center">
<RunControls <RunControls
autorunMode={autorunMode} autorunMode={autorunMode}
isStale={renderedCode !== code} isStale={renderedCode !== code}
@ -377,6 +406,8 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
isRunning={isRunning} isRunning={isRunning}
onAutorunModeChange={setAutorunMode} onAutorunModeChange={setAutorunMode}
/> />
{showShareButton && <ShareButton />}
</div>
</div> </div>
{vars.showEditor ? withEditor : withoutEditor} {vars.showEditor ? withEditor : withoutEditor}
</div> </div>

View File

@ -0,0 +1,22 @@
import clsx from "clsx";
import React from "react";
type Props = {
onClick: () => void;
children: React.ReactNode;
wide?: boolean; // stretch the button horizontally
};
export const Button: React.FC<Props> = ({ onClick, wide, children }) => {
return (
<button
className={clsx(
"rounded-md py-1.5 px-2 bg-slate-500 text-white text-xs font-semibold flex items-center justify-center space-x-1",
wide && "w-full"
)}
onClick={onClick}
>
{children}
</button>
);
};

View File

@ -2,5 +2,6 @@ export { SquiggleChart } from "./components/SquiggleChart";
export { SquiggleEditor, SquigglePartial } from "./components/SquiggleEditor"; export { SquiggleEditor, SquigglePartial } from "./components/SquiggleEditor";
export { SquigglePlayground } from "./components/SquigglePlayground"; export { SquigglePlayground } from "./components/SquigglePlayground";
export { SquiggleContainer } from "./components/SquiggleContainer"; export { SquiggleContainer } from "./components/SquiggleContainer";
export { SquiggleEditorWithImportedBindings } from "./components/SquiggleEditorWithImportedBindings";
export { mergeBindings } from "@quri/squiggle-lang"; export { mergeBindings } from "@quri/squiggle-lang";

View File

@ -21,3 +21,16 @@ including sampling settings, in squiggle.
{Template.bind({})} {Template.bind({})}
</Story> </Story>
</Canvas> </Canvas>
<Canvas>
<Story
name="With share button"
args={{
defaultCode: "normal(5,2)",
height: 800,
showShareButton: true,
}}
>
{Template.bind({})}
</Story>
</Canvas>

View File

@ -1,6 +1,7 @@
module Bindings = Reducer_Bindings module Bindings = Reducer_Bindings
let internalStdLib = Bindings.emptyBindings->SquiggleLibrary_Math.makeBindings->SquiggleLibrary_Versions.makeBindings let internalStdLib =
Bindings.emptyBindings->SquiggleLibrary_Math.makeBindings->SquiggleLibrary_Versions.makeBindings
@genType @genType
let externalStdLib = internalStdLib->Bindings.toTypeScriptBindings let externalStdLib = internalStdLib->Bindings.toTypeScriptBindings

View File

@ -17,7 +17,7 @@ The `to` function is an easy way to generate simple distributions using predicte
If both values are above zero, a `lognormal` distribution is used. If not, a `normal` distribution is used. If both values are above zero, a `lognormal` distribution is used. If not, a `normal` distribution is used.
<Tabs> <Tabs lazy>
<TabItem value="ex1" label="5 to 10" default> <TabItem value="ex1" label="5 to 10" default>
When <code>5 to 10</code> is entered, both numbers are positive, so it When <code>5 to 10</code> is entered, both numbers are positive, so it
generates a lognormal distribution with 5th and 95th percentiles at 5 and generates a lognormal distribution with 5th and 95th percentiles at 5 and
@ -74,7 +74,7 @@ If both values are above zero, a `lognormal` distribution is used. If not, a `no
The `mixture` mixes combines multiple distributions to create a mixture. You can optionally pass in a list of proportional weights. The `mixture` mixes combines multiple distributions to create a mixture. You can optionally pass in a list of proportional weights.
<Tabs> <Tabs lazy>
<TabItem value="ex1" label="Simple" default> <TabItem value="ex1" label="Simple" default>
<SquiggleEditor defaultCode="mixture(1 to 2, 5 to 8, 9 to 10)" /> <SquiggleEditor defaultCode="mixture(1 to 2, 5 to 8, 9 to 10)" />
</TabItem> </TabItem>
@ -139,7 +139,7 @@ mx(forecast, forecast_if_completely_wrong, [1-chance_completely_wrong, chance_co
Creates a [normal distribution](https://en.wikipedia.org/wiki/Normal_distribution) with the given mean and standard deviation. Creates a [normal distribution](https://en.wikipedia.org/wiki/Normal_distribution) with the given mean and standard deviation.
<Tabs> <Tabs lazy>
<TabItem value="ex1" label="normal(5,1)" default> <TabItem value="ex1" label="normal(5,1)" default>
<SquiggleEditor defaultCode="normal(5, 1)" /> <SquiggleEditor defaultCode="normal(5, 1)" />
</TabItem> </TabItem>
@ -234,7 +234,7 @@ with values at 1 and 2. Therefore, this is the same as `mixture(pointMass(1),poi
`pointMass()` distributions are currently the only discrete distributions accessible in Squiggle. `pointMass()` distributions are currently the only discrete distributions accessible in Squiggle.
<Tabs> <Tabs lazy>
<TabItem value="ex1" label="pointMass(3)" default> <TabItem value="ex1" label="pointMass(3)" default>
<SquiggleEditor defaultCode="pointMass(3)" /> <SquiggleEditor defaultCode="pointMass(3)" />
</TabItem> </TabItem>
@ -263,7 +263,7 @@ with values at 1 and 2. Therefore, this is the same as `mixture(pointMass(1),poi
Creates a [beta distribution](https://en.wikipedia.org/wiki/Beta_distribution) with the given `alpha` and `beta` values. For a good summary of the beta distribution, see [this explanation](https://stats.stackexchange.com/a/47782) on Stack Overflow. Creates a [beta distribution](https://en.wikipedia.org/wiki/Beta_distribution) with the given `alpha` and `beta` values. For a good summary of the beta distribution, see [this explanation](https://stats.stackexchange.com/a/47782) on Stack Overflow.
<Tabs> <Tabs lazy>
<TabItem value="ex1" label="beta(10, 20)" default> <TabItem value="ex1" label="beta(10, 20)" default>
<SquiggleEditor defaultCode="beta(10,20)" /> <SquiggleEditor defaultCode="beta(10,20)" />
</TabItem> </TabItem>
@ -300,7 +300,7 @@ Creates a [beta distribution](https://en.wikipedia.org/wiki/Beta_distribution) w
</p> </p>
<details> <details>
<summary>Examples</summary> <summary>Examples</summary>
<Tabs> <Tabs lazy>
<TabItem value="ex1" label="beta(0.3, 0.3)" default> <TabItem value="ex1" label="beta(0.3, 0.3)" default>
<SquiggleEditor defaultCode="beta(0.3, 0.3)" /> <SquiggleEditor defaultCode="beta(0.3, 0.3)" />
</TabItem> </TabItem>

View File

@ -0,0 +1,37 @@
---
title: How to import squiggle files into `.mdx` documents
sidebar_position: 5
---
import { SquiggleEditorWithImportedBindings } from "../../src/components/SquiggleEditor";
_Proof of concept_
## Consider the following squiggle file
In our docusaurus repo, we have a static asset called `demo.squiggle`. It looks like this
```js
x = 1 to 2
y = {a: x, b: 1e1}
f(t) = normal(t, 1.1)
z = y.b * y.a
```
We can call `f(z)` upon the assignments in `demo.squiggle` like so:
```jsx
import { SquiggleEditorWithImportedBindings } from "../../src/components/SquiggleEditor";
<SquiggleEditorWithImportedBindings
defaultCode={"f(z)"}
bindingsImportFile={"/estimates/demo.squiggle"}
/>;
```
Which would then look exactly like
<SquiggleEditorWithImportedBindings
defaultCode={"f(z)"}
bindingsImportUrl={"/estimates/demo.squiggle"}
/>

View File

@ -15,7 +15,7 @@
"@docusaurus/core": "2.0.0-rc.1", "@docusaurus/core": "2.0.0-rc.1",
"@docusaurus/preset-classic": "2.0.0-rc.1", "@docusaurus/preset-classic": "2.0.0-rc.1",
"@heroicons/react": "^1.0.6", "@heroicons/react": "^1.0.6",
"@quri/squiggle-components": "^0.2.20", "@quri/squiggle-components": "^0.2.23",
"base64-js": "^1.5.1", "base64-js": "^1.5.1",
"clsx": "^1.2.1", "clsx": "^1.2.1",
"hast-util-is-element": "2.1.2", "hast-util-is-element": "2.1.2",

View File

@ -12,3 +12,15 @@ export function SquiggleEditor(props) {
</BrowserOnly> </BrowserOnly>
); );
} }
export function SquiggleEditorWithImportedBindings(props) {
return (
<BrowserOnly fallback={<FallbackSpinner height={292} />}>
{() => {
const LibComponent =
require("@quri/squiggle-components").SquiggleEditorWithImportedBindings;
return <LibComponent {...props} />;
}}
</BrowserOnly>
);
}

View File

@ -44,6 +44,7 @@ export default function PlaygroundPage() {
const playgroundProps = { const playgroundProps = {
defaultCode: "normal(0,1)", defaultCode: "normal(0,1)",
height: 700, height: 700,
showShareButton: true,
...hashData, ...hashData,
onCodeChange: (code) => setHashData({ initialSquiggleString: code }), onCodeChange: (code) => setHashData({ initialSquiggleString: code }),
onSettingsChange: (settings) => { onSettingsChange: (settings) => {

View File

@ -0,0 +1,4 @@
x = 1 to 2
y = {a: x, b: 1e1}
f(t) = normal(t, 1.1)
z = y.b * y.a