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
|
||||
todo.txt
|
||||
result
|
||||
shell.nix
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
"@hookform/resolvers": "^2.9.8",
|
||||
"@quri/squiggle-lang": "^0.5.0",
|
||||
"@react-hook/size": "^2.1.2",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"clsx": "^1.2.1",
|
||||
"framer-motion": "^7.5.1",
|
||||
"lodash": "^4.17.21",
|
||||
|
@ -18,6 +19,7 @@
|
|||
"react-hook-form": "^7.36.1",
|
||||
"react-use": "^17.4.0",
|
||||
"react-vega": "^7.6.0",
|
||||
"uuid": "^9.0.0",
|
||||
"vega": "^5.22.1",
|
||||
"vega-embed": "^6.21.0",
|
||||
"vega-lite": "^5.5.0",
|
||||
|
@ -42,6 +44,7 @@
|
|||
"@types/node": "^18.8.0",
|
||||
"@types/react": "^18.0.21",
|
||||
"@types/styled-components": "^5.1.26",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@types/webpack": "^5.28.0",
|
||||
"canvas": "^2.10.1",
|
||||
"cross-env": "^7.0.3",
|
||||
|
@ -82,7 +85,8 @@
|
|||
"format": "prettier --write .",
|
||||
"prepack": "yarn run build:cjs && yarn run bundle",
|
||||
"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": {
|
||||
"extends": [
|
||||
|
|
|
@ -2,23 +2,21 @@ import * as React from "react";
|
|||
import {
|
||||
SqValue,
|
||||
environment,
|
||||
defaultEnvironment,
|
||||
resultMap,
|
||||
SqValueTag,
|
||||
SqProject,
|
||||
} from "@quri/squiggle-lang";
|
||||
import { useSquiggle } from "../lib/hooks";
|
||||
import { SquiggleViewer } from "./SquiggleViewer";
|
||||
import { JsImports } from "../lib/jsImports";
|
||||
|
||||
export interface SquiggleChartProps {
|
||||
export type SquiggleChartProps = {
|
||||
/** The input string for squiggle */
|
||||
code?: string;
|
||||
code: string;
|
||||
/** Allows to re-run the code if code hasn't changed */
|
||||
executionId?: number;
|
||||
/** If the output requires monte carlo sampling, the amount of samples */
|
||||
sampleCount?: number;
|
||||
/** The amount of points returned to draw the distribution */
|
||||
environment?: environment;
|
||||
/** If the result is a function, where the function domain starts */
|
||||
diagramStart?: number;
|
||||
/** 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 */
|
||||
diagramCount?: number;
|
||||
/** When the squiggle code gets reevaluated */
|
||||
onChange?(expr: SqValue | undefined): void;
|
||||
onChange?(expr: SqValue | undefined, sourceName: string): void;
|
||||
/** CSS width of the element */
|
||||
width?: 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 */
|
||||
distributionChartActions?: 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 defaultImports: JsImports = {};
|
||||
|
||||
export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
|
||||
({
|
||||
code = "",
|
||||
executionId = 0,
|
||||
environment,
|
||||
onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here
|
||||
height = 200,
|
||||
jsImports = defaultImports,
|
||||
showSummary = false,
|
||||
width,
|
||||
logX = false,
|
||||
expY = false,
|
||||
diagramStart = 0,
|
||||
diagramStop = 10,
|
||||
diagramCount = 20,
|
||||
tickFormat,
|
||||
minX,
|
||||
maxX,
|
||||
color,
|
||||
title,
|
||||
xAxisType = "number",
|
||||
distributionChartActions,
|
||||
enableLocalSettings = false,
|
||||
}) => {
|
||||
const { result, bindings } = useSquiggle({
|
||||
(props: SquiggleChartProps) => {
|
||||
const {
|
||||
executionId = 0,
|
||||
onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here
|
||||
height = 200,
|
||||
jsImports = defaultImports,
|
||||
showSummary = false,
|
||||
width,
|
||||
logX = false,
|
||||
expY = false,
|
||||
diagramStart = 0,
|
||||
diagramStop = 10,
|
||||
diagramCount = 20,
|
||||
tickFormat,
|
||||
minX,
|
||||
maxX,
|
||||
color,
|
||||
title,
|
||||
xAxisType = "number",
|
||||
distributionChartActions,
|
||||
enableLocalSettings = false,
|
||||
code,
|
||||
continues = [],
|
||||
} = 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,
|
||||
environment,
|
||||
jsImports,
|
||||
onChange,
|
||||
executionId,
|
||||
|
@ -120,7 +149,7 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
|
|||
height={height}
|
||||
distributionPlotSettings={distributionPlotSettings}
|
||||
chartSettings={chartSettings}
|
||||
environment={environment ?? defaultEnvironment}
|
||||
environment={p.getEnvironment()}
|
||||
enableLocalSettings={enableLocalSettings}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -2,7 +2,7 @@ import React, { useContext } from "react";
|
|||
import { SqDistributionTag, SqValue, SqValueTag } from "@quri/squiggle-lang";
|
||||
import { NumberShower } from "../NumberShower";
|
||||
import { DistributionChart, defaultPlot, makePlot } from "../DistributionChart";
|
||||
import { FunctionChart, FunctionChartSettings } from "../FunctionChart";
|
||||
import { FunctionChart } from "../FunctionChart";
|
||||
import clsx from "clsx";
|
||||
import { VariableBox } from "./VariableBox";
|
||||
import { ItemSettingsMenu } from "./ItemSettingsMenu";
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export { SqProject } from "@quri/squiggle-lang/";
|
||||
export { SquiggleChart } from "./components/SquiggleChart";
|
||||
export { SquiggleEditor } from "./components/SquiggleEditor";
|
||||
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 { JsImports, jsImportsToSquiggleCode } from "../jsImports";
|
||||
import * as uuid from "uuid";
|
||||
|
||||
type SquiggleArgs = {
|
||||
code: string;
|
||||
executionId?: number;
|
||||
jsImports?: JsImports;
|
||||
environment?: environment;
|
||||
onChange?: (expr: SqValue | undefined) => void;
|
||||
project: SqProject;
|
||||
continues: string[];
|
||||
onChange?: (expr: SqValue | undefined, sourceName: string) => void;
|
||||
};
|
||||
|
||||
const importSourceName = (sourceName: string) => "imports-" + sourceName;
|
||||
|
||||
export const useSquiggle = (args: SquiggleArgs) => {
|
||||
const sourceName = useMemo(() => uuid.v4(), []);
|
||||
|
||||
const env = args.project.getEnvironment();
|
||||
|
||||
const result = useMemo(
|
||||
() => {
|
||||
const project = SqProject.create();
|
||||
project.setSource("main", args.code);
|
||||
if (args.environment) {
|
||||
project.setEnvironment(args.environment);
|
||||
}
|
||||
const project = args.project;
|
||||
|
||||
project.setSource(sourceName, args.code);
|
||||
let continues = args.continues;
|
||||
if (args.jsImports && Object.keys(args.jsImports).length) {
|
||||
const importsSource = jsImportsToSquiggleCode(args.jsImports);
|
||||
project.setSource("imports", importsSource);
|
||||
project.setContinues("main", ["imports"]);
|
||||
project.setSource(importSourceName(sourceName), importsSource);
|
||||
continues = args.continues.concat(importSourceName(sourceName));
|
||||
}
|
||||
project.run("main");
|
||||
const result = project.getResult("main");
|
||||
const bindings = project.getBindings("main");
|
||||
project.setContinues(sourceName, continues);
|
||||
project.run(sourceName);
|
||||
const result = project.getResult(sourceName);
|
||||
const bindings = project.getBindings(sourceName);
|
||||
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
|
||||
[args.code, args.environment, args.jsImports, args.executionId]
|
||||
[
|
||||
args.code,
|
||||
args.jsImports,
|
||||
args.executionId,
|
||||
sourceName,
|
||||
args.continues,
|
||||
args.project,
|
||||
env,
|
||||
]
|
||||
);
|
||||
|
||||
const { onChange } = args;
|
||||
|
||||
useEffect(() => {
|
||||
onChange?.(result.result.tag === "Ok" ? result.result.value : undefined);
|
||||
}, [result, onChange]);
|
||||
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;
|
||||
};
|
||||
|
|
|
@ -3,11 +3,11 @@ import React from "react";
|
|||
import "@testing-library/jest-dom";
|
||||
import { SquiggleChart } from "../src/index";
|
||||
|
||||
test("Logs no warnings or errors", async () => {
|
||||
debugger;
|
||||
test("Logs nothing on render", async () => {
|
||||
const { unmount } = render(<SquiggleChart code={"normal(0, 1)"} />);
|
||||
unmount();
|
||||
|
||||
expect(console.log).not.toBeCalled();
|
||||
expect(console.warn).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"
|
||||
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":
|
||||
version "1.71.0"
|
||||
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"
|
||||
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:
|
||||
version "3.0.1"
|
||||
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