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,13 +398,16 @@ 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>
<RunControls <div className="flex space-x-2 items-center">
autorunMode={autorunMode} <RunControls
isStale={renderedCode !== code} autorunMode={autorunMode}
run={run} isStale={renderedCode !== code}
isRunning={isRunning} run={run}
onAutorunModeChange={setAutorunMode} isRunning={isRunning}
/> 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

@ -9,7 +9,7 @@ Features:
- Preview `.squiggle` files in a preview pane - Preview `.squiggle` files in a preview pane
- Syntax highlighting for `.squiggle` and `.squiggleU` files - Syntax highlighting for `.squiggle` and `.squiggleU` files
## Installation ## Installation
You can install this extension by going to the "extensions" tab, searching for "Squiggle", and then installing it. You can install this extension by going to the "extensions" tab, searching for "Squiggle", and then installing it.
@ -23,7 +23,7 @@ After loading a `.squiggle` file, an "Open Preview" button will appear. If you c
### Configuration (optional) ### Configuration (optional)
Some preview settings, e.g. whether to show the summary table or types of outputs, can be configurable on in the VS Code settings and persist between different preview sessions. The VS Code settings can be accessed with the shortcut `Ctrl+,` with `Ctrl+Shift+P` + searching "Open Settings", or by accessing a file like `$HOME/.config/Code/User/settings.json` in Linux (see [here](https://stackoverflow.com/questions/65908987/how-can-i-open-visual-studio-codes-settings-json-file)) for other operating systems. Some preview settings, e.g. whether to show the summary table or types of outputs, can be configurable on in the VS Code settings and persist between different preview sessions. The VS Code settings can be accessed with the shortcut `Ctrl+,` with `Ctrl+Shift+P` + searching "Open Settings", or by accessing a file like `$HOME/.config/Code/User/settings.json` in Linux (see [here](https://stackoverflow.com/questions/65908987/how-can-i-open-visual-studio-codes-settings-json-file)) for other operating systems.
![](./images/vs-code-settings.png) ![](./images/vs-code-settings.png)

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