From 07731adc6f2d288f9e80b78e199388647ec144ec Mon Sep 17 00:00:00 2001 From: Sam Nolan Date: Mon, 19 Sep 2022 13:58:20 +1000 Subject: [PATCH 01/15] Add projects to components --- .../src/components/SquiggleChart.tsx | 15 +++++++++++- packages/components/src/index.ts | 1 + .../components/src/lib/hooks/useSquiggle.ts | 23 +++++++++++++------ 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/packages/components/src/components/SquiggleChart.tsx b/packages/components/src/components/SquiggleChart.tsx index 4dd57a1f..5d4c21fc 100644 --- a/packages/components/src/components/SquiggleChart.tsx +++ b/packages/components/src/components/SquiggleChart.tsx @@ -5,6 +5,7 @@ import { defaultEnvironment, resultMap, SqValueTag, + SqProject, } from "@quri/squiggle-lang"; import { useSquiggle } from "../lib/hooks"; import { SquiggleViewer } from "./SquiggleViewer"; @@ -53,6 +54,12 @@ export interface SquiggleChartProps { /** Whether to show vega actions to the user, so they can copy the chart spec */ distributionChartActions?: boolean; enableLocalSettings?: boolean; + /** The project that this execution is part of */ + project?: SqProject; + /** The name of the squiggle execution source. Defaults to "main" */ + sourceName?: string; + /** The sources that this execution continues */ + includes?: string[]; } const defaultOnChange = () => {}; @@ -60,7 +67,7 @@ const defaultImports: JsImports = {}; export const SquiggleChart: React.FC = React.memo( ({ - code = "", + code, executionId = 0, environment, onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here @@ -81,8 +88,14 @@ export const SquiggleChart: React.FC = React.memo( xAxisType = "number", distributionChartActions, enableLocalSettings = false, + sourceName = "main", + includes = [], + project = SqProject.create(), }) => { const { result, bindings } = useSquiggle({ + sourceName, + includes, + project, code, environment, jsImports, diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index bbd0d178..dd6f9a94 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -2,3 +2,4 @@ export { SquiggleChart } from "./components/SquiggleChart"; export { SquiggleEditor } from "./components/SquiggleEditor"; export { SquigglePlayground } from "./components/SquigglePlayground"; export { SquiggleContainer } from "./components/SquiggleContainer"; +export * as squiggle from "@quri/squiggle-lang" diff --git a/packages/components/src/lib/hooks/useSquiggle.ts b/packages/components/src/lib/hooks/useSquiggle.ts index 5916ac54..e6336eea 100644 --- a/packages/components/src/lib/hooks/useSquiggle.ts +++ b/packages/components/src/lib/hooks/useSquiggle.ts @@ -3,29 +3,38 @@ import { useEffect, useMemo } from "react"; import { JsImports, jsImportsToSquiggleCode } from "../jsImports"; type SquiggleArgs = { - code: string; + code?: string; executionId?: number; jsImports?: JsImports; environment?: environment; + project: SqProject; + sourceName: string; + includes: string[]; onChange?: (expr: SqValue | undefined) => void; }; export const useSquiggle = (args: SquiggleArgs) => { const result = useMemo( () => { - const project = SqProject.create(); - project.setSource("main", args.code); + const project = args.project; + let code = project.getSource(args.sourceName) + if(args.code) { + code = args.code + } + project.setSource(args.sourceName, code ?? ""); + let includes = args.includes; if (args.environment) { project.setEnvironment(args.environment); } if (args.jsImports && Object.keys(args.jsImports).length) { const importsSource = jsImportsToSquiggleCode(args.jsImports); project.setSource("imports", importsSource); - project.setContinues("main", ["imports"]); + includes.push("imports") } - project.run("main"); - const result = project.getResult("main"); - const bindings = project.getBindings("main"); + project.setContinues(args.sourceName, includes); + project.run(args.sourceName); + const result = project.getResult(args.sourceName); + const bindings = project.getBindings(args.sourceName); return { result, bindings }; }, // eslint-disable-next-line react-hooks/exhaustive-deps From c20af0edbdfd29ad8845cc9017347399cce9dbbd Mon Sep 17 00:00:00 2001 From: Sam Nolan Date: Mon, 19 Sep 2022 14:00:49 +1000 Subject: [PATCH 02/15] Format components --- packages/components/src/index.ts | 2 +- packages/components/src/lib/hooks/useSquiggle.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index dd6f9a94..21bf157d 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -2,4 +2,4 @@ export { SquiggleChart } from "./components/SquiggleChart"; export { SquiggleEditor } from "./components/SquiggleEditor"; export { SquigglePlayground } from "./components/SquigglePlayground"; export { SquiggleContainer } from "./components/SquiggleContainer"; -export * as squiggle from "@quri/squiggle-lang" +export * as squiggle from "@quri/squiggle-lang"; diff --git a/packages/components/src/lib/hooks/useSquiggle.ts b/packages/components/src/lib/hooks/useSquiggle.ts index e6336eea..15bf89bd 100644 --- a/packages/components/src/lib/hooks/useSquiggle.ts +++ b/packages/components/src/lib/hooks/useSquiggle.ts @@ -17,9 +17,9 @@ export const useSquiggle = (args: SquiggleArgs) => { const result = useMemo( () => { const project = args.project; - let code = project.getSource(args.sourceName) - if(args.code) { - code = args.code + let code = project.getSource(args.sourceName); + if (args.code) { + code = args.code; } project.setSource(args.sourceName, code ?? ""); let includes = args.includes; @@ -29,7 +29,7 @@ export const useSquiggle = (args: SquiggleArgs) => { if (args.jsImports && Object.keys(args.jsImports).length) { const importsSource = jsImportsToSquiggleCode(args.jsImports); project.setSource("imports", importsSource); - includes.push("imports") + includes.push("imports"); } project.setContinues(args.sourceName, includes); project.run(args.sourceName); From bd34b145f0fe4f2eafa671ddfb0cbd2ab1c3b7d1 Mon Sep 17 00:00:00 2001 From: Sam Nolan Date: Mon, 19 Sep 2022 14:04:04 +1000 Subject: [PATCH 03/15] Remove re-export --- packages/components/src/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index 21bf157d..bbd0d178 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -2,4 +2,3 @@ export { SquiggleChart } from "./components/SquiggleChart"; export { SquiggleEditor } from "./components/SquiggleEditor"; export { SquigglePlayground } from "./components/SquigglePlayground"; export { SquiggleContainer } from "./components/SquiggleContainer"; -export * as squiggle from "@quri/squiggle-lang"; From 2458762acd30b074b678a7366e878b937599525d Mon Sep 17 00:00:00 2001 From: Sam Nolan Date: Wed, 21 Sep 2022 13:02:33 +1000 Subject: [PATCH 04/15] Add auto generated cell ids --- packages/components/package.json | 2 + .../src/components/SquiggleChart.tsx | 4 +- packages/components/src/index.ts | 2 + .../components/src/lib/hooks/useSquiggle.ts | 48 ++++++++++++++----- yarn.lock | 20 ++++++-- 5 files changed, 57 insertions(+), 19 deletions(-) diff --git a/packages/components/package.json b/packages/components/package.json index 110608bc..551dc4cb 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -10,6 +10,7 @@ "@hookform/resolvers": "^2.9.8", "@quri/squiggle-lang": "^0.4.2", "@react-hook/size": "^2.1.2", + "@types/uuid": "^8.3.4", "clsx": "^1.2.1", "framer-motion": "^7.3.2", "lodash": "^4.17.21", @@ -18,6 +19,7 @@ "react-hook-form": "^7.35.0", "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", diff --git a/packages/components/src/components/SquiggleChart.tsx b/packages/components/src/components/SquiggleChart.tsx index 5d4c21fc..91e1a7b8 100644 --- a/packages/components/src/components/SquiggleChart.tsx +++ b/packages/components/src/components/SquiggleChart.tsx @@ -27,7 +27,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; @@ -88,7 +88,7 @@ export const SquiggleChart: React.FC = React.memo( xAxisType = "number", distributionChartActions, enableLocalSettings = false, - sourceName = "main", + sourceName, includes = [], project = SqProject.create(), }) => { diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index bbd0d178..aac6944b 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -1,4 +1,6 @@ +import { SqProject } from "@quri/squiggle-lang/"; export { SquiggleChart } from "./components/SquiggleChart"; export { SquiggleEditor } from "./components/SquiggleEditor"; export { SquigglePlayground } from "./components/SquigglePlayground"; export { SquiggleContainer } from "./components/SquiggleContainer"; +export const newProject = () => SqProject.create(); diff --git a/packages/components/src/lib/hooks/useSquiggle.ts b/packages/components/src/lib/hooks/useSquiggle.ts index 15bf89bd..3dc053e6 100644 --- a/packages/components/src/lib/hooks/useSquiggle.ts +++ b/packages/components/src/lib/hooks/useSquiggle.ts @@ -1,6 +1,7 @@ import { environment, SqProject, SqValue } from "@quri/squiggle-lang"; import { useEffect, useMemo } from "react"; import { JsImports, jsImportsToSquiggleCode } from "../jsImports"; +import { v4 as uuidv4 } from "uuid"; type SquiggleArgs = { code?: string; @@ -8,20 +9,34 @@ type SquiggleArgs = { jsImports?: JsImports; environment?: environment; project: SqProject; - sourceName: string; + sourceName?: string; includes: string[]; - onChange?: (expr: SqValue | undefined) => void; + onChange?: (expr: SqValue | undefined, sourceName: string) => void; }; export const useSquiggle = (args: SquiggleArgs) => { + const autogenName = useMemo(() => uuidv4(), []); + const result = useMemo( () => { const project = args.project; - let code = project.getSource(args.sourceName); - if (args.code) { - code = args.code; + let needsClean = true; + + let sourceName = ""; + // If the user specified a source and it already exists, assume we don't + // own the source + if (args.sourceName && project.getSource(args.sourceName)) { + needsClean = false; + sourceName = args.sourceName; + } else { + // Otherwise create a source, either with the name given or an automatic one + if (args.sourceName) { + sourceName = args.sourceName; + } else { + sourceName = autogenName; + } + project.setSource(sourceName, args.code ?? ""); } - project.setSource(args.sourceName, code ?? ""); let includes = args.includes; if (args.environment) { project.setEnvironment(args.environment); @@ -31,11 +46,11 @@ export const useSquiggle = (args: SquiggleArgs) => { project.setSource("imports", importsSource); includes.push("imports"); } - project.setContinues(args.sourceName, includes); - project.run(args.sourceName); - const result = project.getResult(args.sourceName); - const bindings = project.getBindings(args.sourceName); - return { result, bindings }; + project.setContinues(sourceName, includes); + project.run(sourceName); + const result = project.getResult(sourceName); + const bindings = project.getBindings(sourceName); + return { result, bindings, sourceName, needsClean }; }, // eslint-disable-next-line react-hooks/exhaustive-deps [args.code, args.environment, args.jsImports, args.executionId] @@ -44,8 +59,17 @@ export const useSquiggle = (args: SquiggleArgs) => { const { onChange } = args; useEffect(() => { - onChange?.(result.result.tag === "Ok" ? result.result.value : undefined); + onChange?.( + result.result.tag === "Ok" ? result.result.value : undefined, + result.sourceName + ); }, [result, onChange]); + useEffect(() => { + return () => { + if (!args.sourceName) args.project.clean(result.sourceName); + }; + }); + return result; }; diff --git a/yarn.lock b/yarn.lock index 9a156c50..0e4640ce 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4797,10 +4797,10 @@ "@types/history" "^4.7.11" "@types/react" "*" -"@types/react@*", "@types/react@^18.0.1", "@types/react@^18.0.18": - version "18.0.19" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.19.tgz#269a5f35b9a73c69dfb0c7189017013ab02acbaa" - integrity sha512-BDc3Q+4Q3zsn7k9xZrKfjWyJsSlEDMs38gD1qp2eDazLCdcPqAT+vq1ND+Z8AGel/UiwzNUk8ptpywgNQcJ1MQ== +"@types/react@*", "@types/react@17.0.43", "@types/react@^18.0.18": + version "17.0.43" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.43.tgz#4adc142887dd4a2601ce730bc56c3436fdb07a55" + integrity sha512-8Q+LNpdxf057brvPu1lMtC5Vn7J119xrP1aq4qiaefNioQUYANF/CYeK4NsKorSZyUGJ66g0IM+4bbjwx45o2A== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" @@ -4900,6 +4900,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" @@ -15152,7 +15157,7 @@ react-vega@^7.6.0: prop-types "^15.8.1" vega-embed "^6.5.1" -react@^18.0.0, react@^18.1.0: +react@^18.1.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== @@ -17839,6 +17844,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" From b7d634de8651bcaf98c7da41992ad0993f56ab78 Mon Sep 17 00:00:00 2001 From: Sam Nolan Date: Wed, 21 Sep 2022 13:40:15 +1000 Subject: [PATCH 05/15] Rename import as prefix to real source name --- packages/components/src/lib/hooks/useSquiggle.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/components/src/lib/hooks/useSquiggle.ts b/packages/components/src/lib/hooks/useSquiggle.ts index 3dc053e6..085bc6f9 100644 --- a/packages/components/src/lib/hooks/useSquiggle.ts +++ b/packages/components/src/lib/hooks/useSquiggle.ts @@ -14,6 +14,8 @@ type SquiggleArgs = { onChange?: (expr: SqValue | undefined, sourceName: string) => void; }; +const importSourceName = (sourceName: string) => "imports-" + sourceName; + export const useSquiggle = (args: SquiggleArgs) => { const autogenName = useMemo(() => uuidv4(), []); @@ -43,8 +45,8 @@ export const useSquiggle = (args: SquiggleArgs) => { } if (args.jsImports && Object.keys(args.jsImports).length) { const importsSource = jsImportsToSquiggleCode(args.jsImports); - project.setSource("imports", importsSource); - includes.push("imports"); + project.setSource(importSourceName(sourceName), importsSource); + includes.push(importSourceName(sourceName)); } project.setContinues(sourceName, includes); project.run(sourceName); @@ -68,6 +70,8 @@ export const useSquiggle = (args: SquiggleArgs) => { useEffect(() => { return () => { if (!args.sourceName) args.project.clean(result.sourceName); + if (args.project.getSource(importSourceName(result.sourceName))) + args.project.clean(result.sourceName); }; }); From 3ae442ee1f03e5dbe2db97840c2c210a79221347 Mon Sep 17 00:00:00 2001 From: Sam Nolan Date: Thu, 22 Sep 2022 10:02:15 +1000 Subject: [PATCH 06/15] Fix some collection issues --- packages/components/src/components/SquiggleChart.tsx | 2 +- packages/components/src/lib/hooks/useSquiggle.ts | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/components/src/components/SquiggleChart.tsx b/packages/components/src/components/SquiggleChart.tsx index 91e1a7b8..c48696bb 100644 --- a/packages/components/src/components/SquiggleChart.tsx +++ b/packages/components/src/components/SquiggleChart.tsx @@ -56,7 +56,7 @@ export interface SquiggleChartProps { enableLocalSettings?: boolean; /** The project that this execution is part of */ project?: SqProject; - /** The name of the squiggle execution source. Defaults to "main" */ + /** The name of the squiggle execution source. Generates a UUID if not given */ sourceName?: string; /** The sources that this execution continues */ includes?: string[]; diff --git a/packages/components/src/lib/hooks/useSquiggle.ts b/packages/components/src/lib/hooks/useSquiggle.ts index 085bc6f9..9878206e 100644 --- a/packages/components/src/lib/hooks/useSquiggle.ts +++ b/packages/components/src/lib/hooks/useSquiggle.ts @@ -69,11 +69,13 @@ export const useSquiggle = (args: SquiggleArgs) => { useEffect(() => { return () => { - if (!args.sourceName) args.project.clean(result.sourceName); + if (result.needsClean) args.project.clean(result.sourceName); if (args.project.getSource(importSourceName(result.sourceName))) args.project.clean(result.sourceName); }; - }); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + []); return result; }; From b4fbd1f8e8d63b10105f59e440a4b96e3910a476 Mon Sep 17 00:00:00 2001 From: Sam Nolan Date: Mon, 26 Sep 2022 13:55:22 +1000 Subject: [PATCH 07/15] Format and ts errors --- .../SquiggleViewer/ExpressionViewer.tsx | 4 ++-- .../components/src/lib/hooks/useSquiggle.ts | 20 ++++++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/components/src/components/SquiggleViewer/ExpressionViewer.tsx b/packages/components/src/components/SquiggleViewer/ExpressionViewer.tsx index 8bd32292..261c189b 100644 --- a/packages/components/src/components/SquiggleViewer/ExpressionViewer.tsx +++ b/packages/components/src/components/SquiggleViewer/ExpressionViewer.tsx @@ -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"; @@ -298,7 +298,7 @@ export const ExpressionViewer: React.FC = ({ value, width }) => { {() => (
No display for type: {" "} - {value.tag} + {(value as {tag: string}).tag}
)} diff --git a/packages/components/src/lib/hooks/useSquiggle.ts b/packages/components/src/lib/hooks/useSquiggle.ts index 9878206e..eba85eaf 100644 --- a/packages/components/src/lib/hooks/useSquiggle.ts +++ b/packages/components/src/lib/hooks/useSquiggle.ts @@ -67,15 +67,17 @@ export const useSquiggle = (args: SquiggleArgs) => { ); }, [result, onChange]); - useEffect(() => { - return () => { - if (result.needsClean) args.project.clean(result.sourceName); - if (args.project.getSource(importSourceName(result.sourceName))) - args.project.clean(result.sourceName); - }; - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - []); + useEffect( + () => { + return () => { + if (result.needsClean) args.project.clean(result.sourceName); + if (args.project.getSource(importSourceName(result.sourceName))) + args.project.clean(result.sourceName); + }; + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ); return result; }; From a0e3f70dbbe371b35f18f39c7c986ca42f1e777e Mon Sep 17 00:00:00 2001 From: Sam Nolan Date: Tue, 4 Oct 2022 12:22:41 +1100 Subject: [PATCH 08/15] Add cleanup tests --- .gitignore | 1 + packages/components/package.json | 4 +++- .../components/src/lib/hooks/useSquiggle.ts | 8 ++++---- packages/components/test/basic.test.tsx | 4 ++-- packages/components/test/cleanup.test.tsx | 19 +++++++++++++++++++ yarn.lock | 10 +++++----- 6 files changed, 34 insertions(+), 12 deletions(-) create mode 100644 packages/components/test/cleanup.test.tsx diff --git a/.gitignore b/.gitignore index 8f538f09..0712e779 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ yarn-error.log .vscode todo.txt result +shell.nix diff --git a/packages/components/package.json b/packages/components/package.json index cbb83043..6e0cba3f 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -44,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", @@ -84,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": [ diff --git a/packages/components/src/lib/hooks/useSquiggle.ts b/packages/components/src/lib/hooks/useSquiggle.ts index eba85eaf..c291976f 100644 --- a/packages/components/src/lib/hooks/useSquiggle.ts +++ b/packages/components/src/lib/hooks/useSquiggle.ts @@ -1,7 +1,7 @@ import { environment, SqProject, SqValue } from "@quri/squiggle-lang"; import { useEffect, useMemo } from "react"; import { JsImports, jsImportsToSquiggleCode } from "../jsImports"; -import { v4 as uuidv4 } from "uuid"; +import * as uuid from 'uuid'; type SquiggleArgs = { code?: string; @@ -17,7 +17,7 @@ type SquiggleArgs = { const importSourceName = (sourceName: string) => "imports-" + sourceName; export const useSquiggle = (args: SquiggleArgs) => { - const autogenName = useMemo(() => uuidv4(), []); + const autogenName = useMemo(() => uuid.v4(), []); const result = useMemo( () => { @@ -70,9 +70,9 @@ export const useSquiggle = (args: SquiggleArgs) => { useEffect( () => { return () => { - if (result.needsClean) args.project.clean(result.sourceName); + if (result.needsClean) args.project.removeSource(result.sourceName); if (args.project.getSource(importSourceName(result.sourceName))) - args.project.clean(result.sourceName); + args.project.removeSource(result.sourceName); }; }, // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/packages/components/test/basic.test.tsx b/packages/components/test/basic.test.tsx index 9eb4973a..e77f104b 100644 --- a/packages/components/test/basic.test.tsx +++ b/packages/components/test/basic.test.tsx @@ -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(); unmount(); + expect(console.log).not.toBeCalled(); expect(console.warn).not.toBeCalled(); expect(console.error).not.toBeCalled(); }); diff --git a/packages/components/test/cleanup.test.tsx b/packages/components/test/cleanup.test.tsx new file mode 100644 index 00000000..081bfe4f --- /dev/null +++ b/packages/components/test/cleanup.test.tsx @@ -0,0 +1,19 @@ +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(); + 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) +}); diff --git a/yarn.lock b/yarn.lock index 2292e288..f8636023 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5078,10 +5078,10 @@ "@types/history" "^4.7.11" "@types/react" "*" -"@types/react@*", "@types/react@^18.0.1", "@types/react@^18.0.21": - version "18.0.21" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.21.tgz#b8209e9626bb00a34c76f55482697edd2b43cc67" - integrity sha512-7QUCOxvFgnD5Jk8ZKlUAhVcRj7GuJRjnjjiY/IUBWKgOlnvDvTMLD4RTF7NPyVmbRhNrbomZiOepg7M/2Kj1mA== +"@types/react@*", "@types/react@17.0.43", "@types/react@^18.0.21": + version "17.0.43" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.43.tgz#4adc142887dd4a2601ce730bc56c3436fdb07a55" + integrity sha512-8Q+LNpdxf057brvPu1lMtC5Vn7J119xrP1aq4qiaefNioQUYANF/CYeK4NsKorSZyUGJ66g0IM+4bbjwx45o2A== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" @@ -15979,7 +15979,7 @@ react-vega@^7.6.0: prop-types "^15.8.1" vega-embed "^6.5.1" -react@^18.0.0, react@^18.1.0: +react@^18.1.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== From b412eee646c81f001ec99f8b3b465a5f16989de7 Mon Sep 17 00:00:00 2001 From: Sam Nolan Date: Tue, 4 Oct 2022 13:38:15 +1100 Subject: [PATCH 09/15] Add tests for different types of cleanup --- .../components/src/lib/hooks/useSquiggle.ts | 2 +- packages/components/test/cleanup.test.tsx | 56 ++++++++++++++++--- 2 files changed, 50 insertions(+), 8 deletions(-) diff --git a/packages/components/src/lib/hooks/useSquiggle.ts b/packages/components/src/lib/hooks/useSquiggle.ts index c291976f..826c0987 100644 --- a/packages/components/src/lib/hooks/useSquiggle.ts +++ b/packages/components/src/lib/hooks/useSquiggle.ts @@ -1,7 +1,7 @@ import { environment, SqProject, SqValue } from "@quri/squiggle-lang"; import { useEffect, useMemo } from "react"; import { JsImports, jsImportsToSquiggleCode } from "../jsImports"; -import * as uuid from 'uuid'; +import * as uuid from "uuid"; type SquiggleArgs = { code?: string; diff --git a/packages/components/test/cleanup.test.tsx b/packages/components/test/cleanup.test.tsx index 081bfe4f..b9b0c29f 100644 --- a/packages/components/test/cleanup.test.tsx +++ b/packages/components/test/cleanup.test.tsx @@ -4,16 +4,58 @@ import "@testing-library/jest-dom"; import { SquiggleChart } from "../src/index"; import { SqProject } from "@quri/squiggle-lang"; -test("Creates and cleans up source", async () => { +test("Creates and cleans up source with no name", async () => { const project = SqProject.create(); - const { unmount } = render(); - expect(project.getSourceIds().length).toBe(1) + const { unmount } = render( + + ); + expect(project.getSourceIds().length).toBe(1); - const sourceId = project.getSourceIds()[0] - expect(project.getSource(sourceId)).toBe("normal(0, 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) + expect(project.getSourceIds().length).toBe(0); + expect(project.getSource(sourceId)).toBe(undefined); +}); + +test("Does not clean up existing source", async () => { + const project = SqProject.create(); + + project.setSource("main", "normal(0, 1)"); + + const { unmount } = render( + + ); + + expect(project.getSourceIds()).toStrictEqual(["main"]); + + const sourceId = project.getSourceIds()[0]; + expect(project.getSource(sourceId)).toBe("normal(0, 1)"); + + unmount(); + expect(project.getSourceIds()).toStrictEqual(["main"]); + expect(project.getSource(sourceId)).toBe("normal(0, 1)"); +}); + +test("Does clean up when given non-existant source", async () => { + const project = SqProject.create(); + + const { unmount } = render( + + ); + + expect(project.getSourceIds()).toStrictEqual(["main"]); + + const sourceId = project.getSourceIds()[0]; + expect(project.getSource(sourceId)).toBe("normal(0, 1)"); + + unmount(); + expect(project.getSourceIds()).toStrictEqual([]); + expect(project.getSource(sourceId)).toBe(undefined); }); From b512751110b876f04755d9cdd734be26b070dd3a Mon Sep 17 00:00:00 2001 From: Sam Nolan Date: Tue, 4 Oct 2022 14:03:35 +1100 Subject: [PATCH 10/15] Update yarn.lock --- yarn.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/yarn.lock b/yarn.lock index f8636023..2292e288 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5078,10 +5078,10 @@ "@types/history" "^4.7.11" "@types/react" "*" -"@types/react@*", "@types/react@17.0.43", "@types/react@^18.0.21": - version "17.0.43" - resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.43.tgz#4adc142887dd4a2601ce730bc56c3436fdb07a55" - integrity sha512-8Q+LNpdxf057brvPu1lMtC5Vn7J119xrP1aq4qiaefNioQUYANF/CYeK4NsKorSZyUGJ66g0IM+4bbjwx45o2A== +"@types/react@*", "@types/react@^18.0.1", "@types/react@^18.0.21": + version "18.0.21" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.21.tgz#b8209e9626bb00a34c76f55482697edd2b43cc67" + integrity sha512-7QUCOxvFgnD5Jk8ZKlUAhVcRj7GuJRjnjjiY/IUBWKgOlnvDvTMLD4RTF7NPyVmbRhNrbomZiOepg7M/2Kj1mA== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" @@ -15979,7 +15979,7 @@ react-vega@^7.6.0: prop-types "^15.8.1" vega-embed "^6.5.1" -react@^18.1.0: +react@^18.0.0, react@^18.1.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== From bb6fece69427ab096716636539ccaf9b48c7c303 Mon Sep 17 00:00:00 2001 From: Sam Nolan Date: Wed, 5 Oct 2022 11:57:32 +1100 Subject: [PATCH 11/15] Restrict interfaces with projects for components --- .../src/components/SquiggleChart.tsx | 100 ++++++++++-------- .../components/src/lib/hooks/useSquiggle.ts | 53 ++++------ packages/components/test/cleanup.test.tsx | 41 +------ 3 files changed, 78 insertions(+), 116 deletions(-) diff --git a/packages/components/src/components/SquiggleChart.tsx b/packages/components/src/components/SquiggleChart.tsx index c48696bb..d9ab3168 100644 --- a/packages/components/src/components/SquiggleChart.tsx +++ b/packages/components/src/components/SquiggleChart.tsx @@ -11,15 +11,13 @@ 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 */ @@ -54,50 +52,66 @@ export interface SquiggleChartProps { /** Whether to show vega actions to the user, so they can copy the chart spec */ distributionChartActions?: boolean; enableLocalSettings?: boolean; - /** The project that this execution is part of */ - project?: SqProject; - /** The name of the squiggle execution source. Generates a UUID if not given */ - sourceName?: string; - /** The sources that this execution continues */ - includes?: string[]; -} +} & (StandaloneExecutionProps | ProjectExecutionProps); +// Props needed for a standalone execution +type StandaloneExecutionProps = { + /** Project must be undefined */ + project?: undefined; + /** Includes must be undefined */ + includes?: 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 must be undefined (we don't set it here, users can set the environment outside the execution) */ + environment?: undefined; + /** The project that this execution is part of */ + project: SqProject; + /** What other squiggle sources from the project to include. Default none */ + includes?: string[]; +}; const defaultOnChange = () => {}; const defaultImports: JsImports = {}; export const SquiggleChart: React.FC = 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, - sourceName, - includes = [], - project = SqProject.create(), - }) => { - const { result, bindings } = useSquiggle({ - sourceName, - includes, - project, + (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, + project = SqProject.create(), + code, + includes = [], + } = props; + + const p = project ?? SqProject.create(); + if (!project && props.environment) { + p.setEnvironment(props.environment); + } + + const { result, bindings } = useSquiggle({ + includes, + project: p, code, - environment, jsImports, onChange, executionId, @@ -133,7 +147,7 @@ export const SquiggleChart: React.FC = React.memo( height={height} distributionPlotSettings={distributionPlotSettings} chartSettings={chartSettings} - environment={environment ?? defaultEnvironment} + environment={p.getEnvironment()} enableLocalSettings={enableLocalSettings} /> ); diff --git a/packages/components/src/lib/hooks/useSquiggle.ts b/packages/components/src/lib/hooks/useSquiggle.ts index 826c0987..86126a8a 100644 --- a/packages/components/src/lib/hooks/useSquiggle.ts +++ b/packages/components/src/lib/hooks/useSquiggle.ts @@ -7,9 +7,7 @@ type SquiggleArgs = { code?: string; executionId?: number; jsImports?: JsImports; - environment?: environment; project: SqProject; - sourceName?: string; includes: string[]; onChange?: (expr: SqValue | undefined, sourceName: string) => void; }; @@ -17,32 +15,15 @@ type SquiggleArgs = { const importSourceName = (sourceName: string) => "imports-" + sourceName; export const useSquiggle = (args: SquiggleArgs) => { - const autogenName = useMemo(() => uuid.v4(), []); + const sourceName = useMemo(() => uuid.v4(), []); const result = useMemo( () => { const project = args.project; let needsClean = true; - let sourceName = ""; - // If the user specified a source and it already exists, assume we don't - // own the source - if (args.sourceName && project.getSource(args.sourceName)) { - needsClean = false; - sourceName = args.sourceName; - } else { - // Otherwise create a source, either with the name given or an automatic one - if (args.sourceName) { - sourceName = args.sourceName; - } else { - sourceName = autogenName; - } - project.setSource(sourceName, args.code ?? ""); - } + project.setSource(sourceName, args.code ?? ""); let includes = args.includes; - if (args.environment) { - project.setEnvironment(args.environment); - } if (args.jsImports && Object.keys(args.jsImports).length) { const importsSource = jsImportsToSquiggleCode(args.jsImports); project.setSource(importSourceName(sourceName), importsSource); @@ -54,8 +35,18 @@ export const useSquiggle = (args: SquiggleArgs) => { const bindings = project.getBindings(sourceName); return { result, bindings, sourceName, needsClean }; }, + // 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.includes, + args.project, + ] ); const { onChange } = args; @@ -67,17 +58,13 @@ export const useSquiggle = (args: SquiggleArgs) => { ); }, [result, onChange]); - useEffect( - () => { - return () => { - if (result.needsClean) args.project.removeSource(result.sourceName); - if (args.project.getSource(importSourceName(result.sourceName))) - args.project.removeSource(result.sourceName); - }; - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [] - ); + useEffect(() => { + return () => { + args.project.removeSource(result.sourceName); + if (args.project.getSource(importSourceName(result.sourceName))) + args.project.removeSource(result.sourceName); + }; + }, [args.project, result.sourceName]); return result; }; diff --git a/packages/components/test/cleanup.test.tsx b/packages/components/test/cleanup.test.tsx index b9b0c29f..7bd109e4 100644 --- a/packages/components/test/cleanup.test.tsx +++ b/packages/components/test/cleanup.test.tsx @@ -10,6 +10,7 @@ test("Creates and cleans up source with no name", async () => { const { unmount } = render( ); + expect(project.getSourceIds().length).toBe(1); const sourceId = project.getSourceIds()[0]; @@ -19,43 +20,3 @@ test("Creates and cleans up source with no name", async () => { expect(project.getSourceIds().length).toBe(0); expect(project.getSource(sourceId)).toBe(undefined); }); - -test("Does not clean up existing source", async () => { - const project = SqProject.create(); - - project.setSource("main", "normal(0, 1)"); - - const { unmount } = render( - - ); - - expect(project.getSourceIds()).toStrictEqual(["main"]); - - const sourceId = project.getSourceIds()[0]; - expect(project.getSource(sourceId)).toBe("normal(0, 1)"); - - unmount(); - expect(project.getSourceIds()).toStrictEqual(["main"]); - expect(project.getSource(sourceId)).toBe("normal(0, 1)"); -}); - -test("Does clean up when given non-existant source", async () => { - const project = SqProject.create(); - - const { unmount } = render( - - ); - - expect(project.getSourceIds()).toStrictEqual(["main"]); - - const sourceId = project.getSourceIds()[0]; - expect(project.getSource(sourceId)).toBe("normal(0, 1)"); - - unmount(); - expect(project.getSourceIds()).toStrictEqual([]); - expect(project.getSource(sourceId)).toBe(undefined); -}); From f19c096e7ca12cd2b5d5d477422910b891b9d133 Mon Sep 17 00:00:00 2001 From: Sam Nolan Date: Wed, 5 Oct 2022 12:06:03 +1100 Subject: [PATCH 12/15] Remove needsClean --- packages/components/src/lib/hooks/useSquiggle.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/components/src/lib/hooks/useSquiggle.ts b/packages/components/src/lib/hooks/useSquiggle.ts index 86126a8a..089f0367 100644 --- a/packages/components/src/lib/hooks/useSquiggle.ts +++ b/packages/components/src/lib/hooks/useSquiggle.ts @@ -20,7 +20,6 @@ export const useSquiggle = (args: SquiggleArgs) => { const result = useMemo( () => { const project = args.project; - let needsClean = true; project.setSource(sourceName, args.code ?? ""); let includes = args.includes; @@ -33,7 +32,7 @@ export const useSquiggle = (args: SquiggleArgs) => { project.run(sourceName); const result = project.getResult(sourceName); const bindings = project.getBindings(sourceName); - return { result, bindings, sourceName, needsClean }; + return { result, bindings, sourceName }; }, // This complains about executionId not being used inside the function body. // This is on purpose, as executionId simply allows you to run the squiggle From a718b369c2fe56f27f232be066c4d71ce5034ca6 Mon Sep 17 00:00:00 2001 From: Sam Nolan Date: Thu, 6 Oct 2022 13:38:59 +1100 Subject: [PATCH 13/15] Make code neccesary in hook --- .../components/src/lib/hooks/useSquiggle.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/components/src/lib/hooks/useSquiggle.ts b/packages/components/src/lib/hooks/useSquiggle.ts index 089f0367..3a18502a 100644 --- a/packages/components/src/lib/hooks/useSquiggle.ts +++ b/packages/components/src/lib/hooks/useSquiggle.ts @@ -1,10 +1,10 @@ -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; + code: string; executionId?: number; jsImports?: JsImports; project: SqProject; @@ -21,7 +21,7 @@ export const useSquiggle = (args: SquiggleArgs) => { () => { const project = args.project; - project.setSource(sourceName, args.code ?? ""); + project.setSource(sourceName, args.code); let includes = args.includes; if (args.jsImports && Object.keys(args.jsImports).length) { const importsSource = jsImportsToSquiggleCode(args.jsImports); @@ -32,7 +32,7 @@ export const useSquiggle = (args: SquiggleArgs) => { project.run(sourceName); const result = project.getResult(sourceName); const bindings = project.getBindings(sourceName); - return { result, bindings, 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 @@ -53,17 +53,17 @@ export const useSquiggle = (args: SquiggleArgs) => { useEffect(() => { onChange?.( result.result.tag === "Ok" ? result.result.value : undefined, - result.sourceName + sourceName ); - }, [result, onChange]); + }, [result, onChange, sourceName]); useEffect(() => { return () => { - args.project.removeSource(result.sourceName); - if (args.project.getSource(importSourceName(result.sourceName))) - args.project.removeSource(result.sourceName); + args.project.removeSource(sourceName); + if (args.project.getSource(importSourceName(sourceName))) + args.project.removeSource(sourceName); }; - }, [args.project, result.sourceName]); + }, [args.project, sourceName]); return result; }; From 42e9ad810d851ebaf195e5a180b36fa8cfc805bd Mon Sep 17 00:00:00 2001 From: Sam Nolan Date: Thu, 6 Oct 2022 13:40:31 +1100 Subject: [PATCH 14/15] Format typescript --- packages/components/src/lib/hooks/useSquiggle.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/src/lib/hooks/useSquiggle.ts b/packages/components/src/lib/hooks/useSquiggle.ts index 3a18502a..7594781a 100644 --- a/packages/components/src/lib/hooks/useSquiggle.ts +++ b/packages/components/src/lib/hooks/useSquiggle.ts @@ -53,7 +53,7 @@ export const useSquiggle = (args: SquiggleArgs) => { useEffect(() => { onChange?.( result.result.tag === "Ok" ? result.result.value : undefined, - sourceName + sourceName ); }, [result, onChange, sourceName]); From 85a8c061077302c3111515328f17803341bdc2d6 Mon Sep 17 00:00:00 2001 From: Sam Nolan Date: Fri, 7 Oct 2022 11:18:13 +1100 Subject: [PATCH 15/15] Respond to review comments --- .../src/components/SquiggleChart.tsx | 30 ++++++++++--------- packages/components/src/index.ts | 3 +- .../components/src/lib/hooks/useSquiggle.ts | 15 ++++++---- packages/components/test/cleanup.test.tsx | 19 +++++++++++- 4 files changed, 44 insertions(+), 23 deletions(-) diff --git a/packages/components/src/components/SquiggleChart.tsx b/packages/components/src/components/SquiggleChart.tsx index d9ab3168..08c0c4ff 100644 --- a/packages/components/src/components/SquiggleChart.tsx +++ b/packages/components/src/components/SquiggleChart.tsx @@ -2,7 +2,6 @@ import * as React from "react"; import { SqValue, environment, - defaultEnvironment, resultMap, SqValueTag, SqProject, @@ -56,22 +55,19 @@ export type SquiggleChartProps = { // Props needed for a standalone execution type StandaloneExecutionProps = { - /** Project must be undefined */ project?: undefined; - /** Includes must be undefined */ - includes?: 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 must be undefined (we don't set it here, users can set the environment outside the execution) */ environment?: undefined; /** The project that this execution is part of */ project: SqProject; - /** What other squiggle sources from the project to include. Default none */ - includes?: string[]; + /** What other squiggle sources from the project to continue. Default [] */ + continues?: string[]; }; const defaultOnChange = () => {}; const defaultImports: JsImports = {}; @@ -98,18 +94,24 @@ export const SquiggleChart: React.FC = React.memo( xAxisType = "number", distributionChartActions, enableLocalSettings = false, - project = SqProject.create(), code, - includes = [], + continues = [], } = props; - const p = project ?? SqProject.create(); - if (!project && props.environment) { - p.setEnvironment(props.environment); - } + 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({ - includes, + continues, project: p, code, jsImports, diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index aac6944b..b8a6e385 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -1,6 +1,5 @@ -import { SqProject } from "@quri/squiggle-lang/"; +export { SqProject } from "@quri/squiggle-lang/"; export { SquiggleChart } from "./components/SquiggleChart"; export { SquiggleEditor } from "./components/SquiggleEditor"; export { SquigglePlayground } from "./components/SquigglePlayground"; export { SquiggleContainer } from "./components/SquiggleContainer"; -export const newProject = () => SqProject.create(); diff --git a/packages/components/src/lib/hooks/useSquiggle.ts b/packages/components/src/lib/hooks/useSquiggle.ts index 7594781a..7f6fed96 100644 --- a/packages/components/src/lib/hooks/useSquiggle.ts +++ b/packages/components/src/lib/hooks/useSquiggle.ts @@ -8,7 +8,7 @@ type SquiggleArgs = { executionId?: number; jsImports?: JsImports; project: SqProject; - includes: string[]; + continues: string[]; onChange?: (expr: SqValue | undefined, sourceName: string) => void; }; @@ -17,18 +17,20 @@ 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 = args.project; project.setSource(sourceName, args.code); - let includes = args.includes; + let continues = args.continues; if (args.jsImports && Object.keys(args.jsImports).length) { const importsSource = jsImportsToSquiggleCode(args.jsImports); project.setSource(importSourceName(sourceName), importsSource); - includes.push(importSourceName(sourceName)); + continues = args.continues.concat(importSourceName(sourceName)); } - project.setContinues(sourceName, includes); + project.setContinues(sourceName, continues); project.run(sourceName); const result = project.getResult(sourceName); const bindings = project.getBindings(sourceName); @@ -43,8 +45,9 @@ export const useSquiggle = (args: SquiggleArgs) => { args.jsImports, args.executionId, sourceName, - args.includes, + args.continues, args.project, + env, ] ); @@ -61,7 +64,7 @@ export const useSquiggle = (args: SquiggleArgs) => { return () => { args.project.removeSource(sourceName); if (args.project.getSource(importSourceName(sourceName))) - args.project.removeSource(sourceName); + args.project.removeSource(importSourceName(sourceName)); }; }, [args.project, sourceName]); diff --git a/packages/components/test/cleanup.test.tsx b/packages/components/test/cleanup.test.tsx index 7bd109e4..d2be427a 100644 --- a/packages/components/test/cleanup.test.tsx +++ b/packages/components/test/cleanup.test.tsx @@ -4,7 +4,7 @@ import "@testing-library/jest-dom"; import { SquiggleChart } from "../src/index"; import { SqProject } from "@quri/squiggle-lang"; -test("Creates and cleans up source with no name", async () => { +test("Creates and cleans up source", async () => { const project = SqProject.create(); const { unmount } = render( @@ -20,3 +20,20 @@ test("Creates and cleans up source with no name", async () => { 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( + + ); + + expect(project.getSourceIds().length).toBe(2); + + unmount(); + expect(project.getSourceIds()).toStrictEqual([]); +});