From 8a2269b7d00e6b3e6c06cc05552d9553d9669629 Mon Sep 17 00:00:00 2001 From: Vyacheslav Matyukhin Date: Wed, 6 Jul 2022 19:21:54 +0400 Subject: [PATCH 01/21] collapsible results (WIP) --- .../src/components/SquiggleItem.tsx | 225 ++++++++++-------- 1 file changed, 129 insertions(+), 96 deletions(-) diff --git a/packages/components/src/components/SquiggleItem.tsx b/packages/components/src/components/SquiggleItem.tsx index 48a8a0fb..e5eef1a4 100644 --- a/packages/components/src/components/SquiggleItem.tsx +++ b/packages/components/src/components/SquiggleItem.tsx @@ -1,4 +1,4 @@ -import * as React from "react"; +import React, { useState } from "react"; import { squiggleExpression, environment, @@ -10,6 +10,8 @@ import { DistributionPlottingSettings, } from "./DistributionChart"; import { FunctionChart, FunctionChartSettings } from "./FunctionChart"; +import clsx from "clsx"; +import { LayoutGroup, motion } from "framer-motion"; function getRange(x: declaration) { const first = x.args[0]; @@ -35,33 +37,48 @@ function getChartSettings(x: declaration): FunctionChartSettings { } interface VariableBoxProps { + name?: string; heading: string; children: React.ReactNode; showTypes: boolean; } export const VariableBox: React.FC = ({ + name, heading = "Error", children, showTypes = false, }) => { - if (showTypes) { - return ( -
-
-
{heading}
-
-
{children}
-
- ); - } else { - return
{children}
; - } + const [isCollapsed, setIsCollapsed] = useState(false); + return ( + + {name || showTypes ? ( + setIsCollapsed(!isCollapsed)} + > + {name ? {name}: : null} + {showTypes ? {heading} : null} + {isCollapsed ? ( + ... + ) : null} + + ) : null} + {isCollapsed ? null : ( + {children} + )} + + ); }; export interface SquiggleItemProps { - /** The input string for squiggle */ + /** The output of squiggle's run */ expression: squiggleExpression; + name?: string; width?: number; height: number; distributionPlotSettings: DistributionPlottingSettings; @@ -74,6 +91,7 @@ export interface SquiggleItemProps { } export const SquiggleItem: React.FC = ({ + name, expression, width, height, @@ -85,7 +103,7 @@ export const SquiggleItem: React.FC = ({ switch (expression.tag) { case "number": return ( - +
@@ -95,6 +113,7 @@ export const SquiggleItem: React.FC = ({ const distType = expression.value.type(); return ( @@ -112,9 +131,9 @@ export const SquiggleItem: React.FC = ({ } case "string": return ( - + " - + {expression.value} " @@ -122,94 +141,45 @@ export const SquiggleItem: React.FC = ({ ); case "boolean": return ( - + {expression.value.toString()} ); case "symbol": return ( - + Undefined Symbol: {expression.value} ); case "call": return ( - + {expression.value} ); - case "array": - return ( - - {expression.value.map((r, i) => ( -
-
-
{i}
-
-
- -
-
- ))} -
- ); - case "record": - return ( - -
- {Object.entries(expression.value).map(([key, r]) => ( -
-
-
{key}:
-
-
- -
-
- ))} -
-
- ); case "arraystring": return ( - + {expression.value.map((r) => `"${r}"`).join(", ")} ); case "date": return ( - + {expression.value.toDateString()} ); case "timeDuration": { return ( - + ); } case "lambda": return ( - +
{`function(${expression.value.parameters.join( "," )})`}
@@ -227,7 +197,11 @@ export const SquiggleItem: React.FC = ({ ); case "lambdaDeclaration": { return ( - + = ({ } case "module": { return ( - -
- {Object.entries(expression.value) - .filter(([key, r]) => key !== "Math") - .map(([key, r]) => ( -
-
-
{key}:
-
-
- -
-
- ))} + +
+ + {Object.entries(expression.value) + .filter(([key, r]) => key !== "Math") + .map(([key, r]) => ( + + ))} +
); } + case "record": + return ( + +
+ + {Object.entries(expression.value).map(([key, r]) => ( + + ))} + +
+
+ ); + case "array": + return ( + +
+ + {expression.value.map((r, i) => ( + + ))} + +
+
+ ); default: { return (
From afbc1e1455c23e8dc1eccc694306ac1bead7a5f6 Mon Sep 17 00:00:00 2001 From: Vyacheslav Matyukhin Date: Thu, 7 Jul 2022 20:54:40 +0400 Subject: [PATCH 02/21] collapsible tree --- .../src/components/SquiggleItem.tsx | 157 +++++++++--------- 1 file changed, 74 insertions(+), 83 deletions(-) diff --git a/packages/components/src/components/SquiggleItem.tsx b/packages/components/src/components/SquiggleItem.tsx index e5eef1a4..e68da76a 100644 --- a/packages/components/src/components/SquiggleItem.tsx +++ b/packages/components/src/components/SquiggleItem.tsx @@ -43,7 +43,7 @@ interface VariableBoxProps { showTypes: boolean; } -export const VariableBox: React.FC = ({ +const VariableBox: React.FC = ({ name, heading = "Error", children, @@ -51,13 +51,11 @@ export const VariableBox: React.FC = ({ }) => { const [isCollapsed, setIsCollapsed] = useState(false); return ( - + {name || showTypes ? ( setIsCollapsed(!isCollapsed)} > @@ -69,12 +67,35 @@ export const VariableBox: React.FC = ({ ) : null} {isCollapsed ? null : ( - {children} + + {children} + )} ); }; +const VariableList: React.FC<{ + name?: string; + heading: string; + showTypes: boolean; + children: React.ReactNode; +}> = ({ name, heading, showTypes, children }) => ( + + + {children} + + +); + export interface SquiggleItemProps { /** The output of squiggle's run */ expression: squiggleExpression; @@ -217,90 +238,60 @@ export const SquiggleItem: React.FC = ({ } case "module": { return ( - -
- - {Object.entries(expression.value) - .filter(([key, r]) => key !== "Math") - .map(([key, r]) => ( - - ))} - -
-
+ + {Object.entries(expression.value) + .filter(([key, r]) => key !== "Math") + .map(([key, r]) => ( + + ))} + ); } case "record": return ( - -
- - {Object.entries(expression.value).map(([key, r]) => ( - - ))} - -
-
+ + {Object.entries(expression.value).map(([key, r]) => ( + + ))} + ); case "array": return ( - -
- - {expression.value.map((r, i) => ( - - ))} - -
-
+ + {expression.value.map((r, i) => ( + + ))} + ); default: { return ( From c401f395203f8285e5976b89adea73915c160a72 Mon Sep 17 00:00:00 2001 From: Vyacheslav Matyukhin Date: Fri, 8 Jul 2022 16:58:34 +0400 Subject: [PATCH 03/21] remove collapse animation; tooltips with types --- packages/components/package.json | 2 + .../src/components/SquiggleItem.tsx | 93 +++++++------------ .../components/src/components/ui/Tooltip.tsx | 63 +++++++++++++ yarn.lock | 48 ++++++++-- 4 files changed, 142 insertions(+), 64 deletions(-) create mode 100644 packages/components/src/components/ui/Tooltip.tsx diff --git a/packages/components/package.json b/packages/components/package.json index 5015ab35..52a4ebc0 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -3,6 +3,8 @@ "version": "0.2.20", "license": "MIT", "dependencies": { + "@floating-ui/react-dom": "^0.7.2", + "@floating-ui/react-dom-interactions": "^0.6.6", "@headlessui/react": "^1.6.5", "@heroicons/react": "^1.0.6", "@hookform/resolvers": "^2.9.3", diff --git a/packages/components/src/components/SquiggleItem.tsx b/packages/components/src/components/SquiggleItem.tsx index e68da76a..647cc08c 100644 --- a/packages/components/src/components/SquiggleItem.tsx +++ b/packages/components/src/components/SquiggleItem.tsx @@ -11,7 +11,7 @@ import { } from "./DistributionChart"; import { FunctionChart, FunctionChartSettings } from "./FunctionChart"; import clsx from "clsx"; -import { LayoutGroup, motion } from "framer-motion"; +import { Tooltip } from "./ui/Tooltip"; function getRange
(x: declaration) { const first = x.args[0]; @@ -40,59 +40,45 @@ interface VariableBoxProps { name?: string; heading: string; children: React.ReactNode; - showTypes: boolean; } const VariableBox: React.FC = ({ name, heading = "Error", children, - showTypes = false, }) => { const [isCollapsed, setIsCollapsed] = useState(false); return ( - - {name || showTypes ? ( - + {name ? ( +
setIsCollapsed(!isCollapsed)} > - {name ? {name}: : null} - {showTypes ? {heading} : null} + {name ? ( + + {name}: + + ) : null} {isCollapsed ? ( ... ) : null} - +
) : null} - {isCollapsed ? null : ( - - {children} - - )} -
+ {isCollapsed ? null :
{children}
} +
); }; const VariableList: React.FC<{ name?: string; heading: string; - showTypes: boolean; children: React.ReactNode; -}> = ({ name, heading, showTypes, children }) => ( - - - {children} - +}> = ({ name, heading, children }) => ( + +
+ {children} +
); @@ -103,8 +89,8 @@ export interface SquiggleItemProps { width?: number; height: number; distributionPlotSettings: DistributionPlottingSettings; - /** Whether to show type information */ - showTypes: boolean; + /** Whether to show type information; deprecated */ + showTypes?: boolean; /** Settings for displaying functions */ chartSettings: FunctionChartSettings; /** Environment for further function executions */ @@ -124,7 +110,7 @@ export const SquiggleItem: React.FC = ({ switch (expression.tag) { case "number": return ( - +
@@ -135,12 +121,10 @@ export const SquiggleItem: React.FC = ({ return ( - {distType === "Symbolic" && showTypes ? ( -
{expression.value.toString()}
- ) : null} = ({ } case "string": return ( - + " {expression.value} @@ -162,45 +146,45 @@ export const SquiggleItem: React.FC = ({ ); case "boolean": return ( - + {expression.value.toString()} ); case "symbol": return ( - + Undefined Symbol: {expression.value} ); case "call": return ( - + {expression.value} ); case "arraystring": return ( - + {expression.value.map((r) => `"${r}"`).join(", ")} ); case "date": return ( - + {expression.value.toDateString()} ); case "timeDuration": { return ( - + ); } case "lambda": return ( - +
{`function(${expression.value.parameters.join( "," )})`}
@@ -218,11 +202,7 @@ export const SquiggleItem: React.FC = ({ ); case "lambdaDeclaration": { return ( - + = ({ } case "module": { return ( - + {Object.entries(expression.value) .filter(([key, r]) => key !== "Math") .map(([key, r]) => ( @@ -248,7 +228,6 @@ export const SquiggleItem: React.FC = ({ expression={r} width={width !== undefined ? width - 20 : width} height={height / 3} - showTypes={showTypes} distributionPlotSettings={distributionPlotSettings} chartSettings={chartSettings} environment={environment} @@ -259,7 +238,7 @@ export const SquiggleItem: React.FC = ({ } case "record": return ( - + {Object.entries(expression.value).map(([key, r]) => ( = ({ expression={r} width={width !== undefined ? width - 20 : width} height={height / 3} - showTypes={showTypes} distributionPlotSettings={distributionPlotSettings} chartSettings={chartSettings} environment={environment} @@ -277,7 +255,7 @@ export const SquiggleItem: React.FC = ({ ); case "array": return ( - + {expression.value.map((r, i) => ( = ({ width={width !== undefined ? width - 20 : width} height={50} distributionPlotSettings={distributionPlotSettings} - showTypes={showTypes} chartSettings={chartSettings} environment={environment} /> diff --git a/packages/components/src/components/ui/Tooltip.tsx b/packages/components/src/components/ui/Tooltip.tsx new file mode 100644 index 00000000..714f8861 --- /dev/null +++ b/packages/components/src/components/ui/Tooltip.tsx @@ -0,0 +1,63 @@ +import React, { cloneElement, useState } from "react"; +import { AnimatePresence, motion } from "framer-motion"; +import { + shift, + useDismiss, + useFloating, + useHover, + useInteractions, + useRole, +} from "@floating-ui/react-dom-interactions"; + +interface Props { + text: string; + children: JSX.Element; +} + +export const Tooltip: React.FC = ({ text, children }) => { + const [open, setOpen] = useState(false); + + const { x, y, reference, floating, strategy, context } = useFloating({ + placement: "top", + open, + onOpenChange: setOpen, + middleware: [shift()], + }); + + const { getReferenceProps, getFloatingProps } = useInteractions([ + useHover(context), + useRole(context, { role: "tooltip" }), + useDismiss(context), + ]); + + return ( + <> + {cloneElement( + children, + getReferenceProps({ ref: reference, ...children.props }) + )} + + {open && ( + +
{text}
+
+ )} +
+ + ); +}; diff --git a/yarn.lock b/yarn.lock index bb697719..a9c08c38 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1829,6 +1829,35 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" +"@floating-ui/core@^0.7.3": + version "0.7.3" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-0.7.3.tgz#d274116678ffae87f6b60e90f88cc4083eefab86" + integrity sha512-buc8BXHmG9l82+OQXOFU3Kr2XQx9ys01U/Q9HMIrZ300iLc8HLMgh7dcCqgYzAzf4BkoQvDcXf5Y+CuEZ5JBYg== + +"@floating-ui/dom@^0.5.3": + version "0.5.4" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-0.5.4.tgz#4eae73f78bcd4bd553ae2ade30e6f1f9c73fe3f1" + integrity sha512-419BMceRLq0RrmTSDxn8hf9R3VCJv2K9PUfugh5JyEFmdjzDo+e8U5EdR8nzKq8Yj1htzLm3b6eQEEam3/rrtg== + dependencies: + "@floating-ui/core" "^0.7.3" + +"@floating-ui/react-dom-interactions@^0.6.6": + version "0.6.6" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom-interactions/-/react-dom-interactions-0.6.6.tgz#8542e8c4bcbee2cd0d512de676c6a493e0a2d168" + integrity sha512-qnao6UPjSZNHnXrF+u4/n92qVroQkx0Umlhy3Avk1oIebm/5ee6yvDm4xbHob0OjY7ya8WmUnV3rQlPwX3Atwg== + dependencies: + "@floating-ui/react-dom" "^0.7.2" + aria-hidden "^1.1.3" + use-isomorphic-layout-effect "^1.1.1" + +"@floating-ui/react-dom@^0.7.2": + version "0.7.2" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-0.7.2.tgz#0bf4ceccb777a140fc535c87eb5d6241c8e89864" + integrity sha512-1T0sJcpHgX/u4I1OzIEhlcrvkUN8ln39nz7fMoE/2HDHrPiMFoOGR7++GYyfUmIQHkkrTinaeQsO3XWubjSvGg== + dependencies: + "@floating-ui/dom" "^0.5.3" + use-isomorphic-layout-effect "^1.1.1" + "@gar/promisify@^1.0.1": version "1.1.3" resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" @@ -4421,10 +4450,10 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@^18.0.1", "@types/react@^18.0.9": - version "18.0.14" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.14.tgz#e016616ffff51dba01b04945610fe3671fdbe06d" - integrity sha512-x4gGuASSiWmo0xjDLpm5mPb52syZHJx02VKbqUKdLmKtAwIh63XClGsiTI1K6DO5q7ox4xAsQrU+Gl3+gGXF9Q== +"@types/react@*", "@types/react@17.0.43", "@types/react@^18.0.9": + 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" "*" @@ -5373,6 +5402,13 @@ argv@0.0.2: resolved "https://registry.yarnpkg.com/argv/-/argv-0.0.2.tgz#ecbd16f8949b157183711b1bda334f37840185ab" integrity sha512-dEamhpPEwRUBpLNHeuCm/v+g0anFByHahxodVO/BbAarHVBBg2MccCwf9K+o1Pof+2btdnkJelYVUWjW/VrATw== +aria-hidden@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/aria-hidden/-/aria-hidden-1.1.3.tgz#bb48de18dc84787a3c6eee113709c473c64ec254" + integrity sha512-RhVWFtKH5BiGMycI72q2RAFMLQi8JP9bLuQXgR5a8Znp7P5KOIADSJeyfI8PCVxLEp067B2HbP5JIiI/PXIZeA== + dependencies: + tslib "^1.0.0" + aria-query@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b" @@ -14872,7 +14908,7 @@ react-vega@^7.5.1: 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== @@ -17071,7 +17107,7 @@ tsconfig-paths@^3.14.1, tsconfig-paths@^3.9.0: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@^1.8.1: +tslib@^1.0.0, tslib@^1.8.1: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== From a4684ddf39c5acb8564459dbaeb07c2fefb1ddf1 Mon Sep 17 00:00:00 2001 From: Vyacheslav Matyukhin Date: Fri, 8 Jul 2022 17:09:46 +0400 Subject: [PATCH 04/21] collapse on border click --- .../src/components/SquiggleItem.tsx | 51 ++++++++++++------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/packages/components/src/components/SquiggleItem.tsx b/packages/components/src/components/SquiggleItem.tsx index 647cc08c..e9b9c2f6 100644 --- a/packages/components/src/components/SquiggleItem.tsx +++ b/packages/components/src/components/SquiggleItem.tsx @@ -48,24 +48,41 @@ const VariableBox: React.FC = ({ children, }) => { const [isCollapsed, setIsCollapsed] = useState(false); + + const toggleCollapsed = () => { + setIsCollapsed(!isCollapsed); + }; + return (
- {name ? ( -
setIsCollapsed(!isCollapsed)} - > - {name ? ( - - {name}: - - ) : null} - {isCollapsed ? ( - ... - ) : null} -
- ) : null} - {isCollapsed ? null :
{children}
} +
+ {name ? ( +
+ {name ? ( + + {name}: + + ) : null} + {isCollapsed ? ( + ... + ) : null} +
+ ) : null} + {isCollapsed ? null : ( +
+ {name ? ( +
+ ) : null} +
{children}
+
+ )} +
); }; @@ -76,7 +93,7 @@ const VariableList: React.FC<{ children: React.ReactNode; }> = ({ name, heading, children }) => ( -
+
{children}
From 3ca80daee8c43f04c6f69ad2c6504f9b60344883 Mon Sep 17 00:00:00 2001 From: Vyacheslav Matyukhin Date: Fri, 8 Jul 2022 17:13:26 +0400 Subject: [PATCH 05/21] remove showTypes setting --- packages/components/README.md | 1 - packages/components/src/components/SquiggleChart.tsx | 4 ---- packages/components/src/components/SquiggleItem.tsx | 3 --- .../components/src/components/SquigglePlayground.tsx | 11 ----------- packages/vscode-ext/media/previewWebview.js | 1 - packages/vscode-ext/package.json | 5 ----- packages/website/src/pages/playground.js | 5 ++--- 7 files changed, 2 insertions(+), 28 deletions(-) diff --git a/packages/components/README.md b/packages/components/README.md index 63a98d34..2b911caa 100644 --- a/packages/components/README.md +++ b/packages/components/README.md @@ -54,7 +54,6 @@ export function DynamicSquiggleChart({ squiggleString }) { width={445} height={200} showSummary={true} - showTypes={true} /> ); } diff --git a/packages/components/src/components/SquiggleChart.tsx b/packages/components/src/components/SquiggleChart.tsx index bfb69a4e..fecba1fd 100644 --- a/packages/components/src/components/SquiggleChart.tsx +++ b/packages/components/src/components/SquiggleChart.tsx @@ -36,8 +36,6 @@ export interface SquiggleChartProps { jsImports?: jsImports; /** Whether to show a summary of the distribution */ showSummary?: boolean; - /** Whether to show type information about returns, default false */ - showTypes?: boolean; /** Whether to show graph controls (scale etc)*/ showControls?: boolean; /** Set the x scale to be logarithmic by deault */ @@ -58,7 +56,6 @@ export const SquiggleChart: React.FC = React.memo( jsImports = defaultImports, showSummary = false, width, - showTypes = false, showControls = false, logX = false, expY = false, @@ -97,7 +94,6 @@ export const SquiggleChart: React.FC = React.memo( width={width} height={height} distributionPlotSettings={distributionPlotSettings} - showTypes={showTypes} chartSettings={chartSettings} environment={environment ?? defaultEnvironment} /> diff --git a/packages/components/src/components/SquiggleItem.tsx b/packages/components/src/components/SquiggleItem.tsx index e9b9c2f6..6a80c42c 100644 --- a/packages/components/src/components/SquiggleItem.tsx +++ b/packages/components/src/components/SquiggleItem.tsx @@ -106,8 +106,6 @@ export interface SquiggleItemProps { width?: number; height: number; distributionPlotSettings: DistributionPlottingSettings; - /** Whether to show type information; deprecated */ - showTypes?: boolean; /** Settings for displaying functions */ chartSettings: FunctionChartSettings; /** Environment for further function executions */ @@ -120,7 +118,6 @@ export const SquiggleItem: React.FC = ({ width, height, distributionPlotSettings, - showTypes = false, chartSettings, environment, }) => { diff --git a/packages/components/src/components/SquigglePlayground.tsx b/packages/components/src/components/SquigglePlayground.tsx index 301d8892..59389f17 100644 --- a/packages/components/src/components/SquigglePlayground.tsx +++ b/packages/components/src/components/SquigglePlayground.tsx @@ -32,8 +32,6 @@ interface PlaygroundProps { defaultCode?: string; /** How many pixels high is the playground */ height?: number; - /** Whether to show the types of outputs in the playground */ - showTypes?: boolean; /** Whether to show the log scale controls in the playground */ showControls?: boolean; /** Whether to show the summary table in the playground */ @@ -76,7 +74,6 @@ const schema = yup.object({}).shape({ .min(10) .max(100) .default(50), - showTypes: yup.boolean().required(), showControls: yup.boolean().required(), showSummary: yup.boolean().required(), showEditor: yup.boolean().required(), @@ -181,11 +178,6 @@ const ViewSettings: React.FC<{ register: UseFormRegister }> = ({ register={register} label="Chart Height (in pixels)" /> -
@@ -380,7 +372,6 @@ const useRunnerState = (code: string) => { export const SquigglePlayground: FC = ({ defaultCode = "", height = 500, - showTypes = false, showControls = false, showSummary = false, logX = false, @@ -404,7 +395,6 @@ export const SquigglePlayground: FC = ({ sampleCount: 1000, xyPointLength: 1000, chartHeight: 150, - showTypes, showControls, logX, expY, @@ -444,7 +434,6 @@ export const SquigglePlayground: FC = ({ diagramStop={Number(vars.diagramStop)} diagramCount={Number(vars.diagramCount)} height={vars.chartHeight} - showTypes={vars.showTypes} showControls={vars.showControls} showSummary={vars.showSummary} logX={vars.logX} diff --git a/packages/vscode-ext/media/previewWebview.js b/packages/vscode-ext/media/previewWebview.js index 556a893b..13bdbe95 100644 --- a/packages/vscode-ext/media/previewWebview.js +++ b/packages/vscode-ext/media/previewWebview.js @@ -9,7 +9,6 @@ React.createElement(squiggle_components.SquigglePlayground, { code: text, showEditor: false, - showTypes: Boolean(showSettings.showTypes), showControls: Boolean(showSettings.showControls), showSummary: Boolean(showSettings.showSummary), }) diff --git a/packages/vscode-ext/package.json b/packages/vscode-ext/package.json index a0386e21..2198c538 100644 --- a/packages/vscode-ext/package.json +++ b/packages/vscode-ext/package.json @@ -105,11 +105,6 @@ "configuration": { "title": "Squiggle", "properties": { - "squiggle.playground.showTypes": { - "type": "boolean", - "default": false, - "description": "Whether to show the types of outputs in the playground" - }, "squiggle.playground.showControls": { "type": "boolean", "default": false, diff --git a/packages/website/src/pages/playground.js b/packages/website/src/pages/playground.js index 6f2fb9db..5c52a9fa 100644 --- a/packages/website/src/pages/playground.js +++ b/packages/website/src/pages/playground.js @@ -44,12 +44,11 @@ export default function PlaygroundPage() { const playgroundProps = { defaultCode: "normal(0,1)", height: 700, - showTypes: true, ...hashData, onCodeChange: (code) => setHashData({ initialSquiggleString: code }), onSettingsChange: (settings) => { - const { showTypes, showControls, showSummary, showEditor } = settings; - setHashData({ showTypes, showControls, showSummary, showEditor }); + const { showControls, showSummary, showEditor } = settings; + setHashData({ showControls, showSummary, showEditor }); }, }; return ( From 12eb63c789f43deff84c4864525c56319654c2d6 Mon Sep 17 00:00:00 2001 From: Vyacheslav Matyukhin Date: Fri, 8 Jul 2022 18:28:12 +0400 Subject: [PATCH 06/21] SquiggleViewer refactoring; persistent collapsed settings via context --- .../src/components/SquiggleChart.tsx | 14 +-- .../ExpressionViewer.tsx} | 118 +++++------------- .../components/SquiggleViewer/VariableBox.tsx | 61 +++++++++ .../SquiggleViewer/ViewerContext.ts | 15 +++ .../src/components/SquiggleViewer/index.tsx | 79 ++++++++++++ .../src/components/SquiggleViewer/utils.ts | 6 + 6 files changed, 200 insertions(+), 93 deletions(-) rename packages/components/src/components/{SquiggleItem.tsx => SquiggleViewer/ExpressionViewer.tsx} (68%) create mode 100644 packages/components/src/components/SquiggleViewer/VariableBox.tsx create mode 100644 packages/components/src/components/SquiggleViewer/ViewerContext.ts create mode 100644 packages/components/src/components/SquiggleViewer/index.tsx create mode 100644 packages/components/src/components/SquiggleViewer/utils.ts diff --git a/packages/components/src/components/SquiggleChart.tsx b/packages/components/src/components/SquiggleChart.tsx index fecba1fd..f0adb496 100644 --- a/packages/components/src/components/SquiggleChart.tsx +++ b/packages/components/src/components/SquiggleChart.tsx @@ -10,7 +10,7 @@ import { } from "@quri/squiggle-lang"; import { useSquiggle } from "../lib/hooks"; import { SquiggleErrorAlert } from "./SquiggleErrorAlert"; -import { SquiggleItem } from "./SquiggleItem"; +import { SquiggleViewer } from "./SquiggleViewer"; export interface SquiggleChartProps { /** The input string for squiggle */ @@ -71,26 +71,22 @@ export const SquiggleChart: React.FC = React.memo( onChange, }); - if (result.tag !== "Ok") { - return ; - } - - let distributionPlotSettings = { + const distributionPlotSettings = { showControls, showSummary, logX, expY, }; - let chartSettings = { + const chartSettings = { start: diagramStart, stop: diagramStop, count: diagramCount, }; return ( - (x: declaration
) { const first = x.args[0]; @@ -36,73 +36,23 @@ function getChartSettings(x: declaration): FunctionChartSettings { }; } -interface VariableBoxProps { - name?: string; - heading: string; - children: React.ReactNode; -} - -const VariableBox: React.FC = ({ - name, - heading = "Error", - children, -}) => { - const [isCollapsed, setIsCollapsed] = useState(false); - - const toggleCollapsed = () => { - setIsCollapsed(!isCollapsed); - }; - - return ( -
-
- {name ? ( -
- {name ? ( - - {name}: - - ) : null} - {isCollapsed ? ( - ... - ) : null} -
- ) : null} - {isCollapsed ? null : ( -
- {name ? ( -
- ) : null} -
{children}
-
- )} -
-
- ); -}; - const VariableList: React.FC<{ - name?: string; + path: string[]; heading: string; children: React.ReactNode; -}> = ({ name, heading, children }) => ( - -
+}> = ({ path, heading, children }) => ( + +
{children}
); -export interface SquiggleItemProps { +export interface Props { /** The output of squiggle's run */ expression: squiggleExpression; - name?: string; + /** Path to the current item, e.g. `['foo', 'bar', '3']` for `foo.bar[3]`; can be empty on the top-level item. */ + path: string[]; width?: number; height: number; distributionPlotSettings: DistributionPlottingSettings; @@ -112,8 +62,8 @@ export interface SquiggleItemProps { environment: environment; } -export const SquiggleItem: React.FC = ({ - name, +export const ExpressionViewer: React.FC = ({ + path, expression, width, height, @@ -124,7 +74,7 @@ export const SquiggleItem: React.FC = ({ switch (expression.tag) { case "number": return ( - +
@@ -134,7 +84,7 @@ export const SquiggleItem: React.FC = ({ const distType = expression.value.type(); return ( = ({ } case "string": return ( - + " {expression.value} @@ -160,45 +110,45 @@ export const SquiggleItem: React.FC = ({ ); case "boolean": return ( - + {expression.value.toString()} ); case "symbol": return ( - + Undefined Symbol: {expression.value} ); case "call": return ( - + {expression.value} ); case "arraystring": return ( - + {expression.value.map((r) => `"${r}"`).join(", ")} ); case "date": return ( - + {expression.value.toDateString()} ); case "timeDuration": { return ( - + ); } case "lambda": return ( - +
{`function(${expression.value.parameters.join( "," )})`}
@@ -216,7 +166,7 @@ export const SquiggleItem: React.FC = ({ ); case "lambdaDeclaration": { return ( - + = ({ } case "module": { return ( - + {Object.entries(expression.value) .filter(([key, r]) => key !== "Math") .map(([key, r]) => ( - = ({ } case "record": return ( - + {Object.entries(expression.value).map(([key, r]) => ( - = ({ ); case "array": return ( - + {expression.value.map((r, i) => ( - = ({ + path, + heading = "Error", + children, +}) => { + const { setSettings, getSettings } = useContext(ViewerContext); + const [isCollapsed, setIsCollapsed] = useState( + () => getSettings(path).collapsed + ); + + const toggleCollapsed = () => { + setSettings(path, { + collapsed: !isCollapsed, + }); + setIsCollapsed(!isCollapsed); + }; + + const isTopLevel = path.length === 0; + const name = isTopLevel ? "" : path[path.length - 1]; + + return ( +
+
+ {isTopLevel ? null : ( +
+ + {name}: + + {isCollapsed ? ( + ... + ) : null} +
+ )} + {isCollapsed ? null : ( +
+ {path.length ? ( +
+ ) : null} +
{children}
+
+ )} +
+
+ ); +}; diff --git a/packages/components/src/components/SquiggleViewer/ViewerContext.ts b/packages/components/src/components/SquiggleViewer/ViewerContext.ts new file mode 100644 index 00000000..153383e7 --- /dev/null +++ b/packages/components/src/components/SquiggleViewer/ViewerContext.ts @@ -0,0 +1,15 @@ +import React from "react"; +import { ItemSettings, Path } from "./utils"; + +type ViewerContextShape = { + // Note that we don't store settings themselves in the context (that would cause rerenders of the entire tree on each settings update). + // Instead, we keep settings in local state and notify the global context via setSettings to pass them down the component tree again if it got rebuilt from scratch. + // See ./SquiggleViewer.tsx and ./VariableBox.tsx for other implementation details on this. + getSettings(path: Path): ItemSettings; + setSettings(path: Path, value: ItemSettings): void; +}; + +export const ViewerContext = React.createContext({ + getSettings: () => ({ collapsed: false }), + setSettings() {}, +}); diff --git a/packages/components/src/components/SquiggleViewer/index.tsx b/packages/components/src/components/SquiggleViewer/index.tsx new file mode 100644 index 00000000..5969c9df --- /dev/null +++ b/packages/components/src/components/SquiggleViewer/index.tsx @@ -0,0 +1,79 @@ +import React, { useCallback, useRef } from "react"; +import { environment } from "@quri/squiggle-lang"; +import { DistributionPlottingSettings } from "../DistributionChart"; +import { FunctionChartSettings } from "../FunctionChart"; +import { ExpressionViewer } from "./ExpressionViewer"; +import { ViewerContext } from "./ViewerContext"; +import { Path, pathAsString } from "./utils"; +import { useSquiggle } from "../../lib/hooks"; +import { SquiggleErrorAlert } from "../SquiggleErrorAlert"; + +type Props = { + /** The output of squiggle's run */ + result: ReturnType; + width?: number; + height: number; + distributionPlotSettings: DistributionPlottingSettings; + /** Settings for displaying functions */ + chartSettings: FunctionChartSettings; + /** Environment for further function executions */ + environment: environment; +}; + +type ItemSettings = { + collapsed: boolean; +}; + +type Settings = { + [k: string]: ItemSettings; +}; + +const defaultSettings: ItemSettings = { collapsed: false }; + +export const SquiggleViewer: React.FC = ({ + result, + width, + height, + distributionPlotSettings, + chartSettings, + environment, +}) => { + const settingsRef = useRef({}); + + const getSettings = useCallback( + (path: Path) => { + return settingsRef.current[pathAsString(path)] || defaultSettings; + }, + [settingsRef] + ); + + const setSettings = useCallback( + (path: Path, value: ItemSettings) => { + settingsRef.current[pathAsString(path)] = value; + }, + [settingsRef] + ); + + return ( + + {result.tag === "Ok" ? ( + + ) : ( + + )} + + ); +}; diff --git a/packages/components/src/components/SquiggleViewer/utils.ts b/packages/components/src/components/SquiggleViewer/utils.ts new file mode 100644 index 00000000..29648079 --- /dev/null +++ b/packages/components/src/components/SquiggleViewer/utils.ts @@ -0,0 +1,6 @@ +export type ItemSettings = { + collapsed: boolean; +}; +export type Path = string[]; + +export const pathAsString = (path: Path) => path.join("."); From 8d390c4433c7d1f827ae04317f88e1bb70b8882f Mon Sep 17 00:00:00 2001 From: Vyacheslav Matyukhin Date: Wed, 20 Jul 2022 23:16:34 +0400 Subject: [PATCH 07/21] local chart settings via dropdown menu --- .../src/components/SquiggleChart.tsx | 1 - .../SquiggleViewer/ExpressionViewer.tsx | 170 ++++++++++-------- .../SquiggleViewer/ItemSettingsMenu.tsx | 73 ++++++++ .../components/SquiggleViewer/VariableBox.tsx | 90 ++++++---- .../SquiggleViewer/ViewerContext.ts | 24 ++- .../src/components/SquiggleViewer/index.tsx | 44 +++-- .../src/components/SquiggleViewer/utils.ts | 16 +- .../src/components/ui/DropdownMenu.tsx | 75 ++++++++ .../components/src/components/ui/Tooltip.tsx | 8 +- 9 files changed, 369 insertions(+), 132 deletions(-) create mode 100644 packages/components/src/components/SquiggleViewer/ItemSettingsMenu.tsx create mode 100644 packages/components/src/components/ui/DropdownMenu.tsx diff --git a/packages/components/src/components/SquiggleChart.tsx b/packages/components/src/components/SquiggleChart.tsx index f0adb496..7397cb7e 100644 --- a/packages/components/src/components/SquiggleChart.tsx +++ b/packages/components/src/components/SquiggleChart.tsx @@ -9,7 +9,6 @@ import { defaultEnvironment, } from "@quri/squiggle-lang"; import { useSquiggle } from "../lib/hooks"; -import { SquiggleErrorAlert } from "./SquiggleErrorAlert"; import { SquiggleViewer } from "./SquiggleViewer"; export interface SquiggleChartProps { diff --git a/packages/components/src/components/SquiggleViewer/ExpressionViewer.tsx b/packages/components/src/components/SquiggleViewer/ExpressionViewer.tsx index 54e35777..dae4a824 100644 --- a/packages/components/src/components/SquiggleViewer/ExpressionViewer.tsx +++ b/packages/components/src/components/SquiggleViewer/ExpressionViewer.tsx @@ -1,17 +1,11 @@ import React from "react"; -import { - squiggleExpression, - environment, - declaration, -} from "@quri/squiggle-lang"; +import { squiggleExpression, declaration } from "@quri/squiggle-lang"; import { NumberShower } from "../NumberShower"; -import { - DistributionChart, - DistributionPlottingSettings, -} from "../DistributionChart"; +import { DistributionChart } from "../DistributionChart"; import { FunctionChart, FunctionChartSettings } from "../FunctionChart"; import clsx from "clsx"; import { VariableBox } from "./VariableBox"; +import { ItemSettingsMenu } from "./ItemSettingsMenu"; function getRange
(x: declaration) { const first = x.args[0]; @@ -42,9 +36,11 @@ const VariableList: React.FC<{ children: React.ReactNode; }> = ({ path, heading, children }) => ( -
- {children} -
+ {() => ( +
+ {children} +
+ )}
); @@ -55,11 +51,6 @@ export interface Props { path: string[]; width?: number; height: number; - distributionPlotSettings: DistributionPlottingSettings; - /** Settings for displaying functions */ - chartSettings: FunctionChartSettings; - /** Environment for further function executions */ - environment: environment; } export const ExpressionViewer: React.FC = ({ @@ -67,17 +58,16 @@ export const ExpressionViewer: React.FC = ({ expression, width, height, - distributionPlotSettings, - chartSettings, - environment, }) => { switch (expression.tag) { case "number": return ( -
- -
+ {() => ( +
+ +
+ )}
); case "distribution": { @@ -88,95 +78,134 @@ export const ExpressionViewer: React.FC = ({ heading={`Distribution (${distType})\n${ distType === "Symbolic" ? expression.value.toString() : "" }`} + dropdownMenu={({ settings, setSettings }) => { + return ( + + ); + }} > - + {(settings) => { + return ( + + ); + }} ); } case "string": return ( - " - - {expression.value} - - " + {() => ( + <> + " + + {expression.value} + + " + + )} ); case "boolean": return ( - {expression.value.toString()} + {() => expression.value.toString()} ); case "symbol": return ( - Undefined Symbol: - {expression.value} + {() => ( + <> + Undefined Symbol: + {expression.value} + + )} ); case "call": return ( - {expression.value} + {() => expression.value} ); case "arraystring": return ( - {expression.value.map((r) => `"${r}"`).join(", ")} + {() => expression.value.map((r) => `"${r}"`).join(", ")} ); case "date": return ( - {expression.value.toDateString()} + {() => expression.value.toDateString()} ); case "timeDuration": { return ( - + {() => } ); } case "lambda": return ( - -
{`function(${expression.value.parameters.join( - "," - )})`}
- + { + return ( + + ); + }} + > + {(settings) => ( + <> +
{`function(${expression.value.parameters.join( + "," + )})`}
+ + + )}
); case "lambdaDeclaration": { return ( - - + { + return ( + + ); + }} + > + {(settings) => ( + + )} ); } @@ -192,9 +221,6 @@ export const ExpressionViewer: React.FC = ({ expression={r} width={width !== undefined ? width - 20 : width} height={height / 3} - distributionPlotSettings={distributionPlotSettings} - chartSettings={chartSettings} - environment={environment} /> ))} @@ -210,9 +236,6 @@ export const ExpressionViewer: React.FC = ({ expression={r} width={width !== undefined ? width - 20 : width} height={height / 3} - distributionPlotSettings={distributionPlotSettings} - chartSettings={chartSettings} - environment={environment} /> ))} @@ -227,9 +250,6 @@ export const ExpressionViewer: React.FC = ({ expression={r} width={width !== undefined ? width - 20 : width} height={50} - distributionPlotSettings={distributionPlotSettings} - chartSettings={chartSettings} - environment={environment} /> ))} diff --git a/packages/components/src/components/SquiggleViewer/ItemSettingsMenu.tsx b/packages/components/src/components/SquiggleViewer/ItemSettingsMenu.tsx new file mode 100644 index 00000000..898d5fc1 --- /dev/null +++ b/packages/components/src/components/SquiggleViewer/ItemSettingsMenu.tsx @@ -0,0 +1,73 @@ +import React from "react"; +import { DropdownMenu } from "../ui/DropdownMenu"; +import { LocalItemSettings } from "./utils"; + +type Props = { + settings: LocalItemSettings; + setSettings: (value: LocalItemSettings) => void; +}; + +export const ItemSettingsMenu: React.FC = ({ + settings, + setSettings, +}) => { + return ( +
+ + + setSettings({ + ...settings, + distributionPlotSettings: { + ...settings.distributionPlotSettings, + logX: !settings.distributionPlotSettings?.logX, + }, + }) + } + /> + + setSettings({ + ...settings, + distributionPlotSettings: { + ...settings.distributionPlotSettings, + expY: !settings.distributionPlotSettings?.expY, + }, + }) + } + /> + + setSettings({ + ...settings, + distributionPlotSettings: { + ...settings.distributionPlotSettings, + showSummary: !settings.distributionPlotSettings?.showSummary, + }, + }) + } + /> + + {settings.distributionPlotSettings || settings.chartSettings ? ( + + ) : null} +
+ ); +}; diff --git a/packages/components/src/components/SquiggleViewer/VariableBox.tsx b/packages/components/src/components/SquiggleViewer/VariableBox.tsx index eb362db0..84acb30d 100644 --- a/packages/components/src/components/SquiggleViewer/VariableBox.tsx +++ b/packages/components/src/components/SquiggleViewer/VariableBox.tsx @@ -1,61 +1,77 @@ -import React, { useContext, useState } from "react"; +import React, { useContext, useReducer } from "react"; import { Tooltip } from "../ui/Tooltip"; +import { LocalItemSettings, MergedItemSettings } from "./utils"; import { ViewerContext } from "./ViewerContext"; -interface VariableBoxProps { +type DropdownMenuParams = { + settings: LocalItemSettings; + setSettings: (value: LocalItemSettings) => void; +}; + +type VariableBoxProps = { path: string[]; heading: string; - children: React.ReactNode; -} + dropdownMenu?: (params: DropdownMenuParams) => React.ReactNode; + children: (settings: MergedItemSettings) => React.ReactNode; +}; export const VariableBox: React.FC = ({ path, heading = "Error", + dropdownMenu, children, }) => { - const { setSettings, getSettings } = useContext(ViewerContext); - const [isCollapsed, setIsCollapsed] = useState( - () => getSettings(path).collapsed - ); + const { setSettings, getSettings, getMergedSettings } = + useContext(ViewerContext); + const [_, forceUpdate] = useReducer((x) => x + 1, 0); + + const settings = getSettings(path); + + const setSettingsAndUpdate = (newSettings: LocalItemSettings) => { + setSettings(path, newSettings); + forceUpdate(); + }; const toggleCollapsed = () => { - setSettings(path, { - collapsed: !isCollapsed, - }); - setIsCollapsed(!isCollapsed); + setSettingsAndUpdate({ ...settings, collapsed: !settings.collapsed }); }; const isTopLevel = path.length === 0; - const name = isTopLevel ? "" : path[path.length - 1]; + const name = isTopLevel ? "Result" : path[path.length - 1]; return (
-
- {isTopLevel ? null : ( -
+ + - - {name}: - - {isCollapsed ? ( - ... - ) : null} -
- )} - {isCollapsed ? null : ( -
- {path.length ? ( -
- ) : null} -
{children}
-
- )} -
+ {name}: + + + {settings.collapsed ? ( + + ... + + ) : dropdownMenu ? ( + dropdownMenu({ settings, setSettings: setSettingsAndUpdate }) + ) : null} + + {settings.collapsed ? null : ( +
+ {path.length ? ( +
+ ) : null} +
{children(getMergedSettings(path))}
+
+ )}
); }; diff --git a/packages/components/src/components/SquiggleViewer/ViewerContext.ts b/packages/components/src/components/SquiggleViewer/ViewerContext.ts index 153383e7..c8b78b69 100644 --- a/packages/components/src/components/SquiggleViewer/ViewerContext.ts +++ b/packages/components/src/components/SquiggleViewer/ViewerContext.ts @@ -1,15 +1,33 @@ +import { defaultEnvironment } from "@quri/squiggle-lang"; import React from "react"; -import { ItemSettings, Path } from "./utils"; +import { LocalItemSettings, MergedItemSettings, Path } from "./utils"; type ViewerContextShape = { // Note that we don't store settings themselves in the context (that would cause rerenders of the entire tree on each settings update). // Instead, we keep settings in local state and notify the global context via setSettings to pass them down the component tree again if it got rebuilt from scratch. // See ./SquiggleViewer.tsx and ./VariableBox.tsx for other implementation details on this. - getSettings(path: Path): ItemSettings; - setSettings(path: Path, value: ItemSettings): void; + getSettings(path: Path): LocalItemSettings; + getMergedSettings(path: Path): MergedItemSettings; + setSettings(path: Path, value: LocalItemSettings): void; }; export const ViewerContext = React.createContext({ getSettings: () => ({ collapsed: false }), + getMergedSettings: () => ({ + collapsed: false, + // copy-pasted from SquiggleChart + chartSettings: { + start: 0, + stop: 10, + count: 100, + }, + distributionPlotSettings: { + showSummary: false, + showControls: false, + logX: false, + expY: false, + }, + environment: defaultEnvironment, + }), setSettings() {}, }); diff --git a/packages/components/src/components/SquiggleViewer/index.tsx b/packages/components/src/components/SquiggleViewer/index.tsx index 5969c9df..be4dcaab 100644 --- a/packages/components/src/components/SquiggleViewer/index.tsx +++ b/packages/components/src/components/SquiggleViewer/index.tsx @@ -4,7 +4,12 @@ import { DistributionPlottingSettings } from "../DistributionChart"; import { FunctionChartSettings } from "../FunctionChart"; import { ExpressionViewer } from "./ExpressionViewer"; import { ViewerContext } from "./ViewerContext"; -import { Path, pathAsString } from "./utils"; +import { + LocalItemSettings, + MergedItemSettings, + Path, + pathAsString, +} from "./utils"; import { useSquiggle } from "../../lib/hooks"; import { SquiggleErrorAlert } from "../SquiggleErrorAlert"; @@ -20,15 +25,11 @@ type Props = { environment: environment; }; -type ItemSettings = { - collapsed: boolean; -}; - type Settings = { - [k: string]: ItemSettings; + [k: string]: LocalItemSettings; }; -const defaultSettings: ItemSettings = { collapsed: false }; +const defaultSettings: LocalItemSettings = { collapsed: false }; export const SquiggleViewer: React.FC = ({ result, @@ -38,6 +39,7 @@ export const SquiggleViewer: React.FC = ({ chartSettings, environment, }) => { + // can't store settings in the state because we don't want to rerender the entire tree on every change const settingsRef = useRef({}); const getSettings = useCallback( @@ -48,17 +50,40 @@ export const SquiggleViewer: React.FC = ({ ); const setSettings = useCallback( - (path: Path, value: ItemSettings) => { + (path: Path, value: LocalItemSettings) => { settingsRef.current[pathAsString(path)] = value; }, [settingsRef] ); + const getMergedSettings = useCallback( + (path: Path) => { + const localSettings = getSettings(path); + const result: MergedItemSettings = { + distributionPlotSettings: { + ...distributionPlotSettings, + ...(localSettings.distributionPlotSettings || {}), + }, + chartSettings: { + ...chartSettings, + ...(localSettings.chartSettings || {}), + }, + environment: { + ...environment, + ...(localSettings.environment || {}), + }, + }; + return result; + }, + [distributionPlotSettings, chartSettings, environment, getSettings] + ); + return ( {result.tag === "Ok" ? ( @@ -67,9 +92,6 @@ export const SquiggleViewer: React.FC = ({ expression={result.value} width={width} height={height} - distributionPlotSettings={distributionPlotSettings} - chartSettings={chartSettings} - environment={environment} /> ) : ( diff --git a/packages/components/src/components/SquiggleViewer/utils.ts b/packages/components/src/components/SquiggleViewer/utils.ts index 29648079..979d0cec 100644 --- a/packages/components/src/components/SquiggleViewer/utils.ts +++ b/packages/components/src/components/SquiggleViewer/utils.ts @@ -1,6 +1,20 @@ -export type ItemSettings = { +import { DistributionPlottingSettings } from "../DistributionChart"; +import { FunctionChartSettings } from "../FunctionChart"; +import { environment } from "@quri/squiggle-lang"; + +export type LocalItemSettings = { collapsed: boolean; + distributionPlotSettings?: Partial; + chartSettings?: Partial; + environment?: Partial; }; + +export type MergedItemSettings = { + distributionPlotSettings: DistributionPlottingSettings; + chartSettings: FunctionChartSettings; + environment: environment; +}; + export type Path = string[]; export const pathAsString = (path: Path) => path.join("."); diff --git a/packages/components/src/components/ui/DropdownMenu.tsx b/packages/components/src/components/ui/DropdownMenu.tsx new file mode 100644 index 00000000..798741c3 --- /dev/null +++ b/packages/components/src/components/ui/DropdownMenu.tsx @@ -0,0 +1,75 @@ +import { CheckIcon, CogIcon } from "@heroicons/react/solid"; +import React, { useState } from "react"; +import { + shift, + useClick, + useDismiss, + useFloating, + useInteractions, + useRole, +} from "@floating-ui/react-dom-interactions"; + +type Props = { + children: React.ReactNode; +}; + +type DropdownMenuType = React.FC & { + CheckboxItem: React.FC<{ label: string; value: boolean; toggle: () => void }>; +}; + +export const DropdownMenu: DropdownMenuType = ({ children }) => { + const [isOpen, setIsOpen] = useState(false); + const { x, y, reference, floating, strategy, context } = useFloating({ + placement: "bottom-start", + open: isOpen, + onOpenChange: setIsOpen, + middleware: [shift()], + }); + + const { getReferenceProps, getFloatingProps } = useInteractions([ + useClick(context), + useRole(context, { role: "menu" }), + useDismiss(context), + ]); + + return ( +
+ setIsOpen(!isOpen)} + {...getReferenceProps({ ref: reference })} + /> + {isOpen ? ( +
+ {children} +
+ ) : null} +
+ ); +}; + +DropdownMenu.CheckboxItem = ({ label, value, toggle }) => { + return ( +
+ {value ? ( + + ) : ( +
+ )} + {label} +
+ ); +}; diff --git a/packages/components/src/components/ui/Tooltip.tsx b/packages/components/src/components/ui/Tooltip.tsx index 714f8861..60a4ef12 100644 --- a/packages/components/src/components/ui/Tooltip.tsx +++ b/packages/components/src/components/ui/Tooltip.tsx @@ -15,12 +15,12 @@ interface Props { } export const Tooltip: React.FC = ({ text, children }) => { - const [open, setOpen] = useState(false); + const [isOpen, setIsOpen] = useState(false); const { x, y, reference, floating, strategy, context } = useFloating({ placement: "top", - open, - onOpenChange: setOpen, + open: isOpen, + onOpenChange: setIsOpen, middleware: [shift()], }); @@ -37,7 +37,7 @@ export const Tooltip: React.FC = ({ text, children }) => { getReferenceProps({ ref: reference, ...children.props }) )} - {open && ( + {isOpen && ( Date: Fri, 22 Jul 2022 23:24:49 +0400 Subject: [PATCH 08/21] modal window for local settings --- .../src/components/DistributionChart.tsx | 74 +----- .../src/components/SquiggleChart.tsx | 4 - .../src/components/SquigglePlayground.tsx | 246 +++--------------- .../SquiggleViewer/ExpressionViewer.tsx | 105 +++++--- .../SquiggleViewer/ItemSettingsMenu.tsx | 160 ++++++++---- .../components/SquiggleViewer/VariableBox.tsx | 13 +- .../SquiggleViewer/ViewerContext.ts | 2 +- .../src/components/SquiggleViewer/index.tsx | 10 +- .../src/components/SquiggleViewer/utils.ts | 2 + .../src/components/ViewSettings.tsx | 155 +++++++++++ .../src/components/ui/HeadedSection.tsx | 13 + .../src/components/ui/InputItem.tsx | 25 ++ .../components/src/components/ui/Modal.tsx | 84 ++++++ .../components/src/components/ui/Text.tsx | 5 + .../src/lib/distributionSpecBuilder.ts | 7 +- .../components/src/lib/distributionUtils.ts | 5 + 16 files changed, 519 insertions(+), 391 deletions(-) create mode 100644 packages/components/src/components/ViewSettings.tsx create mode 100644 packages/components/src/components/ui/HeadedSection.tsx create mode 100644 packages/components/src/components/ui/InputItem.tsx create mode 100644 packages/components/src/components/ui/Modal.tsx create mode 100644 packages/components/src/components/ui/Text.tsx create mode 100644 packages/components/src/lib/distributionUtils.ts diff --git a/packages/components/src/components/DistributionChart.tsx b/packages/components/src/components/DistributionChart.tsx index af644d29..1e1c3822 100644 --- a/packages/components/src/components/DistributionChart.tsx +++ b/packages/components/src/components/DistributionChart.tsx @@ -8,26 +8,24 @@ import { import { Vega } from "react-vega"; import { ErrorAlert } from "./Alert"; import { useSize } from "react-use"; -import clsx from "clsx"; import { buildVegaSpec, DistributionChartSpecOptions, } from "../lib/distributionSpecBuilder"; import { NumberShower } from "./NumberShower"; +import { hasMassBelowZero } from "../lib/distributionUtils"; export type DistributionPlottingSettings = { /** Whether to show a summary of means, stdev, percentiles etc */ showSummary: boolean; - /** Whether to show the user graph controls (scale etc) */ - showControls: boolean; + actions?: boolean; } & DistributionChartSpecOptions; export type DistributionChartProps = { distribution: Distribution; width?: number; height: number; - actions?: boolean; } & DistributionPlottingSettings; export const DistributionChart: React.FC = (props) => { @@ -36,17 +34,9 @@ export const DistributionChart: React.FC = (props) => { height, showSummary, width, - showControls, logX, - expY, actions = false, } = props; - const [isLogX, setLogX] = React.useState(logX); - const [isExpY, setExpY] = React.useState(expY); - - React.useEffect(() => setLogX(logX), [logX]); - React.useEffect(() => setExpY(expY), [expY]); - const shape = distribution.pointSet(); const [sized] = useSize((size) => { if (shape.tag === "Error") { @@ -57,9 +47,6 @@ export const DistributionChart: React.FC = (props) => { ); } - const massBelow0 = - shape.value.continuous.some((x) => x.x <= 0) || - shape.value.discrete.some((x) => x.x <= 0); const spec = buildVegaSpec(props); let widthProp = width ? width : size.width; @@ -72,7 +59,11 @@ export const DistributionChart: React.FC = (props) => { return (
- {!(isLogX && massBelow0) ? ( + {logX && hasMassBelowZero(shape.value) ? ( + + Cannot graph distribution with negative values on logarithmic scale. + + ) : ( = (props) => { height={height} actions={actions} /> - ) : ( - - Cannot graph distribution with negative values on logarithmic scale. - )}
{showSummary && }
- {showControls && ( -
- - -
- )}
); }); return sized; }; -interface CheckBoxProps { - label: string; - onChange: (x: boolean) => void; - value: boolean; - disabled?: boolean; - tooltip?: string; -} - -export const CheckBox: React.FC = ({ - label, - onChange, - value, - disabled = false, - tooltip, -}) => { - return ( - - onChange(!value)} - disabled={disabled} - className="form-checkbox" - /> - - - ); -}; - const TableHeadCell: React.FC<{ children: React.ReactNode }> = ({ children, }) => ( diff --git a/packages/components/src/components/SquiggleChart.tsx b/packages/components/src/components/SquiggleChart.tsx index a3bb5e4c..9367e63e 100644 --- a/packages/components/src/components/SquiggleChart.tsx +++ b/packages/components/src/components/SquiggleChart.tsx @@ -35,8 +35,6 @@ export interface SquiggleChartProps { jsImports?: jsImports; /** Whether to show a summary of the distribution */ showSummary?: boolean; - /** Whether to show graph controls (scale etc)*/ - showControls?: boolean; /** Set the x scale to be logarithmic by deault */ logX?: boolean; /** Set the y scale to be exponential by deault */ @@ -67,7 +65,6 @@ export const SquiggleChart: React.FC = React.memo( jsImports = defaultImports, showSummary = false, width, - showControls = false, logX = false, expY = false, diagramStart = 0, @@ -89,7 +86,6 @@ export const SquiggleChart: React.FC = React.memo( }); const distributionPlotSettings = { - showControls, showSummary, logX, expY, diff --git a/packages/components/src/components/SquigglePlayground.tsx b/packages/components/src/components/SquigglePlayground.tsx index ae3da135..eb297e7c 100644 --- a/packages/components/src/components/SquigglePlayground.tsx +++ b/packages/components/src/components/SquigglePlayground.tsx @@ -1,5 +1,5 @@ import React, { FC, useState, useEffect, useMemo } from "react"; -import { Path, useForm, UseFormRegister, useWatch } from "react-hook-form"; +import { useForm, UseFormRegister, useWatch } from "react-hook-form"; import * as yup from "yup"; import { useMaybeControlledValue } from "../lib/hooks"; import { yupResolver } from "@hookform/resolvers/yup"; @@ -24,8 +24,15 @@ import { JsonEditor } from "./JsonEditor"; import { ErrorAlert, SuccessAlert } from "./Alert"; import { SquiggleContainer } from "./SquiggleContainer"; import { Toggle } from "./ui/Toggle"; -import { Checkbox } from "./ui/Checkbox"; import { StyledTab } from "./ui/StyledTab"; +import { InputItem } from "./ui/InputItem"; +import { Text } from "./ui/Text"; +import { ViewSettings, viewSettingsSchema } from "./ViewSettings"; +import { HeadedSection } from "./ui/HeadedSection"; +import { + defaultColor, + defaultTickFormat, +} from "../lib/distributionSpecBuilder"; type PlaygroundProps = SquiggleChartProps & { /** The initial squiggle string to put in the playground */ @@ -37,90 +44,30 @@ type PlaygroundProps = SquiggleChartProps & { showEditor?: boolean; }; -const schema = yup.object({}).shape({ - sampleCount: yup - .number() - .required() - .positive() - .integer() - .default(1000) - .min(10) - .max(1000000), - xyPointLength: yup - .number() - .required() - .positive() - .integer() - .default(1000) - .min(10) - .max(10000), - chartHeight: yup.number().required().positive().integer().default(350), - leftSizePercent: yup - .number() - .required() - .positive() - .integer() - .min(10) - .max(100) - .default(50), - showControls: yup.boolean().required(), - showSummary: yup.boolean().required(), - showEditor: yup.boolean().required(), - logX: yup.boolean().required(), - expY: yup.boolean().required(), - tickFormat: yup.string().default(".9~s"), - title: yup.string(), - color: yup.string().default("#739ECC").required(), - minX: yup.number(), - maxX: yup.number(), - distributionChartActions: yup.boolean(), - showSettingsPage: yup.boolean().default(false), - diagramStart: yup.number().required().positive().integer().default(0).min(0), - diagramStop: yup.number().required().positive().integer().default(10).min(0), - diagramCount: yup.number().required().positive().integer().default(20).min(2), -}); +const schema = yup + .object({}) + .shape({ + sampleCount: yup + .number() + .required() + .positive() + .integer() + .default(1000) + .min(10) + .max(1000000), + xyPointLength: yup + .number() + .required() + .positive() + .integer() + .default(1000) + .min(10) + .max(10000), + }) + .concat(viewSettingsSchema); type FormFields = yup.InferType; -const HeadedSection: FC<{ title: string; children: React.ReactNode }> = ({ - title, - children, -}) => ( -
-
- {title} -
-
{children}
-
-); - -const Text: FC<{ children: React.ReactNode }> = ({ children }) => ( -

{children}

-); - -function InputItem({ - name, - label, - type, - register, -}: { - name: Path; - label: string; - type: "number" | "text" | "color"; - register: UseFormRegister; -}) { - return ( - - ); -} - const SamplingSettings: React.FC<{ register: UseFormRegister }> = ({ register, }) => ( @@ -156,123 +103,6 @@ const SamplingSettings: React.FC<{ register: UseFormRegister }> = ({
); -const ViewSettings: React.FC<{ register: UseFormRegister }> = ({ - register, -}) => ( -
- -
- - -
-
- -
- -
- - - - - - - - - - -
-
-
- -
- -
- - When displaying functions of single variables that return numbers or - distributions, we need to use defaults for the x-axis. We need to - select a minimum and maximum value of x to sample, and a number n of - the number of points to sample. - -
- - - -
-
-
-
-
-); - const InputVariablesSettings: React.FC<{ initialImports: any; // TODO - any json type setImports: (imports: any) => void; @@ -402,15 +232,14 @@ const useRunnerState = (code: string) => { export const SquigglePlayground: FC = ({ defaultCode = "", height = 500, - showControls = false, showSummary = false, logX = false, expY = false, title, minX, maxX, - color = "#739ECC", - tickFormat = ".9~s", + color = defaultColor, + tickFormat = defaultTickFormat, distributionChartActions, code: controlledCode, onCodeChange, @@ -431,7 +260,6 @@ export const SquigglePlayground: FC = ({ sampleCount: 1000, xyPointLength: 1000, chartHeight: 150, - showControls, logX, expY, title, @@ -442,8 +270,6 @@ export const SquigglePlayground: FC = ({ distributionChartActions, showSummary, showEditor, - leftSizePercent: 50, - showSettingsPage: false, diagramStart: 0, diagramStop: 10, diagramCount: 20, @@ -500,7 +326,13 @@ export const SquigglePlayground: FC = ({ - + + > + } + /> (x: declaration
) { const first = x.args[0]; @@ -33,12 +35,12 @@ function getChartSettings(x: declaration): FunctionChartSettings { const VariableList: React.FC<{ path: string[]; heading: string; - children: React.ReactNode; + children: (settings: MergedItemSettings) => React.ReactNode; }> = ({ path, heading, children }) => ( - {() => ( + {(settings) => (
- {children} + {children(settings)}
)}
@@ -50,14 +52,12 @@ export interface Props { /** Path to the current item, e.g. `['foo', 'bar', '3']` for `foo.bar[3]`; can be empty on the top-level item. */ path: string[]; width?: number; - height: number; } export const ExpressionViewer: React.FC = ({ path, expression, width, - height, }) => { switch (expression.tag) { case "number": @@ -78,9 +78,17 @@ export const ExpressionViewer: React.FC = ({ heading={`Distribution (${distType})\n${ distType === "Symbolic" ? expression.value.toString() : "" }`} - dropdownMenu={({ settings, setSettings }) => { + renderSettingsMenu={({ onChange }) => { + const shape = expression.value.pointSet(); return ( - + ); }} > @@ -89,7 +97,7 @@ export const ExpressionViewer: React.FC = ({ ); @@ -158,9 +166,13 @@ export const ExpressionViewer: React.FC = ({ { + renderSettingsMenu={({ onChange }) => { return ( - + ); }} > @@ -173,7 +185,7 @@ export const ExpressionViewer: React.FC = ({ fn={expression.value} chartSettings={settings.chartSettings} distributionPlotSettings={settings.distributionPlotSettings} - height={height} + height={settings.height} environment={{ sampleCount: settings.environment.sampleCount / 10, xyPointLength: settings.environment.xyPointLength / 10, @@ -188,9 +200,13 @@ export const ExpressionViewer: React.FC = ({ { + renderSettingsMenu={({ onChange }) => { return ( - + ); }} > @@ -199,7 +215,7 @@ export const ExpressionViewer: React.FC = ({ fn={expression.value.fn} chartSettings={getChartSettings(expression.value)} distributionPlotSettings={settings.distributionPlotSettings} - height={height} + height={settings.height} environment={{ sampleCount: settings.environment.sampleCount / 10, xyPointLength: settings.environment.xyPointLength / 10, @@ -212,46 +228,49 @@ export const ExpressionViewer: React.FC = ({ case "module": { return ( - {Object.entries(expression.value) - .filter(([key, r]) => key !== "Math") - .map(([key, r]) => ( - - ))} + {(settings) => + Object.entries(expression.value) + .filter(([key, r]) => key !== "Math") + .map(([key, r]) => ( + + )) + } ); } case "record": return ( - {Object.entries(expression.value).map(([key, r]) => ( - - ))} + {(settings) => + Object.entries(expression.value).map(([key, r]) => ( + + )) + } ); case "array": return ( - {expression.value.map((r, i) => ( - - ))} + {(settings) => + expression.value.map((r, i) => ( + + )) + } ); default: { diff --git a/packages/components/src/components/SquiggleViewer/ItemSettingsMenu.tsx b/packages/components/src/components/SquiggleViewer/ItemSettingsMenu.tsx index 898d5fc1..94e18493 100644 --- a/packages/components/src/components/SquiggleViewer/ItemSettingsMenu.tsx +++ b/packages/components/src/components/SquiggleViewer/ItemSettingsMenu.tsx @@ -1,73 +1,127 @@ -import React from "react"; -import { DropdownMenu } from "../ui/DropdownMenu"; -import { LocalItemSettings } from "./utils"; +import { CogIcon } from "@heroicons/react/solid"; +import React, { useContext, useState } from "react"; +import { useForm } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; +import { Modal } from "../ui/Modal"; +import { ViewSettings, viewSettingsSchema } from "../ViewSettings"; +import { Path, pathAsString } from "./utils"; +import { ViewerContext } from "./ViewerContext"; +import { + defaultColor, + defaultTickFormat, +} from "../../lib/distributionSpecBuilder"; type Props = { - settings: LocalItemSettings; - setSettings: (value: LocalItemSettings) => void; + path: Path; + onChange: () => void; + disableLogX?: boolean; + withFunctionSettings: boolean; }; -export const ItemSettingsMenu: React.FC = ({ - settings, - setSettings, +const ItemSettingsModal: React.FC void }> = ({ + path, + onChange, + disableLogX, + withFunctionSettings, + close, }) => { + const { setSettings, getSettings, getMergedSettings } = + useContext(ViewerContext); + + const mergedSettings = getMergedSettings(path); + + const { register, watch } = useForm({ + resolver: yupResolver(viewSettingsSchema), + defaultValues: { + showEditor: true, // doesn't matter + chartHeight: mergedSettings.height, + showSummary: mergedSettings.distributionPlotSettings.showSummary, + logX: mergedSettings.distributionPlotSettings.logX, + expY: mergedSettings.distributionPlotSettings.expY, + tickFormat: + mergedSettings.distributionPlotSettings.format || defaultTickFormat, + title: mergedSettings.distributionPlotSettings.title, + color: mergedSettings.distributionPlotSettings.color || defaultColor, + minX: mergedSettings.distributionPlotSettings.minX, + maxX: mergedSettings.distributionPlotSettings.maxX, + distributionChartActions: mergedSettings.distributionPlotSettings.actions, + diagramStart: mergedSettings.chartSettings.start, + diagramStop: mergedSettings.chartSettings.stop, + diagramCount: mergedSettings.chartSettings.count, + }, + }); + React.useEffect(() => { + const subscription = watch((vars) => { + const settings = getSettings(path); // get the latest version + setSettings(path, { + ...settings, + distributionPlotSettings: { + showSummary: vars.showSummary, + logX: vars.logX, + expY: vars.expY, + format: vars.tickFormat, + title: vars.title, + color: vars.color, + minX: vars.minX, + maxX: vars.maxX, + actions: vars.distributionChartActions, + }, + chartSettings: { + start: vars.diagramStart, + stop: vars.diagramStop, + count: vars.diagramCount, + }, + }); + onChange(); + }); + return () => subscription.unsubscribe(); + }, [getSettings, setSettings, onChange, path, watch]); + return ( -
- - - setSettings({ - ...settings, - distributionPlotSettings: { - ...settings.distributionPlotSettings, - logX: !settings.distributionPlotSettings?.logX, - }, - }) - } + + + Chart settings{path.length ? " for " + pathAsString(path) : ""} + + + - - setSettings({ - ...settings, - distributionPlotSettings: { - ...settings.distributionPlotSettings, - expY: !settings.distributionPlotSettings?.expY, - }, - }) - } - /> - - setSettings({ - ...settings, - distributionPlotSettings: { - ...settings.distributionPlotSettings, - showSummary: !settings.distributionPlotSettings?.showSummary, - }, - }) - } - /> - + + + ); +}; + +export const ItemSettingsMenu: React.FC = (props) => { + const [isOpen, setIsOpen] = useState(false); + const { setSettings, getSettings } = useContext(ViewerContext); + const settings = getSettings(props.path); + + return ( +
+ setIsOpen(!isOpen)} + /> {settings.distributionPlotSettings || settings.chartSettings ? ( ) : null} + {isOpen ? ( + setIsOpen(false)} /> + ) : null}
); }; diff --git a/packages/components/src/components/SquiggleViewer/VariableBox.tsx b/packages/components/src/components/SquiggleViewer/VariableBox.tsx index 84acb30d..f05fbca3 100644 --- a/packages/components/src/components/SquiggleViewer/VariableBox.tsx +++ b/packages/components/src/components/SquiggleViewer/VariableBox.tsx @@ -3,22 +3,21 @@ import { Tooltip } from "../ui/Tooltip"; import { LocalItemSettings, MergedItemSettings } from "./utils"; import { ViewerContext } from "./ViewerContext"; -type DropdownMenuParams = { - settings: LocalItemSettings; - setSettings: (value: LocalItemSettings) => void; +type SettingsMenuParams = { + onChange: () => void; // used to notify VariableBox that settings have changed, so that VariableBox could re-render itself }; type VariableBoxProps = { path: string[]; heading: string; - dropdownMenu?: (params: DropdownMenuParams) => React.ReactNode; + renderSettingsMenu?: (params: SettingsMenuParams) => React.ReactNode; children: (settings: MergedItemSettings) => React.ReactNode; }; export const VariableBox: React.FC = ({ path, heading = "Error", - dropdownMenu, + renderSettingsMenu, children, }) => { const { setSettings, getSettings, getMergedSettings } = @@ -57,8 +56,8 @@ export const VariableBox: React.FC = ({ > ... - ) : dropdownMenu ? ( - dropdownMenu({ settings, setSettings: setSettingsAndUpdate }) + ) : renderSettingsMenu ? ( + renderSettingsMenu({ onChange: forceUpdate }) ) : null} {settings.collapsed ? null : ( diff --git a/packages/components/src/components/SquiggleViewer/ViewerContext.ts b/packages/components/src/components/SquiggleViewer/ViewerContext.ts index c8b78b69..58270a90 100644 --- a/packages/components/src/components/SquiggleViewer/ViewerContext.ts +++ b/packages/components/src/components/SquiggleViewer/ViewerContext.ts @@ -23,11 +23,11 @@ export const ViewerContext = React.createContext({ }, distributionPlotSettings: { showSummary: false, - showControls: false, logX: false, expY: false, }, environment: defaultEnvironment, + height: 150, }), setSettings() {}, }); diff --git a/packages/components/src/components/SquiggleViewer/index.tsx b/packages/components/src/components/SquiggleViewer/index.tsx index be4dcaab..0c6a20dd 100644 --- a/packages/components/src/components/SquiggleViewer/index.tsx +++ b/packages/components/src/components/SquiggleViewer/index.tsx @@ -72,10 +72,11 @@ export const SquiggleViewer: React.FC = ({ ...environment, ...(localSettings.environment || {}), }, + height: localSettings.height || height, }; return result; }, - [distributionPlotSettings, chartSettings, environment, getSettings] + [distributionPlotSettings, chartSettings, environment, height, getSettings] ); return ( @@ -87,12 +88,7 @@ export const SquiggleViewer: React.FC = ({ }} > {result.tag === "Ok" ? ( - + ) : ( )} diff --git a/packages/components/src/components/SquiggleViewer/utils.ts b/packages/components/src/components/SquiggleViewer/utils.ts index 979d0cec..3053966b 100644 --- a/packages/components/src/components/SquiggleViewer/utils.ts +++ b/packages/components/src/components/SquiggleViewer/utils.ts @@ -6,12 +6,14 @@ export type LocalItemSettings = { collapsed: boolean; distributionPlotSettings?: Partial; chartSettings?: Partial; + height?: number; environment?: Partial; }; export type MergedItemSettings = { distributionPlotSettings: DistributionPlottingSettings; chartSettings: FunctionChartSettings; + height: number; environment: environment; }; diff --git a/packages/components/src/components/ViewSettings.tsx b/packages/components/src/components/ViewSettings.tsx new file mode 100644 index 00000000..def18d8d --- /dev/null +++ b/packages/components/src/components/ViewSettings.tsx @@ -0,0 +1,155 @@ +import React from "react"; +import * as yup from "yup"; +import { UseFormRegister } from "react-hook-form"; +import { InputItem } from "./ui/InputItem"; +import { Checkbox } from "./ui/Checkbox"; +import { HeadedSection } from "./ui/HeadedSection"; +import { Text } from "./ui/Text"; +import { + defaultColor, + defaultTickFormat, +} from "../lib/distributionSpecBuilder"; + +export const viewSettingsSchema = yup.object({}).shape({ + chartHeight: yup.number().required().positive().integer().default(350), + showSummary: yup.boolean().required(), + showEditor: yup.boolean().required(), + logX: yup.boolean().required(), + expY: yup.boolean().required(), + tickFormat: yup.string().default(defaultTickFormat), + title: yup.string(), + color: yup.string().default(defaultColor).required(), + minX: yup.number(), + maxX: yup.number(), + distributionChartActions: yup.boolean(), + diagramStart: yup.number().required().positive().integer().default(0).min(0), + diagramStop: yup.number().required().positive().integer().default(10).min(0), + diagramCount: yup.number().required().positive().integer().default(20).min(2), +}); + +type FormFields = yup.InferType; + +// This component is used in two places: for global settings in SquigglePlayground, and for item-specific settings in modal dialogs. +export const ViewSettings: React.FC<{ + withShowEditorSetting?: boolean; + withFunctionSettings?: boolean; + register: UseFormRegister; +}> = ({ + withShowEditorSetting = true, + withFunctionSettings = true, + register, +}) => { + return ( +
+ +
+ {withShowEditorSetting ? ( + + ) : null} + +
+
+ +
+ +
+ + + + + + + + + +
+
+
+ + {withFunctionSettings ? ( +
+ +
+ + When displaying functions of single variables that return + numbers or distributions, we need to use defaults for the + x-axis. We need to select a minimum and maximum value of x to + sample, and a number n of the number of points to sample. + +
+ + + +
+
+
+
+ ) : null} +
+ ); +}; diff --git a/packages/components/src/components/ui/HeadedSection.tsx b/packages/components/src/components/ui/HeadedSection.tsx new file mode 100644 index 00000000..e4389814 --- /dev/null +++ b/packages/components/src/components/ui/HeadedSection.tsx @@ -0,0 +1,13 @@ +import React from "react"; + +export const HeadedSection: React.FC<{ + title: string; + children: React.ReactNode; +}> = ({ title, children }) => ( +
+
+ {title} +
+
{children}
+
+); diff --git a/packages/components/src/components/ui/InputItem.tsx b/packages/components/src/components/ui/InputItem.tsx new file mode 100644 index 00000000..5d0ca613 --- /dev/null +++ b/packages/components/src/components/ui/InputItem.tsx @@ -0,0 +1,25 @@ +import React from "react"; +import { Path, UseFormRegister } from "react-hook-form"; + +export function InputItem({ + name, + label, + type, + register, +}: { + name: Path; + label: string; + type: "number" | "text" | "color"; + register: UseFormRegister; +}) { + return ( + + ); +} diff --git a/packages/components/src/components/ui/Modal.tsx b/packages/components/src/components/ui/Modal.tsx new file mode 100644 index 00000000..d0565ddc --- /dev/null +++ b/packages/components/src/components/ui/Modal.tsx @@ -0,0 +1,84 @@ +import { motion } from "framer-motion"; +import * as React from "react"; +import * as ReactDOM from "react-dom"; +import { XIcon } from "@heroicons/react/solid"; + +const Overlay: React.FC = () => ( + +); + +const ModalHeader: React.FC<{ + close: () => void; + children: React.ReactNode; +}> = ({ children, close }) => { + return ( +
+
{children}
+ +
+ ); +}; + +// TODO - get rid of forwardRef, support `focus` and `{...hotkeys}` via smart props +const ModalBody = React.forwardRef< + HTMLDivElement, + JSX.IntrinsicElements["div"] +>(function ModalBody(props, ref) { + return
; +}); + +const ModalFooter: React.FC<{ children: React.ReactNode }> = ({ children }) => ( +
{children}
+); + +const ModalWindow: React.FC<{ children: React.ReactNode }> = ({ children }) => ( +
+ {children} +
+); + +type ModalType = React.FC<{ children: React.ReactNode }> & { + Body: typeof ModalBody; + Footer: typeof ModalFooter; + Header: typeof ModalHeader; +}; + +export const Modal: ModalType = ({ children }) => { + const [el] = React.useState(() => document.createElement("div")); + + React.useEffect(() => { + document.body.appendChild(el); + + return () => { + document.body.removeChild(el); + }; + }, [el]); + + const modal = ( +
+
+ + {children} +
+
+ ); + + return ReactDOM.createPortal(modal, el); +}; + +Modal.Body = ModalBody; +Modal.Footer = ModalFooter; +Modal.Header = ModalHeader; diff --git a/packages/components/src/components/ui/Text.tsx b/packages/components/src/components/ui/Text.tsx new file mode 100644 index 00000000..edf9471f --- /dev/null +++ b/packages/components/src/components/ui/Text.tsx @@ -0,0 +1,5 @@ +import React from "react"; + +export const Text: React.FC<{ children: React.ReactNode }> = ({ children }) => ( +

{children}

+); diff --git a/packages/components/src/lib/distributionSpecBuilder.ts b/packages/components/src/lib/distributionSpecBuilder.ts index d04f0c44..4dc87baa 100644 --- a/packages/components/src/lib/distributionSpecBuilder.ts +++ b/packages/components/src/lib/distributionSpecBuilder.ts @@ -100,12 +100,15 @@ export let expYScale: PowScale = { }, }; +export const defaultTickFormat = ".9~s"; +export const defaultColor = "#739ECC"; + export function buildVegaSpec( specOptions: DistributionChartSpecOptions ): VisualizationSpec { let { - format = ".9~s", - color = "#739ECC", + format = defaultTickFormat, + color = defaultColor, title, minX, maxX, diff --git a/packages/components/src/lib/distributionUtils.ts b/packages/components/src/lib/distributionUtils.ts new file mode 100644 index 00000000..086aac42 --- /dev/null +++ b/packages/components/src/lib/distributionUtils.ts @@ -0,0 +1,5 @@ +import { shape } from "@quri/squiggle-lang"; + +export const hasMassBelowZero = (shape: shape) => + shape.continuous.some((x) => x.x <= 0) || + shape.discrete.some((x) => x.x <= 0); From d2fb973e1da783b8b84ad7950003fe51ee164453 Mon Sep 17 00:00:00 2001 From: Vyacheslav Matyukhin Date: Fri, 22 Jul 2022 23:32:39 +0400 Subject: [PATCH 09/21] disableLogX flag in local settings --- .../SquiggleViewer/ItemSettingsMenu.tsx | 1 + .../components/src/components/ViewSettings.tsx | 8 ++++++++ .../components/src/components/ui/Checkbox.tsx | 17 +++++++++++++++-- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/packages/components/src/components/SquiggleViewer/ItemSettingsMenu.tsx b/packages/components/src/components/SquiggleViewer/ItemSettingsMenu.tsx index 94e18493..878fe1ab 100644 --- a/packages/components/src/components/SquiggleViewer/ItemSettingsMenu.tsx +++ b/packages/components/src/components/SquiggleViewer/ItemSettingsMenu.tsx @@ -87,6 +87,7 @@ const ItemSettingsModal: React.FC void }> = ({ register={register} withShowEditorSetting={false} withFunctionSettings={withFunctionSettings} + disableLogXSetting={disableLogX} /> diff --git a/packages/components/src/components/ViewSettings.tsx b/packages/components/src/components/ViewSettings.tsx index def18d8d..9a2ce562 100644 --- a/packages/components/src/components/ViewSettings.tsx +++ b/packages/components/src/components/ViewSettings.tsx @@ -33,10 +33,12 @@ type FormFields = yup.InferType; export const ViewSettings: React.FC<{ withShowEditorSetting?: boolean; withFunctionSettings?: boolean; + disableLogXSetting?: boolean; register: UseFormRegister; }> = ({ withShowEditorSetting = true, withFunctionSettings = true, + disableLogXSetting, register, }) => { return ( @@ -66,6 +68,12 @@ export const ViewSettings: React.FC<{ register={register} name="logX" label="Show x scale logarithmically" + disabled={disableLogXSetting} + tooltip={ + disableLogXSetting + ? "Your distribution has mass lower than or equal to 0. Log only works on strictly positive values." + : undefined + } /> ({ name, label, register, + disabled, + tooltip, }: { name: Path; label: string; register: UseFormRegister; + disabled?: boolean; + tooltip?: string; }) { return ( -