Merge pull request #1142 from quantified-uncertainty/project-component
Add projects to components
This commit is contained in:
commit
b2c10924cd
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -11,3 +11,4 @@ yarn-error.log
|
||||||
.vscode
|
.vscode
|
||||||
todo.txt
|
todo.txt
|
||||||
result
|
result
|
||||||
|
shell.nix
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
"@hookform/resolvers": "^2.9.8",
|
"@hookform/resolvers": "^2.9.8",
|
||||||
"@quri/squiggle-lang": "^0.5.0",
|
"@quri/squiggle-lang": "^0.5.0",
|
||||||
"@react-hook/size": "^2.1.2",
|
"@react-hook/size": "^2.1.2",
|
||||||
|
"@types/uuid": "^8.3.4",
|
||||||
"clsx": "^1.2.1",
|
"clsx": "^1.2.1",
|
||||||
"framer-motion": "^7.5.1",
|
"framer-motion": "^7.5.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
@ -18,6 +19,7 @@
|
||||||
"react-hook-form": "^7.36.1",
|
"react-hook-form": "^7.36.1",
|
||||||
"react-use": "^17.4.0",
|
"react-use": "^17.4.0",
|
||||||
"react-vega": "^7.6.0",
|
"react-vega": "^7.6.0",
|
||||||
|
"uuid": "^9.0.0",
|
||||||
"vega": "^5.22.1",
|
"vega": "^5.22.1",
|
||||||
"vega-embed": "^6.21.0",
|
"vega-embed": "^6.21.0",
|
||||||
"vega-lite": "^5.5.0",
|
"vega-lite": "^5.5.0",
|
||||||
|
@ -42,6 +44,7 @@
|
||||||
"@types/node": "^18.8.0",
|
"@types/node": "^18.8.0",
|
||||||
"@types/react": "^18.0.21",
|
"@types/react": "^18.0.21",
|
||||||
"@types/styled-components": "^5.1.26",
|
"@types/styled-components": "^5.1.26",
|
||||||
|
"@types/uuid": "^8.3.4",
|
||||||
"@types/webpack": "^5.28.0",
|
"@types/webpack": "^5.28.0",
|
||||||
"canvas": "^2.10.1",
|
"canvas": "^2.10.1",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
|
@ -82,7 +85,8 @@
|
||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
"prepack": "yarn run build:cjs && yarn run bundle",
|
"prepack": "yarn run build:cjs && yarn run bundle",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand"
|
"test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand",
|
||||||
|
"test:profile": "node --cpu-prof node_modules/.bin/jest --runInBand"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": [
|
"extends": [
|
||||||
|
|
|
@ -2,23 +2,21 @@ import * as React from "react";
|
||||||
import {
|
import {
|
||||||
SqValue,
|
SqValue,
|
||||||
environment,
|
environment,
|
||||||
defaultEnvironment,
|
|
||||||
resultMap,
|
resultMap,
|
||||||
SqValueTag,
|
SqValueTag,
|
||||||
|
SqProject,
|
||||||
} from "@quri/squiggle-lang";
|
} from "@quri/squiggle-lang";
|
||||||
import { useSquiggle } from "../lib/hooks";
|
import { useSquiggle } from "../lib/hooks";
|
||||||
import { SquiggleViewer } from "./SquiggleViewer";
|
import { SquiggleViewer } from "./SquiggleViewer";
|
||||||
import { JsImports } from "../lib/jsImports";
|
import { JsImports } from "../lib/jsImports";
|
||||||
|
|
||||||
export interface SquiggleChartProps {
|
export type SquiggleChartProps = {
|
||||||
/** The input string for squiggle */
|
/** The input string for squiggle */
|
||||||
code?: string;
|
code: string;
|
||||||
/** Allows to re-run the code if code hasn't changed */
|
/** Allows to re-run the code if code hasn't changed */
|
||||||
executionId?: number;
|
executionId?: number;
|
||||||
/** If the output requires monte carlo sampling, the amount of samples */
|
/** If the output requires monte carlo sampling, the amount of samples */
|
||||||
sampleCount?: number;
|
sampleCount?: number;
|
||||||
/** The amount of points returned to draw the distribution */
|
|
||||||
environment?: environment;
|
|
||||||
/** If the result is a function, where the function domain starts */
|
/** If the result is a function, where the function domain starts */
|
||||||
diagramStart?: number;
|
diagramStart?: number;
|
||||||
/** If the result is a function, where the function domain ends */
|
/** If the result is a function, where the function domain ends */
|
||||||
|
@ -26,7 +24,7 @@ export interface SquiggleChartProps {
|
||||||
/** If the result is a function, the amount of stops sampled */
|
/** If the result is a function, the amount of stops sampled */
|
||||||
diagramCount?: number;
|
diagramCount?: number;
|
||||||
/** When the squiggle code gets reevaluated */
|
/** When the squiggle code gets reevaluated */
|
||||||
onChange?(expr: SqValue | undefined): void;
|
onChange?(expr: SqValue | undefined, sourceName: string): void;
|
||||||
/** CSS width of the element */
|
/** CSS width of the element */
|
||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
|
@ -53,38 +51,69 @@ export interface SquiggleChartProps {
|
||||||
/** Whether to show vega actions to the user, so they can copy the chart spec */
|
/** Whether to show vega actions to the user, so they can copy the chart spec */
|
||||||
distributionChartActions?: boolean;
|
distributionChartActions?: boolean;
|
||||||
enableLocalSettings?: boolean;
|
enableLocalSettings?: boolean;
|
||||||
}
|
} & (StandaloneExecutionProps | ProjectExecutionProps);
|
||||||
|
|
||||||
|
// Props needed for a standalone execution
|
||||||
|
type StandaloneExecutionProps = {
|
||||||
|
project?: undefined;
|
||||||
|
continues?: undefined;
|
||||||
|
/** The amount of points returned to draw the distribution, not needed if using a project */
|
||||||
|
environment?: environment;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Props needed when executing inside a project.
|
||||||
|
type ProjectExecutionProps = {
|
||||||
|
environment?: undefined;
|
||||||
|
/** The project that this execution is part of */
|
||||||
|
project: SqProject;
|
||||||
|
/** What other squiggle sources from the project to continue. Default [] */
|
||||||
|
continues?: string[];
|
||||||
|
};
|
||||||
const defaultOnChange = () => {};
|
const defaultOnChange = () => {};
|
||||||
const defaultImports: JsImports = {};
|
const defaultImports: JsImports = {};
|
||||||
|
|
||||||
export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
|
export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
|
||||||
({
|
(props: SquiggleChartProps) => {
|
||||||
code = "",
|
const {
|
||||||
executionId = 0,
|
executionId = 0,
|
||||||
environment,
|
onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here
|
||||||
onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here
|
height = 200,
|
||||||
height = 200,
|
jsImports = defaultImports,
|
||||||
jsImports = defaultImports,
|
showSummary = false,
|
||||||
showSummary = false,
|
width,
|
||||||
width,
|
logX = false,
|
||||||
logX = false,
|
expY = false,
|
||||||
expY = false,
|
diagramStart = 0,
|
||||||
diagramStart = 0,
|
diagramStop = 10,
|
||||||
diagramStop = 10,
|
diagramCount = 20,
|
||||||
diagramCount = 20,
|
tickFormat,
|
||||||
tickFormat,
|
minX,
|
||||||
minX,
|
maxX,
|
||||||
maxX,
|
color,
|
||||||
color,
|
title,
|
||||||
title,
|
xAxisType = "number",
|
||||||
xAxisType = "number",
|
distributionChartActions,
|
||||||
distributionChartActions,
|
enableLocalSettings = false,
|
||||||
enableLocalSettings = false,
|
code,
|
||||||
}) => {
|
continues = [],
|
||||||
const { result, bindings } = useSquiggle({
|
} = props;
|
||||||
|
|
||||||
|
const p = React.useMemo(() => {
|
||||||
|
if (props.project) {
|
||||||
|
return props.project;
|
||||||
|
} else {
|
||||||
|
const p = SqProject.create();
|
||||||
|
if (props.environment) {
|
||||||
|
p.setEnvironment(props.environment);
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
}, [props.project, props.environment]);
|
||||||
|
|
||||||
|
const { result, bindings } = useSquiggle({
|
||||||
|
continues,
|
||||||
|
project: p,
|
||||||
code,
|
code,
|
||||||
environment,
|
|
||||||
jsImports,
|
jsImports,
|
||||||
onChange,
|
onChange,
|
||||||
executionId,
|
executionId,
|
||||||
|
@ -120,7 +149,7 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
|
||||||
height={height}
|
height={height}
|
||||||
distributionPlotSettings={distributionPlotSettings}
|
distributionPlotSettings={distributionPlotSettings}
|
||||||
chartSettings={chartSettings}
|
chartSettings={chartSettings}
|
||||||
environment={environment ?? defaultEnvironment}
|
environment={p.getEnvironment()}
|
||||||
enableLocalSettings={enableLocalSettings}
|
enableLocalSettings={enableLocalSettings}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React, { useContext } from "react";
|
||||||
import { SqDistributionTag, SqValue, SqValueTag } from "@quri/squiggle-lang";
|
import { SqDistributionTag, SqValue, SqValueTag } from "@quri/squiggle-lang";
|
||||||
import { NumberShower } from "../NumberShower";
|
import { NumberShower } from "../NumberShower";
|
||||||
import { DistributionChart, defaultPlot, makePlot } from "../DistributionChart";
|
import { DistributionChart, defaultPlot, makePlot } from "../DistributionChart";
|
||||||
import { FunctionChart, FunctionChartSettings } from "../FunctionChart";
|
import { FunctionChart } from "../FunctionChart";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { VariableBox } from "./VariableBox";
|
import { VariableBox } from "./VariableBox";
|
||||||
import { ItemSettingsMenu } from "./ItemSettingsMenu";
|
import { ItemSettingsMenu } from "./ItemSettingsMenu";
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
export { SqProject } from "@quri/squiggle-lang/";
|
||||||
export { SquiggleChart } from "./components/SquiggleChart";
|
export { SquiggleChart } from "./components/SquiggleChart";
|
||||||
export { SquiggleEditor } from "./components/SquiggleEditor";
|
export { SquiggleEditor } from "./components/SquiggleEditor";
|
||||||
export { SquigglePlayground } from "./components/SquigglePlayground";
|
export { SquigglePlayground } from "./components/SquigglePlayground";
|
||||||
|
|
|
@ -1,42 +1,72 @@
|
||||||
import { environment, SqProject, SqValue } from "@quri/squiggle-lang";
|
import { SqProject, SqValue } from "@quri/squiggle-lang";
|
||||||
import { useEffect, useMemo } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
import { JsImports, jsImportsToSquiggleCode } from "../jsImports";
|
import { JsImports, jsImportsToSquiggleCode } from "../jsImports";
|
||||||
|
import * as uuid from "uuid";
|
||||||
|
|
||||||
type SquiggleArgs = {
|
type SquiggleArgs = {
|
||||||
code: string;
|
code: string;
|
||||||
executionId?: number;
|
executionId?: number;
|
||||||
jsImports?: JsImports;
|
jsImports?: JsImports;
|
||||||
environment?: environment;
|
project: SqProject;
|
||||||
onChange?: (expr: SqValue | undefined) => void;
|
continues: string[];
|
||||||
|
onChange?: (expr: SqValue | undefined, sourceName: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const importSourceName = (sourceName: string) => "imports-" + sourceName;
|
||||||
|
|
||||||
export const useSquiggle = (args: SquiggleArgs) => {
|
export const useSquiggle = (args: SquiggleArgs) => {
|
||||||
|
const sourceName = useMemo(() => uuid.v4(), []);
|
||||||
|
|
||||||
|
const env = args.project.getEnvironment();
|
||||||
|
|
||||||
const result = useMemo(
|
const result = useMemo(
|
||||||
() => {
|
() => {
|
||||||
const project = SqProject.create();
|
const project = args.project;
|
||||||
project.setSource("main", args.code);
|
|
||||||
if (args.environment) {
|
project.setSource(sourceName, args.code);
|
||||||
project.setEnvironment(args.environment);
|
let continues = args.continues;
|
||||||
}
|
|
||||||
if (args.jsImports && Object.keys(args.jsImports).length) {
|
if (args.jsImports && Object.keys(args.jsImports).length) {
|
||||||
const importsSource = jsImportsToSquiggleCode(args.jsImports);
|
const importsSource = jsImportsToSquiggleCode(args.jsImports);
|
||||||
project.setSource("imports", importsSource);
|
project.setSource(importSourceName(sourceName), importsSource);
|
||||||
project.setContinues("main", ["imports"]);
|
continues = args.continues.concat(importSourceName(sourceName));
|
||||||
}
|
}
|
||||||
project.run("main");
|
project.setContinues(sourceName, continues);
|
||||||
const result = project.getResult("main");
|
project.run(sourceName);
|
||||||
const bindings = project.getBindings("main");
|
const result = project.getResult(sourceName);
|
||||||
|
const bindings = project.getBindings(sourceName);
|
||||||
return { result, bindings };
|
return { result, bindings };
|
||||||
},
|
},
|
||||||
|
// This complains about executionId not being used inside the function body.
|
||||||
|
// This is on purpose, as executionId simply allows you to run the squiggle
|
||||||
|
// code again
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
[args.code, args.environment, args.jsImports, args.executionId]
|
[
|
||||||
|
args.code,
|
||||||
|
args.jsImports,
|
||||||
|
args.executionId,
|
||||||
|
sourceName,
|
||||||
|
args.continues,
|
||||||
|
args.project,
|
||||||
|
env,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
const { onChange } = args;
|
const { onChange } = args;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onChange?.(result.result.tag === "Ok" ? result.result.value : undefined);
|
onChange?.(
|
||||||
}, [result, onChange]);
|
result.result.tag === "Ok" ? result.result.value : undefined,
|
||||||
|
sourceName
|
||||||
|
);
|
||||||
|
}, [result, onChange, sourceName]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
args.project.removeSource(sourceName);
|
||||||
|
if (args.project.getSource(importSourceName(sourceName)))
|
||||||
|
args.project.removeSource(importSourceName(sourceName));
|
||||||
|
};
|
||||||
|
}, [args.project, sourceName]);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,11 +3,11 @@ import React from "react";
|
||||||
import "@testing-library/jest-dom";
|
import "@testing-library/jest-dom";
|
||||||
import { SquiggleChart } from "../src/index";
|
import { SquiggleChart } from "../src/index";
|
||||||
|
|
||||||
test("Logs no warnings or errors", async () => {
|
test("Logs nothing on render", async () => {
|
||||||
debugger;
|
|
||||||
const { unmount } = render(<SquiggleChart code={"normal(0, 1)"} />);
|
const { unmount } = render(<SquiggleChart code={"normal(0, 1)"} />);
|
||||||
unmount();
|
unmount();
|
||||||
|
|
||||||
|
expect(console.log).not.toBeCalled();
|
||||||
expect(console.warn).not.toBeCalled();
|
expect(console.warn).not.toBeCalled();
|
||||||
expect(console.error).not.toBeCalled();
|
expect(console.error).not.toBeCalled();
|
||||||
});
|
});
|
||||||
|
|
39
packages/components/test/cleanup.test.tsx
Normal file
39
packages/components/test/cleanup.test.tsx
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import { render } from "@testing-library/react";
|
||||||
|
import React from "react";
|
||||||
|
import "@testing-library/jest-dom";
|
||||||
|
import { SquiggleChart } from "../src/index";
|
||||||
|
import { SqProject } from "@quri/squiggle-lang";
|
||||||
|
|
||||||
|
test("Creates and cleans up source", async () => {
|
||||||
|
const project = SqProject.create();
|
||||||
|
|
||||||
|
const { unmount } = render(
|
||||||
|
<SquiggleChart code={"normal(0, 1)"} project={project} />
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(project.getSourceIds().length).toBe(1);
|
||||||
|
|
||||||
|
const sourceId = project.getSourceIds()[0];
|
||||||
|
expect(project.getSource(sourceId)).toBe("normal(0, 1)");
|
||||||
|
|
||||||
|
unmount();
|
||||||
|
expect(project.getSourceIds().length).toBe(0);
|
||||||
|
expect(project.getSource(sourceId)).toBe(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Creates and cleans up source and imports", async () => {
|
||||||
|
const project = SqProject.create();
|
||||||
|
|
||||||
|
const { unmount } = render(
|
||||||
|
<SquiggleChart
|
||||||
|
code={"normal($x, 1)"}
|
||||||
|
project={project}
|
||||||
|
jsImports={{ x: 3 }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(project.getSourceIds().length).toBe(2);
|
||||||
|
|
||||||
|
unmount();
|
||||||
|
expect(project.getSourceIds()).toStrictEqual([]);
|
||||||
|
});
|
10
yarn.lock
10
yarn.lock
|
@ -5186,6 +5186,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d"
|
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d"
|
||||||
integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==
|
integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==
|
||||||
|
|
||||||
|
"@types/uuid@^8.3.4":
|
||||||
|
version "8.3.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc"
|
||||||
|
integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==
|
||||||
|
|
||||||
"@types/vscode@^1.70.0":
|
"@types/vscode@^1.70.0":
|
||||||
version "1.71.0"
|
version "1.71.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/vscode/-/vscode-1.71.0.tgz#a8d9bb7aca49b0455060e6eb978711b510bdd2e2"
|
resolved "https://registry.yarnpkg.com/@types/vscode/-/vscode-1.71.0.tgz#a8d9bb7aca49b0455060e6eb978711b510bdd2e2"
|
||||||
|
@ -18723,6 +18728,11 @@ uuid@^8.0.0, uuid@^8.3.2:
|
||||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
||||||
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
|
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
|
||||||
|
|
||||||
|
uuid@^9.0.0:
|
||||||
|
version "9.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5"
|
||||||
|
integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==
|
||||||
|
|
||||||
v8-compile-cache-lib@^3.0.1:
|
v8-compile-cache-lib@^3.0.1:
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"
|
resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user