frontend/three_d_garden/config_overlays.tsx
import React from "react";
import { Config, modifyConfig } from "./config";
import { setUrlParam } from "./zoom_beacons_constants";
import { ExternalUrl } from "../external_urls";
export interface ToolTip {
timeoutId: number;
text: string;
}
export interface OverlayProps {
config: Config;
setConfig(config: Config): void;
toolTip: ToolTip;
setToolTip(tooltip: ToolTip): void;
activeFocus: string;
setActiveFocus(focus: string): void;
}
interface SectionProps {
title: string;
configKey: keyof Config;
options: Record<string, string>;
}
export const PublicOverlay = (props: OverlayProps) => {
const { config, setConfig, toolTip, setToolTip } = props;
const Section = (sectionProps: SectionProps) => {
const { title, configKey, options } = sectionProps;
return <div className={"setting-section"}>
<div className="setting-title">{title}</div>
<div className={"row"}>
{Object.entries(options).map(([preset, label]) => {
const active = label == config[configKey];
const disabled = label == "Mobile"
&& config.sizePreset == "Genesis XL";
const className = [
preset,
active ? "active" : "",
disabled ? "disabled" : "",
].join(" ");
const update = { [configKey]: label };
return <button key={preset} className={className}
onClick={() => {
clearTimeout(toolTip.timeoutId);
if (disabled) {
const text =
"Mobile beds are not recommended for Genesis XL machines";
const timeoutId = setTimeout(() =>
setToolTip({ timeoutId: 0, text: "" }), 3000);
setToolTip(({ timeoutId: timeoutId as unknown as number, text }));
return;
} else {
setToolTip({ timeoutId: 0, text: "" });
}
setConfig(modifyConfig(config, update));
}}>
{label}
</button>;
})}
</div>
</div>;
};
return <div className={"overlay"}>
{config.settingsBar && !props.activeFocus &&
<div className={"settings-bar"}>
<Section
title={"FarmBot"}
configKey={"sizePreset"}
options={{
"genesis": "Genesis",
"genesis-xl": "Genesis XL",
}} />
<Section
title={"Season"}
configKey={"plants"}
options={{
"winter": "Winter",
"spring": "Spring",
"summer": "Summer",
"fall": "Fall",
}} />
<Section
title={"Bed Type"}
configKey={"bedType"}
options={{
"standard": "Standard",
"mobile": "Mobile",
}} />
<Section
title={"Environment"}
configKey={"scene"}
options={{
"outdoor": "Outdoor",
"lab": "Lab",
}} />
</div>}
{config.promoInfo && !props.activeFocus &&
<PromoInfo isGenesis={config.sizePreset == "Genesis"} />}
</div>;
};
interface PromoInfoProps {
isGenesis: boolean;
}
const PromoInfo = (props: PromoInfoProps) => {
const { isGenesis } = props;
return <div className="promo-info">
<h2 className="title">Explore our models</h2>
{isGenesis
? <div className="description">
<p className="short">
FarmBot Genesis is our flagship kit for prosumers and enthusiasts.
</p>
<p className="full">
FarmBot Genesis is our flagship kit for prosumers and enthusiasts
featuring our most advanced technology, features, and options.
Coming 90% pre-assembled in the box, Genesis can be installed on
an existing raised bed in an afternoon. It is suitable for fixed
or mobile raised beds in classrooms, research labs, and backyards.
</p>
</div>
: <div className="description">
<p className="short">
Covering 400% the area, Genesis XL can grow enough veggies for a
family of four.
</p>
<p className="full">
Covering 400% the area, FarmBot Genesis XL can grow enough veggies
for a family of four, provides ample room for student competitions,
and can take research experiments to new scale. Suitable for fixed
installations at home, farm to fork restaurants, schools and
universities, and commercial research facilities.
</p>
</div>}
<a className="buy-button"
target="_top"
href={isGenesis
? ExternalUrl.Store.genesisKit
: ExternalUrl.Store.genesisXlKit}>
<p>Order Genesis</p>
<p className="genesis-xl"
style={{ display: isGenesis ? "none" : "inline-block" }}>
XL
</p>
</a>
</div>;
};
interface ConfigRowProps {
configKey: keyof Config;
children: React.ReactNode;
}
const ConfigRow = (props: ConfigRowProps) => {
const { configKey } = props;
return <div className={"config-row"}>
<span className={"config-key"}>{configKey}</span>
{props.children}
</div>;
};
export const maybeAddParam =
(paramAdd: boolean, configKey: string, value: string) =>
paramAdd && setUrlParam(configKey, value);
interface SliderProps extends OverlayProps {
configKey: keyof Config;
min: number;
max: number;
paramAdd: boolean;
}
const Slider = (props: SliderProps) => {
const { config, setConfig, configKey, min, max } = props;
const change = (e: React.ChangeEvent<HTMLInputElement>) => {
const newValue = parseInt(e.target.value);
if (isNaN(newValue)) { return; }
const update = { [configKey]: newValue };
setConfig(modifyConfig(config, update));
maybeAddParam(props.paramAdd, configKey, "" + newValue);
};
const value = config[configKey] as number;
return <ConfigRow configKey={configKey}>
<input type={"number"} value={value} onChange={change} />
<input
type={"range"}
min={min}
max={max}
value={value}
onChange={change}
/>
</ConfigRow>;
};
interface ToggleProps extends OverlayProps {
configKey: keyof Config;
paramAdd: boolean;
}
const Toggle = (props: ToggleProps) => {
const { config, setConfig, configKey } = props;
return <ConfigRow configKey={configKey}>
<input
type={"checkbox"}
checked={!!config[configKey]}
onChange={e => {
const newValue = e.target.checked;
const update = { [configKey]: newValue };
setConfig(modifyConfig(config, update));
maybeAddParam(props.paramAdd, configKey, "" + newValue);
}}
/>
</ConfigRow>;
};
interface RadioProps extends OverlayProps {
configKey: keyof Config;
options: string[];
paramAdd: boolean;
}
const Radio = (props: RadioProps) => {
const { config, setConfig, configKey, options } = props;
const change = (e: React.ChangeEvent<HTMLInputElement>) => {
const newValue = e.target.value;
const update = { [configKey]: newValue };
setConfig(modifyConfig(config, update));
maybeAddParam(props.paramAdd, configKey, "" + newValue);
};
return <ConfigRow configKey={configKey}>
<div className={"options"}>
{options.map(value =>
<div key={value}>
<input key={value}
type={"radio"}
name={configKey}
value={value}
checked={config[configKey] == value}
onChange={change}
/>
<label>{value}</label>
</div>)}
</div>
</ConfigRow>;
};
export const PrivateOverlay = (props: OverlayProps) => {
const bedMin = props.config.bedWallThickness * 2;
const { config, setConfig } = props;
const [paramAdd, setParamAdd] = React.useState(false);
const common = { ...props, paramAdd };
return <div className={"all-configs"}>
<details>
<summary>
{"Configs"}
<p className={"close"}
onClick={() => setConfig(modifyConfig(config, { config: false }))}>
X
</p>
</summary>
<div className={"spacer"} />
<div className={"config-row"}>
<span className={"config-key"}>{"auto-add to URL"}</span>
<input
type={"checkbox"}
checked={paramAdd}
onChange={e => setParamAdd(e.target.checked)} />
</div>
<Toggle {...common} configKey={"promoInfo"} />
<Toggle {...common} configKey={"settingsBar"} />
<Toggle {...common} configKey={"zoomBeacons"} />
<label>{"Presets"}</label>
<Radio {...common} configKey={"sizePreset"}
options={["Jr", "Genesis", "Genesis XL"]} />
<Radio {...common} configKey={"bedType"}
options={["Standard", "Mobile"]} />
<Radio {...common} configKey={"otherPreset"}
options={["Initial", "Minimal", "Maximal", "Reset all"]} />
<label>{"Bot Position"}</label>
<Slider {...common} configKey={"x"} min={0} max={props.config.botSizeX} />
<Slider {...common} configKey={"y"} min={0} max={props.config.botSizeY} />
<Slider {...common} configKey={"z"} min={0} max={props.config.botSizeZ} />
<Radio {...common} configKey={"tool"} options={["rotaryTool", "None"]} />
<Toggle {...common} configKey={"trail"} />
<Toggle {...common} configKey={"laser"} />
<label>{"Bot Dimensions"}</label>
<Slider {...common} configKey={"botSizeX"} min={0} max={6000} />
<Slider {...common} configKey={"botSizeY"} min={0} max={4000} />
<Slider {...common} configKey={"botSizeZ"} min={0} max={1000} />
<Toggle {...common} configKey={"bounds"} />
<Toggle {...common} configKey={"xyDimensions"} />
<Toggle {...common} configKey={"zDimension"} />
<Toggle {...common} configKey={"axes"} />
<Slider {...common} configKey={"beamLength"} min={0} max={4000} />
<Slider {...common} configKey={"columnLength"} min={0} max={1000} />
<Slider {...common} configKey={"zAxisLength"} min={0} max={2000} />
<Slider {...common} configKey={"bedXOffset"} min={-500} max={500} />
<Slider {...common} configKey={"bedYOffset"} min={-1500} max={1500} />
<Slider {...common} configKey={"zGantryOffset"} min={0} max={500} />
<Toggle {...common} configKey={"tracks"} />
<Toggle {...common} configKey={"cableCarriers"} />
<Toggle {...common} configKey={"bot"} />
<label>{"Bed Properties"}</label>
<Slider {...common} configKey={"bedWallThickness"} min={0} max={200} />
<Slider {...common} configKey={"bedHeight"} min={0} max={1000} />
<Slider {...common} configKey={"ccSupportSize"} min={0} max={200} />
<Slider {...common} configKey={"bedWidthOuter"} min={bedMin} max={3100} />
<Slider {...common} configKey={"bedLengthOuter"} min={bedMin} max={6100} />
<Slider {...common} configKey={"bedZOffset"} min={0} max={1000} />
<Slider {...common} configKey={"legSize"} min={0} max={200} />
<Toggle {...common} configKey={"legsFlush"} />
<Slider {...common} configKey={"extraLegsX"} min={0} max={10} />
<Slider {...common} configKey={"extraLegsY"} min={0} max={10} />
<Slider {...common} configKey={"bedBrightness"} min={1} max={12} />
<Slider {...common} configKey={"soilBrightness"} min={1} max={12} />
<Slider {...common} configKey={"soilHeight"} min={0} max={1000} />
<Radio {...common} configKey={"plants"}
options={["Winter", "Spring", "Summer", "Fall", "Random", "None"]} />
<label>{"Camera"}</label>
<Toggle {...common} configKey={"perspective"} />
<Toggle {...common} configKey={"zoom"} />
<Toggle {...common} configKey={"pan"} />
<Toggle {...common} configKey={"lowDetail"} />
<label>{"Environment"}</label>
<Radio {...common} configKey={"scene"}
options={["Outdoor", "Lab"]} />
<Toggle {...common} configKey={"ground"} />
<Toggle {...common} configKey={"grid"} />
<Toggle {...common} configKey={"utilitiesPost"} />
<Toggle {...common} configKey={"packaging"} />
<Toggle {...common} configKey={"labels"} />
<Toggle {...common} configKey={"labelsOnHover"} />
<Toggle {...common} configKey={"clouds"} />
<Toggle {...common} configKey={"solar"} />
<Toggle {...common} configKey={"lab"} />
<Toggle {...common} configKey={"people"} />
<Slider {...common} configKey={"sunInclination"} min={0} max={180} />
<Slider {...common} configKey={"sunAzimuth"} min={0} max={360} />
<label>{"Dev"}</label>
<Toggle {...common} configKey={"threeAxes"} />
<Toggle {...common} configKey={"stats"} />
<Toggle {...common} configKey={"viewCube"} />
<Toggle {...common} configKey={"eventDebug"} />
<Toggle {...common} configKey={"cableDebug"} />
<Toggle {...common} configKey={"zoomBeaconDebug"} />
<Toggle {...common} configKey={"animate"} />
<Toggle {...common} configKey={"config"} />
</details>
</div>;
};