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,16 +51,31 @@ 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 = "", | ||||
|   (props: SquiggleChartProps) => { | ||||
|     const { | ||||
|       executionId = 0, | ||||
|     environment, | ||||
|       onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here
 | ||||
|       height = 200, | ||||
|       jsImports = defaultImports, | ||||
|  | @ -81,10 +94,26 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo( | |||
|       xAxisType = "number", | ||||
|       distributionChartActions, | ||||
|       enableLocalSettings = false, | ||||
|   }) => { | ||||
|     const { result, bindings } = useSquiggle({ | ||||
|       code, | ||||
|       environment, | ||||
|       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, | ||||
|       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