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"