store location in values; render both result and bindings
This commit is contained in:
parent
ddfd4e0024
commit
a7bbfad94b
|
@ -71,7 +71,7 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
|
|||
distributionChartActions,
|
||||
enableLocalSettings = false,
|
||||
}) => {
|
||||
const result = useSquiggle({
|
||||
const { result, bindings } = useSquiggle({
|
||||
code,
|
||||
environment,
|
||||
// jsImports,
|
||||
|
@ -98,15 +98,27 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
|
|||
};
|
||||
|
||||
return (
|
||||
<SquiggleViewer
|
||||
result={result}
|
||||
width={width}
|
||||
height={height}
|
||||
distributionPlotSettings={distributionPlotSettings}
|
||||
chartSettings={chartSettings}
|
||||
environment={environment ?? defaultEnvironment}
|
||||
enableLocalSettings={enableLocalSettings}
|
||||
/>
|
||||
<div>
|
||||
<SquiggleViewer
|
||||
result={result}
|
||||
width={width}
|
||||
height={height}
|
||||
distributionPlotSettings={distributionPlotSettings}
|
||||
chartSettings={chartSettings}
|
||||
environment={environment ?? defaultEnvironment}
|
||||
enableLocalSettings={enableLocalSettings}
|
||||
/>
|
||||
<hr className="my-4" />
|
||||
<SquiggleViewer
|
||||
result={{ tag: "Ok", value: bindings.asValue() }}
|
||||
width={width}
|
||||
height={height}
|
||||
distributionPlotSettings={distributionPlotSettings}
|
||||
chartSettings={chartSettings}
|
||||
environment={environment ?? defaultEnvironment}
|
||||
enableLocalSettings={enableLocalSettings}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -37,13 +37,18 @@ function getChartSettings<a>(x: declaration<a>): FunctionChartSettings {
|
|||
*/
|
||||
|
||||
const VariableList: React.FC<{
|
||||
path: string[];
|
||||
value: SqValue;
|
||||
heading: string;
|
||||
children: (settings: MergedItemSettings) => React.ReactNode;
|
||||
}> = ({ path, heading, children }) => (
|
||||
<VariableBox path={path} heading={heading}>
|
||||
}> = ({ value, heading, children }) => (
|
||||
<VariableBox value={value} heading={heading}>
|
||||
{(settings) => (
|
||||
<div className={clsx("space-y-3", path.length ? "pt-1 mt-1" : null)}>
|
||||
<div
|
||||
className={clsx(
|
||||
"space-y-3",
|
||||
value.location.path.items.length ? "pt-1 mt-1" : null
|
||||
)}
|
||||
>
|
||||
{children(settings)}
|
||||
</div>
|
||||
)}
|
||||
|
@ -52,54 +57,41 @@ const VariableList: React.FC<{
|
|||
|
||||
export interface Props {
|
||||
/** The output of squiggle's run */
|
||||
expression: SqValue;
|
||||
/** Path to the current item, e.g. `['foo', 'bar', '3']` for `foo.bar[3]`; can be empty on the top-level item. */
|
||||
path: string[];
|
||||
value: SqValue;
|
||||
width?: number;
|
||||
}
|
||||
|
||||
export const ExpressionViewer: React.FC<Props> = ({
|
||||
path,
|
||||
expression,
|
||||
width,
|
||||
}) => {
|
||||
export const ExpressionViewer: React.FC<Props> = ({ value, width }) => {
|
||||
const { getMergedSettings } = useContext(ViewerContext);
|
||||
|
||||
if (typeof expression !== "object") {
|
||||
return (
|
||||
<VariableList path={path} heading="Error">
|
||||
{() => `Unknown expression: ${expression}`}
|
||||
</VariableList>
|
||||
);
|
||||
}
|
||||
switch (expression.tag) {
|
||||
switch (value.tag) {
|
||||
case SqValueTag.Number:
|
||||
return (
|
||||
<VariableBox path={path} heading="Number">
|
||||
<VariableBox value={value} heading="Number">
|
||||
{() => (
|
||||
<div className="font-semibold text-slate-600">
|
||||
<NumberShower precision={3} number={expression.value} />
|
||||
<NumberShower precision={3} number={value.value} />
|
||||
</div>
|
||||
)}
|
||||
</VariableBox>
|
||||
);
|
||||
case SqValueTag.Distribution: {
|
||||
const distType = expression.value.tag;
|
||||
const distType = value.value.tag;
|
||||
return (
|
||||
<VariableBox
|
||||
path={path}
|
||||
value={value}
|
||||
heading={`Distribution (${distType})\n${
|
||||
distType === SqDistributionTag.Symbolic
|
||||
? expression.value.toString()
|
||||
? value.value.toString()
|
||||
: ""
|
||||
}`}
|
||||
renderSettingsMenu={({ onChange }) => {
|
||||
const shape = expression.value.pointSet(
|
||||
getMergedSettings(path).environment
|
||||
const shape = value.value.pointSet(
|
||||
getMergedSettings(value.location).environment
|
||||
);
|
||||
return (
|
||||
<ItemSettingsMenu
|
||||
path={path}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
disableLogX={
|
||||
shape.tag === "Ok" && hasMassBelowZero(shape.value.asShape())
|
||||
|
@ -112,7 +104,7 @@ export const ExpressionViewer: React.FC<Props> = ({
|
|||
{(settings) => {
|
||||
return (
|
||||
<DistributionChart
|
||||
plot={defaultPlot(expression.value)}
|
||||
plot={defaultPlot(value.value)}
|
||||
environment={settings.environment}
|
||||
{...settings.distributionPlotSettings}
|
||||
height={settings.height}
|
||||
|
@ -125,12 +117,12 @@ export const ExpressionViewer: React.FC<Props> = ({
|
|||
}
|
||||
case SqValueTag.String:
|
||||
return (
|
||||
<VariableBox path={path} heading="String">
|
||||
<VariableBox value={value} heading="String">
|
||||
{() => (
|
||||
<>
|
||||
<span className="text-slate-400">"</span>
|
||||
<span className="text-slate-600 font-semibold font-mono">
|
||||
{expression.value}
|
||||
{value.value}
|
||||
</span>
|
||||
<span className="text-slate-400">"</span>
|
||||
</>
|
||||
|
@ -139,61 +131,61 @@ export const ExpressionViewer: React.FC<Props> = ({
|
|||
);
|
||||
case SqValueTag.Bool:
|
||||
return (
|
||||
<VariableBox path={path} heading="Boolean">
|
||||
{() => expression.value.toString()}
|
||||
<VariableBox value={value} heading="Boolean">
|
||||
{() => value.value.toString()}
|
||||
</VariableBox>
|
||||
);
|
||||
case SqValueTag.Symbol:
|
||||
return (
|
||||
<VariableBox path={path} heading="Symbol">
|
||||
<VariableBox value={value} heading="Symbol">
|
||||
{() => (
|
||||
<>
|
||||
<span className="text-slate-500 mr-2">Undefined Symbol:</span>
|
||||
<span className="text-slate-600">{expression.value}</span>
|
||||
<span className="text-slate-600">{value.value}</span>
|
||||
</>
|
||||
)}
|
||||
</VariableBox>
|
||||
);
|
||||
case SqValueTag.Call:
|
||||
return (
|
||||
<VariableBox path={path} heading="Call">
|
||||
{() => expression.value}
|
||||
<VariableBox value={value} heading="Call">
|
||||
{() => value.value}
|
||||
</VariableBox>
|
||||
);
|
||||
case SqValueTag.ArrayString:
|
||||
return (
|
||||
<VariableBox path={path} heading="Array String">
|
||||
{() => expression.value.map((r) => `"${r}"`).join(", ")}
|
||||
<VariableBox value={value} heading="Array String">
|
||||
{() => value.value.map((r) => `"${r}"`).join(", ")}
|
||||
</VariableBox>
|
||||
);
|
||||
case SqValueTag.Date:
|
||||
return (
|
||||
<VariableBox path={path} heading="Date">
|
||||
{() => expression.value.toDateString()}
|
||||
<VariableBox value={value} heading="Date">
|
||||
{() => value.value.toDateString()}
|
||||
</VariableBox>
|
||||
);
|
||||
case SqValueTag.Void:
|
||||
return (
|
||||
<VariableBox path={path} heading="Void">
|
||||
<VariableBox value={value} heading="Void">
|
||||
{() => "Void"}
|
||||
</VariableBox>
|
||||
);
|
||||
case SqValueTag.TimeDuration: {
|
||||
return (
|
||||
<VariableBox path={path} heading="Time Duration">
|
||||
{() => <NumberShower precision={3} number={expression.value} />}
|
||||
<VariableBox value={value} heading="Time Duration">
|
||||
{() => <NumberShower precision={3} number={value.value} />}
|
||||
</VariableBox>
|
||||
);
|
||||
}
|
||||
case SqValueTag.Lambda:
|
||||
return (
|
||||
<VariableBox
|
||||
path={path}
|
||||
value={value}
|
||||
heading="Function"
|
||||
renderSettingsMenu={({ onChange }) => {
|
||||
return (
|
||||
<ItemSettingsMenu
|
||||
path={path}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
withFunctionSettings={true}
|
||||
/>
|
||||
|
@ -202,11 +194,11 @@ export const ExpressionViewer: React.FC<Props> = ({
|
|||
>
|
||||
{(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
|
||||
<div className="text-amber-700 bg-amber-100 rounded-md font-mono p-1 pl-2 mb-3 mt-1 text-sm">{`function(${value.value
|
||||
.parameters()
|
||||
.join(",")})`}</div>
|
||||
<FunctionChart
|
||||
fn={expression.value}
|
||||
fn={value.value}
|
||||
chartSettings={settings.chartSettings}
|
||||
distributionPlotSettings={settings.distributionPlotSettings}
|
||||
height={settings.height}
|
||||
|
@ -222,13 +214,13 @@ export const ExpressionViewer: React.FC<Props> = ({
|
|||
case SqValueTag.Declaration: {
|
||||
return (
|
||||
<VariableBox
|
||||
path={path}
|
||||
value={value}
|
||||
heading="Function Declaration"
|
||||
renderSettingsMenu={({ onChange }) => {
|
||||
return (
|
||||
<ItemSettingsMenu
|
||||
onChange={onChange}
|
||||
path={path}
|
||||
value={value}
|
||||
withFunctionSettings={true}
|
||||
/>
|
||||
);
|
||||
|
@ -252,16 +244,15 @@ export const ExpressionViewer: React.FC<Props> = ({
|
|||
}
|
||||
case SqValueTag.Module: {
|
||||
return (
|
||||
<VariableList path={path} heading="Module">
|
||||
<VariableList value={value} heading="Module">
|
||||
{(_) =>
|
||||
expression.value
|
||||
value.value
|
||||
.entries()
|
||||
.filter(([key, _]) => !key.match(/^(Math|System)\./))
|
||||
.map(([key, r]) => (
|
||||
<ExpressionViewer
|
||||
key={key}
|
||||
path={[...path, key]}
|
||||
expression={r}
|
||||
value={r}
|
||||
width={width !== undefined ? width - 20 : width}
|
||||
/>
|
||||
))
|
||||
|
@ -270,16 +261,16 @@ export const ExpressionViewer: React.FC<Props> = ({
|
|||
);
|
||||
}
|
||||
case SqValueTag.Record:
|
||||
const plot = makePlot(expression.value);
|
||||
const plot = makePlot(value.value);
|
||||
if (plot) {
|
||||
return (
|
||||
<VariableBox
|
||||
path={path}
|
||||
value={value}
|
||||
heading="Plot"
|
||||
renderSettingsMenu={({ onChange }) => {
|
||||
let disableLogX = plot.distributions.some((x) => {
|
||||
let pointSet = x.distribution.pointSet(
|
||||
getMergedSettings(path).environment
|
||||
getMergedSettings(value.location).environment
|
||||
);
|
||||
return (
|
||||
pointSet.tag === "Ok" &&
|
||||
|
@ -288,7 +279,7 @@ export const ExpressionViewer: React.FC<Props> = ({
|
|||
});
|
||||
return (
|
||||
<ItemSettingsMenu
|
||||
path={path}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
disableLogX={disableLogX}
|
||||
withFunctionSettings={false}
|
||||
|
@ -311,15 +302,14 @@ export const ExpressionViewer: React.FC<Props> = ({
|
|||
);
|
||||
} else {
|
||||
return (
|
||||
<VariableList path={path} heading="Record">
|
||||
<VariableList value={value} heading="Record">
|
||||
{(_) =>
|
||||
expression.value
|
||||
value.value
|
||||
.entries()
|
||||
.map(([key, r]) => (
|
||||
<ExpressionViewer
|
||||
key={key}
|
||||
path={[...path, key]}
|
||||
expression={r}
|
||||
value={r}
|
||||
width={width !== undefined ? width - 20 : width}
|
||||
/>
|
||||
))
|
||||
|
@ -329,15 +319,14 @@ export const ExpressionViewer: React.FC<Props> = ({
|
|||
}
|
||||
case SqValueTag.Array:
|
||||
return (
|
||||
<VariableList path={path} heading="Array">
|
||||
<VariableList value={value} heading="Array">
|
||||
{(_) =>
|
||||
expression.value
|
||||
value.value
|
||||
.getValues()
|
||||
.map((r, i) => (
|
||||
<ExpressionViewer
|
||||
key={i}
|
||||
path={[...path, String(i)]}
|
||||
expression={r}
|
||||
value={r}
|
||||
width={width !== undefined ? width - 20 : width}
|
||||
/>
|
||||
))
|
||||
|
@ -346,13 +335,11 @@ export const ExpressionViewer: React.FC<Props> = ({
|
|||
);
|
||||
default: {
|
||||
return (
|
||||
<VariableList path={path} heading="Error">
|
||||
<VariableList value={value} heading="Error">
|
||||
{() => (
|
||||
<div>
|
||||
<span>No display for type: </span>{" "}
|
||||
<span className="font-semibold text-slate-600">
|
||||
{expression.tag}
|
||||
</span>
|
||||
<span className="font-semibold text-slate-600">{value.tag}</span>
|
||||
</div>
|
||||
)}
|
||||
</VariableList>
|
||||
|
|
|
@ -4,13 +4,14 @@ 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 { defaultTickFormat } from "../../lib/distributionSpecBuilder";
|
||||
import { PlaygroundContext } from "../SquigglePlayground";
|
||||
import { SqValue } from "@quri/squiggle-lang";
|
||||
import { locationAsString } from "./utils";
|
||||
|
||||
type Props = {
|
||||
path: Path;
|
||||
value: SqValue;
|
||||
onChange: () => void;
|
||||
disableLogX?: boolean;
|
||||
withFunctionSettings: boolean;
|
||||
|
@ -19,7 +20,7 @@ type Props = {
|
|||
const ItemSettingsModal: React.FC<
|
||||
Props & { close: () => void; resetScroll: () => void }
|
||||
> = ({
|
||||
path,
|
||||
value,
|
||||
onChange,
|
||||
disableLogX,
|
||||
withFunctionSettings,
|
||||
|
@ -29,7 +30,7 @@ const ItemSettingsModal: React.FC<
|
|||
const { setSettings, getSettings, getMergedSettings } =
|
||||
useContext(ViewerContext);
|
||||
|
||||
const mergedSettings = getMergedSettings(path);
|
||||
const mergedSettings = getMergedSettings(value.location);
|
||||
|
||||
const { register, watch } = useForm({
|
||||
resolver: yupResolver(viewSettingsSchema),
|
||||
|
@ -53,8 +54,8 @@ const ItemSettingsModal: React.FC<
|
|||
});
|
||||
useEffect(() => {
|
||||
const subscription = watch((vars) => {
|
||||
const settings = getSettings(path); // get the latest version
|
||||
setSettings(path, {
|
||||
const settings = getSettings(value.location); // get the latest version
|
||||
setSettings(value.location, {
|
||||
...settings,
|
||||
distributionPlotSettings: {
|
||||
showSummary: vars.showSummary,
|
||||
|
@ -75,7 +76,7 @@ const ItemSettingsModal: React.FC<
|
|||
onChange();
|
||||
});
|
||||
return () => subscription.unsubscribe();
|
||||
}, [getSettings, setSettings, onChange, path, watch]);
|
||||
}, [getSettings, setSettings, onChange, value.location, watch]);
|
||||
|
||||
const { getLeftPanelElement } = useContext(PlaygroundContext);
|
||||
|
||||
|
@ -83,7 +84,7 @@ const ItemSettingsModal: React.FC<
|
|||
<Modal container={getLeftPanelElement()} close={close}>
|
||||
<Modal.Header>
|
||||
Chart settings
|
||||
{path.length ? (
|
||||
{value.location.path.items.length ? (
|
||||
<>
|
||||
{" for "}
|
||||
<span
|
||||
|
@ -91,7 +92,7 @@ const ItemSettingsModal: React.FC<
|
|||
className="cursor-pointer"
|
||||
onClick={resetScroll}
|
||||
>
|
||||
{pathAsString(path)}
|
||||
{locationAsString(value.location)}
|
||||
</span>{" "}
|
||||
</>
|
||||
) : (
|
||||
|
@ -120,7 +121,7 @@ export const ItemSettingsMenu: React.FC<Props> = (props) => {
|
|||
if (!enableLocalSettings) {
|
||||
return null;
|
||||
}
|
||||
const settings = getSettings(props.path);
|
||||
const settings = getSettings(props.value.location);
|
||||
|
||||
const resetScroll = () => {
|
||||
if (!ref.current) return;
|
||||
|
@ -139,7 +140,7 @@ export const ItemSettingsMenu: React.FC<Props> = (props) => {
|
|||
{settings.distributionPlotSettings || settings.chartSettings ? (
|
||||
<button
|
||||
onClick={() => {
|
||||
setSettings(props.path, {
|
||||
setSettings(props.value.location, {
|
||||
...settings,
|
||||
distributionPlotSettings: undefined,
|
||||
chartSettings: undefined,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { SqValue, SqValueLocation } from "@quri/squiggle-lang";
|
||||
import React, { useContext, useReducer } from "react";
|
||||
import { Tooltip } from "../ui/Tooltip";
|
||||
import { LocalItemSettings, MergedItemSettings } from "./utils";
|
||||
|
@ -8,14 +9,14 @@ type SettingsMenuParams = {
|
|||
};
|
||||
|
||||
type VariableBoxProps = {
|
||||
path: string[];
|
||||
value: SqValue;
|
||||
heading: string;
|
||||
renderSettingsMenu?: (params: SettingsMenuParams) => React.ReactNode;
|
||||
children: (settings: MergedItemSettings) => React.ReactNode;
|
||||
};
|
||||
|
||||
export const VariableBox: React.FC<VariableBoxProps> = ({
|
||||
path,
|
||||
value: { location },
|
||||
heading = "Error",
|
||||
renderSettingsMenu,
|
||||
children,
|
||||
|
@ -27,10 +28,10 @@ export const VariableBox: React.FC<VariableBoxProps> = ({
|
|||
// So we use `forceUpdate` to force rerendering.
|
||||
const [_, forceUpdate] = useReducer((x) => x + 1, 0);
|
||||
|
||||
const settings = getSettings(path);
|
||||
const settings = getSettings(location);
|
||||
|
||||
const setSettingsAndUpdate = (newSettings: LocalItemSettings) => {
|
||||
setSettings(path, newSettings);
|
||||
setSettings(location, newSettings);
|
||||
forceUpdate();
|
||||
};
|
||||
|
||||
|
@ -38,8 +39,10 @@ export const VariableBox: React.FC<VariableBoxProps> = ({
|
|||
setSettingsAndUpdate({ ...settings, collapsed: !settings.collapsed });
|
||||
};
|
||||
|
||||
const isTopLevel = path.length === 0;
|
||||
const name = isTopLevel ? "Result" : path[path.length - 1];
|
||||
const isTopLevel = location.path.items.length === 0;
|
||||
const name = isTopLevel
|
||||
? { result: "Result", bindings: "Bindings" }[location.path.root]
|
||||
: location.path.items[location.path.items.length - 1];
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
@ -65,13 +68,13 @@ export const VariableBox: React.FC<VariableBoxProps> = ({
|
|||
</header>
|
||||
{settings.collapsed ? null : (
|
||||
<div className="flex w-full">
|
||||
{path.length ? (
|
||||
{location.path.items.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 className="grow">{children(getMergedSettings(location))}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import { defaultEnvironment } from "@quri/squiggle-lang";
|
||||
import { defaultEnvironment, SqValueLocation } from "@quri/squiggle-lang";
|
||||
import React from "react";
|
||||
import { LocalItemSettings, MergedItemSettings, Path } from "./utils";
|
||||
import { LocalItemSettings, MergedItemSettings } 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;
|
||||
getSettings(location: SqValueLocation): LocalItemSettings;
|
||||
getMergedSettings(location: SqValueLocation): MergedItemSettings;
|
||||
setSettings(location: SqValueLocation, value: LocalItemSettings): void;
|
||||
enableLocalSettings: boolean; // show local settings icon in the UI
|
||||
};
|
||||
|
||||
|
|
|
@ -1,21 +1,20 @@
|
|||
import React, { useCallback, useRef } from "react";
|
||||
import { environment } from "@quri/squiggle-lang";
|
||||
import { environment, SqValueLocation } from "@quri/squiggle-lang";
|
||||
import { DistributionPlottingSettings } from "../DistributionChart";
|
||||
import { FunctionChartSettings } from "../FunctionChart";
|
||||
import { ExpressionViewer } from "./ExpressionViewer";
|
||||
import { ViewerContext } from "./ViewerContext";
|
||||
import {
|
||||
LocalItemSettings,
|
||||
locationAsString,
|
||||
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>;
|
||||
result: ReturnType<typeof useSquiggle>["result"];
|
||||
width?: number;
|
||||
height: number;
|
||||
distributionPlotSettings: DistributionPlottingSettings;
|
||||
|
@ -45,22 +44,22 @@ export const SquiggleViewer: React.FC<Props> = ({
|
|||
const settingsRef = useRef<Settings>({});
|
||||
|
||||
const getSettings = useCallback(
|
||||
(path: Path) => {
|
||||
return settingsRef.current[pathAsString(path)] || defaultSettings;
|
||||
(location: SqValueLocation) => {
|
||||
return settingsRef.current[locationAsString(location)] || defaultSettings;
|
||||
},
|
||||
[settingsRef]
|
||||
);
|
||||
|
||||
const setSettings = useCallback(
|
||||
(path: Path, value: LocalItemSettings) => {
|
||||
settingsRef.current[pathAsString(path)] = value;
|
||||
(location: SqValueLocation, value: LocalItemSettings) => {
|
||||
settingsRef.current[locationAsString(location)] = value;
|
||||
},
|
||||
[settingsRef]
|
||||
);
|
||||
|
||||
const getMergedSettings = useCallback(
|
||||
(path: Path) => {
|
||||
const localSettings = getSettings(path);
|
||||
(location: SqValueLocation) => {
|
||||
const localSettings = getSettings(location);
|
||||
const result: MergedItemSettings = {
|
||||
distributionPlotSettings: {
|
||||
...distributionPlotSettings,
|
||||
|
@ -91,7 +90,7 @@ export const SquiggleViewer: React.FC<Props> = ({
|
|||
}}
|
||||
>
|
||||
{result.tag === "Ok" ? (
|
||||
<ExpressionViewer path={[]} expression={result.value} width={width} />
|
||||
<ExpressionViewer value={result.value} width={width} />
|
||||
) : (
|
||||
<SquiggleErrorAlert error={result.value} />
|
||||
)}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { DistributionPlottingSettings } from "../DistributionChart";
|
||||
import { FunctionChartSettings } from "../FunctionChart";
|
||||
import { environment } from "@quri/squiggle-lang";
|
||||
import { environment, SqValueLocation } from "@quri/squiggle-lang";
|
||||
|
||||
export type LocalItemSettings = {
|
||||
collapsed: boolean;
|
||||
|
@ -17,6 +17,5 @@ export type MergedItemSettings = {
|
|||
environment: environment;
|
||||
};
|
||||
|
||||
export type Path = string[];
|
||||
|
||||
export const pathAsString = (path: Path) => path.join(".");
|
||||
export const locationAsString = (location: SqValueLocation) =>
|
||||
location.path.root + "/" + location.path.items.join(".");
|
||||
|
|
|
@ -18,12 +18,10 @@ type SquiggleArgs = {
|
|||
export const useSquiggle = (args: SquiggleArgs) => {
|
||||
const result = useMemo(
|
||||
() => {
|
||||
const { result, bindings } = run(args.code, {
|
||||
const result = run(args.code, {
|
||||
environment: args.environment,
|
||||
});
|
||||
return resultMap(result, (v) =>
|
||||
v.tag === SqValueTag.Void ? bindings.asValue() : v
|
||||
);
|
||||
return result;
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[
|
||||
|
@ -37,10 +35,8 @@ export const useSquiggle = (args: SquiggleArgs) => {
|
|||
const { onChange } = args;
|
||||
|
||||
useEffect(() => {
|
||||
onChange?.(result.tag === "Ok" ? result.value : undefined);
|
||||
onChange?.(result.result.tag === "Ok" ? result.result.value : undefined);
|
||||
}, [result, onChange]);
|
||||
|
||||
console.log(result);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
import * as RSArray from "../rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_Array.gen";
|
||||
import { wrapValue } from "./SqValue";
|
||||
import { SqValueLocation } from "./SqValueLocation";
|
||||
|
||||
type T = RSArray.squiggleValue_Array;
|
||||
|
||||
export class SqArray {
|
||||
_value: T;
|
||||
|
||||
constructor(_value: T) {
|
||||
this._value = _value;
|
||||
}
|
||||
constructor(private _value: T, public location: SqValueLocation) {}
|
||||
|
||||
getValues() {
|
||||
return RSArray.getValues(this._value).map(wrapValue);
|
||||
return RSArray.getValues(this._value).map((v, i) =>
|
||||
wrapValue(v, this.location.extend(i))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,11 +16,14 @@ export const wrapDistribution = (value: T): SqDistribution => {
|
|||
|
||||
abstract class SqAbstractDistribution {
|
||||
abstract tag: Tag;
|
||||
_value: T;
|
||||
|
||||
constructor(value: T) {
|
||||
this._value = value;
|
||||
}
|
||||
constructor(private _value: T) {}
|
||||
|
||||
protected valueMethod = <IR>(rsMethod: (v: T) => IR | null | undefined) => {
|
||||
const value = rsMethod(this._value);
|
||||
if (!value) throw new Error("Internal casting error");
|
||||
return value;
|
||||
};
|
||||
|
||||
pointSet(env: environment) {
|
||||
const innerResult = RSDistribution.toPointSet(this._value, env);
|
||||
|
@ -76,20 +79,11 @@ abstract class SqAbstractDistribution {
|
|||
}
|
||||
}
|
||||
|
||||
const valueMethod = <IR>(
|
||||
_this: SqAbstractDistribution,
|
||||
rsMethod: (v: T) => IR | null | undefined
|
||||
) => {
|
||||
const value = rsMethod(_this._value);
|
||||
if (!value) throw new Error("Internal casting error");
|
||||
return value;
|
||||
};
|
||||
|
||||
export class SqPointSetDistribution extends SqAbstractDistribution {
|
||||
tag = Tag.PointSet;
|
||||
|
||||
value() {
|
||||
return valueMethod(this, RSDistribution.getPointSet);
|
||||
return this.valueMethod(RSDistribution.getPointSet);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -97,7 +91,7 @@ export class SqSampleSetDistribution extends SqAbstractDistribution {
|
|||
tag = Tag.SampleSet;
|
||||
|
||||
value() {
|
||||
return valueMethod(this, RSDistribution.getSampleSet);
|
||||
return this.valueMethod(RSDistribution.getSampleSet);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -105,7 +99,7 @@ export class SqSymbolicDistribution extends SqAbstractDistribution {
|
|||
tag = Tag.Symbolic;
|
||||
|
||||
value() {
|
||||
return valueMethod(this, RSDistribution.getSymbolic);
|
||||
return this.valueMethod(RSDistribution.getSymbolic);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,11 +3,7 @@ import * as RSDistributionError from "../rescript/ForTS/ForTS_Distribution/ForTS
|
|||
type T = RSDistributionError.distributionError;
|
||||
|
||||
export class SqDistributionError {
|
||||
_value: T;
|
||||
|
||||
constructor(_value: T) {
|
||||
this._value = _value;
|
||||
}
|
||||
constructor(private _value: T) {}
|
||||
|
||||
toString() {
|
||||
return RSDistributionError.toString(this._value);
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
import * as RSErrorValue from "../rescript/ForTS/ForTS_Reducer_ErrorValue.gen";
|
||||
|
||||
export class SqError {
|
||||
_value: RSErrorValue.reducerErrorValue;
|
||||
|
||||
constructor(_value: RSErrorValue.reducerErrorValue) {
|
||||
this._value = _value;
|
||||
}
|
||||
constructor(private _value: RSErrorValue.reducerErrorValue) {}
|
||||
|
||||
toString() {
|
||||
return RSErrorValue.toString(this._value);
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
import * as RSLambda from "../rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_Lambda.gen";
|
||||
import { SqValueLocation } from "./SqValueLocation";
|
||||
|
||||
type T = RSLambda.squiggleValue_Lambda;
|
||||
|
||||
export class SqLambda {
|
||||
_value: T;
|
||||
|
||||
constructor(_value: T) {
|
||||
this._value = _value;
|
||||
}
|
||||
constructor(private _value: T, public location: SqValueLocation) {}
|
||||
|
||||
parameters() {
|
||||
return RSLambda.parameters(this._value);
|
||||
|
|
|
@ -3,9 +3,5 @@ import * as RSDeclaration from "../rescript/ForTS/ForTS_SquiggleValue/ForTS_Squi
|
|||
type T = RSDeclaration.squiggleValue_Declaration;
|
||||
|
||||
export class SqLambdaDeclaration {
|
||||
_value: T;
|
||||
|
||||
constructor(_value: T) {
|
||||
this._value = _value;
|
||||
}
|
||||
constructor(private _value: T) {}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,23 @@
|
|||
import * as RSModuleValue from "../rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_Module.gen";
|
||||
import { SqModuleValue, wrapValue } from "./SqValue";
|
||||
import { SqValueLocation } from "./SqValueLocation";
|
||||
|
||||
export class SqModule {
|
||||
_value: RSModuleValue.squiggleValue_Module;
|
||||
|
||||
constructor(_value: RSModuleValue.squiggleValue_Module) {
|
||||
this._value = _value;
|
||||
}
|
||||
constructor(
|
||||
private _value: RSModuleValue.squiggleValue_Module,
|
||||
public location: SqValueLocation
|
||||
) {}
|
||||
|
||||
entries() {
|
||||
return RSModuleValue.getKeyValuePairs(this._value).map(
|
||||
([k, v]) => [k, wrapValue(v)] as const
|
||||
([k, v]) => [k, wrapValue(v, this.location.extend(k))] as const
|
||||
);
|
||||
}
|
||||
|
||||
asValue() {
|
||||
return new SqModuleValue(RSModuleValue.toSquiggleValue(this._value));
|
||||
return new SqModuleValue(
|
||||
RSModuleValue.toSquiggleValue(this._value),
|
||||
this.location
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,29 +25,22 @@ export const wrapPointSetDist = (value: T) => {
|
|||
};
|
||||
|
||||
abstract class SqAbstractPointSetDist {
|
||||
_value: T;
|
||||
|
||||
constructor(_value: T) {
|
||||
this._value = _value;
|
||||
}
|
||||
constructor(private _value: T) {}
|
||||
|
||||
abstract asShape(): SqShape;
|
||||
}
|
||||
|
||||
const valueMethod = <IR>(
|
||||
_this: SqAbstractPointSetDist,
|
||||
rsMethod: (v: T) => IR | null | undefined
|
||||
) => {
|
||||
const value = rsMethod(_this._value);
|
||||
if (!value) throw new Error("Internal casting error");
|
||||
return value;
|
||||
};
|
||||
protected valueMethod = <IR>(rsMethod: (v: T) => IR | null | undefined) => {
|
||||
const value = rsMethod(this._value);
|
||||
if (!value) throw new Error("Internal casting error");
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
||||
export class SqMixedPointSetDist extends SqAbstractPointSetDist {
|
||||
tag = Tag.Mixed as const;
|
||||
|
||||
get value(): RSPointSetDist.mixedShape {
|
||||
return valueMethod(this, RSPointSetDist.getMixed);
|
||||
return this.valueMethod(RSPointSetDist.getMixed);
|
||||
}
|
||||
|
||||
asShape() {
|
||||
|
@ -63,7 +56,7 @@ export class SqDiscretePointSetDist extends SqAbstractPointSetDist {
|
|||
tag = Tag.Discrete as const;
|
||||
|
||||
get value(): RSPointSetDist.discreteShape {
|
||||
return valueMethod(this, RSPointSetDist.getDiscrete);
|
||||
return this.valueMethod(RSPointSetDist.getDiscrete);
|
||||
}
|
||||
|
||||
asShape() {
|
||||
|
@ -79,7 +72,7 @@ export class SqContinuousPointSetDist extends SqAbstractPointSetDist {
|
|||
tag = Tag.Continuous as const;
|
||||
|
||||
get value(): RSPointSetDist.continuousShape {
|
||||
return valueMethod(this, RSPointSetDist.getContinues);
|
||||
return this.valueMethod(RSPointSetDist.getContinues);
|
||||
}
|
||||
|
||||
asShape() {
|
||||
|
|
|
@ -5,13 +5,10 @@ import { SqError } from "./SqError";
|
|||
import { SqModule } from "./SqModule";
|
||||
import { wrapValue } from "./SqValue";
|
||||
import { resultMap2 } from "./types";
|
||||
import { SqValueLocation } from "./SqValueLocation";
|
||||
|
||||
export class SqProject {
|
||||
_value: RSProject.reducerProject;
|
||||
|
||||
constructor(_value: RSProject.reducerProject) {
|
||||
this._value = _value;
|
||||
}
|
||||
constructor(private _value: RSProject.reducerProject) {}
|
||||
|
||||
static create() {
|
||||
return new SqProject(RSProject.createProject());
|
||||
|
@ -94,14 +91,27 @@ export class SqProject {
|
|||
}
|
||||
|
||||
getBindings(sourceId: string) {
|
||||
return new SqModule(RSProject.getBindings(this._value, sourceId));
|
||||
return new SqModule(
|
||||
RSProject.getBindings(this._value, sourceId),
|
||||
new SqValueLocation(this, sourceId, {
|
||||
root: "bindings",
|
||||
items: [],
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
getResult(sourceId: string) {
|
||||
const innerResult = RSProject.getResult(this._value, sourceId);
|
||||
return resultMap2(
|
||||
innerResult,
|
||||
wrapValue,
|
||||
(v) =>
|
||||
wrapValue(
|
||||
v,
|
||||
new SqValueLocation(this, sourceId, {
|
||||
root: "result",
|
||||
items: [],
|
||||
})
|
||||
),
|
||||
(v: reducerErrorValue) => new SqError(v)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,18 +1,15 @@
|
|||
import * as RSRecord from "../rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_Record.gen";
|
||||
import { wrapValue } from "./SqValue";
|
||||
import { SqValueLocation } from "./SqValueLocation";
|
||||
|
||||
type T = RSRecord.squiggleValue_Record;
|
||||
|
||||
export class SqRecord {
|
||||
_value: T;
|
||||
|
||||
constructor(_value: T) {
|
||||
this._value = _value;
|
||||
}
|
||||
constructor(private _value: T, public location: SqValueLocation) {}
|
||||
|
||||
entries() {
|
||||
return RSRecord.getKeyValuePairs(this._value).map(
|
||||
([k, v]) => [k, wrapValue(v)] as const
|
||||
([k, v]) => [k, wrapValue(v, this.location.extend(k))] as const
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,9 +3,5 @@ import * as RSType from "../rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleVal
|
|||
type T = RSType.squiggleValue_Type;
|
||||
|
||||
export class SqType {
|
||||
_value: T;
|
||||
|
||||
constructor(_value: T) {
|
||||
this._value = _value;
|
||||
}
|
||||
constructor(private _value: T) {}
|
||||
}
|
||||
|
|
|
@ -7,42 +7,38 @@ import { SqModule } from "./SqModule";
|
|||
import { SqRecord } from "./SqRecord";
|
||||
import { SqArray } from "./SqArray";
|
||||
import { SqType } from "./SqType";
|
||||
import { SqProject } from "./SqProject";
|
||||
import { SqValueLocation } from "./SqValueLocation";
|
||||
|
||||
export { Tag as SqValueTag };
|
||||
|
||||
type T = RSValue.squiggleValue;
|
||||
|
||||
export const wrapValue = (value: T): SqValue => {
|
||||
export const wrapValue = (value: T, location: SqValueLocation): SqValue => {
|
||||
const tag = RSValue.getTag(value);
|
||||
|
||||
return new tagToClass[tag](value);
|
||||
return new tagToClass[tag](value, location);
|
||||
};
|
||||
|
||||
export abstract class SqAbstractValue {
|
||||
abstract tag: Tag;
|
||||
_value: T;
|
||||
|
||||
constructor(value: T) {
|
||||
this._value = value;
|
||||
}
|
||||
constructor(private _value: T, public location: SqValueLocation) {}
|
||||
|
||||
protected valueMethod = <IR>(rsMethod: (v: T) => IR | null | undefined) => {
|
||||
const value = rsMethod(this._value);
|
||||
if (value === undefined || value === null) {
|
||||
throw new Error("Internal casting error");
|
||||
}
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
||||
const valueMethod = <IR>(
|
||||
_this: SqAbstractValue,
|
||||
rsMethod: (v: T) => IR | null | undefined
|
||||
) => {
|
||||
const value = rsMethod(_this._value);
|
||||
if (value === undefined || value === null) {
|
||||
throw new Error("Internal casting error");
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
export class SqArrayValue extends SqAbstractValue {
|
||||
tag = Tag.Array as const;
|
||||
|
||||
get value() {
|
||||
return new SqArray(valueMethod(this, RSValue.getArray));
|
||||
return new SqArray(this.valueMethod(RSValue.getArray), this.location);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,7 +46,7 @@ export class SqArrayStringValue extends SqAbstractValue {
|
|||
tag = Tag.ArrayString as const;
|
||||
|
||||
get value() {
|
||||
return valueMethod(this, RSValue.getArrayString);
|
||||
return this.valueMethod(RSValue.getArrayString);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,7 +54,7 @@ export class SqBoolValue extends SqAbstractValue {
|
|||
tag = Tag.Bool as const;
|
||||
|
||||
get value() {
|
||||
return valueMethod(this, RSValue.getBool);
|
||||
return this.valueMethod(RSValue.getBool);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,7 +62,7 @@ export class SqCallValue extends SqAbstractValue {
|
|||
tag = Tag.Call as const;
|
||||
|
||||
get value() {
|
||||
return valueMethod(this, RSValue.getCall);
|
||||
return this.valueMethod(RSValue.getCall);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,7 +70,7 @@ export class SqDateValue extends SqAbstractValue {
|
|||
tag = Tag.Date as const;
|
||||
|
||||
get value() {
|
||||
return valueMethod(this, RSValue.getDate);
|
||||
return this.valueMethod(RSValue.getDate);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -82,7 +78,7 @@ export class SqDeclarationValue extends SqAbstractValue {
|
|||
tag = Tag.Declaration as const;
|
||||
|
||||
get value() {
|
||||
return new SqLambdaDeclaration(valueMethod(this, RSValue.getDeclaration));
|
||||
return new SqLambdaDeclaration(this.valueMethod(RSValue.getDeclaration));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -90,7 +86,7 @@ export class SqDistributionValue extends SqAbstractValue {
|
|||
tag = Tag.Distribution as const;
|
||||
|
||||
get value() {
|
||||
return wrapDistribution(valueMethod(this, RSValue.getDistribution));
|
||||
return wrapDistribution(this.valueMethod(RSValue.getDistribution));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -98,7 +94,7 @@ export class SqLambdaValue extends SqAbstractValue {
|
|||
tag = Tag.Lambda as const;
|
||||
|
||||
get value() {
|
||||
return new SqLambda(valueMethod(this, RSValue.getLambda));
|
||||
return new SqLambda(this.valueMethod(RSValue.getLambda), this.location);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,7 +102,7 @@ export class SqModuleValue extends SqAbstractValue {
|
|||
tag = Tag.Module as const;
|
||||
|
||||
get value() {
|
||||
return new SqModule(valueMethod(this, RSValue.getModule));
|
||||
return new SqModule(this.valueMethod(RSValue.getModule), this.location);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,7 +110,7 @@ export class SqNumberValue extends SqAbstractValue {
|
|||
tag = Tag.Number as const;
|
||||
|
||||
get value() {
|
||||
return valueMethod(this, RSValue.getNumber);
|
||||
return this.valueMethod(RSValue.getNumber);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -122,7 +118,7 @@ export class SqRecordValue extends SqAbstractValue {
|
|||
tag = Tag.Record as const;
|
||||
|
||||
get value() {
|
||||
return new SqRecord(valueMethod(this, RSValue.getRecord));
|
||||
return new SqRecord(this.valueMethod(RSValue.getRecord), this.location);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -130,7 +126,7 @@ export class SqStringValue extends SqAbstractValue {
|
|||
tag = Tag.String as const;
|
||||
|
||||
get value(): string {
|
||||
return valueMethod(this, RSValue.getString);
|
||||
return this.valueMethod(RSValue.getString);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -138,7 +134,7 @@ export class SqSymbolValue extends SqAbstractValue {
|
|||
tag = Tag.Symbol as const;
|
||||
|
||||
get value(): string {
|
||||
return valueMethod(this, RSValue.getSymbol);
|
||||
return this.valueMethod(RSValue.getSymbol);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -146,7 +142,7 @@ export class SqTimeDurationValue extends SqAbstractValue {
|
|||
tag = Tag.TimeDuration as const;
|
||||
|
||||
get value() {
|
||||
return valueMethod(this, RSValue.getTimeDuration);
|
||||
return this.valueMethod(RSValue.getTimeDuration);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -154,7 +150,7 @@ export class SqTypeValue extends SqAbstractValue {
|
|||
tag = Tag.Type as const;
|
||||
|
||||
get value() {
|
||||
return new SqType(valueMethod(this, RSValue.getType));
|
||||
return new SqType(this.valueMethod(RSValue.getType));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -162,7 +158,7 @@ export class SqTypeIdentifierValue extends SqAbstractValue {
|
|||
tag = Tag.TypeIdentifier as const;
|
||||
|
||||
get value() {
|
||||
return valueMethod(this, RSValue.getTypeIdentifier);
|
||||
return this.valueMethod(RSValue.getTypeIdentifier);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
24
packages/squiggle-lang/src/js/SqValueLocation.ts
Normal file
24
packages/squiggle-lang/src/js/SqValueLocation.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
import { isParenthesisNode } from "mathjs";
|
||||
import { SqProject } from "./SqProject";
|
||||
|
||||
type PathItem = string | number;
|
||||
|
||||
type SqValuePath = {
|
||||
root: "result" | "bindings";
|
||||
items: PathItem[];
|
||||
};
|
||||
|
||||
export class SqValueLocation {
|
||||
constructor(
|
||||
public project: SqProject,
|
||||
public sourceId: string,
|
||||
public path: SqValuePath
|
||||
) {}
|
||||
|
||||
extend(item: PathItem) {
|
||||
return new SqValueLocation(this.project, this.sourceId, {
|
||||
root: this.path.root,
|
||||
items: [...this.path.items, item],
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
import { environment } from "../rescript/ForTS/ForTS_ReducerProject.gen";
|
||||
import { SqProject } from "./SqProject";
|
||||
import { SqValue, SqValueTag } from "./SqValue";
|
||||
export { SqValueLocation } from "./SqValueLocation";
|
||||
export { result } from "../rescript/ForTS/ForTS_Result_tag";
|
||||
export { SqDistribution, SqDistributionTag } from "./SqDistribution";
|
||||
export { SqDistributionError } from "./SqDistributionError";
|
||||
|
|
Loading…
Reference in New Issue
Block a user