Merge branch 'develop' into documentation-refactors-jul
* develop: (35 commits) don't spin pause icon on runs delayed overlay and autorun spinner playground renders code on second pass executionId for code re-runs spinner in autorun mode; autorun refactorings increase modal width remove toggle animation hideOnScoll navbar on website smooth scrolling when scrolling back to item from modal improve tooltip positioning fix tooltip padding on website close modals on escape key change variable border color smart modal positioning fixes #872 ⬆️ Bump @typescript-eslint/parser from 5.30.6 to 5.30.7 ⬆️ Bump @types/node from 18.0.6 to 18.6.1 ⬆️ Bump mathjs from 10.6.4 to 11.0.1 ⬆️ Bump webpack from 5.73.0 to 5.74.0 ⬆️ Bump @testing-library/user-event from 14.2.6 to 14.3.0 ...
This commit is contained in:
commit
b1cc459d13
|
@ -54,7 +54,6 @@ export function DynamicSquiggleChart({ squiggleString }) {
|
||||||
width={445}
|
width={445}
|
||||||
height={200}
|
height={200}
|
||||||
showSummary={true}
|
showSummary={true}
|
||||||
showTypes={true}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
"version": "0.2.23",
|
"version": "0.2.23",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@floating-ui/react-dom": "^0.7.2",
|
||||||
|
"@floating-ui/react-dom-interactions": "^0.6.6",
|
||||||
"@headlessui/react": "^1.6.6",
|
"@headlessui/react": "^1.6.6",
|
||||||
"@heroicons/react": "^1.0.6",
|
"@heroicons/react": "^1.0.6",
|
||||||
"@hookform/resolvers": "^2.9.6",
|
"@hookform/resolvers": "^2.9.6",
|
||||||
|
@ -34,10 +36,10 @@
|
||||||
"@storybook/react": "^6.5.9",
|
"@storybook/react": "^6.5.9",
|
||||||
"@testing-library/jest-dom": "^5.16.4",
|
"@testing-library/jest-dom": "^5.16.4",
|
||||||
"@testing-library/react": "^13.3.0",
|
"@testing-library/react": "^13.3.0",
|
||||||
"@testing-library/user-event": "^14.2.6",
|
"@testing-library/user-event": "^14.3.0",
|
||||||
"@types/jest": "^27.5.0",
|
"@types/jest": "^27.5.0",
|
||||||
"@types/lodash": "^4.14.182",
|
"@types/lodash": "^4.14.182",
|
||||||
"@types/node": "^18.0.6",
|
"@types/node": "^18.6.1",
|
||||||
"@types/react": "^18.0.9",
|
"@types/react": "^18.0.9",
|
||||||
"@types/styled-components": "^5.1.24",
|
"@types/styled-components": "^5.1.24",
|
||||||
"@types/webpack": "^5.28.0",
|
"@types/webpack": "^5.28.0",
|
||||||
|
@ -54,7 +56,7 @@
|
||||||
"tsconfig-paths-webpack-plugin": "^3.5.2",
|
"tsconfig-paths-webpack-plugin": "^3.5.2",
|
||||||
"typescript": "^4.7.4",
|
"typescript": "^4.7.4",
|
||||||
"web-vitals": "^2.1.4",
|
"web-vitals": "^2.1.4",
|
||||||
"webpack": "^5.73.0",
|
"webpack": "^5.74.0",
|
||||||
"webpack-cli": "^4.10.0",
|
"webpack-cli": "^4.10.0",
|
||||||
"webpack-dev-server": "^4.9.3"
|
"webpack-dev-server": "^4.9.3"
|
||||||
},
|
},
|
||||||
|
|
|
@ -8,26 +8,24 @@ import {
|
||||||
import { Vega } from "react-vega";
|
import { Vega } from "react-vega";
|
||||||
import { ErrorAlert } from "./Alert";
|
import { ErrorAlert } from "./Alert";
|
||||||
import { useSize } from "react-use";
|
import { useSize } from "react-use";
|
||||||
import clsx from "clsx";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
buildVegaSpec,
|
buildVegaSpec,
|
||||||
DistributionChartSpecOptions,
|
DistributionChartSpecOptions,
|
||||||
} from "../lib/distributionSpecBuilder";
|
} from "../lib/distributionSpecBuilder";
|
||||||
import { NumberShower } from "./NumberShower";
|
import { NumberShower } from "./NumberShower";
|
||||||
|
import { hasMassBelowZero } from "../lib/distributionUtils";
|
||||||
|
|
||||||
export type DistributionPlottingSettings = {
|
export type DistributionPlottingSettings = {
|
||||||
/** Whether to show a summary of means, stdev, percentiles etc */
|
/** Whether to show a summary of means, stdev, percentiles etc */
|
||||||
showSummary: boolean;
|
showSummary: boolean;
|
||||||
/** Whether to show the user graph controls (scale etc) */
|
actions?: boolean;
|
||||||
showControls: boolean;
|
|
||||||
} & DistributionChartSpecOptions;
|
} & DistributionChartSpecOptions;
|
||||||
|
|
||||||
export type DistributionChartProps = {
|
export type DistributionChartProps = {
|
||||||
distribution: Distribution;
|
distribution: Distribution;
|
||||||
width?: number;
|
width?: number;
|
||||||
height: number;
|
height: number;
|
||||||
actions?: boolean;
|
|
||||||
} & DistributionPlottingSettings;
|
} & DistributionPlottingSettings;
|
||||||
|
|
||||||
export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
|
export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
|
||||||
|
@ -36,17 +34,9 @@ export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
|
||||||
height,
|
height,
|
||||||
showSummary,
|
showSummary,
|
||||||
width,
|
width,
|
||||||
showControls,
|
|
||||||
logX,
|
logX,
|
||||||
expY,
|
|
||||||
actions = false,
|
actions = false,
|
||||||
} = props;
|
} = 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 shape = distribution.pointSet();
|
||||||
const [sized] = useSize((size) => {
|
const [sized] = useSize((size) => {
|
||||||
if (shape.tag === "Error") {
|
if (shape.tag === "Error") {
|
||||||
|
@ -57,9 +47,6 @@ export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const massBelow0 =
|
|
||||||
shape.value.continuous.some((x) => x.x <= 0) ||
|
|
||||||
shape.value.discrete.some((x) => x.x <= 0);
|
|
||||||
const spec = buildVegaSpec(props);
|
const spec = buildVegaSpec(props);
|
||||||
|
|
||||||
let widthProp = width ? width : size.width;
|
let widthProp = width ? width : size.width;
|
||||||
|
@ -72,7 +59,11 @@ export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ width: widthProp }}>
|
<div style={{ width: widthProp }}>
|
||||||
{!(isLogX && massBelow0) ? (
|
{logX && hasMassBelowZero(shape.value) ? (
|
||||||
|
<ErrorAlert heading="Log Domain Error">
|
||||||
|
Cannot graph distribution with negative values on logarithmic scale.
|
||||||
|
</ErrorAlert>
|
||||||
|
) : (
|
||||||
<Vega
|
<Vega
|
||||||
spec={spec}
|
spec={spec}
|
||||||
data={{ con: shape.value.continuous, dis: shape.value.discrete }}
|
data={{ con: shape.value.continuous, dis: shape.value.discrete }}
|
||||||
|
@ -80,67 +71,16 @@ export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
|
||||||
height={height}
|
height={height}
|
||||||
actions={actions}
|
actions={actions}
|
||||||
/>
|
/>
|
||||||
) : (
|
|
||||||
<ErrorAlert heading="Log Domain Error">
|
|
||||||
Cannot graph distribution with negative values on logarithmic scale.
|
|
||||||
</ErrorAlert>
|
|
||||||
)}
|
)}
|
||||||
<div className="flex justify-center">
|
<div className="flex justify-center">
|
||||||
{showSummary && <SummaryTable distribution={distribution} />}
|
{showSummary && <SummaryTable distribution={distribution} />}
|
||||||
</div>
|
</div>
|
||||||
{showControls && (
|
|
||||||
<div>
|
|
||||||
<CheckBox
|
|
||||||
label="Log X scale"
|
|
||||||
value={isLogX}
|
|
||||||
onChange={setLogX}
|
|
||||||
// Check whether we should disable the checkbox
|
|
||||||
{...(massBelow0
|
|
||||||
? {
|
|
||||||
disabled: true,
|
|
||||||
tooltip:
|
|
||||||
"Your distribution has mass lower than or equal to 0. Log only works on strictly positive values.",
|
|
||||||
}
|
|
||||||
: {})}
|
|
||||||
/>
|
|
||||||
<CheckBox label="Exp Y scale" value={isExpY} onChange={setExpY} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
return sized;
|
return sized;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface CheckBoxProps {
|
|
||||||
label: string;
|
|
||||||
onChange: (x: boolean) => void;
|
|
||||||
value: boolean;
|
|
||||||
disabled?: boolean;
|
|
||||||
tooltip?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const CheckBox: React.FC<CheckBoxProps> = ({
|
|
||||||
label,
|
|
||||||
onChange,
|
|
||||||
value,
|
|
||||||
disabled = false,
|
|
||||||
tooltip,
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<span title={tooltip}>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={value}
|
|
||||||
onChange={() => onChange(!value)}
|
|
||||||
disabled={disabled}
|
|
||||||
className="form-checkbox"
|
|
||||||
/>
|
|
||||||
<label className={clsx(disabled && "text-slate-400")}> {label}</label>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const TableHeadCell: React.FC<{ children: React.ReactNode }> = ({
|
const TableHeadCell: React.FC<{ children: React.ReactNode }> = ({
|
||||||
children,
|
children,
|
||||||
}) => (
|
}) => (
|
||||||
|
|
|
@ -9,12 +9,13 @@ import {
|
||||||
defaultEnvironment,
|
defaultEnvironment,
|
||||||
} from "@quri/squiggle-lang";
|
} from "@quri/squiggle-lang";
|
||||||
import { useSquiggle } from "../lib/hooks";
|
import { useSquiggle } from "../lib/hooks";
|
||||||
import { SquiggleErrorAlert } from "./SquiggleErrorAlert";
|
import { SquiggleViewer } from "./SquiggleViewer";
|
||||||
import { SquiggleItem } from "./SquiggleItem";
|
|
||||||
|
|
||||||
export interface SquiggleChartProps {
|
export interface SquiggleChartProps {
|
||||||
/** The input string for squiggle */
|
/** 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 */
|
/** If the output requires monte carlo sampling, the amount of samples */
|
||||||
sampleCount?: number;
|
sampleCount?: number;
|
||||||
/** The amount of points returned to draw the distribution */
|
/** The amount of points returned to draw the distribution */
|
||||||
|
@ -36,10 +37,6 @@ export interface SquiggleChartProps {
|
||||||
jsImports?: jsImports;
|
jsImports?: jsImports;
|
||||||
/** Whether to show a summary of the distribution */
|
/** Whether to show a summary of the distribution */
|
||||||
showSummary?: boolean;
|
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 */
|
/** Set the x scale to be logarithmic by deault */
|
||||||
logX?: boolean;
|
logX?: boolean;
|
||||||
/** Set the y scale to be exponential by deault */
|
/** Set the y scale to be exponential by deault */
|
||||||
|
@ -56,6 +53,7 @@ export interface SquiggleChartProps {
|
||||||
maxX?: number;
|
maxX?: number;
|
||||||
/** Whether to show vega actions to the user, so they can copy the chart spec */
|
/** Whether to show vega actions to the user, so they can copy the chart spec */
|
||||||
distributionChartActions?: boolean;
|
distributionChartActions?: boolean;
|
||||||
|
enableLocalSettings?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultOnChange = () => {};
|
const defaultOnChange = () => {};
|
||||||
|
@ -63,6 +61,7 @@ const defaultOnChange = () => {};
|
||||||
export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
|
export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
|
||||||
({
|
({
|
||||||
code = "",
|
code = "",
|
||||||
|
executionId = 0,
|
||||||
environment,
|
environment,
|
||||||
onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here
|
onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here
|
||||||
height = 200,
|
height = 200,
|
||||||
|
@ -70,8 +69,6 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
|
||||||
jsImports = defaultImports,
|
jsImports = defaultImports,
|
||||||
showSummary = false,
|
showSummary = false,
|
||||||
width,
|
width,
|
||||||
showTypes = false,
|
|
||||||
showControls = false,
|
|
||||||
logX = false,
|
logX = false,
|
||||||
expY = false,
|
expY = false,
|
||||||
diagramStart = 0,
|
diagramStart = 0,
|
||||||
|
@ -83,6 +80,7 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
|
||||||
color,
|
color,
|
||||||
title,
|
title,
|
||||||
distributionChartActions,
|
distributionChartActions,
|
||||||
|
enableLocalSettings = false,
|
||||||
}) => {
|
}) => {
|
||||||
const result = useSquiggle({
|
const result = useSquiggle({
|
||||||
code,
|
code,
|
||||||
|
@ -90,14 +88,10 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
|
||||||
environment,
|
environment,
|
||||||
jsImports,
|
jsImports,
|
||||||
onChange,
|
onChange,
|
||||||
|
executionId,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result.tag !== "Ok") {
|
const distributionPlotSettings = {
|
||||||
return <SquiggleErrorAlert error={result.value} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
let distributionPlotSettings = {
|
|
||||||
showControls,
|
|
||||||
showSummary,
|
showSummary,
|
||||||
logX,
|
logX,
|
||||||
expY,
|
expY,
|
||||||
|
@ -109,21 +103,21 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
|
||||||
actions: distributionChartActions,
|
actions: distributionChartActions,
|
||||||
};
|
};
|
||||||
|
|
||||||
let chartSettings = {
|
const chartSettings = {
|
||||||
start: diagramStart,
|
start: diagramStart,
|
||||||
stop: diagramStop,
|
stop: diagramStop,
|
||||||
count: diagramCount,
|
count: diagramCount,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SquiggleItem
|
<SquiggleViewer
|
||||||
expression={result.value}
|
result={result}
|
||||||
width={width}
|
width={width}
|
||||||
height={height}
|
height={height}
|
||||||
distributionPlotSettings={distributionPlotSettings}
|
distributionPlotSettings={distributionPlotSettings}
|
||||||
showTypes={showTypes}
|
|
||||||
chartSettings={chartSettings}
|
chartSettings={chartSettings}
|
||||||
environment={environment ?? defaultEnvironment}
|
environment={environment ?? defaultEnvironment}
|
||||||
|
enableLocalSettings={enableLocalSettings}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ const SquiggleContext = React.createContext<SquiggleContextShape>({
|
||||||
|
|
||||||
export const SquiggleContainer: React.FC<Props> = ({ children }) => {
|
export const SquiggleContainer: React.FC<Props> = ({ children }) => {
|
||||||
const context = useContext(SquiggleContext);
|
const context = useContext(SquiggleContext);
|
||||||
|
|
||||||
if (context.containerized) {
|
if (context.containerized) {
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,7 +1,14 @@
|
||||||
import React, { FC, useState, useEffect, useMemo } from "react";
|
import React, {
|
||||||
import { Path, useForm, UseFormRegister, useWatch } from "react-hook-form";
|
FC,
|
||||||
|
useState,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useCallback,
|
||||||
|
} from "react";
|
||||||
|
import { useForm, UseFormRegister, useWatch } from "react-hook-form";
|
||||||
import * as yup from "yup";
|
import * as yup from "yup";
|
||||||
import { useMaybeControlledValue } from "../lib/hooks";
|
import { useMaybeControlledValue, useRunnerState } from "../lib/hooks";
|
||||||
import { yupResolver } from "@hookform/resolvers/yup";
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
import {
|
import {
|
||||||
ChartSquareBarIcon,
|
ChartSquareBarIcon,
|
||||||
|
@ -24,13 +31,19 @@ import { JsonEditor } from "./JsonEditor";
|
||||||
import { ErrorAlert, SuccessAlert } from "./Alert";
|
import { ErrorAlert, SuccessAlert } from "./Alert";
|
||||||
import { SquiggleContainer } from "./SquiggleContainer";
|
import { SquiggleContainer } from "./SquiggleContainer";
|
||||||
import { Toggle } from "./ui/Toggle";
|
import { Toggle } from "./ui/Toggle";
|
||||||
import { Checkbox } from "./ui/Checkbox";
|
|
||||||
import { StyledTab } from "./ui/StyledTab";
|
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 & {
|
type PlaygroundProps = SquiggleChartProps & {
|
||||||
/** The initial squiggle string to put in the playground */
|
/** The initial squiggle string to put in the playground */
|
||||||
defaultCode?: string;
|
defaultCode?: string;
|
||||||
/** How many pixels high is the playground */
|
|
||||||
onCodeChange?(expr: string): void;
|
onCodeChange?(expr: string): void;
|
||||||
/* When settings change */
|
/* When settings change */
|
||||||
onSettingsChange?(settings: any): void;
|
onSettingsChange?(settings: any): void;
|
||||||
|
@ -38,91 +51,30 @@ type PlaygroundProps = SquiggleChartProps & {
|
||||||
showEditor?: boolean;
|
showEditor?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const schema = yup.object({}).shape({
|
const schema = yup
|
||||||
sampleCount: yup
|
.object({})
|
||||||
.number()
|
.shape({
|
||||||
.required()
|
sampleCount: yup
|
||||||
.positive()
|
.number()
|
||||||
.integer()
|
.required()
|
||||||
.default(1000)
|
.positive()
|
||||||
.min(10)
|
.integer()
|
||||||
.max(1000000),
|
.default(1000)
|
||||||
xyPointLength: yup
|
.min(10)
|
||||||
.number()
|
.max(1000000),
|
||||||
.required()
|
xyPointLength: yup
|
||||||
.positive()
|
.number()
|
||||||
.integer()
|
.required()
|
||||||
.default(1000)
|
.positive()
|
||||||
.min(10)
|
.integer()
|
||||||
.max(10000),
|
.default(1000)
|
||||||
chartHeight: yup.number().required().positive().integer().default(350),
|
.min(10)
|
||||||
leftSizePercent: yup
|
.max(10000),
|
||||||
.number()
|
})
|
||||||
.required()
|
.concat(viewSettingsSchema);
|
||||||
.positive()
|
|
||||||
.integer()
|
|
||||||
.min(10)
|
|
||||||
.max(100)
|
|
||||||
.default(50),
|
|
||||||
showTypes: yup.boolean().required(),
|
|
||||||
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),
|
|
||||||
});
|
|
||||||
|
|
||||||
type FormFields = yup.InferType<typeof schema>;
|
type FormFields = yup.InferType<typeof schema>;
|
||||||
|
|
||||||
const HeadedSection: FC<{ title: string; children: React.ReactNode }> = ({
|
|
||||||
title,
|
|
||||||
children,
|
|
||||||
}) => (
|
|
||||||
<div>
|
|
||||||
<header className="text-lg leading-6 font-medium text-gray-900">
|
|
||||||
{title}
|
|
||||||
</header>
|
|
||||||
<div className="mt-4">{children}</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const Text: FC<{ children: React.ReactNode }> = ({ children }) => (
|
|
||||||
<p className="text-sm text-gray-500">{children}</p>
|
|
||||||
);
|
|
||||||
|
|
||||||
function InputItem<T>({
|
|
||||||
name,
|
|
||||||
label,
|
|
||||||
type,
|
|
||||||
register,
|
|
||||||
}: {
|
|
||||||
name: Path<T>;
|
|
||||||
label: string;
|
|
||||||
type: "number" | "text" | "color";
|
|
||||||
register: UseFormRegister<T>;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<label className="block">
|
|
||||||
<div className="text-sm font-medium text-gray-600 mb-1">{label}</div>
|
|
||||||
<input
|
|
||||||
type={type}
|
|
||||||
{...register(name, { valueAsNumber: type === "number" })}
|
|
||||||
className="form-input max-w-lg block w-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:max-w-xs sm:text-sm border-gray-300 rounded-md"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const SamplingSettings: React.FC<{ register: UseFormRegister<FormFields> }> = ({
|
const SamplingSettings: React.FC<{ register: UseFormRegister<FormFields> }> = ({
|
||||||
register,
|
register,
|
||||||
}) => (
|
}) => (
|
||||||
|
@ -158,128 +110,6 @@ const SamplingSettings: React.FC<{ register: UseFormRegister<FormFields> }> = ({
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const ViewSettings: React.FC<{ register: UseFormRegister<FormFields> }> = ({
|
|
||||||
register,
|
|
||||||
}) => (
|
|
||||||
<div className="space-y-6 p-3 divide-y divide-gray-200 max-w-xl">
|
|
||||||
<HeadedSection title="General Display Settings">
|
|
||||||
<div className="space-y-4">
|
|
||||||
<Checkbox
|
|
||||||
name="showEditor"
|
|
||||||
register={register}
|
|
||||||
label="Show code editor on left"
|
|
||||||
/>
|
|
||||||
<InputItem
|
|
||||||
name="chartHeight"
|
|
||||||
type="number"
|
|
||||||
register={register}
|
|
||||||
label="Chart Height (in pixels)"
|
|
||||||
/>
|
|
||||||
<Checkbox
|
|
||||||
name="showTypes"
|
|
||||||
register={register}
|
|
||||||
label="Show information about displayed types"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</HeadedSection>
|
|
||||||
|
|
||||||
<div className="pt-8">
|
|
||||||
<HeadedSection title="Distribution Display Settings">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Checkbox
|
|
||||||
register={register}
|
|
||||||
name="logX"
|
|
||||||
label="Show x scale logarithmically"
|
|
||||||
/>
|
|
||||||
<Checkbox
|
|
||||||
register={register}
|
|
||||||
name="expY"
|
|
||||||
label="Show y scale exponentially"
|
|
||||||
/>
|
|
||||||
<Checkbox
|
|
||||||
register={register}
|
|
||||||
name="distributionChartActions"
|
|
||||||
label="Show vega chart controls"
|
|
||||||
/>
|
|
||||||
<Checkbox
|
|
||||||
register={register}
|
|
||||||
name="showControls"
|
|
||||||
label="Show toggles to adjust scale of x and y axes"
|
|
||||||
/>
|
|
||||||
<Checkbox
|
|
||||||
register={register}
|
|
||||||
name="showSummary"
|
|
||||||
label="Show summary statistics"
|
|
||||||
/>
|
|
||||||
<InputItem
|
|
||||||
name="minX"
|
|
||||||
type="number"
|
|
||||||
register={register}
|
|
||||||
label="Min X Value"
|
|
||||||
/>
|
|
||||||
<InputItem
|
|
||||||
name="maxX"
|
|
||||||
type="number"
|
|
||||||
register={register}
|
|
||||||
label="Max X Value"
|
|
||||||
/>
|
|
||||||
<InputItem
|
|
||||||
name="title"
|
|
||||||
type="text"
|
|
||||||
register={register}
|
|
||||||
label="Title"
|
|
||||||
/>
|
|
||||||
<InputItem
|
|
||||||
name="tickFormat"
|
|
||||||
type="text"
|
|
||||||
register={register}
|
|
||||||
label="Tick Format"
|
|
||||||
/>
|
|
||||||
<InputItem
|
|
||||||
name="color"
|
|
||||||
type="color"
|
|
||||||
register={register}
|
|
||||||
label="Color"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</HeadedSection>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="pt-8">
|
|
||||||
<HeadedSection title="Function Display Settings">
|
|
||||||
<div className="space-y-6">
|
|
||||||
<Text>
|
|
||||||
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.
|
|
||||||
</Text>
|
|
||||||
<div className="space-y-4">
|
|
||||||
<InputItem
|
|
||||||
type="number"
|
|
||||||
name="diagramStart"
|
|
||||||
register={register}
|
|
||||||
label="Min X Value"
|
|
||||||
/>
|
|
||||||
<InputItem
|
|
||||||
type="number"
|
|
||||||
name="diagramStop"
|
|
||||||
register={register}
|
|
||||||
label="Max X Value"
|
|
||||||
/>
|
|
||||||
<InputItem
|
|
||||||
type="number"
|
|
||||||
name="diagramCount"
|
|
||||||
register={register}
|
|
||||||
label="Points between X min and X max to sample"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</HeadedSection>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const InputVariablesSettings: React.FC<{
|
const InputVariablesSettings: React.FC<{
|
||||||
initialImports: any; // TODO - any json type
|
initialImports: any; // TODO - any json type
|
||||||
setImports: (imports: any) => void;
|
setImports: (imports: any) => void;
|
||||||
|
@ -361,64 +191,30 @@ const RunControls: React.FC<{
|
||||||
icons={[CheckCircleIcon, PauseIcon]}
|
icons={[CheckCircleIcon, PauseIcon]}
|
||||||
status={autorunMode}
|
status={autorunMode}
|
||||||
onChange={onAutorunModeChange}
|
onChange={onAutorunModeChange}
|
||||||
|
spinIcon={autorunMode && isRunning}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const useRunnerState = (code: string) => {
|
type PlaygroundContextShape = {
|
||||||
const [autorunMode, setAutorunMode] = useState(true);
|
getLeftPanelElement: () => HTMLDivElement | undefined;
|
||||||
const [renderedCode, setRenderedCode] = useState(code); // used in manual run mode only
|
|
||||||
const [isRunning, setIsRunning] = useState(false); // used in manual run mode only
|
|
||||||
|
|
||||||
// This part is tricky and fragile; we need to re-render first to make sure that the icon is spinning,
|
|
||||||
// and only then evaluate the squiggle code (which freezes the UI).
|
|
||||||
// Also note that `useEffect` execution order matters here.
|
|
||||||
// Hopefully it'll all go away after we make squiggle code evaluation async.
|
|
||||||
useEffect(() => {
|
|
||||||
if (renderedCode === code && isRunning) {
|
|
||||||
// It's not possible to put this after `setRenderedCode(code)` below because React would apply
|
|
||||||
// `setIsRunning` and `setRenderedCode` together and spinning icon will disappear immediately.
|
|
||||||
setIsRunning(false);
|
|
||||||
}
|
|
||||||
}, [renderedCode, code, isRunning]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!autorunMode && isRunning) {
|
|
||||||
setRenderedCode(code); // TODO - force run even if code hasn't changed
|
|
||||||
}
|
|
||||||
}, [autorunMode, code, isRunning]);
|
|
||||||
|
|
||||||
const run = () => {
|
|
||||||
// The rest will be handled by useEffects above, but we need to update the spinner first.
|
|
||||||
setIsRunning(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
run,
|
|
||||||
renderedCode: autorunMode ? code : renderedCode,
|
|
||||||
isRunning,
|
|
||||||
autorunMode,
|
|
||||||
setAutorunMode: (newValue: boolean) => {
|
|
||||||
if (!newValue) setRenderedCode(code);
|
|
||||||
setAutorunMode(newValue);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
export const PlaygroundContext = React.createContext<PlaygroundContextShape>({
|
||||||
|
getLeftPanelElement: () => undefined,
|
||||||
|
});
|
||||||
|
|
||||||
export const SquigglePlayground: FC<PlaygroundProps> = ({
|
export const SquigglePlayground: FC<PlaygroundProps> = ({
|
||||||
defaultCode = "",
|
defaultCode = "",
|
||||||
height = 500,
|
height = 500,
|
||||||
showTypes = false,
|
|
||||||
showControls = false,
|
|
||||||
showSummary = false,
|
showSummary = false,
|
||||||
logX = false,
|
logX = false,
|
||||||
expY = false,
|
expY = false,
|
||||||
title,
|
title,
|
||||||
minX,
|
minX,
|
||||||
maxX,
|
maxX,
|
||||||
color = "#739ECC",
|
color = defaultColor,
|
||||||
tickFormat = ".9~s",
|
tickFormat = defaultTickFormat,
|
||||||
distributionChartActions,
|
distributionChartActions,
|
||||||
code: controlledCode,
|
code: controlledCode,
|
||||||
onCodeChange,
|
onCodeChange,
|
||||||
|
@ -439,8 +235,6 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
|
||||||
sampleCount: 1000,
|
sampleCount: 1000,
|
||||||
xyPointLength: 1000,
|
xyPointLength: 1000,
|
||||||
chartHeight: 150,
|
chartHeight: 150,
|
||||||
showTypes,
|
|
||||||
showControls,
|
|
||||||
logX,
|
logX,
|
||||||
expY,
|
expY,
|
||||||
title,
|
title,
|
||||||
|
@ -451,8 +245,6 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
|
||||||
distributionChartActions,
|
distributionChartActions,
|
||||||
showSummary,
|
showSummary,
|
||||||
showEditor,
|
showEditor,
|
||||||
leftSizePercent: 50,
|
|
||||||
showSettingsPage: false,
|
|
||||||
diagramStart: 0,
|
diagramStart: 0,
|
||||||
diagramStop: 10,
|
diagramStop: 10,
|
||||||
diagramCount: 20,
|
diagramCount: 20,
|
||||||
|
@ -474,18 +266,32 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
|
||||||
[vars.sampleCount, vars.xyPointLength]
|
[vars.sampleCount, vars.xyPointLength]
|
||||||
);
|
);
|
||||||
|
|
||||||
const { run, autorunMode, setAutorunMode, isRunning, renderedCode } =
|
const {
|
||||||
useRunnerState(code);
|
run,
|
||||||
|
autorunMode,
|
||||||
|
setAutorunMode,
|
||||||
|
isRunning,
|
||||||
|
renderedCode,
|
||||||
|
executionId,
|
||||||
|
} = useRunnerState(code);
|
||||||
|
|
||||||
const squiggleChart = (
|
const squiggleChart =
|
||||||
<SquiggleChart
|
renderedCode === "" ? null : (
|
||||||
code={renderedCode}
|
<div className="relative">
|
||||||
environment={env}
|
{isRunning ? (
|
||||||
{...vars}
|
<div className="absolute inset-0 bg-white opacity-0 animate-semi-appear" />
|
||||||
bindings={defaultBindings}
|
) : null}
|
||||||
jsImports={imports}
|
<SquiggleChart
|
||||||
/>
|
code={renderedCode}
|
||||||
);
|
executionId={executionId}
|
||||||
|
environment={env}
|
||||||
|
{...vars}
|
||||||
|
bindings={defaultBindings}
|
||||||
|
jsImports={imports}
|
||||||
|
enableLocalSettings={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
const firstTab = vars.showEditor ? (
|
const firstTab = vars.showEditor ? (
|
||||||
<div className="border border-slate-200">
|
<div className="border border-slate-200">
|
||||||
|
@ -509,7 +315,15 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
|
||||||
<SamplingSettings register={register} />
|
<SamplingSettings register={register} />
|
||||||
</StyledTab.Panel>
|
</StyledTab.Panel>
|
||||||
<StyledTab.Panel>
|
<StyledTab.Panel>
|
||||||
<ViewSettings register={register} />
|
<ViewSettings
|
||||||
|
register={
|
||||||
|
// This is dangerous, but doesn't cause any problems.
|
||||||
|
// I tried to make `ViewSettings` generic (to allow it to accept any extension of a settings schema), but it didn't work.
|
||||||
|
register as unknown as UseFormRegister<
|
||||||
|
yup.InferType<typeof viewSettingsSchema>
|
||||||
|
>
|
||||||
|
}
|
||||||
|
/>
|
||||||
</StyledTab.Panel>
|
</StyledTab.Panel>
|
||||||
<StyledTab.Panel>
|
<StyledTab.Panel>
|
||||||
<InputVariablesSettings
|
<InputVariablesSettings
|
||||||
|
@ -520,40 +334,54 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
|
||||||
</StyledTab.Panels>
|
</StyledTab.Panels>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const leftPanelRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
const withEditor = (
|
const withEditor = (
|
||||||
<div className="flex mt-2">
|
<div className="flex mt-2">
|
||||||
<div className="w-1/2">{tabs}</div>
|
<div
|
||||||
|
className="w-1/2 relative"
|
||||||
|
style={{ minHeight: height }}
|
||||||
|
ref={leftPanelRef}
|
||||||
|
>
|
||||||
|
{tabs}
|
||||||
|
</div>
|
||||||
<div className="w-1/2 p-2 pl-4">{squiggleChart}</div>
|
<div className="w-1/2 p-2 pl-4">{squiggleChart}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const withoutEditor = <div className="mt-3">{tabs}</div>;
|
const withoutEditor = <div className="mt-3">{tabs}</div>;
|
||||||
|
|
||||||
|
const getLeftPanelElement = useCallback(() => {
|
||||||
|
return leftPanelRef.current ?? undefined;
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SquiggleContainer>
|
<SquiggleContainer>
|
||||||
<StyledTab.Group>
|
<PlaygroundContext.Provider value={{ getLeftPanelElement }}>
|
||||||
<div className="pb-4">
|
<StyledTab.Group>
|
||||||
<div className="flex justify-between items-center">
|
<div className="pb-4">
|
||||||
<StyledTab.List>
|
<div className="flex justify-between items-center">
|
||||||
<StyledTab
|
<StyledTab.List>
|
||||||
name={vars.showEditor ? "Code" : "Display"}
|
<StyledTab
|
||||||
icon={vars.showEditor ? CodeIcon : EyeIcon}
|
name={vars.showEditor ? "Code" : "Display"}
|
||||||
|
icon={vars.showEditor ? CodeIcon : EyeIcon}
|
||||||
|
/>
|
||||||
|
<StyledTab name="Sampling Settings" icon={CogIcon} />
|
||||||
|
<StyledTab name="View Settings" icon={ChartSquareBarIcon} />
|
||||||
|
<StyledTab name="Input Variables" icon={CurrencyDollarIcon} />
|
||||||
|
</StyledTab.List>
|
||||||
|
<RunControls
|
||||||
|
autorunMode={autorunMode}
|
||||||
|
isStale={renderedCode !== code}
|
||||||
|
run={run}
|
||||||
|
isRunning={isRunning}
|
||||||
|
onAutorunModeChange={setAutorunMode}
|
||||||
/>
|
/>
|
||||||
<StyledTab name="Sampling Settings" icon={CogIcon} />
|
</div>
|
||||||
<StyledTab name="View Settings" icon={ChartSquareBarIcon} />
|
{vars.showEditor ? withEditor : withoutEditor}
|
||||||
<StyledTab name="Input Variables" icon={CurrencyDollarIcon} />
|
|
||||||
</StyledTab.List>
|
|
||||||
<RunControls
|
|
||||||
autorunMode={autorunMode}
|
|
||||||
isStale={renderedCode !== code}
|
|
||||||
run={run}
|
|
||||||
isRunning={isRunning}
|
|
||||||
onAutorunModeChange={setAutorunMode}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{vars.showEditor ? withEditor : withoutEditor}
|
</StyledTab.Group>
|
||||||
</div>
|
</PlaygroundContext.Provider>
|
||||||
</StyledTab.Group>
|
|
||||||
</SquiggleContainer>
|
</SquiggleContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,291 @@
|
||||||
|
import React from "react";
|
||||||
|
import { squiggleExpression, declaration } from "@quri/squiggle-lang";
|
||||||
|
import { NumberShower } from "../NumberShower";
|
||||||
|
import { DistributionChart } from "../DistributionChart";
|
||||||
|
import { FunctionChart, FunctionChartSettings } from "../FunctionChart";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import { VariableBox } from "./VariableBox";
|
||||||
|
import { ItemSettingsMenu } from "./ItemSettingsMenu";
|
||||||
|
import { hasMassBelowZero } from "../../lib/distributionUtils";
|
||||||
|
import { MergedItemSettings } from "./utils";
|
||||||
|
|
||||||
|
function getRange<a>(x: declaration<a>) {
|
||||||
|
const first = x.args[0];
|
||||||
|
switch (first.tag) {
|
||||||
|
case "Float": {
|
||||||
|
return { floats: { min: first.value.min, max: first.value.max } };
|
||||||
|
}
|
||||||
|
case "Date": {
|
||||||
|
return { time: { min: first.value.min, max: first.value.max } };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getChartSettings<a>(x: declaration<a>): FunctionChartSettings {
|
||||||
|
const range = getRange(x);
|
||||||
|
const min = range.floats ? range.floats.min : 0;
|
||||||
|
const max = range.floats ? range.floats.max : 10;
|
||||||
|
return {
|
||||||
|
start: min,
|
||||||
|
stop: max,
|
||||||
|
count: 20,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const VariableList: React.FC<{
|
||||||
|
path: string[];
|
||||||
|
heading: string;
|
||||||
|
children: (settings: MergedItemSettings) => React.ReactNode;
|
||||||
|
}> = ({ path, heading, children }) => (
|
||||||
|
<VariableBox path={path} heading={heading}>
|
||||||
|
{(settings) => (
|
||||||
|
<div className={clsx("space-y-3", path.length ? "pt-1 mt-1" : null)}>
|
||||||
|
{children(settings)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</VariableBox>
|
||||||
|
);
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
/** The output of squiggle's run */
|
||||||
|
expression: squiggleExpression;
|
||||||
|
/** 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ExpressionViewer: React.FC<Props> = ({
|
||||||
|
path,
|
||||||
|
expression,
|
||||||
|
width,
|
||||||
|
}) => {
|
||||||
|
switch (expression.tag) {
|
||||||
|
case "number":
|
||||||
|
return (
|
||||||
|
<VariableBox path={path} heading="Number">
|
||||||
|
{() => (
|
||||||
|
<div className="font-semibold text-slate-600">
|
||||||
|
<NumberShower precision={3} number={expression.value} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</VariableBox>
|
||||||
|
);
|
||||||
|
case "distribution": {
|
||||||
|
const distType = expression.value.type();
|
||||||
|
return (
|
||||||
|
<VariableBox
|
||||||
|
path={path}
|
||||||
|
heading={`Distribution (${distType})\n${
|
||||||
|
distType === "Symbolic" ? expression.value.toString() : ""
|
||||||
|
}`}
|
||||||
|
renderSettingsMenu={({ onChange }) => {
|
||||||
|
const shape = expression.value.pointSet();
|
||||||
|
return (
|
||||||
|
<ItemSettingsMenu
|
||||||
|
path={path}
|
||||||
|
onChange={onChange}
|
||||||
|
disableLogX={
|
||||||
|
shape.tag === "Ok" && hasMassBelowZero(shape.value)
|
||||||
|
}
|
||||||
|
withFunctionSettings={false}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(settings) => {
|
||||||
|
return (
|
||||||
|
<DistributionChart
|
||||||
|
distribution={expression.value}
|
||||||
|
{...settings.distributionPlotSettings}
|
||||||
|
height={settings.height}
|
||||||
|
width={width}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</VariableBox>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case "string":
|
||||||
|
return (
|
||||||
|
<VariableBox path={path} heading="String">
|
||||||
|
{() => (
|
||||||
|
<>
|
||||||
|
<span className="text-slate-400">"</span>
|
||||||
|
<span className="text-slate-600 font-semibold font-mono">
|
||||||
|
{expression.value}
|
||||||
|
</span>
|
||||||
|
<span className="text-slate-400">"</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</VariableBox>
|
||||||
|
);
|
||||||
|
case "boolean":
|
||||||
|
return (
|
||||||
|
<VariableBox path={path} heading="Boolean">
|
||||||
|
{() => expression.value.toString()}
|
||||||
|
</VariableBox>
|
||||||
|
);
|
||||||
|
case "symbol":
|
||||||
|
return (
|
||||||
|
<VariableBox path={path} heading="Symbol">
|
||||||
|
{() => (
|
||||||
|
<>
|
||||||
|
<span className="text-slate-500 mr-2">Undefined Symbol:</span>
|
||||||
|
<span className="text-slate-600">{expression.value}</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</VariableBox>
|
||||||
|
);
|
||||||
|
case "call":
|
||||||
|
return (
|
||||||
|
<VariableBox path={path} heading="Call">
|
||||||
|
{() => expression.value}
|
||||||
|
</VariableBox>
|
||||||
|
);
|
||||||
|
case "arraystring":
|
||||||
|
return (
|
||||||
|
<VariableBox path={path} heading="Array String">
|
||||||
|
{() => expression.value.map((r) => `"${r}"`).join(", ")}
|
||||||
|
</VariableBox>
|
||||||
|
);
|
||||||
|
case "date":
|
||||||
|
return (
|
||||||
|
<VariableBox path={path} heading="Date">
|
||||||
|
{() => expression.value.toDateString()}
|
||||||
|
</VariableBox>
|
||||||
|
);
|
||||||
|
case "void":
|
||||||
|
return (
|
||||||
|
<VariableBox path={path} heading="Void">
|
||||||
|
{() => "Void"}
|
||||||
|
</VariableBox>
|
||||||
|
);
|
||||||
|
case "timeDuration": {
|
||||||
|
return (
|
||||||
|
<VariableBox path={path} heading="Time Duration">
|
||||||
|
{() => <NumberShower precision={3} number={expression.value} />}
|
||||||
|
</VariableBox>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case "lambda":
|
||||||
|
return (
|
||||||
|
<VariableBox
|
||||||
|
path={path}
|
||||||
|
heading="Function"
|
||||||
|
renderSettingsMenu={({ onChange }) => {
|
||||||
|
return (
|
||||||
|
<ItemSettingsMenu
|
||||||
|
path={path}
|
||||||
|
onChange={onChange}
|
||||||
|
withFunctionSettings={true}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(settings) => (
|
||||||
|
<>
|
||||||
|
<div className="text-amber-700 bg-amber-100 rounded-md font-mono p-1 pl-2 mb-3 mt-1 text-sm">{`function(${expression.value.parameters.join(
|
||||||
|
","
|
||||||
|
)})`}</div>
|
||||||
|
<FunctionChart
|
||||||
|
fn={expression.value}
|
||||||
|
chartSettings={settings.chartSettings}
|
||||||
|
distributionPlotSettings={settings.distributionPlotSettings}
|
||||||
|
height={settings.height}
|
||||||
|
environment={{
|
||||||
|
sampleCount: settings.environment.sampleCount / 10,
|
||||||
|
xyPointLength: settings.environment.xyPointLength / 10,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</VariableBox>
|
||||||
|
);
|
||||||
|
case "lambdaDeclaration": {
|
||||||
|
return (
|
||||||
|
<VariableBox
|
||||||
|
path={path}
|
||||||
|
heading="Function Declaration"
|
||||||
|
renderSettingsMenu={({ onChange }) => {
|
||||||
|
return (
|
||||||
|
<ItemSettingsMenu
|
||||||
|
onChange={onChange}
|
||||||
|
path={path}
|
||||||
|
withFunctionSettings={true}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(settings) => (
|
||||||
|
<FunctionChart
|
||||||
|
fn={expression.value.fn}
|
||||||
|
chartSettings={getChartSettings(expression.value)}
|
||||||
|
distributionPlotSettings={settings.distributionPlotSettings}
|
||||||
|
height={settings.height}
|
||||||
|
environment={{
|
||||||
|
sampleCount: settings.environment.sampleCount / 10,
|
||||||
|
xyPointLength: settings.environment.xyPointLength / 10,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</VariableBox>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case "module": {
|
||||||
|
return (
|
||||||
|
<VariableList path={path} heading="Module">
|
||||||
|
{(settings) =>
|
||||||
|
Object.entries(expression.value)
|
||||||
|
.filter(([key, r]) => !key.match(/^(Math|System)\./))
|
||||||
|
.map(([key, r]) => (
|
||||||
|
<ExpressionViewer
|
||||||
|
key={key}
|
||||||
|
path={[...path, key]}
|
||||||
|
expression={r}
|
||||||
|
width={width !== undefined ? width - 20 : width}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</VariableList>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case "record":
|
||||||
|
return (
|
||||||
|
<VariableList path={path} heading="Record">
|
||||||
|
{(settings) =>
|
||||||
|
Object.entries(expression.value).map(([key, r]) => (
|
||||||
|
<ExpressionViewer
|
||||||
|
key={key}
|
||||||
|
path={[...path, key]}
|
||||||
|
expression={r}
|
||||||
|
width={width !== undefined ? width - 20 : width}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</VariableList>
|
||||||
|
);
|
||||||
|
case "array":
|
||||||
|
return (
|
||||||
|
<VariableList path={path} heading="Array">
|
||||||
|
{(settings) =>
|
||||||
|
expression.value.map((r, i) => (
|
||||||
|
<ExpressionViewer
|
||||||
|
key={i}
|
||||||
|
path={[...path, String(i)]}
|
||||||
|
expression={r}
|
||||||
|
width={width !== undefined ? width - 20 : width}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</VariableList>
|
||||||
|
);
|
||||||
|
default: {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<span>No display for type: </span>{" "}
|
||||||
|
<span className="font-semibold text-slate-600">{expression.tag}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,168 @@
|
||||||
|
import { CogIcon } from "@heroicons/react/solid";
|
||||||
|
import React, { useContext, useRef, useState, useEffect } 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";
|
||||||
|
import { PlaygroundContext } from "../SquigglePlayground";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
path: Path;
|
||||||
|
onChange: () => void;
|
||||||
|
disableLogX?: boolean;
|
||||||
|
withFunctionSettings: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ItemSettingsModal: React.FC<
|
||||||
|
Props & { close: () => void; resetScroll: () => void }
|
||||||
|
> = ({
|
||||||
|
path,
|
||||||
|
onChange,
|
||||||
|
disableLogX,
|
||||||
|
withFunctionSettings,
|
||||||
|
close,
|
||||||
|
resetScroll,
|
||||||
|
}) => {
|
||||||
|
const { setSettings, getSettings, getMergedSettings } =
|
||||||
|
useContext(ViewerContext);
|
||||||
|
|
||||||
|
const mergedSettings = getMergedSettings(path);
|
||||||
|
|
||||||
|
const { register, watch } = useForm({
|
||||||
|
resolver: yupResolver(viewSettingsSchema),
|
||||||
|
defaultValues: {
|
||||||
|
// this is a mess and should be fixed
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
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]);
|
||||||
|
|
||||||
|
const { getLeftPanelElement } = useContext(PlaygroundContext);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal container={getLeftPanelElement()} close={close}>
|
||||||
|
<Modal.Header>
|
||||||
|
Chart settings
|
||||||
|
{path.length ? (
|
||||||
|
<>
|
||||||
|
{" for "}
|
||||||
|
<span
|
||||||
|
title="Scroll to item"
|
||||||
|
className="cursor-pointer"
|
||||||
|
onClick={resetScroll}
|
||||||
|
>
|
||||||
|
{pathAsString(path)}
|
||||||
|
</span>{" "}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Body>
|
||||||
|
<ViewSettings
|
||||||
|
register={register}
|
||||||
|
withShowEditorSetting={false}
|
||||||
|
withFunctionSettings={withFunctionSettings}
|
||||||
|
disableLogXSetting={disableLogX}
|
||||||
|
/>
|
||||||
|
</Modal.Body>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ItemSettingsMenu: React.FC<Props> = (props) => {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const { enableLocalSettings, setSettings, getSettings } =
|
||||||
|
useContext(ViewerContext);
|
||||||
|
|
||||||
|
const ref = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
|
if (!enableLocalSettings) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const settings = getSettings(props.path);
|
||||||
|
|
||||||
|
const resetScroll = () => {
|
||||||
|
if (!ref.current) return;
|
||||||
|
window.scroll({
|
||||||
|
top: ref.current.getBoundingClientRect().y + window.scrollY,
|
||||||
|
behavior: "smooth",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex gap-2" ref={ref}>
|
||||||
|
<CogIcon
|
||||||
|
className="h-5 w-5 cursor-pointer text-slate-400 hover:text-slate-500"
|
||||||
|
onClick={() => setIsOpen(!isOpen)}
|
||||||
|
/>
|
||||||
|
{settings.distributionPlotSettings || settings.chartSettings ? (
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setSettings(props.path, {
|
||||||
|
...settings,
|
||||||
|
distributionPlotSettings: undefined,
|
||||||
|
chartSettings: undefined,
|
||||||
|
});
|
||||||
|
props.onChange();
|
||||||
|
}}
|
||||||
|
className="text-xs px-1 py-0.5 rounded bg-slate-300"
|
||||||
|
>
|
||||||
|
Reset settings
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
|
{isOpen ? (
|
||||||
|
<ItemSettingsModal
|
||||||
|
{...props}
|
||||||
|
close={() => setIsOpen(false)}
|
||||||
|
resetScroll={resetScroll}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,79 @@
|
||||||
|
import React, { useContext, useReducer } from "react";
|
||||||
|
import { Tooltip } from "../ui/Tooltip";
|
||||||
|
import { LocalItemSettings, MergedItemSettings } from "./utils";
|
||||||
|
import { ViewerContext } from "./ViewerContext";
|
||||||
|
|
||||||
|
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;
|
||||||
|
renderSettingsMenu?: (params: SettingsMenuParams) => React.ReactNode;
|
||||||
|
children: (settings: MergedItemSettings) => React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const VariableBox: React.FC<VariableBoxProps> = ({
|
||||||
|
path,
|
||||||
|
heading = "Error",
|
||||||
|
renderSettingsMenu,
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
const { setSettings, getSettings, getMergedSettings } =
|
||||||
|
useContext(ViewerContext);
|
||||||
|
|
||||||
|
// Since ViewerContext doesn't keep the actual settings, VariableBox won't rerender when setSettings is called.
|
||||||
|
// So we use `forceUpdate` to force rerendering.
|
||||||
|
const [_, forceUpdate] = useReducer((x) => x + 1, 0);
|
||||||
|
|
||||||
|
const settings = getSettings(path);
|
||||||
|
|
||||||
|
const setSettingsAndUpdate = (newSettings: LocalItemSettings) => {
|
||||||
|
setSettings(path, newSettings);
|
||||||
|
forceUpdate();
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleCollapsed = () => {
|
||||||
|
setSettingsAndUpdate({ ...settings, collapsed: !settings.collapsed });
|
||||||
|
};
|
||||||
|
|
||||||
|
const isTopLevel = path.length === 0;
|
||||||
|
const name = isTopLevel ? "Result" : path[path.length - 1];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<header className="inline-flex space-x-1">
|
||||||
|
<Tooltip text={heading}>
|
||||||
|
<span
|
||||||
|
className="text-slate-500 font-mono text-sm cursor-pointer"
|
||||||
|
onClick={toggleCollapsed}
|
||||||
|
>
|
||||||
|
{name}:
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
{settings.collapsed ? (
|
||||||
|
<span
|
||||||
|
className="rounded p-0.5 bg-slate-200 text-slate-500 font-mono text-xs cursor-pointer"
|
||||||
|
onClick={toggleCollapsed}
|
||||||
|
>
|
||||||
|
...
|
||||||
|
</span>
|
||||||
|
) : renderSettingsMenu ? (
|
||||||
|
renderSettingsMenu({ onChange: forceUpdate })
|
||||||
|
) : null}
|
||||||
|
</header>
|
||||||
|
{settings.collapsed ? null : (
|
||||||
|
<div className="flex w-full">
|
||||||
|
{path.length ? (
|
||||||
|
<div
|
||||||
|
className="border-l-2 border-slate-200 hover:border-indigo-600 w-4 cursor-pointer"
|
||||||
|
onClick={toggleCollapsed}
|
||||||
|
></div>
|
||||||
|
) : null}
|
||||||
|
<div className="grow">{children(getMergedSettings(path))}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { defaultEnvironment } from "@quri/squiggle-lang";
|
||||||
|
import React from "react";
|
||||||
|
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): LocalItemSettings;
|
||||||
|
getMergedSettings(path: Path): MergedItemSettings;
|
||||||
|
setSettings(path: Path, value: LocalItemSettings): void;
|
||||||
|
enableLocalSettings: boolean; // show local settings icon in the UI
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ViewerContext = React.createContext<ViewerContextShape>({
|
||||||
|
getSettings: () => ({ collapsed: false }),
|
||||||
|
getMergedSettings: () => ({
|
||||||
|
collapsed: false,
|
||||||
|
// copy-pasted from SquiggleChart
|
||||||
|
chartSettings: {
|
||||||
|
start: 0,
|
||||||
|
stop: 10,
|
||||||
|
count: 100,
|
||||||
|
},
|
||||||
|
distributionPlotSettings: {
|
||||||
|
showSummary: false,
|
||||||
|
logX: false,
|
||||||
|
expY: false,
|
||||||
|
},
|
||||||
|
environment: defaultEnvironment,
|
||||||
|
height: 150,
|
||||||
|
}),
|
||||||
|
setSettings() {},
|
||||||
|
enableLocalSettings: false,
|
||||||
|
});
|
100
packages/components/src/components/SquiggleViewer/index.tsx
Normal file
100
packages/components/src/components/SquiggleViewer/index.tsx
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
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 {
|
||||||
|
LocalItemSettings,
|
||||||
|
MergedItemSettings,
|
||||||
|
Path,
|
||||||
|
pathAsString,
|
||||||
|
} from "./utils";
|
||||||
|
import { useSquiggle } from "../../lib/hooks";
|
||||||
|
import { SquiggleErrorAlert } from "../SquiggleErrorAlert";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
/** The output of squiggle's run */
|
||||||
|
result: ReturnType<typeof useSquiggle>;
|
||||||
|
width?: number;
|
||||||
|
height: number;
|
||||||
|
distributionPlotSettings: DistributionPlottingSettings;
|
||||||
|
/** Settings for displaying functions */
|
||||||
|
chartSettings: FunctionChartSettings;
|
||||||
|
/** Environment for further function executions */
|
||||||
|
environment: environment;
|
||||||
|
enableLocalSettings?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Settings = {
|
||||||
|
[k: string]: LocalItemSettings;
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultSettings: LocalItemSettings = { collapsed: false };
|
||||||
|
|
||||||
|
export const SquiggleViewer: React.FC<Props> = ({
|
||||||
|
result,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
distributionPlotSettings,
|
||||||
|
chartSettings,
|
||||||
|
environment,
|
||||||
|
enableLocalSettings = false,
|
||||||
|
}) => {
|
||||||
|
// can't store settings in the state because we don't want to rerender the entire tree on every change
|
||||||
|
const settingsRef = useRef<Settings>({});
|
||||||
|
|
||||||
|
const getSettings = useCallback(
|
||||||
|
(path: Path) => {
|
||||||
|
return settingsRef.current[pathAsString(path)] || defaultSettings;
|
||||||
|
},
|
||||||
|
[settingsRef]
|
||||||
|
);
|
||||||
|
|
||||||
|
const setSettings = useCallback(
|
||||||
|
(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 || {}),
|
||||||
|
},
|
||||||
|
height: localSettings.height || height,
|
||||||
|
};
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
[distributionPlotSettings, chartSettings, environment, height, getSettings]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ViewerContext.Provider
|
||||||
|
value={{
|
||||||
|
getSettings,
|
||||||
|
setSettings,
|
||||||
|
getMergedSettings,
|
||||||
|
enableLocalSettings,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{result.tag === "Ok" ? (
|
||||||
|
<ExpressionViewer path={[]} expression={result.value} width={width} />
|
||||||
|
) : (
|
||||||
|
<SquiggleErrorAlert error={result.value} />
|
||||||
|
)}
|
||||||
|
</ViewerContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
22
packages/components/src/components/SquiggleViewer/utils.ts
Normal file
22
packages/components/src/components/SquiggleViewer/utils.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { DistributionPlottingSettings } from "../DistributionChart";
|
||||||
|
import { FunctionChartSettings } from "../FunctionChart";
|
||||||
|
import { environment } from "@quri/squiggle-lang";
|
||||||
|
|
||||||
|
export type LocalItemSettings = {
|
||||||
|
collapsed: boolean;
|
||||||
|
distributionPlotSettings?: Partial<DistributionPlottingSettings>;
|
||||||
|
chartSettings?: Partial<FunctionChartSettings>;
|
||||||
|
height?: number;
|
||||||
|
environment?: Partial<environment>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MergedItemSettings = {
|
||||||
|
distributionPlotSettings: DistributionPlottingSettings;
|
||||||
|
chartSettings: FunctionChartSettings;
|
||||||
|
height: number;
|
||||||
|
environment: environment;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Path = string[];
|
||||||
|
|
||||||
|
export const pathAsString = (path: Path) => path.join(".");
|
163
packages/components/src/components/ViewSettings.tsx
Normal file
163
packages/components/src/components/ViewSettings.tsx
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
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<typeof viewSettingsSchema>;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
disableLogXSetting?: boolean;
|
||||||
|
register: UseFormRegister<FormFields>;
|
||||||
|
}> = ({
|
||||||
|
withShowEditorSetting = true,
|
||||||
|
withFunctionSettings = true,
|
||||||
|
disableLogXSetting,
|
||||||
|
register,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className="space-y-6 p-3 divide-y divide-gray-200 max-w-xl">
|
||||||
|
<HeadedSection title="General Display Settings">
|
||||||
|
<div className="space-y-4">
|
||||||
|
{withShowEditorSetting ? (
|
||||||
|
<Checkbox
|
||||||
|
name="showEditor"
|
||||||
|
register={register}
|
||||||
|
label="Show code editor on left"
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<InputItem
|
||||||
|
name="chartHeight"
|
||||||
|
type="number"
|
||||||
|
register={register}
|
||||||
|
label="Chart Height (in pixels)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</HeadedSection>
|
||||||
|
|
||||||
|
<div className="pt-8">
|
||||||
|
<HeadedSection title="Distribution Display Settings">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Checkbox
|
||||||
|
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
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
register={register}
|
||||||
|
name="expY"
|
||||||
|
label="Show y scale exponentially"
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
register={register}
|
||||||
|
name="distributionChartActions"
|
||||||
|
label="Show vega chart controls"
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
register={register}
|
||||||
|
name="showSummary"
|
||||||
|
label="Show summary statistics"
|
||||||
|
/>
|
||||||
|
<InputItem
|
||||||
|
name="minX"
|
||||||
|
type="number"
|
||||||
|
register={register}
|
||||||
|
label="Min X Value"
|
||||||
|
/>
|
||||||
|
<InputItem
|
||||||
|
name="maxX"
|
||||||
|
type="number"
|
||||||
|
register={register}
|
||||||
|
label="Max X Value"
|
||||||
|
/>
|
||||||
|
<InputItem
|
||||||
|
name="title"
|
||||||
|
type="text"
|
||||||
|
register={register}
|
||||||
|
label="Title"
|
||||||
|
/>
|
||||||
|
<InputItem
|
||||||
|
name="tickFormat"
|
||||||
|
type="text"
|
||||||
|
register={register}
|
||||||
|
label="Tick Format"
|
||||||
|
/>
|
||||||
|
<InputItem
|
||||||
|
name="color"
|
||||||
|
type="color"
|
||||||
|
register={register}
|
||||||
|
label="Color"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</HeadedSection>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{withFunctionSettings ? (
|
||||||
|
<div className="pt-8">
|
||||||
|
<HeadedSection title="Function Display Settings">
|
||||||
|
<div className="space-y-6">
|
||||||
|
<Text>
|
||||||
|
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.
|
||||||
|
</Text>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<InputItem
|
||||||
|
type="number"
|
||||||
|
name="diagramStart"
|
||||||
|
register={register}
|
||||||
|
label="Min X Value"
|
||||||
|
/>
|
||||||
|
<InputItem
|
||||||
|
type="number"
|
||||||
|
name="diagramStop"
|
||||||
|
register={register}
|
||||||
|
label="Max X Value"
|
||||||
|
/>
|
||||||
|
<InputItem
|
||||||
|
type="number"
|
||||||
|
name="diagramCount"
|
||||||
|
register={register}
|
||||||
|
label="Points between X min and X max to sample"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</HeadedSection>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,3 +1,4 @@
|
||||||
|
import clsx from "clsx";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Path, UseFormRegister } from "react-hook-form";
|
import { Path, UseFormRegister } from "react-hook-form";
|
||||||
|
|
||||||
|
@ -5,20 +6,32 @@ export function Checkbox<T>({
|
||||||
name,
|
name,
|
||||||
label,
|
label,
|
||||||
register,
|
register,
|
||||||
|
disabled,
|
||||||
|
tooltip,
|
||||||
}: {
|
}: {
|
||||||
name: Path<T>;
|
name: Path<T>;
|
||||||
label: string;
|
label: string;
|
||||||
register: UseFormRegister<T>;
|
register: UseFormRegister<T>;
|
||||||
|
disabled?: boolean;
|
||||||
|
tooltip?: string;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<label className="flex items-center">
|
<label className="flex items-center" title={tooltip}>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
disabled={disabled}
|
||||||
{...register(name)}
|
{...register(name)}
|
||||||
className="form-checkbox focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
|
className="form-checkbox focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
|
||||||
/>
|
/>
|
||||||
{/* Clicking on the div makes the checkbox lose focus while mouse button is pressed, leading to annoying blinking; I couldn't figure out how to fix this. */}
|
{/* Clicking on the div makes the checkbox lose focus while mouse button is pressed, leading to annoying blinking; I couldn't figure out how to fix this. */}
|
||||||
<div className="ml-3 text-sm font-medium text-gray-700">{label}</div>
|
<div
|
||||||
|
className={clsx(
|
||||||
|
"ml-3 text-sm font-medium",
|
||||||
|
disabled ? "text-gray-400" : "text-gray-700"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</div>
|
||||||
</label>
|
</label>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
13
packages/components/src/components/ui/HeadedSection.tsx
Normal file
13
packages/components/src/components/ui/HeadedSection.tsx
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export const HeadedSection: React.FC<{
|
||||||
|
title: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}> = ({ title, children }) => (
|
||||||
|
<div>
|
||||||
|
<header className="text-lg leading-6 font-medium text-gray-900">
|
||||||
|
{title}
|
||||||
|
</header>
|
||||||
|
<div className="mt-4">{children}</div>
|
||||||
|
</div>
|
||||||
|
);
|
25
packages/components/src/components/ui/InputItem.tsx
Normal file
25
packages/components/src/components/ui/InputItem.tsx
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import React from "react";
|
||||||
|
import { Path, UseFormRegister } from "react-hook-form";
|
||||||
|
|
||||||
|
export function InputItem<T>({
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
type,
|
||||||
|
register,
|
||||||
|
}: {
|
||||||
|
name: Path<T>;
|
||||||
|
label: string;
|
||||||
|
type: "number" | "text" | "color";
|
||||||
|
register: UseFormRegister<T>;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<label className="block">
|
||||||
|
<div className="text-sm font-medium text-gray-600 mb-1">{label}</div>
|
||||||
|
<input
|
||||||
|
type={type}
|
||||||
|
{...register(name, { valueAsNumber: type === "number" })}
|
||||||
|
className="form-input max-w-lg block w-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:max-w-xs sm:text-sm border-gray-300 rounded-md"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
}
|
184
packages/components/src/components/ui/Modal.tsx
Normal file
184
packages/components/src/components/ui/Modal.tsx
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import React, { useContext } from "react";
|
||||||
|
import * as ReactDOM from "react-dom";
|
||||||
|
import { XIcon } from "@heroicons/react/solid";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import { useWindowScroll, useWindowSize } from "react-use";
|
||||||
|
|
||||||
|
type ModalContextShape = {
|
||||||
|
close: () => void;
|
||||||
|
};
|
||||||
|
const ModalContext = React.createContext<ModalContextShape>({
|
||||||
|
close: () => undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
const Overlay: React.FC = () => {
|
||||||
|
const { close } = useContext(ModalContext);
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
className="absolute inset-0 -z-10 bg-black"
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 0.1 }}
|
||||||
|
onClick={close}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ModalHeader: React.FC<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
}> = ({ children }) => {
|
||||||
|
const { close } = useContext(ModalContext);
|
||||||
|
return (
|
||||||
|
<header className="px-5 py-3 border-b border-gray-200 font-bold flex items-center justify-between">
|
||||||
|
<div>{children}</div>
|
||||||
|
<button
|
||||||
|
className="px-1 bg-transparent cursor-pointer text-gray-700 hover:text-accent-500"
|
||||||
|
type="button"
|
||||||
|
onClick={close}
|
||||||
|
>
|
||||||
|
<XIcon className="h-5 w-5 cursor-pointer text-slate-400 hover:text-slate-500" />
|
||||||
|
</button>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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 <div ref={ref} className="px-5 py-3 overflow-auto" {...props} />;
|
||||||
|
});
|
||||||
|
|
||||||
|
const ModalFooter: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
||||||
|
<div className="px-5 py-3 border-t border-gray-200">{children}</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const ModalWindow: React.FC<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
container?: HTMLElement;
|
||||||
|
}> = ({ children, container }) => {
|
||||||
|
// This component works in two possible modes:
|
||||||
|
// 1. container mode - the modal is rendered inside a container element
|
||||||
|
// 2. centered mode - the modal is rendered in the middle of the screen
|
||||||
|
// The mode is determined by the presence of the `container` prop and by whether the available space is large enough to fit the modal.
|
||||||
|
|
||||||
|
// Necessary for container mode - need to reposition the modal on scroll and resize events.
|
||||||
|
useWindowSize();
|
||||||
|
useWindowScroll();
|
||||||
|
|
||||||
|
let position:
|
||||||
|
| {
|
||||||
|
left: number;
|
||||||
|
top: number;
|
||||||
|
maxWidth: number;
|
||||||
|
maxHeight: number;
|
||||||
|
transform: string;
|
||||||
|
}
|
||||||
|
| undefined;
|
||||||
|
|
||||||
|
// If available space in `visibleRect` is smaller than these, fallback to positioning in the middle of the screen.
|
||||||
|
const minWidth = 384;
|
||||||
|
const minHeight = 300;
|
||||||
|
const offset = 8;
|
||||||
|
const naturalWidth = 576; // maximum possible width; modal tries to take this much space, but can be smaller
|
||||||
|
|
||||||
|
if (container) {
|
||||||
|
const { clientWidth: screenWidth, clientHeight: screenHeight } =
|
||||||
|
document.documentElement;
|
||||||
|
const rect = container?.getBoundingClientRect();
|
||||||
|
|
||||||
|
const visibleRect = {
|
||||||
|
left: Math.max(rect.left, 0),
|
||||||
|
right: Math.min(rect.right, screenWidth),
|
||||||
|
top: Math.max(rect.top, 0),
|
||||||
|
bottom: Math.min(rect.bottom, screenHeight),
|
||||||
|
};
|
||||||
|
const maxWidth = visibleRect.right - visibleRect.left - 2 * offset;
|
||||||
|
const maxHeight = visibleRect.bottom - visibleRect.top - 2 * offset;
|
||||||
|
|
||||||
|
const center = {
|
||||||
|
left: visibleRect.left + (visibleRect.right - visibleRect.left) / 2,
|
||||||
|
top: visibleRect.top + (visibleRect.bottom - visibleRect.top) / 2,
|
||||||
|
};
|
||||||
|
position = {
|
||||||
|
left: center.left,
|
||||||
|
top: center.top,
|
||||||
|
transform: "translate(-50%, -50%)",
|
||||||
|
maxWidth,
|
||||||
|
maxHeight,
|
||||||
|
};
|
||||||
|
if (maxWidth < minWidth || maxHeight < minHeight) {
|
||||||
|
position = undefined; // modal is hard to fit in the container, fallback to positioning it in the middle of the screen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
"bg-white rounded-md shadow-toast flex flex-col overflow-auto border",
|
||||||
|
position ? "fixed" : null
|
||||||
|
)}
|
||||||
|
style={{
|
||||||
|
width: naturalWidth,
|
||||||
|
...(position ?? {
|
||||||
|
maxHeight: "calc(100% - 20px)",
|
||||||
|
maxWidth: "calc(100% - 20px)",
|
||||||
|
width: naturalWidth,
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type ModalType = React.FC<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
container?: HTMLElement; // if specified, modal will be positioned over the visible part of the container, if it's not too small
|
||||||
|
close: () => void;
|
||||||
|
}> & {
|
||||||
|
Body: typeof ModalBody;
|
||||||
|
Footer: typeof ModalFooter;
|
||||||
|
Header: typeof ModalHeader;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Modal: ModalType = ({ children, container, close }) => {
|
||||||
|
const [el] = React.useState(() => document.createElement("div"));
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
document.body.appendChild(el);
|
||||||
|
return () => {
|
||||||
|
document.body.removeChild(el);
|
||||||
|
};
|
||||||
|
}, [el]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const handleEscape = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === "Escape") {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener("keydown", handleEscape);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("keydown", handleEscape);
|
||||||
|
};
|
||||||
|
}, [close]);
|
||||||
|
|
||||||
|
const modal = (
|
||||||
|
<ModalContext.Provider value={{ close }}>
|
||||||
|
<div className="squiggle">
|
||||||
|
<div className="fixed inset-0 z-40 flex justify-center items-center">
|
||||||
|
<Overlay />
|
||||||
|
<ModalWindow container={container}>{children}</ModalWindow>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ModalContext.Provider>
|
||||||
|
);
|
||||||
|
|
||||||
|
return ReactDOM.createPortal(modal, container || el);
|
||||||
|
};
|
||||||
|
|
||||||
|
Modal.Body = ModalBody;
|
||||||
|
Modal.Footer = ModalFooter;
|
||||||
|
Modal.Header = ModalHeader;
|
5
packages/components/src/components/ui/Text.tsx
Normal file
5
packages/components/src/components/ui/Text.tsx
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export const Text: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
||||||
|
<p className="text-sm text-gray-500">{children}</p>
|
||||||
|
);
|
|
@ -1,5 +1,5 @@
|
||||||
|
import { RefreshIcon } from "@heroicons/react/solid";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { motion } from "framer-motion";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
type IconType = (props: React.ComponentProps<"svg">) => JSX.Element;
|
type IconType = (props: React.ComponentProps<"svg">) => JSX.Element;
|
||||||
|
@ -9,19 +9,19 @@ type Props = {
|
||||||
onChange: (status: boolean) => void;
|
onChange: (status: boolean) => void;
|
||||||
texts: [string, string];
|
texts: [string, string];
|
||||||
icons: [IconType, IconType];
|
icons: [IconType, IconType];
|
||||||
|
spinIcon?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Toggle: React.FC<Props> = ({
|
export const Toggle: React.FC<Props> = ({
|
||||||
texts: [onText, offText],
|
|
||||||
icons: [OnIcon, OffIcon],
|
|
||||||
status,
|
status,
|
||||||
onChange,
|
onChange,
|
||||||
|
texts: [onText, offText],
|
||||||
|
icons: [OnIcon, OffIcon],
|
||||||
|
spinIcon,
|
||||||
}) => {
|
}) => {
|
||||||
const CurrentIcon = status ? OnIcon : OffIcon;
|
const CurrentIcon = status ? OnIcon : OffIcon;
|
||||||
return (
|
return (
|
||||||
<motion.button
|
<button
|
||||||
layout
|
|
||||||
transition={{ duration: 0.2 }}
|
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"rounded-md py-0.5 bg-slate-500 text-white text-xs font-semibold flex items-center space-x-1",
|
"rounded-md py-0.5 bg-slate-500 text-white text-xs font-semibold flex items-center space-x-1",
|
||||||
status ? "bg-slate-500" : "bg-gray-400",
|
status ? "bg-slate-500" : "bg-gray-400",
|
||||||
|
@ -30,12 +30,18 @@ export const Toggle: React.FC<Props> = ({
|
||||||
)}
|
)}
|
||||||
onClick={() => onChange(!status)}
|
onClick={() => onChange(!status)}
|
||||||
>
|
>
|
||||||
<motion.div layout transition={{ duration: 0.2 }}>
|
<div className="relative w-6 h-6" key={String(spinIcon)}>
|
||||||
<CurrentIcon className="w-6 h-6" />
|
<CurrentIcon
|
||||||
</motion.div>
|
className={clsx(
|
||||||
<motion.span layout transition={{ duration: 0.2 }}>
|
"w-6 h-6 absolute opacity-100",
|
||||||
{status ? onText : offText}
|
spinIcon && "animate-hide"
|
||||||
</motion.span>
|
)}
|
||||||
</motion.button>
|
/>
|
||||||
|
{spinIcon && (
|
||||||
|
<RefreshIcon className="w-6 h-6 absolute opacity-0 animate-appear-and-spin" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<span>{status ? onText : offText}</span>
|
||||||
|
</button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
64
packages/components/src/components/ui/Tooltip.tsx
Normal file
64
packages/components/src/components/ui/Tooltip.tsx
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import React, { cloneElement, useState } from "react";
|
||||||
|
import { AnimatePresence, motion } from "framer-motion";
|
||||||
|
import {
|
||||||
|
flip,
|
||||||
|
shift,
|
||||||
|
useDismiss,
|
||||||
|
useFloating,
|
||||||
|
useHover,
|
||||||
|
useInteractions,
|
||||||
|
useRole,
|
||||||
|
} from "@floating-ui/react-dom-interactions";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
text: string;
|
||||||
|
children: JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Tooltip: React.FC<Props> = ({ text, children }) => {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
|
const { x, y, reference, floating, strategy, context } = useFloating({
|
||||||
|
placement: "top",
|
||||||
|
open: isOpen,
|
||||||
|
onOpenChange: setIsOpen,
|
||||||
|
middleware: [shift(), flip()],
|
||||||
|
});
|
||||||
|
|
||||||
|
const { getReferenceProps, getFloatingProps } = useInteractions([
|
||||||
|
useHover(context),
|
||||||
|
useRole(context, { role: "tooltip" }),
|
||||||
|
useDismiss(context),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{cloneElement(
|
||||||
|
children,
|
||||||
|
getReferenceProps({ ref: reference, ...children.props })
|
||||||
|
)}
|
||||||
|
<AnimatePresence>
|
||||||
|
{isOpen && (
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
exit={{ opacity: 0 }}
|
||||||
|
transition={{ duration: 0.15 }}
|
||||||
|
{...getFloatingProps({
|
||||||
|
ref: floating,
|
||||||
|
className:
|
||||||
|
"text-xs p-2 border border-gray-300 rounded bg-white z-10",
|
||||||
|
style: {
|
||||||
|
position: strategy,
|
||||||
|
top: y ?? 0,
|
||||||
|
left: x ?? 0,
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div className="font-mono whitespace-pre">{text}</div>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
|
@ -100,12 +100,15 @@ export let expYScale: PowScale = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const defaultTickFormat = ".9~s";
|
||||||
|
export const defaultColor = "#739ECC";
|
||||||
|
|
||||||
export function buildVegaSpec(
|
export function buildVegaSpec(
|
||||||
specOptions: DistributionChartSpecOptions
|
specOptions: DistributionChartSpecOptions
|
||||||
): VisualizationSpec {
|
): VisualizationSpec {
|
||||||
let {
|
let {
|
||||||
format = ".9~s",
|
format = defaultTickFormat,
|
||||||
color = "#739ECC",
|
color = defaultColor,
|
||||||
title,
|
title,
|
||||||
minX,
|
minX,
|
||||||
maxX,
|
maxX,
|
||||||
|
|
5
packages/components/src/lib/distributionUtils.ts
Normal file
5
packages/components/src/lib/distributionUtils.ts
Normal file
|
@ -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);
|
3
packages/components/src/lib/hooks/index.ts
Normal file
3
packages/components/src/lib/hooks/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export { useMaybeControlledValue } from "./useMaybeControlledValue";
|
||||||
|
export { useSquiggle, useSquigglePartial } from "./useSquiggle";
|
||||||
|
export { useRunnerState } from "./useRunnerState";
|
22
packages/components/src/lib/hooks/useMaybeControlledValue.ts
Normal file
22
packages/components/src/lib/hooks/useMaybeControlledValue.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
type ControlledValueArgs<T> = {
|
||||||
|
value?: T;
|
||||||
|
defaultValue: T;
|
||||||
|
onChange?: (x: T) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useMaybeControlledValue<T>(
|
||||||
|
args: ControlledValueArgs<T>
|
||||||
|
): [T, (x: T) => void] {
|
||||||
|
let [uncontrolledValue, setUncontrolledValue] = useState(args.defaultValue);
|
||||||
|
let value = args.value ?? uncontrolledValue;
|
||||||
|
let onChange = (newValue: T) => {
|
||||||
|
if (args.value === undefined) {
|
||||||
|
// uncontrolled mode
|
||||||
|
setUncontrolledValue(newValue);
|
||||||
|
}
|
||||||
|
args.onChange?.(newValue);
|
||||||
|
};
|
||||||
|
return [value, onChange];
|
||||||
|
}
|
100
packages/components/src/lib/hooks/useRunnerState.ts
Normal file
100
packages/components/src/lib/hooks/useRunnerState.ts
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
import { useLayoutEffect, useReducer } from "react";
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
autorunMode: boolean;
|
||||||
|
renderedCode: string;
|
||||||
|
// "prepared" is for rendering a spinner; "run" for executing squiggle code; then it gets back to "none" on the next render
|
||||||
|
runningState: "none" | "prepared" | "run";
|
||||||
|
executionId: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildInitialState = (code: string): State => ({
|
||||||
|
autorunMode: true,
|
||||||
|
renderedCode: "",
|
||||||
|
runningState: "none",
|
||||||
|
executionId: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
type Action =
|
||||||
|
| {
|
||||||
|
type: "SET_AUTORUN_MODE";
|
||||||
|
value: boolean;
|
||||||
|
code: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "PREPARE_RUN";
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "RUN";
|
||||||
|
code: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "STOP_RUN";
|
||||||
|
};
|
||||||
|
|
||||||
|
const reducer = (state: State, action: Action): State => {
|
||||||
|
switch (action.type) {
|
||||||
|
case "SET_AUTORUN_MODE":
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
autorunMode: action.value,
|
||||||
|
};
|
||||||
|
case "PREPARE_RUN":
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
runningState: "prepared",
|
||||||
|
};
|
||||||
|
case "RUN":
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
runningState: "run",
|
||||||
|
renderedCode: action.code,
|
||||||
|
executionId: state.executionId + 1,
|
||||||
|
};
|
||||||
|
case "STOP_RUN":
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
runningState: "none",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useRunnerState = (code: string) => {
|
||||||
|
const [state, dispatch] = useReducer(reducer, buildInitialState(code));
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (state.runningState === "prepared") {
|
||||||
|
// this is necessary for async playground loading - otherwise it executes the code synchronously on the initial load
|
||||||
|
// (it's surprising that this is necessary, but empirically it _is_ necessary, both with `useEffect` and `useLayoutEffect`)
|
||||||
|
setTimeout(() => {
|
||||||
|
dispatch({ type: "RUN", code });
|
||||||
|
}, 0);
|
||||||
|
} else if (state.runningState === "run") {
|
||||||
|
dispatch({ type: "STOP_RUN" });
|
||||||
|
}
|
||||||
|
}, [state.runningState, code]);
|
||||||
|
|
||||||
|
const run = () => {
|
||||||
|
// The rest will be handled by dispatches above on following renders, but we need to update the spinner first.
|
||||||
|
dispatch({ type: "PREPARE_RUN" });
|
||||||
|
};
|
||||||
|
|
||||||
|
if (
|
||||||
|
state.autorunMode &&
|
||||||
|
state.renderedCode !== code &&
|
||||||
|
state.runningState === "none"
|
||||||
|
) {
|
||||||
|
run();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
run,
|
||||||
|
autorunMode: state.autorunMode,
|
||||||
|
renderedCode: state.renderedCode,
|
||||||
|
isRunning: state.runningState !== "none",
|
||||||
|
executionId: state.executionId,
|
||||||
|
setAutorunMode: (newValue: boolean) => {
|
||||||
|
dispatch({ type: "SET_AUTORUN_MODE", value: newValue, code });
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
|
@ -5,10 +5,11 @@ import {
|
||||||
run,
|
run,
|
||||||
runPartial,
|
runPartial,
|
||||||
} from "@quri/squiggle-lang";
|
} from "@quri/squiggle-lang";
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
|
|
||||||
type SquiggleArgs<T extends ReturnType<typeof run | typeof runPartial>> = {
|
type SquiggleArgs<T extends ReturnType<typeof run | typeof runPartial>> = {
|
||||||
code: string;
|
code: string;
|
||||||
|
executionId?: number;
|
||||||
bindings?: bindings;
|
bindings?: bindings;
|
||||||
jsImports?: jsImports;
|
jsImports?: jsImports;
|
||||||
environment?: environment;
|
environment?: environment;
|
||||||
|
@ -21,7 +22,15 @@ const useSquiggleAny = <T extends ReturnType<typeof run | typeof runPartial>>(
|
||||||
) => {
|
) => {
|
||||||
const result: T = useMemo<T>(
|
const result: T = useMemo<T>(
|
||||||
() => f(args.code, args.bindings, args.environment, args.jsImports),
|
() => f(args.code, args.bindings, args.environment, args.jsImports),
|
||||||
[f, args.code, args.bindings, args.environment, args.jsImports]
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[
|
||||||
|
f,
|
||||||
|
args.code,
|
||||||
|
args.bindings,
|
||||||
|
args.environment,
|
||||||
|
args.jsImports,
|
||||||
|
args.executionId,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
const { onChange } = args;
|
const { onChange } = args;
|
||||||
|
@ -42,23 +51,3 @@ export const useSquigglePartial = (
|
||||||
export const useSquiggle = (args: SquiggleArgs<ReturnType<typeof run>>) => {
|
export const useSquiggle = (args: SquiggleArgs<ReturnType<typeof run>>) => {
|
||||||
return useSquiggleAny(args, run);
|
return useSquiggleAny(args, run);
|
||||||
};
|
};
|
||||||
|
|
||||||
type ControlledValueArgs<T> = {
|
|
||||||
value?: T;
|
|
||||||
defaultValue: T;
|
|
||||||
onChange?: (x: T) => void;
|
|
||||||
};
|
|
||||||
export function useMaybeControlledValue<T>(
|
|
||||||
args: ControlledValueArgs<T>
|
|
||||||
): [T, (x: T) => void] {
|
|
||||||
let [uncontrolledValue, setUncontrolledValue] = useState(args.defaultValue);
|
|
||||||
let value = args.value ?? uncontrolledValue;
|
|
||||||
let onChange = (newValue: T) => {
|
|
||||||
if (args.value === undefined) {
|
|
||||||
// uncontrolled mode
|
|
||||||
setUncontrolledValue(newValue);
|
|
||||||
}
|
|
||||||
args.onChange?.(newValue);
|
|
||||||
};
|
|
||||||
return [value, onChange];
|
|
||||||
}
|
|
|
@ -5,6 +5,27 @@ module.exports = {
|
||||||
},
|
},
|
||||||
important: ".squiggle",
|
important: ".squiggle",
|
||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {
|
||||||
|
animation: {
|
||||||
|
"appear-and-spin":
|
||||||
|
"spin 1s linear infinite, squiggle-appear 0.2s forwards",
|
||||||
|
"semi-appear": "squiggle-semi-appear 0.2s forwards",
|
||||||
|
hide: "squiggle-hide 0.2s forwards",
|
||||||
|
},
|
||||||
|
keyframes: {
|
||||||
|
"squiggle-appear": {
|
||||||
|
from: { opacity: 0 },
|
||||||
|
to: { opacity: 1 },
|
||||||
|
},
|
||||||
|
"squiggle-semi-appear": {
|
||||||
|
from: { opacity: 0 },
|
||||||
|
to: { opacity: 0.5 },
|
||||||
|
},
|
||||||
|
"squiggle-hide": {
|
||||||
|
from: { opacity: 1 },
|
||||||
|
to: { opacity: 0 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -44,7 +44,7 @@
|
||||||
"@stdlib/stats": "^0.0.13",
|
"@stdlib/stats": "^0.0.13",
|
||||||
"jstat": "^1.9.5",
|
"jstat": "^1.9.5",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"mathjs": "^10.6.4",
|
"mathjs": "^11.0.1",
|
||||||
"pdfast": "^0.2.0"
|
"pdfast": "^0.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -55,7 +55,7 @@
|
||||||
"bisect_ppx": "^2.7.1",
|
"bisect_ppx": "^2.7.1",
|
||||||
"chalk": "^5.0.1",
|
"chalk": "^5.0.1",
|
||||||
"codecov": "^3.8.3",
|
"codecov": "^3.8.3",
|
||||||
"fast-check": "^3.0.1",
|
"fast-check": "^3.1.0",
|
||||||
"gentype": "^4.5.0",
|
"gentype": "^4.5.0",
|
||||||
"jest": "^27.5.1",
|
"jest": "^27.5.1",
|
||||||
"moduleserve": "^0.9.1",
|
"moduleserve": "^0.9.1",
|
||||||
|
@ -69,7 +69,7 @@
|
||||||
"ts-loader": "^9.3.0",
|
"ts-loader": "^9.3.0",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"typescript": "^4.7.4",
|
"typescript": "^4.7.4",
|
||||||
"webpack": "^5.73.0",
|
"webpack": "^5.74.0",
|
||||||
"webpack-cli": "^4.10.0"
|
"webpack-cli": "^4.10.0"
|
||||||
},
|
},
|
||||||
"source": "./src/js/index.ts",
|
"source": "./src/js/index.ts",
|
||||||
|
|
|
@ -46,11 +46,11 @@ voidStatement
|
||||||
return h.nodeLetStatement(variable, value); }
|
return h.nodeLetStatement(variable, value); }
|
||||||
|
|
||||||
letStatement
|
letStatement
|
||||||
= variable:identifier _ assignmentOp _nl value:zeroOMoreArgumentsBlockOrExpression
|
= variable:variable _ assignmentOp _nl value:zeroOMoreArgumentsBlockOrExpression
|
||||||
{ return h.nodeLetStatement(variable, value) }
|
{ return h.nodeLetStatement(variable, value) }
|
||||||
|
|
||||||
defunStatement
|
defunStatement
|
||||||
= variable:identifier '(' _nl args:array_parameters _nl ')' _ assignmentOp _nl body:innerBlockOrExpression
|
= variable:variable '(' _nl args:array_parameters _nl ')' _ assignmentOp _nl body:innerBlockOrExpression
|
||||||
{ var value = h.nodeLambda(args, body)
|
{ var value = h.nodeLambda(args, body)
|
||||||
return h.nodeLetStatement(variable, value) }
|
return h.nodeLetStatement(variable, value) }
|
||||||
|
|
||||||
|
@ -144,11 +144,11 @@ chainFunctionCall
|
||||||
}, head)}
|
}, head)}
|
||||||
|
|
||||||
chainedFunction
|
chainedFunction
|
||||||
= fn:dollarIdentifier '(' _nl args:array_functionArguments _nl ')'
|
= fn:variable '(' _nl args:array_functionArguments _nl ')'
|
||||||
{ return {fnName: fn.value, args: args}}
|
{ return {fnName: fn.value, args: args}}
|
||||||
/ fn:dollarIdentifier '(' _nl ')'
|
/ fn:variable '(' _nl ')'
|
||||||
{ return {fnName: fn.value, args: []}}
|
{ return {fnName: fn.value, args: []}}
|
||||||
/ fn:dollarIdentifier
|
/ fn:variable
|
||||||
{ return {fnName: fn.value, args: []}}
|
{ return {fnName: fn.value, args: []}}
|
||||||
|
|
||||||
// end of binary operators
|
// end of binary operators
|
||||||
|
@ -192,13 +192,14 @@ basicLiteral
|
||||||
= string
|
= string
|
||||||
/ number
|
/ number
|
||||||
/ boolean
|
/ boolean
|
||||||
/ dollarIdentifierWithModule
|
/ variable
|
||||||
/ dollarIdentifier
|
|
||||||
/ voidLiteral
|
/ voidLiteral
|
||||||
|
|
||||||
voidLiteral 'void'
|
voidLiteral 'void'
|
||||||
= "()" {return h.nodeVoid();}
|
= "()" {return h.nodeVoid();}
|
||||||
|
|
||||||
|
variable = dollarIdentifierWithModule / dollarIdentifier
|
||||||
|
|
||||||
dollarIdentifierWithModule 'identifier'
|
dollarIdentifierWithModule 'identifier'
|
||||||
= head:$moduleIdentifier
|
= head:$moduleIdentifier
|
||||||
tail:('.' _nl @$moduleIdentifier)* '.' _nl
|
tail:('.' _nl @$moduleIdentifier)* '.' _nl
|
||||||
|
|
|
@ -9,8 +9,6 @@
|
||||||
React.createElement(squiggle_components.SquigglePlayground, {
|
React.createElement(squiggle_components.SquigglePlayground, {
|
||||||
code: text,
|
code: text,
|
||||||
showEditor: false,
|
showEditor: false,
|
||||||
showTypes: Boolean(showSettings.showTypes),
|
|
||||||
showControls: Boolean(showSettings.showControls),
|
|
||||||
showSummary: Boolean(showSettings.showSummary),
|
showSummary: Boolean(showSettings.showSummary),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
|
@ -105,16 +105,6 @@
|
||||||
"configuration": {
|
"configuration": {
|
||||||
"title": "Squiggle",
|
"title": "Squiggle",
|
||||||
"properties": {
|
"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,
|
|
||||||
"description": "Whether to show the log scale controls in the playground"
|
|
||||||
},
|
|
||||||
"squiggle.playground.showSummary": {
|
"squiggle.playground.showSummary": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": false,
|
"default": false,
|
||||||
|
@ -139,8 +129,8 @@
|
||||||
"@types/glob": "^7.2.0",
|
"@types/glob": "^7.2.0",
|
||||||
"@types/node": "18.x",
|
"@types/node": "18.x",
|
||||||
"@types/vscode": "^1.69.0",
|
"@types/vscode": "^1.69.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.30.6",
|
"@typescript-eslint/eslint-plugin": "^5.30.7",
|
||||||
"@typescript-eslint/parser": "^5.30.6",
|
"@typescript-eslint/parser": "^5.30.7",
|
||||||
"eslint": "^8.20.0",
|
"eslint": "^8.20.0",
|
||||||
"glob": "^8.0.3",
|
"glob": "^8.0.3",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
|
|
|
@ -54,6 +54,7 @@ const config = {
|
||||||
({
|
({
|
||||||
navbar: {
|
navbar: {
|
||||||
title: "Squiggle",
|
title: "Squiggle",
|
||||||
|
hideOnScroll: true,
|
||||||
logo: {
|
logo: {
|
||||||
alt: "Squiggle Logo",
|
alt: "Squiggle Logo",
|
||||||
src: "img/squiggle-logo.png",
|
src: "img/squiggle-logo.png",
|
||||||
|
|
|
@ -44,12 +44,11 @@ export default function PlaygroundPage() {
|
||||||
const playgroundProps = {
|
const playgroundProps = {
|
||||||
defaultCode: "normal(0,1)",
|
defaultCode: "normal(0,1)",
|
||||||
height: 700,
|
height: 700,
|
||||||
showTypes: true,
|
|
||||||
...hashData,
|
...hashData,
|
||||||
onCodeChange: (code) => setHashData({ initialSquiggleString: code }),
|
onCodeChange: (code) => setHashData({ initialSquiggleString: code }),
|
||||||
onSettingsChange: (settings) => {
|
onSettingsChange: (settings) => {
|
||||||
const { showTypes, showControls, showSummary, showEditor } = settings;
|
const { showSummary, showEditor } = settings;
|
||||||
setHashData({ showTypes, showControls, showSummary, showEditor });
|
setHashData({ showSummary, showEditor });
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
|
|
212
yarn.lock
212
yarn.lock
|
@ -1562,10 +1562,10 @@
|
||||||
core-js-pure "^3.20.2"
|
core-js-pure "^3.20.2"
|
||||||
regenerator-runtime "^0.13.4"
|
regenerator-runtime "^0.13.4"
|
||||||
|
|
||||||
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.6", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.0", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
|
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.6", "@babel/runtime@^7.18.9", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.0", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
|
||||||
version "7.18.6"
|
version "7.18.9"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.6.tgz#6a1ef59f838debd670421f8c7f2cbb8da9751580"
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.9.tgz#b4fcfce55db3d2e5e080d2490f608a3b9f407f4a"
|
||||||
integrity sha512-t9wi7/AW6XtKahAe20Yw0/mMljKq0B1r2fPdvaAdV/KPDZewFXdaaa6K7lxmZBZ8FBNpCiAT6iHPmd6QO9bKfQ==
|
integrity sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==
|
||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime "^0.13.4"
|
regenerator-runtime "^0.13.4"
|
||||||
|
|
||||||
|
@ -2190,6 +2190,35 @@
|
||||||
minimatch "^3.1.2"
|
minimatch "^3.1.2"
|
||||||
strip-json-comments "^3.1.1"
|
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":
|
"@gar/promisify@^1.0.1":
|
||||||
version "1.1.3"
|
version "1.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6"
|
resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6"
|
||||||
|
@ -4378,10 +4407,10 @@
|
||||||
"@testing-library/dom" "^8.5.0"
|
"@testing-library/dom" "^8.5.0"
|
||||||
"@types/react-dom" "^18.0.0"
|
"@types/react-dom" "^18.0.0"
|
||||||
|
|
||||||
"@testing-library/user-event@^14.2.6":
|
"@testing-library/user-event@^14.3.0":
|
||||||
version "14.2.6"
|
version "14.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.2.6.tgz#9ba313a212994eea66e018520e23542ac3eb6fbe"
|
resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.3.0.tgz#0a6750b94b40e4739706d41e8efc2ccf64d2aad9"
|
||||||
integrity sha512-l/4W4x3Lm24wkWNkPasXqvEzG+a6n2X872XCUjhyfbNqcoOapaWyCxC5Fz+E4r7JPu8gysQKSSCrK0OO2x+D+A==
|
integrity sha512-P02xtBBa8yMaLhK8CzJCIns8rqwnF6FxhR9zs810flHOBXUYCFjLd8Io1rQrAkQRWEmW2PGdZIEdMxf/KLsqFA==
|
||||||
|
|
||||||
"@tootallnate/once@1":
|
"@tootallnate/once@1":
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
|
@ -4686,10 +4715,10 @@
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
form-data "^3.0.0"
|
form-data "^3.0.0"
|
||||||
|
|
||||||
"@types/node@*", "@types/node@18.x", "@types/node@^18.0.6":
|
"@types/node@*", "@types/node@18.x", "@types/node@^18.6.1":
|
||||||
version "18.0.6"
|
version "18.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.0.6.tgz#0ba49ac517ad69abe7a1508bc9b3a5483df9d5d7"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.6.1.tgz#828e4785ccca13f44e2fb6852ae0ef11e3e20ba5"
|
||||||
integrity sha512-/xUq6H2aQm261exT6iZTMifUySEt4GR5KX8eYyY+C4MSNPqSh9oNIP7tz2GLKTlFaiBbgZNxffoR3CVRG+cljw==
|
integrity sha512-z+2vB6yDt1fNwKOeGbckpmirO+VBDuQqecXkgeIqDlaOtmKn6hPR/viQ8cxCfqLU4fTlvM3+YjM367TukWdxpg==
|
||||||
|
|
||||||
"@types/node@^14.0.10 || ^16.0.0", "@types/node@^14.14.20 || ^16.0.0":
|
"@types/node@^14.0.10 || ^16.0.0", "@types/node@^14.14.20 || ^16.0.0":
|
||||||
version "16.11.41"
|
version "16.11.41"
|
||||||
|
@ -4796,7 +4825,7 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/react" "*"
|
"@types/react" "*"
|
||||||
|
|
||||||
"@types/react@*", "@types/react@^18.0.9":
|
"@types/react@*", "@types/react@^18.0.1", "@types/react@^18.0.9":
|
||||||
version "18.0.15"
|
version "18.0.15"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.15.tgz#d355644c26832dc27f3e6cbf0c4f4603fc4ab7fe"
|
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.15.tgz#d355644c26832dc27f3e6cbf0c4f4603fc4ab7fe"
|
||||||
integrity sha512-iz3BtLuIYH1uWdsv6wXYdhozhqj20oD4/Hk2DNXIn1kFsmp9x8d9QB6FnPhfkbhd2PgEONt9Q1x/ebkwjfFLow==
|
integrity sha512-iz3BtLuIYH1uWdsv6wXYdhozhqj20oD4/Hk2DNXIn1kFsmp9x8d9QB6FnPhfkbhd2PgEONt9Q1x/ebkwjfFLow==
|
||||||
|
@ -4972,14 +5001,14 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/yargs-parser" "*"
|
"@types/yargs-parser" "*"
|
||||||
|
|
||||||
"@typescript-eslint/eslint-plugin@^5.30.6", "@typescript-eslint/eslint-plugin@^5.5.0":
|
"@typescript-eslint/eslint-plugin@^5.30.7", "@typescript-eslint/eslint-plugin@^5.5.0":
|
||||||
version "5.30.6"
|
version "5.30.7"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.30.6.tgz#9c6017b6c1d04894141b4a87816388967f64c359"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.30.7.tgz#1621dabc1ae4084310e19e9efc80dfdbb97e7493"
|
||||||
integrity sha512-J4zYMIhgrx4MgnZrSDD7sEnQp7FmhKNOaqaOpaoQ/SfdMfRB/0yvK74hTnvH+VQxndZynqs5/Hn4t+2/j9bADg==
|
integrity sha512-l4L6Do+tfeM2OK0GJsU7TUcM/1oN/N25xHm3Jb4z3OiDU4Lj8dIuxX9LpVMS9riSXQs42D1ieX7b85/r16H9Fw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@typescript-eslint/scope-manager" "5.30.6"
|
"@typescript-eslint/scope-manager" "5.30.7"
|
||||||
"@typescript-eslint/type-utils" "5.30.6"
|
"@typescript-eslint/type-utils" "5.30.7"
|
||||||
"@typescript-eslint/utils" "5.30.6"
|
"@typescript-eslint/utils" "5.30.7"
|
||||||
debug "^4.3.4"
|
debug "^4.3.4"
|
||||||
functional-red-black-tree "^1.0.1"
|
functional-red-black-tree "^1.0.1"
|
||||||
ignore "^5.2.0"
|
ignore "^5.2.0"
|
||||||
|
@ -4994,14 +5023,14 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@typescript-eslint/utils" "5.29.0"
|
"@typescript-eslint/utils" "5.29.0"
|
||||||
|
|
||||||
"@typescript-eslint/parser@^5.30.6", "@typescript-eslint/parser@^5.5.0":
|
"@typescript-eslint/parser@^5.30.7", "@typescript-eslint/parser@^5.5.0":
|
||||||
version "5.30.6"
|
version "5.30.7"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.30.6.tgz#add440db038fa9d777e4ebdaf66da9e7fb7abe92"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.30.7.tgz#99d09729392aec9e64b1de45cd63cb81a4ddd980"
|
||||||
integrity sha512-gfF9lZjT0p2ZSdxO70Xbw8w9sPPJGfAdjK7WikEjB3fcUI/yr9maUVEdqigBjKincUYNKOmf7QBMiTf719kbrA==
|
integrity sha512-Rg5xwznHWWSy7v2o0cdho6n+xLhK2gntImp0rJroVVFkcYFYQ8C8UJTSuTw/3CnExBmPjycjmUJkxVmjXsld6A==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@typescript-eslint/scope-manager" "5.30.6"
|
"@typescript-eslint/scope-manager" "5.30.7"
|
||||||
"@typescript-eslint/types" "5.30.6"
|
"@typescript-eslint/types" "5.30.7"
|
||||||
"@typescript-eslint/typescript-estree" "5.30.6"
|
"@typescript-eslint/typescript-estree" "5.30.7"
|
||||||
debug "^4.3.4"
|
debug "^4.3.4"
|
||||||
|
|
||||||
"@typescript-eslint/scope-manager@5.29.0":
|
"@typescript-eslint/scope-manager@5.29.0":
|
||||||
|
@ -5012,20 +5041,20 @@
|
||||||
"@typescript-eslint/types" "5.29.0"
|
"@typescript-eslint/types" "5.29.0"
|
||||||
"@typescript-eslint/visitor-keys" "5.29.0"
|
"@typescript-eslint/visitor-keys" "5.29.0"
|
||||||
|
|
||||||
"@typescript-eslint/scope-manager@5.30.6":
|
"@typescript-eslint/scope-manager@5.30.7":
|
||||||
version "5.30.6"
|
version "5.30.7"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.30.6.tgz#ce1b49ff5ce47f55518d63dbe8fc9181ddbd1a33"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.30.7.tgz#8269a931ef1e5ae68b5eb80743cc515c4ffe3dd7"
|
||||||
integrity sha512-Hkq5PhLgtVoW1obkqYH0i4iELctEKixkhWLPTYs55doGUKCASvkjOXOd/pisVeLdO24ZX9D6yymJ/twqpJiG3g==
|
integrity sha512-7BM1bwvdF1UUvt+b9smhqdc/eniOnCKxQT/kj3oXtj3LqnTWCAM0qHRHfyzCzhEfWX0zrW7KqXXeE4DlchZBKw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@typescript-eslint/types" "5.30.6"
|
"@typescript-eslint/types" "5.30.7"
|
||||||
"@typescript-eslint/visitor-keys" "5.30.6"
|
"@typescript-eslint/visitor-keys" "5.30.7"
|
||||||
|
|
||||||
"@typescript-eslint/type-utils@5.30.6":
|
"@typescript-eslint/type-utils@5.30.7":
|
||||||
version "5.30.6"
|
version "5.30.7"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.30.6.tgz#a64aa9acbe609ab77f09f53434a6af2b9685f3af"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.30.7.tgz#5693dc3db6f313f302764282d614cfdbc8a9fcfd"
|
||||||
integrity sha512-GFVVzs2j0QPpM+NTDMXtNmJKlF842lkZKDSanIxf+ArJsGeZUIaeT4jGg+gAgHt7AcQSFwW7htzF/rbAh2jaVA==
|
integrity sha512-nD5qAE2aJX/YLyKMvOU5jvJyku4QN5XBVsoTynFrjQZaDgDV6i7QHFiYCx10wvn7hFvfuqIRNBtsgaLe0DbWhw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@typescript-eslint/utils" "5.30.6"
|
"@typescript-eslint/utils" "5.30.7"
|
||||||
debug "^4.3.4"
|
debug "^4.3.4"
|
||||||
tsutils "^3.21.0"
|
tsutils "^3.21.0"
|
||||||
|
|
||||||
|
@ -5034,10 +5063,10 @@
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.29.0.tgz#7861d3d288c031703b2d97bc113696b4d8c19aab"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.29.0.tgz#7861d3d288c031703b2d97bc113696b4d8c19aab"
|
||||||
integrity sha512-X99VbqvAXOMdVyfFmksMy3u8p8yoRGITgU1joBJPzeYa0rhdf5ok9S56/itRoUSh99fiDoMtarSIJXo7H/SnOg==
|
integrity sha512-X99VbqvAXOMdVyfFmksMy3u8p8yoRGITgU1joBJPzeYa0rhdf5ok9S56/itRoUSh99fiDoMtarSIJXo7H/SnOg==
|
||||||
|
|
||||||
"@typescript-eslint/types@5.30.6":
|
"@typescript-eslint/types@5.30.7":
|
||||||
version "5.30.6"
|
version "5.30.7"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.30.6.tgz#86369d0a7af8c67024115ac1da3e8fb2d38907e1"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.30.7.tgz#18331487cc92d0f1fb1a6f580c8ec832528079d0"
|
||||||
integrity sha512-HdnP8HioL1F7CwVmT4RaaMX57RrfqsOMclZc08wGMiDYJBsLGBM7JwXM4cZJmbWLzIR/pXg1kkrBBVpxTOwfUg==
|
integrity sha512-ocVkETUs82+U+HowkovV6uxf1AnVRKCmDRNUBUUo46/5SQv1owC/EBFkiu4MOHeZqhKz2ktZ3kvJJ1uFqQ8QPg==
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree@5.29.0":
|
"@typescript-eslint/typescript-estree@5.29.0":
|
||||||
version "5.29.0"
|
version "5.29.0"
|
||||||
|
@ -5052,13 +5081,13 @@
|
||||||
semver "^7.3.7"
|
semver "^7.3.7"
|
||||||
tsutils "^3.21.0"
|
tsutils "^3.21.0"
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree@5.30.6":
|
"@typescript-eslint/typescript-estree@5.30.7":
|
||||||
version "5.30.6"
|
version "5.30.7"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.30.6.tgz#a84a0d6a486f9b54042da1de3d671a2c9f14484e"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.30.7.tgz#05da9f1b281985bfedcf62349847f8d168eecc07"
|
||||||
integrity sha512-Z7TgPoeYUm06smfEfYF0RBkpF8csMyVnqQbLYiGgmUSTaSXTP57bt8f0UFXstbGxKIreTwQCujtaH0LY9w9B+A==
|
integrity sha512-tNslqXI1ZdmXXrHER83TJ8OTYl4epUzJC0aj2i4DMDT4iU+UqLT3EJeGQvJ17BMbm31x5scSwo3hPM0nqQ1AEA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@typescript-eslint/types" "5.30.6"
|
"@typescript-eslint/types" "5.30.7"
|
||||||
"@typescript-eslint/visitor-keys" "5.30.6"
|
"@typescript-eslint/visitor-keys" "5.30.7"
|
||||||
debug "^4.3.4"
|
debug "^4.3.4"
|
||||||
globby "^11.1.0"
|
globby "^11.1.0"
|
||||||
is-glob "^4.0.3"
|
is-glob "^4.0.3"
|
||||||
|
@ -5077,15 +5106,15 @@
|
||||||
eslint-scope "^5.1.1"
|
eslint-scope "^5.1.1"
|
||||||
eslint-utils "^3.0.0"
|
eslint-utils "^3.0.0"
|
||||||
|
|
||||||
"@typescript-eslint/utils@5.30.6", "@typescript-eslint/utils@^5.13.0":
|
"@typescript-eslint/utils@5.30.7", "@typescript-eslint/utils@^5.13.0":
|
||||||
version "5.30.6"
|
version "5.30.7"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.30.6.tgz#1de2da14f678e7d187daa6f2e4cdb558ed0609dc"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.30.7.tgz#7135be070349e9f7caa262b0ca59dc96123351bb"
|
||||||
integrity sha512-xFBLc/esUbLOJLk9jKv0E9gD/OH966M40aY9jJ8GiqpSkP2xOV908cokJqqhVd85WoIvHVHYXxSFE4cCSDzVvA==
|
integrity sha512-Z3pHdbFw+ftZiGUnm1GZhkJgVqsDL5CYW2yj+TB2mfXDFOMqtbzQi2dNJIyPqPbx9mv2kUxS1gU+r2gKlKi1rQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/json-schema" "^7.0.9"
|
"@types/json-schema" "^7.0.9"
|
||||||
"@typescript-eslint/scope-manager" "5.30.6"
|
"@typescript-eslint/scope-manager" "5.30.7"
|
||||||
"@typescript-eslint/types" "5.30.6"
|
"@typescript-eslint/types" "5.30.7"
|
||||||
"@typescript-eslint/typescript-estree" "5.30.6"
|
"@typescript-eslint/typescript-estree" "5.30.7"
|
||||||
eslint-scope "^5.1.1"
|
eslint-scope "^5.1.1"
|
||||||
eslint-utils "^3.0.0"
|
eslint-utils "^3.0.0"
|
||||||
|
|
||||||
|
@ -5097,12 +5126,12 @@
|
||||||
"@typescript-eslint/types" "5.29.0"
|
"@typescript-eslint/types" "5.29.0"
|
||||||
eslint-visitor-keys "^3.3.0"
|
eslint-visitor-keys "^3.3.0"
|
||||||
|
|
||||||
"@typescript-eslint/visitor-keys@5.30.6":
|
"@typescript-eslint/visitor-keys@5.30.7":
|
||||||
version "5.30.6"
|
version "5.30.7"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.30.6.tgz#94dd10bb481c8083378d24de1742a14b38a2678c"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.30.7.tgz#c093abae75b4fd822bfbad9fc337f38a7a14909a"
|
||||||
integrity sha512-41OiCjdL2mCaSDi2SvYbzFLlqqlm5v1ZW9Ym55wXKL/Rx6OOB1IbuFGo71Fj6Xy90gJDFTlgOS+vbmtGHPTQQA==
|
integrity sha512-KrRXf8nnjvcpxDFOKej4xkD7657+PClJs5cJVSG7NNoCNnjEdc46juNAQt7AyuWctuCgs6mVRc1xGctEqrjxWw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@typescript-eslint/types" "5.30.6"
|
"@typescript-eslint/types" "5.30.7"
|
||||||
eslint-visitor-keys "^3.3.0"
|
eslint-visitor-keys "^3.3.0"
|
||||||
|
|
||||||
"@webassemblyjs/ast@1.11.1":
|
"@webassemblyjs/ast@1.11.1":
|
||||||
|
@ -5748,6 +5777,13 @@ argv@0.0.2:
|
||||||
resolved "https://registry.yarnpkg.com/argv/-/argv-0.0.2.tgz#ecbd16f8949b157183711b1bda334f37840185ab"
|
resolved "https://registry.yarnpkg.com/argv/-/argv-0.0.2.tgz#ecbd16f8949b157183711b1bda334f37840185ab"
|
||||||
integrity sha512-dEamhpPEwRUBpLNHeuCm/v+g0anFByHahxodVO/BbAarHVBBg2MccCwf9K+o1Pof+2btdnkJelYVUWjW/VrATw==
|
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:
|
aria-query@^4.2.2:
|
||||||
version "4.2.2"
|
version "4.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b"
|
resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b"
|
||||||
|
@ -8577,10 +8613,10 @@ enhanced-resolve@^4.5.0:
|
||||||
memory-fs "^0.5.0"
|
memory-fs "^0.5.0"
|
||||||
tapable "^1.0.0"
|
tapable "^1.0.0"
|
||||||
|
|
||||||
enhanced-resolve@^5.0.0, enhanced-resolve@^5.7.0, enhanced-resolve@^5.9.3:
|
enhanced-resolve@^5.0.0, enhanced-resolve@^5.10.0, enhanced-resolve@^5.7.0:
|
||||||
version "5.9.3"
|
version "5.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.9.3.tgz#44a342c012cbc473254af5cc6ae20ebd0aae5d88"
|
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz#0dc579c3bb2a1032e357ac45b8f3a6f3ad4fb1e6"
|
||||||
integrity sha512-Bq9VSor+kjvW3f9/MiiR4eE3XYgOl7/rS8lnSxbRbF3kS0B2r+Y9w5krBWxZgDxASVZbdYrn5wT4j/Wb0J9qow==
|
integrity sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
graceful-fs "^4.2.4"
|
graceful-fs "^4.2.4"
|
||||||
tapable "^2.2.0"
|
tapable "^2.2.0"
|
||||||
|
@ -9207,10 +9243,10 @@ fast-check@^2.17.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
pure-rand "^5.0.1"
|
pure-rand "^5.0.1"
|
||||||
|
|
||||||
fast-check@^3.0.1:
|
fast-check@^3.1.0:
|
||||||
version "3.0.1"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/fast-check/-/fast-check-3.0.1.tgz#b9e7b57c4643a4e62893aca85e21c270591d0eac"
|
resolved "https://registry.yarnpkg.com/fast-check/-/fast-check-3.1.0.tgz#0c958b6592f45fb66ea291a0bd1be2e7045f7e57"
|
||||||
integrity sha512-AriFDYpYVOBynpPZq/quxSLumFOo2hPB2H5Nz2vc1QlNfjOaA62zX8USNXcOY5nwKHEq7lZ84dG9M1W+LAND1g==
|
integrity sha512-LHM6ZiznfQ5ZxReDUZnlKzSI/nlwCdfwaQ6qrCYBIkuWT6PfI3GOK/O2Xj50DF9oC0HIP22vbbHA8v4itAiyGA==
|
||||||
dependencies:
|
dependencies:
|
||||||
pure-rand "^5.0.1"
|
pure-rand "^5.0.1"
|
||||||
|
|
||||||
|
@ -12401,12 +12437,12 @@ markdown-it@^8.3.1:
|
||||||
mdurl "^1.0.1"
|
mdurl "^1.0.1"
|
||||||
uc.micro "^1.0.5"
|
uc.micro "^1.0.5"
|
||||||
|
|
||||||
mathjs@^10.6.4:
|
mathjs@^11.0.1:
|
||||||
version "10.6.4"
|
version "11.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/mathjs/-/mathjs-10.6.4.tgz#1b87a1268781d64f0c8b4e5e1b36cf7ecf58bb05"
|
resolved "https://registry.yarnpkg.com/mathjs/-/mathjs-11.0.1.tgz#7fb5150ef8c427f8bcddba52a084a3d8bffda7ea"
|
||||||
integrity sha512-omQyvRE1jIy+3k2qsqkWASOcd45aZguXZDckr3HtnTYyXk5+2xpVfC3kATgbO2Srjxlqww3TVdhD0oUdZ/hiFA==
|
integrity sha512-Kgm+GcTxwD68zupr7BPK0yrlWpTh2q8sMH6VcBcQe5+JCBqcwOrBxBF11WPah7hVv0NCLDnJnFTiXtik1Phasg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.18.6"
|
"@babel/runtime" "^7.18.9"
|
||||||
complex.js "^2.1.1"
|
complex.js "^2.1.1"
|
||||||
decimal.js "^10.3.1"
|
decimal.js "^10.3.1"
|
||||||
escape-latex "^1.2.0"
|
escape-latex "^1.2.0"
|
||||||
|
@ -12414,7 +12450,7 @@ mathjs@^10.6.4:
|
||||||
javascript-natural-sort "^0.7.1"
|
javascript-natural-sort "^0.7.1"
|
||||||
seedrandom "^3.0.5"
|
seedrandom "^3.0.5"
|
||||||
tiny-emitter "^2.1.0"
|
tiny-emitter "^2.1.0"
|
||||||
typed-function "^2.1.0"
|
typed-function "^3.0.0"
|
||||||
|
|
||||||
md5.js@^1.3.4:
|
md5.js@^1.3.4:
|
||||||
version "1.3.5"
|
version "1.3.5"
|
||||||
|
@ -15225,7 +15261,7 @@ react-vega@^7.6.0:
|
||||||
prop-types "^15.8.1"
|
prop-types "^15.8.1"
|
||||||
vega-embed "^6.5.1"
|
vega-embed "^6.5.1"
|
||||||
|
|
||||||
react@^18.1.0:
|
react@^18.0.0, react@^18.1.0:
|
||||||
version "18.2.0"
|
version "18.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
|
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
|
||||||
integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
|
integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
|
||||||
|
@ -17418,7 +17454,7 @@ tsconfig-paths@^3.14.1, tsconfig-paths@^3.9.0:
|
||||||
minimist "^1.2.6"
|
minimist "^1.2.6"
|
||||||
strip-bom "^3.0.0"
|
strip-bom "^3.0.0"
|
||||||
|
|
||||||
tslib@^1.8.1:
|
tslib@^1.0.0, tslib@^1.8.1:
|
||||||
version "1.14.1"
|
version "1.14.1"
|
||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||||
|
@ -17502,10 +17538,10 @@ type-is@~1.6.18:
|
||||||
media-typer "0.3.0"
|
media-typer "0.3.0"
|
||||||
mime-types "~2.1.24"
|
mime-types "~2.1.24"
|
||||||
|
|
||||||
typed-function@^2.1.0:
|
typed-function@^3.0.0:
|
||||||
version "2.1.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/typed-function/-/typed-function-2.1.0.tgz#ded6f8a442ba8749ff3fe75bc41419c8d46ccc3f"
|
resolved "https://registry.yarnpkg.com/typed-function/-/typed-function-3.0.0.tgz#42f75ffdd7dd63bf5dcc950847138f2bb65f1ad3"
|
||||||
integrity sha512-bctQIOqx2iVbWGDGPWwIm18QScpu2XRmkC19D8rQGFsjKSgteq/o1hTZvIG/wuDq8fanpBDrLkLq+aEN/6y5XQ==
|
integrity sha512-mKJKkt2xYxJUuMD7jyfgUxfn5KCsCxkEKBVjep5yYellJJ5aEDO2QUAmIGdvcZmfQnIrplkzELIaG+5b1475qg==
|
||||||
|
|
||||||
typed-rest-client@1.2.0:
|
typed-rest-client@1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
|
@ -18484,7 +18520,7 @@ watchpack@^1.7.4:
|
||||||
chokidar "^3.4.1"
|
chokidar "^3.4.1"
|
||||||
watchpack-chokidar2 "^2.0.1"
|
watchpack-chokidar2 "^2.0.1"
|
||||||
|
|
||||||
watchpack@^2.2.0, watchpack@^2.3.1:
|
watchpack@^2.2.0, watchpack@^2.4.0:
|
||||||
version "2.4.0"
|
version "2.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d"
|
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d"
|
||||||
integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==
|
integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==
|
||||||
|
@ -18732,21 +18768,21 @@ webpack@4:
|
||||||
watchpack "^1.7.4"
|
watchpack "^1.7.4"
|
||||||
webpack-sources "^1.4.1"
|
webpack-sources "^1.4.1"
|
||||||
|
|
||||||
"webpack@>=4.43.0 <6.0.0", webpack@^5, webpack@^5.64.4, webpack@^5.73.0, webpack@^5.9.0:
|
"webpack@>=4.43.0 <6.0.0", webpack@^5, webpack@^5.64.4, webpack@^5.73.0, webpack@^5.74.0, webpack@^5.9.0:
|
||||||
version "5.73.0"
|
version "5.74.0"
|
||||||
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.73.0.tgz#bbd17738f8a53ee5760ea2f59dce7f3431d35d38"
|
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.74.0.tgz#02a5dac19a17e0bb47093f2be67c695102a55980"
|
||||||
integrity sha512-svjudQRPPa0YiOYa2lM/Gacw0r6PvxptHj4FuEKQ2kX05ZLkjbVc5MnPs6its5j7IZljnIqSVo/OsY2X0IpHGA==
|
integrity sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/eslint-scope" "^3.7.3"
|
"@types/eslint-scope" "^3.7.3"
|
||||||
"@types/estree" "^0.0.51"
|
"@types/estree" "^0.0.51"
|
||||||
"@webassemblyjs/ast" "1.11.1"
|
"@webassemblyjs/ast" "1.11.1"
|
||||||
"@webassemblyjs/wasm-edit" "1.11.1"
|
"@webassemblyjs/wasm-edit" "1.11.1"
|
||||||
"@webassemblyjs/wasm-parser" "1.11.1"
|
"@webassemblyjs/wasm-parser" "1.11.1"
|
||||||
acorn "^8.4.1"
|
acorn "^8.7.1"
|
||||||
acorn-import-assertions "^1.7.6"
|
acorn-import-assertions "^1.7.6"
|
||||||
browserslist "^4.14.5"
|
browserslist "^4.14.5"
|
||||||
chrome-trace-event "^1.0.2"
|
chrome-trace-event "^1.0.2"
|
||||||
enhanced-resolve "^5.9.3"
|
enhanced-resolve "^5.10.0"
|
||||||
es-module-lexer "^0.9.0"
|
es-module-lexer "^0.9.0"
|
||||||
eslint-scope "5.1.1"
|
eslint-scope "5.1.1"
|
||||||
events "^3.2.0"
|
events "^3.2.0"
|
||||||
|
@ -18759,7 +18795,7 @@ webpack@4:
|
||||||
schema-utils "^3.1.0"
|
schema-utils "^3.1.0"
|
||||||
tapable "^2.1.1"
|
tapable "^2.1.1"
|
||||||
terser-webpack-plugin "^5.1.3"
|
terser-webpack-plugin "^5.1.3"
|
||||||
watchpack "^2.3.1"
|
watchpack "^2.4.0"
|
||||||
webpack-sources "^3.2.3"
|
webpack-sources "^3.2.3"
|
||||||
|
|
||||||
webpackbar@^5.0.2:
|
webpackbar@^5.0.2:
|
||||||
|
|
Loading…
Reference in New Issue
Block a user