Refactored Alert, added more to Playground forms

This commit is contained in:
Ozzie Gooen 2022-06-01 13:13:16 -07:00
parent 6b7b3b049a
commit 43643124e0
9 changed files with 279 additions and 211 deletions

View File

@ -0,0 +1,72 @@
import * as React from "react";
import { XCircleIcon, InformationCircleIcon, CheckCircleIcon } from "@heroicons/react/solid";
export const Alert: React.FC<{
heading: string;
backgroundColor: string;
headingColor: string;
bodyColor: string;
icon: React.ReactNode;
children: React.ReactNode;
}> = ({
heading = "Error",
backgroundColor,
headingColor,
bodyColor,
icon,
children,
}) => {
return (
<div className={`rounded-md p-4 ${backgroundColor}`}>
<div className="flex">
<div className="flex-shrink-0">{icon}</div>
<div className="ml-3">
<h3 className={`text-sm font-medium ${headingColor}`}>{heading}</h3>
<div className={`mt-2 text-sm ${bodyColor}`}>{children}</div>
</div>
</div>
</div>
);
};
export const ErrorAlert: React.FC<{
heading: string;
children: React.ReactNode;
}> = ({ heading = "Error", children }) => (
<Alert
heading={heading}
children={children}
backgroundColor="bg-red-100"
headingColor="text-red-800"
bodyColor="text-red-700"
icon={<XCircleIcon className="h-5 w-5 text-red-400" aria-hidden="true" />}
/>
);
export const MessageAlert: React.FC<{
heading: string;
children: React.ReactNode;
}> = ({ heading = "Error", children }) => (
<Alert
heading={heading}
children={children}
backgroundColor="bg-slate-100"
headingColor="text-slate-700"
bodyColor="text-slate-700"
icon={<InformationCircleIcon className="h-5 w-5 text-slate-400" aria-hidden="true" />}
/>
);
export const SuccessAlert: React.FC<{
heading: string;
children: React.ReactNode;
}> = ({ heading = "Error", children }) => (
<Alert
heading={heading}
children={children}
backgroundColor="bg-green-50"
headingColor="text-green-800"
bodyColor="text-green-700"
icon={<CheckCircleIcon className="h-5 w-5 text-green-400" aria-hidden="true" />}
/>
);

View File

@ -8,7 +8,7 @@ import {
} from "@quri/squiggle-lang"; } from "@quri/squiggle-lang";
import { Vega, VisualizationSpec } from "react-vega"; import { Vega, VisualizationSpec } from "react-vega";
import * as chartSpecification from "../vega-specs/spec-distributions.json"; import * as chartSpecification from "../vega-specs/spec-distributions.json";
import { ErrorBox } from "./ErrorBox"; import { ErrorAlert } from "./Alert";
import { useSize } from "react-use"; import { useSize } from "react-use";
import { import {
linearXScale, linearXScale,
@ -90,9 +90,9 @@ export const DistributionChart: React.FC<DistributionChartProps> = ({
); );
} else { } else {
var result = ( var result = (
<ErrorBox heading="Distribution Error"> <ErrorAlert heading="Distribution Error">
{distributionErrorToString(shape.value)} {distributionErrorToString(shape.value)}
</ErrorBox> </ErrorAlert>
); );
} }
@ -161,9 +161,9 @@ const SummaryTable: React.FC<SummaryTableProps> = ({
return <NumberShower number={x.value} />; return <NumberShower number={x.value} />;
} else { } else {
return ( return (
<ErrorBox heading="Distribution Error"> <ErrorAlert heading="Distribution Error">
{distributionErrorToString(x.value)} {distributionErrorToString(x.value)}
</ErrorBox> </ErrorAlert>
); );
} }
}; };

View File

@ -1,41 +0,0 @@
import * as React from "react";
import { XCircleIcon } from "@heroicons/react/solid";
import { InformationCircleIcon } from "@heroicons/react/solid";
export const ErrorBox: React.FC<{
heading: string;
children: React.ReactNode;
}> = ({ heading = "Error", children }) => {
return (
<div className="rounded-md bg-red-100 p-4">
<div className="flex">
<div className="flex-shrink-0">
<XCircleIcon className="h-5 w-5 text-red-400" aria-hidden="true" />
</div>
<div className="ml-3">
<h3 className="text-sm font-medium text-red-800">{heading}</h3>
<div className="mt-2 text-sm text-red-700">{children}</div>
</div>
</div>
</div>
);
};
export const MessageBox: React.FC<{
heading: string;
children: React.ReactNode;
}> = ({ heading = "Note", children }) => {
return (
<div className="rounded-md bg-slate-100 p-4">
<div className="flex">
<div className="flex-shrink-0">
<InformationCircleIcon className="h-5 w-5 text-slate-400" aria-hidden="true" />
</div>
<div className="ml-3">
<h3 className="text-sm font-medium text-slate-700">{heading}</h3>
<div className="mt-2 text-sm text-slate-700">{children}</div>
</div>
</div>
</div>
);
};

View File

@ -2,7 +2,7 @@ import * as React from "react";
import { lambdaValue, environment, runForeign } from "@quri/squiggle-lang"; import { lambdaValue, environment, runForeign } from "@quri/squiggle-lang";
import { FunctionChart1Dist } from "./FunctionChart1Dist"; import { FunctionChart1Dist } from "./FunctionChart1Dist";
import { FunctionChart1Number } from "./FunctionChart1Number"; import { FunctionChart1Number } from "./FunctionChart1Number";
import { ErrorBox, MessageBox } from "./ErrorBox"; import { ErrorAlert, MessageAlert } from "./Alert";
export type FunctionChartSettings = { export type FunctionChartSettings = {
start: number; start: number;
@ -25,58 +25,52 @@ export const FunctionChart: React.FC<FunctionChartProps> = ({
}: FunctionChartProps) => { }: FunctionChartProps) => {
if (fn.parameters.length > 1) { if (fn.parameters.length > 1) {
return ( return (
<MessageBox heading="Function Display Not Supported"> <MessageAlert heading="Function Display Not Supported">
Only functions with one parameter are displayed. Only functions with one parameter are displayed.
</MessageBox> </MessageAlert>
); );
} else { }
let result1 = runForeign(fn, [chartSettings.start], environment); const result1 = runForeign(fn, [chartSettings.start], environment);
let result2 = runForeign(fn, [chartSettings.stop], environment); const result2 = runForeign(fn, [chartSettings.stop], environment);
let getValidResult = () => { const getValidResult = () => {
if (result1.tag === "Ok") { if (result1.tag === "Ok") {
return result1; return result1;
} else if (result2.tag === "Ok") { } else if (result2.tag === "Ok") {
return result2; return result2;
} else { } else {
return result1; return result1;
} }
}; };
let validResult = getValidResult(); const validResult = getValidResult();
let resultType = validResult.tag === "Ok" ? validResult.value.tag : "Error"; const resultType =
validResult.tag === "Ok" ? validResult.value.tag : ("Error" as const);
let component = () => { switch (resultType) {
switch (resultType) { case "distribution":
case "distribution": return (
return ( <FunctionChart1Dist
<FunctionChart1Dist fn={fn}
fn={fn} chartSettings={chartSettings}
chartSettings={chartSettings} environment={environment}
environment={environment} height={height}
height={height} />
/> );
); case "number":
case "number": return (
return ( <FunctionChart1Number
<FunctionChart1Number fn={fn}
fn={fn} chartSettings={chartSettings}
chartSettings={chartSettings} environment={environment}
environment={environment} height={height}
height={height} />
/> );
); case "Error":
case "Error": return <ErrorAlert heading="Error">The function failed to be run</ErrorAlert>;
return ( default:
<ErrorBox heading="Error">The function failed to be run</ErrorBox> return (
); <MessageAlert heading="Function Display Not Supported">
default: There is no function visualization for this type of output
return ( </MessageAlert>
<MessageBox heading="Function Display Not Supported"> );
There is no function visualization for this type of output
</MessageBox>
);
}
};
return component();
} }
}; };

View File

@ -15,7 +15,7 @@ import { createClassFromSpec } from "react-vega";
import * as percentilesSpec from "../vega-specs/spec-percentiles.json"; import * as percentilesSpec from "../vega-specs/spec-percentiles.json";
import { DistributionChart } from "./DistributionChart"; import { DistributionChart } from "./DistributionChart";
import { NumberShower } from "./NumberShower"; import { NumberShower } from "./NumberShower";
import { ErrorBox } from "./ErrorBox"; import { ErrorAlert } from "./Alert";
let SquigglePercentilesChart = createClassFromSpec({ let SquigglePercentilesChart = createClassFromSpec({
spec: percentilesSpec as Spec, spec: percentilesSpec as Spec,
@ -197,7 +197,7 @@ export const FunctionChart1Dist: React.FC<FunctionChart1DistProps> = ({
{showChart} {showChart}
{_.entries(getPercentilesMemoized.errors).map( {_.entries(getPercentilesMemoized.errors).map(
([errorName, errorPoints]) => ( ([errorName, errorPoints]) => (
<ErrorBox key={errorName} heading={errorName}> <ErrorAlert key={errorName} heading={errorName}>
Values:{" "} Values:{" "}
{errorPoints {errorPoints
.map((r, i) => <NumberShower key={i} number={r.x} />) .map((r, i) => <NumberShower key={i} number={r.x} />)
@ -206,7 +206,7 @@ export const FunctionChart1Dist: React.FC<FunctionChart1DistProps> = ({
{a}, {b} {a}, {b}
</> </>
))} ))}
</ErrorBox> </ErrorAlert>
) )
)} )}
</> </>

View File

@ -10,7 +10,7 @@ import {
} from "@quri/squiggle-lang"; } from "@quri/squiggle-lang";
import { createClassFromSpec } from "react-vega"; import { createClassFromSpec } from "react-vega";
import * as lineChartSpec from "../vega-specs/spec-line-chart.json"; import * as lineChartSpec from "../vega-specs/spec-line-chart.json";
import { ErrorBox } from "./ErrorBox"; import { ErrorAlert } from "./Alert";
let SquiggleLineChart = createClassFromSpec({ let SquiggleLineChart = createClassFromSpec({
spec: lineChartSpec as Spec, spec: lineChartSpec as Spec,
@ -110,9 +110,9 @@ export const FunctionChart1Number: React.FC<FunctionChart1NumberProps> = ({
actions={false} actions={false}
/> />
{getFunctionImageMemoized.errors.map(({ x, value }) => ( {getFunctionImageMemoized.errors.map(({ x, value }) => (
<ErrorBox key={x} heading={value}> <ErrorAlert key={x} heading={value}>
Error at point ${x} Error at point ${x}
</ErrorBox> </ErrorAlert>
))} ))}
</> </>
); );

View File

@ -14,7 +14,7 @@ import {
} from "@quri/squiggle-lang"; } from "@quri/squiggle-lang";
import { NumberShower } from "./NumberShower"; import { NumberShower } from "./NumberShower";
import { DistributionChart } from "./DistributionChart"; import { DistributionChart } from "./DistributionChart";
import { ErrorBox } from "./ErrorBox"; import { ErrorAlert } from "./Alert";
import { FunctionChart, FunctionChartSettings } from "./FunctionChart"; import { FunctionChart, FunctionChartSettings } from "./FunctionChart";
function getRange<a>(x: declaration<a>) { function getRange<a>(x: declaration<a>) {
@ -320,9 +320,9 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = ({
); );
} else { } else {
internal = ( internal = (
<ErrorBox heading={"Parse Error"}> <ErrorAlert heading={"Parse Error"}>
{errorValueToString(expressionResult.value)} {errorValueToString(expressionResult.value)}
</ErrorBox> </ErrorAlert>
); );
} }
return internal; return internal;

View File

@ -14,7 +14,7 @@ import {
defaultImports, defaultImports,
defaultBindings, defaultBindings,
} from "@quri/squiggle-lang"; } from "@quri/squiggle-lang";
import { ErrorBox } from "./ErrorBox"; import { ErrorAlert } from "./Alert";
export interface SquiggleEditorProps { export interface SquiggleEditorProps {
/** The input string for squiggle */ /** The input string for squiggle */
@ -181,7 +181,7 @@ export let SquigglePartial: React.FC<SquigglePartialProps> = ({
height={20} height={20}
/> />
</div> </div>
{error !== null ? <ErrorBox heading="Error">{error}</ErrorBox> : <></>} {error !== null ? <ErrorAlert heading="Error">{error}</ErrorAlert> : <></>}
</div> </div>
); );
}; };

View File

@ -14,25 +14,7 @@ import { CogIcon } from "@heroicons/react/solid";
import { ChartSquareBarIcon } from "@heroicons/react/solid"; import { ChartSquareBarIcon } from "@heroicons/react/solid";
import { CurrencyDollarIcon } from "@heroicons/react/solid"; import { CurrencyDollarIcon } from "@heroicons/react/solid";
import { Fragment } from "react"; import { Fragment } from "react";
import { ErrorBox } from "./ErrorBox"; import { ErrorAlert, SuccessAlert } from "./Alert";
interface ShowBoxProps {
height: number;
children: React.ReactNode;
}
const ShowBox: React.FC<ShowBoxProps> = ({ height, children }) => (
<div className="border border-gray-500">{children}</div>
);
interface TitleProps {
readonly maxHeight: number;
children: React.ReactNode;
}
const Display: React.FC<TitleProps> = ({ maxHeight, children }) => (
<div style={{ maxHeight: maxHeight + "px" }}>{children}</div>
);
interface PlaygroundProps { interface PlaygroundProps {
/** The initial squiggle string to put in the playground */ /** The initial squiggle string to put in the playground */
@ -79,6 +61,27 @@ const schema = yup
showControls: yup.boolean(), showControls: yup.boolean(),
showSummary: yup.boolean(), showSummary: yup.boolean(),
showSettingsPage: yup.boolean().default(false), 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),
}) })
.required(); .required();
@ -88,7 +91,7 @@ type InputProps = {
}; };
const InputItem: React.FC<InputProps> = ({ label, children }) => ( const InputItem: React.FC<InputProps> = ({ label, children }) => (
<div className="col-span-3"> <div className="col-span-4">
<label className="block text-sm font-medium text-gray-600">{label}</label> <label className="block text-sm font-medium text-gray-600">{label}</label>
<div className="mt-1">{children}</div> <div className="mt-1">{children}</div>
</div> </div>
@ -96,15 +99,8 @@ const InputItem: React.FC<InputProps> = ({ label, children }) => (
let numberStyle = let numberStyle =
"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"; "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";
let checkboxStyle =
"focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded";
const tabs = [ function classNames(...classes: string[]) {
{ name: "Editor", href: "#", current: true },
{ name: "Settings", href: "#", current: false },
];
function classNames(...classes) {
return classes.filter(Boolean).join(" "); return classes.filter(Boolean).join(" ");
} }
@ -119,14 +115,6 @@ let SquigglePlayground: FC<PlaygroundProps> = ({
let [importString, setImportString] = useState("{}"); let [importString, setImportString] = useState("{}");
let [imports, setImports] = useState({}); let [imports, setImports] = useState({});
let [importsAreValid, setImportsAreValid] = useState(true); let [importsAreValid, setImportsAreValid] = useState(true);
let [diagramStart, setDiagramStart] = useState(0);
let [diagramStop, setDiagramStop] = useState(10);
let [diagramCount, setDiagramCount] = useState(20);
let chartSettings = {
start: diagramStart,
stop: diagramStop,
count: diagramCount,
};
const { register, control } = useForm({ const { register, control } = useForm({
resolver: yupResolver(schema), resolver: yupResolver(schema),
defaultValues: { defaultValues: {
@ -138,11 +126,19 @@ let SquigglePlayground: FC<PlaygroundProps> = ({
showSummary: showSummary, showSummary: showSummary,
leftSizePercent: 50, leftSizePercent: 50,
showSettingsPage: false, showSettingsPage: false,
diagramStart: 0,
diagramStop: 10,
diagramCount: 20,
}, },
}); });
const vars = useWatch({ const vars = useWatch({
control, control,
}); });
let chartSettings = {
start: Number(vars.diagramStart),
stop: Number(vars.diagramStop),
count: Number(vars.diagramCount),
};
let env: environment = { let env: environment = {
sampleCount: Number(vars.sampleCount), sampleCount: Number(vars.sampleCount),
xyPointLength: Number(vars.xyPointLength), xyPointLength: Number(vars.xyPointLength),
@ -157,15 +153,16 @@ let SquigglePlayground: FC<PlaygroundProps> = ({
} }
}; };
// className="text-gray-400 group-hover:text-gray-500 -ml-0.5 mr-2 h-4 w-4" let tab = (key: string, iconName: string) => {
let tab = (key, iconName) => { let iconStyle = (isSelected: boolean) =>
let iconStyle = (isSelected) =>
classNames( classNames(
"-ml-0.5 mr-2 h-4 w-4 ", "-ml-0.5 mr-2 h-4 w-4 ",
isSelected ? "text-slate-500" : "text-gray-400 group-hover:text-gray-900" isSelected
? "text-slate-500"
: "text-gray-400 group-hover:text-gray-900"
); );
let icon = (selected) => let icon = (selected: boolean) =>
({ ({
code: <CodeIcon className={iconStyle(selected)} />, code: <CodeIcon className={iconStyle(selected)} />,
cog: <CogIcon className={iconStyle(selected)} />, cog: <CogIcon className={iconStyle(selected)} />,
@ -176,10 +173,7 @@ let SquigglePlayground: FC<PlaygroundProps> = ({
return ( return (
<Tab key={key} as={Fragment}> <Tab key={key} as={Fragment}>
{({ selected }) => ( {({ selected }) => (
<button <button className="flex rounded-md focus:outline-none focus-visible:ring-offset-gray-100 ">
className=
"flex rounded-md focus:outline-none focus-visible:ring-offset-gray-100 "
>
<span <span
className={classNames( className={classNames(
"p-1 pl-2.5 pr-3.5 rounded-md flex items-center text-sm font-medium", "p-1 pl-2.5 pr-3.5 rounded-md flex items-center text-sm font-medium",
@ -190,12 +184,11 @@ let SquigglePlayground: FC<PlaygroundProps> = ({
> >
{icon(selected)} {icon(selected)}
<span <span
className={classNames( className={
"",
selected selected
? "text-gray-900" ? "text-gray-900"
: "text-gray-600 group-hover:text-gray-900" : "text-gray-600 group-hover:text-gray-900"
)} }
> >
{key} {key}
</span> </span>
@ -233,7 +226,7 @@ let SquigglePlayground: FC<PlaygroundProps> = ({
</Tab.Panel> </Tab.Panel>
<Tab.Panel> <Tab.Panel>
<form className="space-y-6 p-3"> <div className="space-y-6 p-3">
<InputItem label="Sample Count"> <InputItem label="Sample Count">
<> <>
<input <input
@ -243,7 +236,8 @@ let SquigglePlayground: FC<PlaygroundProps> = ({
/> />
<p className="mt-2 text-sm text-gray-500"> <p className="mt-2 text-sm text-gray-500">
How many samples to use for Monte Carlo simulations. How many samples to use for Monte Carlo simulations.
This can be overridden by specific programs. This can occasionally be overridden by specific Squiggle
programs.
</p> </p>
</> </>
</InputItem> </InputItem>
@ -260,42 +254,43 @@ let SquigglePlayground: FC<PlaygroundProps> = ({
</p> </p>
</> </>
</InputItem> </InputItem>
</form> </div>
</Tab.Panel> </Tab.Panel>
<Tab.Panel> <Tab.Panel>
<form className="space-y-6 p-3"> <div className="space-y-6 divide-y divide-gray-200">
<InputItem label="Chart Height (in Pixels)"> <div className="space-y-2">
<input <h3 className="text-lg leading-6 font-medium text-gray-900 pb-2">
type="number" General Display Settings
{...register("chartHeight")} </h3>
className={numberStyle} <InputItem label="Chart Height (in pixels)">
/> <input
</InputItem> type="number"
<InputItem label="Editor Width"> {...register("chartHeight")}
<input className={numberStyle}
type="range" />
min="1" </InputItem>
max="100" <div className="relative flex items-start pt-3">
className="slider" <div className="flex items-center h-5">
{...register("leftSizePercent")} <input
/> type="checkbox"
</InputItem> {...register("showTypes")}
<fieldset className="space-y-2"> className="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
<div className="relative flex items-start"> />
<div className="flex items-center h-5">
<input
type="checkbox"
{...register("showTypes")}
className="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
/>
</div>
<div className="ml-3 text-sm">
<label className="font-medium text-gray-700">
Type Names
</label>
</div>
</div> </div>
<div className="ml-3 text-sm">
<label className="font-medium text-gray-700">
Show information about displayed types.
</label>
</div>
</div>
</div>
<div className="space-y-2 pt-8">
<h3 className="text-lg leading-6 font-medium text-gray-900 pb-2">
Distribution Display Settings
</h3>
<div className="relative flex items-start"> <div className="relative flex items-start">
<div className="flex items-center h-5"> <div className="flex items-center h-5">
<input <input
@ -306,7 +301,7 @@ let SquigglePlayground: FC<PlaygroundProps> = ({
</div> </div>
<div className="ml-3 text-sm"> <div className="ml-3 text-sm">
<label className="font-medium text-gray-700"> <label className="font-medium text-gray-700">
X-Y Coordinate Scales Show toggles to adjust scale of x and y axes
</label> </label>
</div> </div>
</div> </div>
@ -320,35 +315,83 @@ let SquigglePlayground: FC<PlaygroundProps> = ({
</div> </div>
<div className="ml-3 text-sm"> <div className="ml-3 text-sm">
<label className="font-medium text-gray-700"> <label className="font-medium text-gray-700">
Summary Statistics Show summary statistics
</label> </label>
</div> </div>
</div> </div>
</fieldset> </div>
</form>
<div className="space-y-2 pt-8">
<h3 className="text-lg leading-6 font-medium text-gray-900 pb-2">
Function Display Settings
</h3>
<p className="mt-2 text-sm text-gray-500">
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.
</p>
<div className="pt-4 grid grid-cols-1 gap-y-4 gap-x-4">
<InputItem label="Min X Value">
<input
type="number"
{...register("diagramStart")}
className={numberStyle}
/>
</InputItem>
<InputItem label="Max X Value">
<input
type="number"
{...register("diagramStop")}
className={numberStyle}
/>
</InputItem>
<InputItem label="Points between X min and X max to sample">
<input
type="number"
{...register("diagramCount")}
className={numberStyle}
/>
</InputItem>
</div>
</div>
</div>
</Tab.Panel> </Tab.Panel>
<Tab.Panel> <Tab.Panel>
<InputItem label="Json Editor for imports"> <h3 className="text-lg leading-6 font-medium text-gray-900">
<> Import Variables from JSON
<JsonEditor </h3>
value={importString} <p className="mt-2 text-sm text-gray-500">
onChange={getChangeJson} You can import variables from JSON into your Squiggle code.
oneLine={false} Variables are accessed with dollar signs. For example,
showGutter={true} "timeNow" would be accessed as "$timeNow".
height={100} </p>
/> <>
<div className="border border-slate-200 mt-6 mb-2">
<JsonEditor
value={importString}
onChange={getChangeJson}
oneLine={false}
showGutter={true}
height={150}
/>
</div>
<div className="p-1 pt-2">
{importsAreValid ? ( {importsAreValid ? (
"Valid" <SuccessAlert heading="Valid Json">
<></>
</SuccessAlert>
) : ( ) : (
<div className="p-2"> <ErrorAlert heading="Invalid JSON">
<ErrorBox heading="Invalid JSON"> You must use valid json in this editor.
You must use valid json in this editor. </ErrorAlert>
</ErrorBox>
</div>
)} )}
</> </div>
</InputItem> </>
</Tab.Panel> </Tab.Panel>
</Tab.Panels> </Tab.Panels>
</div> </div>