From 741ace3bf1ffdacf5a5a33a153bf28bc85ec8de4 Mon Sep 17 00:00:00 2001 From: Br2850 Date: Mon, 30 Sep 2024 15:08:16 -0400 Subject: [PATCH 01/39] add spinner variant --- .../src/components/Loading/LoadingSpinner.tsx | 29 +++++++++++++++++++ .../SchemaIO/components/LoadingView.tsx | 9 ++++-- 2 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 app/packages/components/src/components/Loading/LoadingSpinner.tsx diff --git a/app/packages/components/src/components/Loading/LoadingSpinner.tsx b/app/packages/components/src/components/Loading/LoadingSpinner.tsx new file mode 100644 index 0000000000..b14e99daf3 --- /dev/null +++ b/app/packages/components/src/components/Loading/LoadingSpinner.tsx @@ -0,0 +1,29 @@ +import React from "react"; + +import { CircularProgress } from "@mui/material"; + +const LoadingSpinner = ({ + color = "base", + size = "medium", +}: { + color?: string; + size?: string; +}) => { + const COLORS: { [key: string]: string } = { + base: "#FFC59B", + primary: "primary", + secondary: "secondary", + error: "error", + warning: "warning", + info: "info", + success: "success", + }; + const SIZES: { [key: string]: string } = { + small: "1rem", + medium: "2rem", + large: "3rem", + }; + return ; +}; + +export default LoadingSpinner; diff --git a/app/packages/core/src/plugins/SchemaIO/components/LoadingView.tsx b/app/packages/core/src/plugins/SchemaIO/components/LoadingView.tsx index 719c6f3ec6..bf5bf4090e 100644 --- a/app/packages/core/src/plugins/SchemaIO/components/LoadingView.tsx +++ b/app/packages/core/src/plugins/SchemaIO/components/LoadingView.tsx @@ -1,4 +1,5 @@ import LoadingDots from "@fiftyone/components/src/components/Loading/LoadingDots"; +import LoadingSpinner from "@fiftyone/components/src/components/Loading/LoadingSpinner"; import { Box } from "@mui/material"; import React from "react"; import { getComponentProps } from "../utils"; @@ -6,11 +7,15 @@ import { getComponentProps } from "../utils"; export default function LoadingView(props) { const { schema } = props; const { view = {} } = schema; - const { label = "Loading" } = view; + const { text = "Loading", variant, color, size } = view; return ( - + {variant === "spinner" ? ( + + ) : ( + + )} ); } From 5d456151e94a6bf5d50432339b61a17dcc42260d Mon Sep 17 00:00:00 2001 From: Lanny W Date: Thu, 3 Oct 2024 13:38:13 +0200 Subject: [PATCH 02/39] allow to only show icon in dropdownview --- .../core/src/plugins/SchemaIO/components/DropdownView.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/packages/core/src/plugins/SchemaIO/components/DropdownView.tsx b/app/packages/core/src/plugins/SchemaIO/components/DropdownView.tsx index 74c3bb09e5..b51aa27776 100644 --- a/app/packages/core/src/plugins/SchemaIO/components/DropdownView.tsx +++ b/app/packages/core/src/plugins/SchemaIO/components/DropdownView.tsx @@ -1,3 +1,4 @@ +import SettingsIcon from "@mui/icons-material/Settings"; import { MenuItem, Select } from "@mui/material"; import React, { useState } from "react"; import { useKey } from "../hooks"; @@ -24,6 +25,7 @@ export default function DropdownView(props: ViewPropsType) { description, color, variant, + useIconOnly = false, } = view; const [key, setUserChanged] = useKey(path, schema, data, true); const [selected, setSelected] = useState(false); @@ -82,6 +84,9 @@ export default function DropdownView(props: ViewPropsType) { displayEmpty title={compact ? description : undefined} renderValue={(value) => { + if (useIconOnly) { + return ; + } const unselected = value?.length === 0; setSelected(!unselected); if (unselected) { From 8a36c84f0856244743ec656ba27ac4b1936fbc6c Mon Sep 17 00:00:00 2001 From: Lanny W Date: Mon, 7 Oct 2024 14:24:51 -0500 Subject: [PATCH 03/39] update type and minor tweak --- .../core/src/plugins/SchemaIO/components/DropdownView.tsx | 8 ++++++-- fiftyone/operators/types.py | 3 +++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/packages/core/src/plugins/SchemaIO/components/DropdownView.tsx b/app/packages/core/src/plugins/SchemaIO/components/DropdownView.tsx index b51aa27776..40989ef2cb 100644 --- a/app/packages/core/src/plugins/SchemaIO/components/DropdownView.tsx +++ b/app/packages/core/src/plugins/SchemaIO/components/DropdownView.tsx @@ -1,3 +1,4 @@ +import MoreVertIcon from "@mui/icons-material/MoreVert"; import SettingsIcon from "@mui/icons-material/Settings"; import { MenuItem, Select } from "@mui/material"; import React, { useState } from "react"; @@ -25,7 +26,7 @@ export default function DropdownView(props: ViewPropsType) { description, color, variant, - useIconOnly = false, + icon, } = view; const [key, setUserChanged] = useKey(path, schema, data, true); const [selected, setSelected] = useState(false); @@ -84,9 +85,12 @@ export default function DropdownView(props: ViewPropsType) { displayEmpty title={compact ? description : undefined} renderValue={(value) => { - if (useIconOnly) { + if (icon === "SettingsIcon") { return ; } + if (icon === "MoreVertIcon") { + return ; + } const unselected = value?.length === 0; setSelected(!unselected); if (unselected) { diff --git a/fiftyone/operators/types.py b/fiftyone/operators/types.py index a22bd01230..53fe20c7e8 100644 --- a/fiftyone/operators/types.py +++ b/fiftyone/operators/types.py @@ -406,6 +406,9 @@ def menu(self, name, **kwargs): ``"top-center"``, ``"top-right"``, ``"bottom-left"``, `"bottom-center"``, or ``"bottom-right"``. Overlay is useful when you want to display a floating menu on top of another content (for example, menu for full-panel-width plot) + icon (None): when set, the icon will be displayed as the menu button instead of the label. + Can be "SettingsIcon", "MoreVertIcon". + Returns: a :class:`Object` """ From 5bea4b708c71d06e121185aabda601531adefdf2 Mon Sep 17 00:00:00 2001 From: Lanny W Date: Mon, 7 Oct 2024 16:46:45 -0500 Subject: [PATCH 04/39] style tweak and clean up --- .../SchemaIO/components/DropdownView.tsx | 101 ++++++++++++++---- fiftyone/operators/types.py | 2 + 2 files changed, 85 insertions(+), 18 deletions(-) diff --git a/app/packages/core/src/plugins/SchemaIO/components/DropdownView.tsx b/app/packages/core/src/plugins/SchemaIO/components/DropdownView.tsx index 40989ef2cb..a14068f17d 100644 --- a/app/packages/core/src/plugins/SchemaIO/components/DropdownView.tsx +++ b/app/packages/core/src/plugins/SchemaIO/components/DropdownView.tsx @@ -1,7 +1,5 @@ -import MoreVertIcon from "@mui/icons-material/MoreVert"; -import SettingsIcon from "@mui/icons-material/Settings"; -import { MenuItem, Select } from "@mui/material"; -import React, { useState } from "react"; +import { IconButton, MenuItem, Select } from "@mui/material"; +import React, { useEffect, useMemo, useState } from "react"; import { useKey } from "../hooks"; import { getComponentProps, getFieldSx } from "../utils"; import autoFocus from "../utils/auto-focus"; @@ -10,6 +8,14 @@ import AlertView from "./AlertView"; import ChoiceMenuItemBody from "./ChoiceMenuItemBody"; import FieldWrapper from "./FieldWrapper"; +// if we want to support more icons in the future, add them here +const iconImports: { + [key: string]: () => Promise<{ default: React.ComponentType }>; +} = { + MoreVertIcon: () => import("@mui/icons-material/MoreVert"), + SettingsIcon: () => import("@mui/icons-material/Settings"), +}; + const MULTI_SELECT_TYPES = ["string", "array"]; export default function DropdownView(props: ViewPropsType) { @@ -28,6 +34,8 @@ export default function DropdownView(props: ViewPropsType) { variant, icon, } = view; + const [IconComponent, setIconComponent] = + useState | null>(null); const [key, setUserChanged] = useKey(path, schema, data, true); const [selected, setSelected] = useState(false); @@ -55,24 +63,84 @@ export default function DropdownView(props: ViewPropsType) { ? rawDefaultValue.toString().split(separator) : rawDefaultValue; - const choiceLabels = choices.reduce((labels, choice) => { - labels[choice.value] = choice.label; - return labels; - }, {}); + const choiceLabels = useMemo(() => { + return choices.reduce((labels, choice) => { + labels[choice.value] = choice.label; + return labels; + }, {}); + }, [choices]); + + const getIconOnlyStyles = () => ({ + "&.MuiInputBase-root.MuiOutlinedInput-root.MuiInputBase-colorPrimary": { + backgroundColor: "transparent !important", + borderRadius: "0 !important", + border: "none !important", + boxShadow: "none !important", + "&:hover, &:focus": { + backgroundColor: "transparent !important", + boxShadow: "none !important", + }, + }, + "& .MuiSelect-select": { + padding: 0, + background: "transparent", + "&:focus": { + background: "transparent", + }, + }, + "& .MuiInputBase-root": { + background: "transparent", + }, + "& .MuiOutlinedInput-notchedOutline": { + border: "none", + }, + "& .MuiSelect-icon": { + display: "none", + }, + }); + + const getDefaultStyles = (selected: boolean) => ({ + ".MuiSelect-select": { + padding: "0.45rem 2rem 0.45rem 1rem", + opacity: selected ? 1 : 0.5, + }, + }); + + const getDropdownStyles = (icon: string | undefined, selected: boolean) => { + return icon ? getIconOnlyStyles() : getDefaultStyles(selected); + }; + + // Now, condense the code like this: const { MenuProps = {}, ...selectProps } = getComponentProps( props, "select", { sx: { - ".MuiSelect-select": { - padding: "0.45rem 2rem 0.45rem 1rem", - opacity: selected ? 1 : 0.5, - }, + ...getDropdownStyles(icon, selected), ...getFieldSx({ color, variant }), }, } ); + // dynamically import the icon component + useEffect(() => { + if (icon && iconImports[icon]) { + iconImports[icon]().then((module) => { + setIconComponent(() => module.default); + }); + } + }, [icon]); + + const renderIcon = () => { + if (!IconComponent) return null; + + return ( + + + + ); + }; + return ( ) => { + setUnit(event.target.value); + setMinText( + formatValue( + data?.[0] || min, + min, + max, + event.target.value as ValueFormat, + valuePrecision + ) + ); + setMaxText( + formatValue( + data?.[1] || max, + min, + max, + event.target.value as ValueFormat, + valuePrecision + ) + ); + }} + label="Select Option" + > + % + flt + + + + + { + const value = e.target.value; + console.log(value, maxText); + if (!value && !!maxText) { + setMaxText(null); + } else if (unit === "percentage") { + const percentageValue = parseInt(value); + if ( + !isNaN(percentageValue) && + percentageValue >= 0 && + percentageValue <= 100 + ) { + setMaxText(percentageValue.toFixed()); + } + } else if (unit === "flt") { + const floatValue = parseFloat(value); + if (!isNaN(floatValue)) { + setMaxText(value); + } + } + }} + onKeyDown={(e) => { + if (e.key === "Enter") { + let finalValue = e.target.value; + + if (!finalValue) { + return; + } + + if (unit === "percentage") { + finalValue = + ((computedMax - computedMin) / 100) * finalValue; + } + + onChange( + path, + [parseFloat(data?.[0] || min), parseFloat(finalValue)], + schema + ); + } + }} + sx={{ width: "70px" }} + /> + + + + + + )} - )} - + + ); } From b589949217a7d8581860a35f727c091986314a1d Mon Sep 17 00:00:00 2001 From: manivoxel51 Date: Sun, 6 Oct 2024 19:59:33 -0700 Subject: [PATCH 07/39] complete slider --- .../SchemaIO/components/SliderView.tsx | 421 +++++++----------- 1 file changed, 162 insertions(+), 259 deletions(-) diff --git a/app/packages/core/src/plugins/SchemaIO/components/SliderView.tsx b/app/packages/core/src/plugins/SchemaIO/components/SliderView.tsx index fe87bfcb3b..323f057028 100644 --- a/app/packages/core/src/plugins/SchemaIO/components/SliderView.tsx +++ b/app/packages/core/src/plugins/SchemaIO/components/SliderView.tsx @@ -1,9 +1,8 @@ -import React, { useEffect, useRef, useState } from "react"; +import React, { useEffect, useMemo, useRef, useState } from "react"; import { Box, FormControl, Grid, - InputLabel, MenuItem, Select, Slider, @@ -15,40 +14,62 @@ import { autoFocus, getComponentProps } from "../utils"; import { useKey } from "../hooks"; import { isNumber } from "lodash"; -type ValueFormat = "float" | "percentage"; -type LabelPosition = "top" | "left"; +type ValueFormat = "flt" | "%"; -function valueLabelFormat( +const valueLabelFormat = ( value: number, min: number, max: number, valueFormat: ValueFormat, - valuePrecision = 0 -) { - if (valueFormat === "percentage") { - const finalValue = (((value - min) / (max - min)) * 100).toFixed(); - return `${finalValue}${"%"}`; - } - return `${value.toFixed(valuePrecision)}`; -} + valuePrecision = 0, + skipUnit = true +) => { + const formattedValue = + valueFormat === "%" + ? (((value - min) / (max - min)) * 100).toFixed() + : value.toFixed(valuePrecision); -const formatValue = ( - value: number, - min: number, - max: number, - valueFormat: ValueFormat, - valuePrecision = 0 -): string => { - if (valueFormat === "percentage") { - return (((value - min) / (max - min)) * 100).toFixed(); - } else { - return value.toFixed(valuePrecision); - } + return skipUnit ? formattedValue : `${formattedValue} ${valueFormat}`; }; +interface SliderInputFieldProps { + label: string; + value: string; + onChange: (e: React.ChangeEvent) => void; + onKeyDown: (e: React.KeyboardEvent) => void; + UnitSelection: React.ReactNode; +} + +const SliderInputField: React.FC = ({ + label, + value, + onChange, + onKeyDown, + UnitSelection, +}) => ( + + + {UnitSelection} + +); + export default function SliderView(props) { const { data, onChange, path, schema } = props; - const sliderRef = useRef(null); const focus = autoFocus(props); @@ -62,106 +83,148 @@ export default function SliderView(props) { const { min: viewMin, max: viewMax, - defaultValue = 0, - valueFormat = "percentage", - valuePrecision = 2, + value_format: valueFormat = "%", + value_label_display: valueLabelDisplay = "on", + value_precision: valuePrecision = 2, variant = null, - valueLabelDisplay = "on", - labelPosition = "left", + label_position: labelPosition = "left", label = "Threshold", viewMultipleOf = null, } = view; - const [title, _] = useState(label); - console.log("label", title); const multipleOf = viewMultipleOf || schemaMultipleOf; - const [min, max] = [ - isNumber(viewMin) ? viewMin : schemaMin, - isNumber(viewMax) ? viewMax : schemaMax, + isNumber(viewMin) ? viewMin : isNumber(schemaMin) ? schemaMin : 0, + isNumber(viewMax) ? viewMax : isNumber(schemaMax) ? schemaMax : 100, ]; - console.log("view", min, max); - - const computedMin = isNumber(min) ? min : 0; - const computedMax = isNumber(max) ? max : 100; const computedMultipleOf = isNumber(multipleOf) ? multipleOf - : (computedMax - computedMin) / 100; - console.log("computedMultipleOf", computedMultipleOf); + : (max - min) / 100; + + const [key, setUserChanged] = useKey(path, schema, data, true); + const [unit, setUnit] = useState(valueFormat); + const [minText, setMinText] = useState( + valueLabelFormat(data?.[0] || min, min, max, unit, valuePrecision) + ); + const [maxText, setMaxText] = useState( + valueLabelFormat(data?.[1] || max, min, max, unit, valuePrecision) + ); useEffect(() => { if (sliderRef.current && focus) { sliderRef.current.querySelector("input")?.focus(); } - }, [sliderRef, focus]); + }, [focus]); - const [key, setUserChanged] = useKey(path, schema, data, true); - const [unit, setUnit] = useState(valueFormat); + const handleInputChange = (e, isMin: boolean) => { + const value = e.target.value; - console.log("data", data); + if (!value) { + isMin ? setMinText("") : setMaxText(""); + } else if (unit === "%") { + const percentageValue = parseInt(value); + if (percentageValue >= 0 && percentageValue <= 100) { + isMin + ? setMinText(percentageValue.toFixed()) + : setMaxText(percentageValue.toFixed()); + } + } else { + const floatValue = parseFloat(value); + if (!isNaN(floatValue)) { + isMin ? setMinText(value) : setMaxText(value); + } + } + }; - const [minText, setMinText] = useState( - formatValue(data?.[0] || min, min, max, unit as ValueFormat, valuePrecision) - ); - const [maxText, setMaxText] = useState( - formatValue(data?.[1] || max, min, max, unit as ValueFormat, valuePrecision) + const handleKeyDown = (e, isMin: boolean) => { + if (e.key === "Enter") { + let finalValue = e.target.value; + if (!finalValue) return; + + if (unit === "%") { + finalValue = ((max - min) / 100) * finalValue; + } + + onChange( + path, + isMin + ? [parseFloat(finalValue), parseFloat(data?.[1] || max)] + : [parseFloat(data?.[0] || min), parseFloat(finalValue)], + schema + ); + } + }; + + const UnitSelection = useMemo( + () => ( + + + + ), + [data, max, min, unit, valuePrecision] ); if (labelPosition === "left") { delete props?.schema?.view?.label; } - console.log("props", props); - return ( - {labelPosition === "left" && title && ( + {labelPosition === "left" && label && ( - - {title} + + {label} )} - - valueLabelFormat( - value, - min, - max, - unit as ValueFormat, - valuePrecision - ) - } - getAriaValueText={(value: number) => - valueLabelFormat( - value, - min, - max, - unit as ValueFormat, - valuePrecision - ) + value={data || [min, max]} + valueLabelFormat={(value) => + valueLabelFormat(value, min, max, unit, valuePrecision, false) } - onChange={(_, value: string) => { + onChange={(_, value: number) => { onChange( path, type === "number" ? parseFloat(value) : value, @@ -170,24 +233,11 @@ export default function SliderView(props) { setUserChanged(); if (variant === "withInputs") { - isNumber(min) && - setMinText( - formatValue( - parseFloat(value[0]), - min, - max, - unit as ValueFormat, - valuePrecision - ) - ); + setMinText( + valueLabelFormat(value[0], min, max, unit, valuePrecision) + ); setMaxText( - formatValue( - parseFloat(value[1]), - min, - max, - unit as ValueFormat, - valuePrecision - ) + valueLabelFormat(value[1], min, max, unit, valuePrecision) ); } }} @@ -197,170 +247,23 @@ export default function SliderView(props) { {variant === "withInputs" && ( - - + { - const value = e.target.value; - console.log("asdasd", value, minText); - if (!value && !!minText) { - setMinText(null); - } else if (unit === "percentage") { - const percentageValue = parseInt(value); - console.log("percentageValue", percentageValue); - if ( - !isNaN(percentageValue) && - percentageValue >= 0 && - percentageValue <= 100 - ) { - setMinText(percentageValue.toFixed()); - } - } else if (unit === "flt") { - const floatValue = parseFloat(value); - if (!isNaN(floatValue)) { - setMinText(value); - } - } - }} - onKeyDown={(e) => { - if (e.key === "Enter") { - let finalValue = e.target.value; - - if (!finalValue) { - return; - } - - if (unit === "percentage") { - finalValue = - ((computedMax - computedMin) / 100) * finalValue; - } - - onChange( - path, - [parseFloat(finalValue), parseFloat(data?.[1] || max)], - schema - ); - } - }} - sx={{ width: "70px" }} + onChange={(e) => handleInputChange(e, true)} + onKeyDown={(e) => handleKeyDown(e, true)} + UnitSelection={UnitSelection} /> - - - - { - const value = e.target.value; - console.log(value, maxText); - if (!value && !!maxText) { - setMaxText(null); - } else if (unit === "percentage") { - const percentageValue = parseInt(value); - if ( - !isNaN(percentageValue) && - percentageValue >= 0 && - percentageValue <= 100 - ) { - setMaxText(percentageValue.toFixed()); - } - } else if (unit === "flt") { - const floatValue = parseFloat(value); - if (!isNaN(floatValue)) { - setMaxText(value); - } - } - }} - onKeyDown={(e) => { - if (e.key === "Enter") { - let finalValue = e.target.value; - - if (!finalValue) { - return; - } - - if (unit === "percentage") { - finalValue = - ((computedMax - computedMin) / 100) * finalValue; - } - - onChange( - path, - [parseFloat(data?.[0] || min), parseFloat(finalValue)], - schema - ); - } - }} - sx={{ width: "70px" }} + onChange={(e) => handleInputChange(e, false)} + onKeyDown={(e) => handleKeyDown(e, false)} + UnitSelection={UnitSelection} /> - - - )} From 9c1a51907b207258325032d9aa45a527de64788e Mon Sep 17 00:00:00 2001 From: Br2850 Date: Tue, 8 Oct 2024 12:30:33 -0400 Subject: [PATCH 08/39] pillBadge outline --- .../src/components/PillBadge/PillBadge.tsx | 26 +++++++++++++++++++ .../src/components/PillBadge/index.ts | 1 + .../SchemaIO/components/PillBadgeView.tsx | 15 +++++++++++ .../src/plugins/SchemaIO/components/index.ts | 1 + 4 files changed, 43 insertions(+) create mode 100644 app/packages/components/src/components/PillBadge/PillBadge.tsx create mode 100644 app/packages/components/src/components/PillBadge/index.ts create mode 100644 app/packages/core/src/plugins/SchemaIO/components/PillBadgeView.tsx diff --git a/app/packages/components/src/components/PillBadge/PillBadge.tsx b/app/packages/components/src/components/PillBadge/PillBadge.tsx new file mode 100644 index 0000000000..fe1b861b6a --- /dev/null +++ b/app/packages/components/src/components/PillBadge/PillBadge.tsx @@ -0,0 +1,26 @@ +import React from "react"; +import { Badge, Chip } from "@mui/material"; + +const PillBadge = ({ + text, + style, +}: { + text: string; + color?: string; + style?: React.CSSProperties; +}) => { + COLORS = { + default: "grey", + success: "green", + warning: "orange", + error: "red", + }; + return ( + + } label={text} variant={} /> + {text} + + ); +}; + +export default PillBadge; diff --git a/app/packages/components/src/components/PillBadge/index.ts b/app/packages/components/src/components/PillBadge/index.ts new file mode 100644 index 0000000000..45ed630ea2 --- /dev/null +++ b/app/packages/components/src/components/PillBadge/index.ts @@ -0,0 +1 @@ +export { default as PillBadge } from "./PillBadge"; diff --git a/app/packages/core/src/plugins/SchemaIO/components/PillBadgeView.tsx b/app/packages/core/src/plugins/SchemaIO/components/PillBadgeView.tsx new file mode 100644 index 0000000000..b6824a29ce --- /dev/null +++ b/app/packages/core/src/plugins/SchemaIO/components/PillBadgeView.tsx @@ -0,0 +1,15 @@ +import { Box } from "@mui/material"; +import React from "react"; +import { getComponentProps } from "../utils"; +import PillBadge from "@fiftyone/components/src/components/PillBadge/PillBadge"; + +export default function PillBadgeView(props) { + const { schema } = props; + const { view = {} } = schema; + + return ( + + + + ); +} diff --git a/app/packages/core/src/plugins/SchemaIO/components/index.ts b/app/packages/core/src/plugins/SchemaIO/components/index.ts index bb0fca6f6e..fefdc1c759 100644 --- a/app/packages/core/src/plugins/SchemaIO/components/index.ts +++ b/app/packages/core/src/plugins/SchemaIO/components/index.ts @@ -49,3 +49,4 @@ export { default as TextFieldView } from "./TextFieldView"; export { default as TupleView } from "./TupleView"; export { default as UnsupportedView } from "./UnsupportedView"; export { default as FrameLoaderView } from "./FrameLoaderView"; +export { default as PillBadgeView } from "./PillBadgeView"; From 9b02e46326bbf657498f30f435a0fb5a4f22d7b2 Mon Sep 17 00:00:00 2001 From: Br2850 Date: Tue, 8 Oct 2024 12:35:44 -0400 Subject: [PATCH 09/39] coloring --- .../src/components/PillBadge/PillBadge.tsx | 28 ++++++------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/app/packages/components/src/components/PillBadge/PillBadge.tsx b/app/packages/components/src/components/PillBadge/PillBadge.tsx index fe1b861b6a..1b66ff2238 100644 --- a/app/packages/components/src/components/PillBadge/PillBadge.tsx +++ b/app/packages/components/src/components/PillBadge/PillBadge.tsx @@ -1,26 +1,16 @@ import React from "react"; import { Badge, Chip } from "@mui/material"; -const PillBadge = ({ - text, - style, -}: { - text: string; - color?: string; - style?: React.CSSProperties; -}) => { - COLORS = { - default: "grey", - success: "green", - warning: "orange", - error: "red", +const PillBadge = ({ text, color }: { text: string; color?: string }) => { + const COLORS: { [key: string]: string } = { + default: "#777777", + primary: "#FFB682", + error: "error", + warning: "warning", + info: "info", + success: "#8BC18D", }; - return ( - - } label={text} variant={} /> - {text} - - ); + return } label={text} variant={} />; }; export default PillBadge; From 162f883b323fa61c875758869ba111a0114daee8 Mon Sep 17 00:00:00 2001 From: Br2850 Date: Tue, 8 Oct 2024 19:32:47 -0400 Subject: [PATCH 10/39] base Chip --- .../src/components/PillBadge/PillBadge.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/packages/components/src/components/PillBadge/PillBadge.tsx b/app/packages/components/src/components/PillBadge/PillBadge.tsx index 1b66ff2238..d6e27960b4 100644 --- a/app/packages/components/src/components/PillBadge/PillBadge.tsx +++ b/app/packages/components/src/components/PillBadge/PillBadge.tsx @@ -1,7 +1,15 @@ import React from "react"; import { Badge, Chip } from "@mui/material"; -const PillBadge = ({ text, color }: { text: string; color?: string }) => { +const PillBadge = ({ + text, + color, + variant, +}: { + text: string; + color?: string; + variant?: string; +}) => { const COLORS: { [key: string]: string } = { default: "#777777", primary: "#FFB682", @@ -10,7 +18,7 @@ const PillBadge = ({ text, color }: { text: string; color?: string }) => { info: "info", success: "#8BC18D", }; - return } label={text} variant={} />; + return ; }; export default PillBadge; From 4827e4457f0e74787a77a864645288ade34362e4 Mon Sep 17 00:00:00 2001 From: Br2850 Date: Tue, 8 Oct 2024 20:45:24 -0400 Subject: [PATCH 11/39] add PillBadgeView to Python Panel types --- fiftyone/operators/types.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/fiftyone/operators/types.py b/fiftyone/operators/types.py index 8a630191f2..b23f6c2091 100644 --- a/fiftyone/operators/types.py +++ b/fiftyone/operators/types.py @@ -1478,6 +1478,19 @@ def __init__(self, **kwargs): super().__init__(**kwargs) +class PillBadgeView(ReadOnlyView): + """Displays a pill shaped badge. + + Args: + text ("Reviewed"): a label for the pill badge + color ("primary"): the color of the pill + variant ("outlined"): the variant of the pill + """ + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + class PlotlyView(View): """Displays a Plotly chart. From da99e2de06b3bcaa4347ba8026fe9e7ac55c5e22 Mon Sep 17 00:00:00 2001 From: Br2850 Date: Wed, 9 Oct 2024 21:32:25 -0600 Subject: [PATCH 12/39] colored badges --- .../src/components/PillBadge/PillBadge.tsx | 21 +++++++++++++++---- .../SchemaIO/components/PillBadgeView.tsx | 8 ++++++- fiftyone/operators/types.py | 2 +- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/app/packages/components/src/components/PillBadge/PillBadge.tsx b/app/packages/components/src/components/PillBadge/PillBadge.tsx index d6e27960b4..8f5f8ca371 100644 --- a/app/packages/components/src/components/PillBadge/PillBadge.tsx +++ b/app/packages/components/src/components/PillBadge/PillBadge.tsx @@ -4,11 +4,11 @@ import { Badge, Chip } from "@mui/material"; const PillBadge = ({ text, color, - variant, + variant = "filled", }: { - text: string; + text: string | string[]; color?: string; - variant?: string; + variant?: "filled" | "outlined"; }) => { const COLORS: { [key: string]: string } = { default: "#777777", @@ -18,7 +18,20 @@ const PillBadge = ({ info: "info", success: "#8BC18D", }; - return ; + return ( + + {typeof text === "string" ? ( + } + label={text} + sx={{ color: COLORS[color || "default"], fontWeight: 500 }} + variant={variant as "filled" | "outlined" | undefined} + /> + ) : ( + + )} + + ); }; export default PillBadge; diff --git a/app/packages/core/src/plugins/SchemaIO/components/PillBadgeView.tsx b/app/packages/core/src/plugins/SchemaIO/components/PillBadgeView.tsx index b6824a29ce..24fcf399f9 100644 --- a/app/packages/core/src/plugins/SchemaIO/components/PillBadgeView.tsx +++ b/app/packages/core/src/plugins/SchemaIO/components/PillBadgeView.tsx @@ -6,10 +6,16 @@ import PillBadge from "@fiftyone/components/src/components/PillBadge/PillBadge"; export default function PillBadgeView(props) { const { schema } = props; const { view = {} } = schema; + const { text, color, variant } = view; return ( - + ); } diff --git a/fiftyone/operators/types.py b/fiftyone/operators/types.py index b23f6c2091..e351ffca3d 100644 --- a/fiftyone/operators/types.py +++ b/fiftyone/operators/types.py @@ -1482,7 +1482,7 @@ class PillBadgeView(ReadOnlyView): """Displays a pill shaped badge. Args: - text ("Reviewed"): a label for the pill badge + text ("Reviewed" | ["Reviewed", "Not Reviewed"]): a label or set of label options for the pill badge color ("primary"): the color of the pill variant ("outlined"): the variant of the pill """ From 930acd655b048a528e7e89fb3852927ecc6141b7 Mon Sep 17 00:00:00 2001 From: Br2850 Date: Wed, 9 Oct 2024 22:08:06 -0600 Subject: [PATCH 13/39] badge with circle and color --- .../src/components/PillBadge/PillBadge.tsx | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/app/packages/components/src/components/PillBadge/PillBadge.tsx b/app/packages/components/src/components/PillBadge/PillBadge.tsx index 8f5f8ca371..7df22be501 100644 --- a/app/packages/components/src/components/PillBadge/PillBadge.tsx +++ b/app/packages/components/src/components/PillBadge/PillBadge.tsx @@ -1,5 +1,6 @@ import React from "react"; -import { Badge, Chip } from "@mui/material"; +import CircleIcon from "@mui/icons-material/Circle"; +import { Chip } from "@mui/material"; const PillBadge = ({ text, @@ -11,20 +12,32 @@ const PillBadge = ({ variant?: "filled" | "outlined"; }) => { const COLORS: { [key: string]: string } = { - default: "#777777", + default: "#999999", primary: "#FFB682", error: "error", warning: "warning", info: "info", success: "#8BC18D", }; + + const chipStyle: { [key: string]: string | number } = { + color: COLORS[color || "default"], + fontWeight: 500, + paddingLeft: 1, + }; + return ( {typeof text === "string" ? ( } + icon={} label={text} - sx={{ color: COLORS[color || "default"], fontWeight: 500 }} + sx={{ + ...chipStyle, + "& .MuiChip-label": { + marginBottom: "2px", + }, + }} variant={variant as "filled" | "outlined" | undefined} /> ) : ( From 2f1bf5ea2c3ebc4cc6d12fa126ff7a520cae1026 Mon Sep 17 00:00:00 2001 From: Br2850 Date: Wed, 9 Oct 2024 23:35:52 -0600 Subject: [PATCH 14/39] extend badge to include dropdowns --- .../src/components/PillBadge/PillBadge.tsx | 79 +++++++++++++++++-- fiftyone/operators/types.py | 2 +- 2 files changed, 75 insertions(+), 6 deletions(-) diff --git a/app/packages/components/src/components/PillBadge/PillBadge.tsx b/app/packages/components/src/components/PillBadge/PillBadge.tsx index 7df22be501..6e74297a66 100644 --- a/app/packages/components/src/components/PillBadge/PillBadge.tsx +++ b/app/packages/components/src/components/PillBadge/PillBadge.tsx @@ -1,16 +1,25 @@ -import React from "react"; +import React, { useState } from "react"; import CircleIcon from "@mui/icons-material/Circle"; -import { Chip } from "@mui/material"; +import { Chip, FormControl, MenuItem, Select } from "@mui/material"; const PillBadge = ({ text, color, variant = "filled", }: { - text: string | string[]; + text: string | string[] | [string, string][]; color?: string; variant?: "filled" | "outlined"; }) => { + const [chipSelection, setChipSelection] = useState( + typeof text === "string" + ? text + : Array.isArray(text) + ? Array.isArray(text[0]) + ? text[0][0] + : text[0] + : "" + ); const COLORS: { [key: string]: string } = { default: "#999999", primary: "#FFB682", @@ -34,14 +43,74 @@ const PillBadge = ({ label={text} sx={{ ...chipStyle, + "& .MuiChip-icon": { + marginRight: "-7px", + }, "& .MuiChip-label": { - marginBottom: "2px", + marginBottom: "1px", }, }} variant={variant as "filled" | "outlined" | undefined} /> ) : ( - + + } + label={ + Array.isArray(text) && Array.isArray(text[0]) ? ( + + ) : ( + + ) + } + sx={{ + ...chipStyle, + "& .MuiChip-icon": { + marginRight: "-7px", + }, + "& .MuiChip-label": { + marginBottom: "1px", + }, + }} + variant={variant as "filled" | "outlined" | undefined} + > + )} ); diff --git a/fiftyone/operators/types.py b/fiftyone/operators/types.py index e351ffca3d..13bfd64ada 100644 --- a/fiftyone/operators/types.py +++ b/fiftyone/operators/types.py @@ -1482,7 +1482,7 @@ class PillBadgeView(ReadOnlyView): """Displays a pill shaped badge. Args: - text ("Reviewed" | ["Reviewed", "Not Reviewed"]): a label or set of label options for the pill badge + text ("Reviewed" | ["Reviewed", "Not Reviewed"] | [["Not Started", "primary"], ["Reviewed", "success"], ["In Review", "warning"]): a label or set of label options with or without a color for the pill badge color ("primary"): the color of the pill variant ("outlined"): the variant of the pill """ From e56ecc2a8ec8559c3b089df42e935891d26e5a94 Mon Sep 17 00:00:00 2001 From: Br2850 Date: Wed, 9 Oct 2024 23:47:37 -0600 Subject: [PATCH 15/39] show/hide icon --- .../src/components/PillBadge/PillBadge.tsx | 14 ++++++++++++-- .../plugins/SchemaIO/components/PillBadgeView.tsx | 3 ++- fiftyone/operators/types.py | 1 + 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/app/packages/components/src/components/PillBadge/PillBadge.tsx b/app/packages/components/src/components/PillBadge/PillBadge.tsx index 6e74297a66..e690a6bbbd 100644 --- a/app/packages/components/src/components/PillBadge/PillBadge.tsx +++ b/app/packages/components/src/components/PillBadge/PillBadge.tsx @@ -6,10 +6,12 @@ const PillBadge = ({ text, color, variant = "filled", + showIcon = true, }: { text: string | string[] | [string, string][]; color?: string; variant?: "filled" | "outlined"; + showIcon?: boolean; }) => { const [chipSelection, setChipSelection] = useState( typeof text === "string" @@ -39,7 +41,11 @@ const PillBadge = ({ {typeof text === "string" ? ( } + icon={ + showIcon ? ( + + ) : undefined + } label={text} sx={{ ...chipStyle, @@ -55,7 +61,11 @@ const PillBadge = ({ ) : ( } + icon={ + showIcon ? ( + + ) : undefined + } label={ Array.isArray(text) && Array.isArray(text[0]) ? ( Date: Fri, 11 Oct 2024 09:04:46 -0500 Subject: [PATCH 20/39] add debouncing and throttle to slider onchange event --- .../SchemaIO/components/SliderView.tsx | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/app/packages/core/src/plugins/SchemaIO/components/SliderView.tsx b/app/packages/core/src/plugins/SchemaIO/components/SliderView.tsx index 323f057028..f69bdd4a9a 100644 --- a/app/packages/core/src/plugins/SchemaIO/components/SliderView.tsx +++ b/app/packages/core/src/plugins/SchemaIO/components/SliderView.tsx @@ -1,4 +1,3 @@ -import React, { useEffect, useMemo, useRef, useState } from "react"; import { Box, FormControl, @@ -9,10 +8,11 @@ import { TextField, Typography, } from "@mui/material"; -import FieldWrapper from "./FieldWrapper"; -import { autoFocus, getComponentProps } from "../utils"; +import { debounce, isNumber, throttle } from "lodash"; +import React, { useEffect, useMemo, useRef, useState } from "react"; import { useKey } from "../hooks"; -import { isNumber } from "lodash"; +import { autoFocus, getComponentProps } from "../utils"; +import FieldWrapper from "./FieldWrapper"; type ValueFormat = "flt" | "%"; @@ -146,13 +146,23 @@ export default function SliderView(props) { finalValue = ((max - min) / 100) * finalValue; } - onChange( - path, - isMin - ? [parseFloat(finalValue), parseFloat(data?.[1] || max)] - : [parseFloat(data?.[0] || min), parseFloat(finalValue)], - schema - ); + const updateValue = () => { + onChange( + path, + isMin + ? [parseFloat(finalValue), parseFloat(data?.[1] || max)] + : [parseFloat(data?.[0] || min), parseFloat(finalValue)], + schema + ); + }; + + // Throttle the onChange action to once every 300ms + const throttledUpdate = throttle(updateValue, 300); + + // Debounce for 200ms to ensure final typing before triggering update + const debouncedUpdate = debounce(throttledUpdate, 200); + + debouncedUpdate(); } }; From 6404f050f9f6e48f4613f2a999ccf0e407c105aa Mon Sep 17 00:00:00 2001 From: Lanny W Date: Mon, 14 Oct 2024 12:19:58 -0500 Subject: [PATCH 21/39] use onChangeComitted instead --- .../SchemaIO/components/SliderView.tsx | 55 +++++++------------ 1 file changed, 21 insertions(+), 34 deletions(-) diff --git a/app/packages/core/src/plugins/SchemaIO/components/SliderView.tsx b/app/packages/core/src/plugins/SchemaIO/components/SliderView.tsx index f69bdd4a9a..87b3ff5ec6 100644 --- a/app/packages/core/src/plugins/SchemaIO/components/SliderView.tsx +++ b/app/packages/core/src/plugins/SchemaIO/components/SliderView.tsx @@ -8,7 +8,7 @@ import { TextField, Typography, } from "@mui/material"; -import { debounce, isNumber, throttle } from "lodash"; +import { isNumber } from "lodash"; import React, { useEffect, useMemo, useRef, useState } from "react"; import { useKey } from "../hooks"; import { autoFocus, getComponentProps } from "../utils"; @@ -146,24 +146,26 @@ export default function SliderView(props) { finalValue = ((max - min) / 100) * finalValue; } - const updateValue = () => { - onChange( - path, - isMin - ? [parseFloat(finalValue), parseFloat(data?.[1] || max)] - : [parseFloat(data?.[0] || min), parseFloat(finalValue)], - schema - ); - }; - - // Throttle the onChange action to once every 300ms - const throttledUpdate = throttle(updateValue, 300); + onChange( + path, + isMin + ? [parseFloat(finalValue), parseFloat(data?.[1] || max)] + : [parseFloat(data?.[0] || min), parseFloat(finalValue)], + schema + ); + } + }; - // Debounce for 200ms to ensure final typing before triggering update - const debouncedUpdate = debounce(throttledUpdate, 200); + // Update the UI immediately during sliding + const handleSliderChange = (_, value: number) => { + setMinText(valueLabelFormat(value[0], min, max, unit, valuePrecision)); + setMaxText(valueLabelFormat(value[1], min, max, unit, valuePrecision)); + }; - debouncedUpdate(); - } + // Trigger actual onChange when the slider is released + const handleSliderCommit = (_, value: number) => { + onChange(path, value, schema); + setUserChanged(); }; const UnitSelection = useMemo( @@ -234,23 +236,8 @@ export default function SliderView(props) { valueLabelFormat={(value) => valueLabelFormat(value, min, max, unit, valuePrecision, false) } - onChange={(_, value: number) => { - onChange( - path, - type === "number" ? parseFloat(value) : value, - schema - ); - setUserChanged(); - - if (variant === "withInputs") { - setMinText( - valueLabelFormat(value[0], min, max, unit, valuePrecision) - ); - setMaxText( - valueLabelFormat(value[1], min, max, unit, valuePrecision) - ); - } - }} + onChange={handleSliderChange} // Smooth UI update + onChangeCommitted={handleSliderCommit} // Final change on slider release ref={sliderRef} {...getComponentProps(props, "slider")} /> From 81fe8f8be1e404f144ce7834de8df2d6aec2b3ae Mon Sep 17 00:00:00 2001 From: Lanny W Date: Mon, 14 Oct 2024 12:21:29 -0500 Subject: [PATCH 22/39] cleanup --- .../core/src/plugins/SchemaIO/components/SliderView.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/packages/core/src/plugins/SchemaIO/components/SliderView.tsx b/app/packages/core/src/plugins/SchemaIO/components/SliderView.tsx index 87b3ff5ec6..852e3888b8 100644 --- a/app/packages/core/src/plugins/SchemaIO/components/SliderView.tsx +++ b/app/packages/core/src/plugins/SchemaIO/components/SliderView.tsx @@ -236,8 +236,8 @@ export default function SliderView(props) { valueLabelFormat={(value) => valueLabelFormat(value, min, max, unit, valuePrecision, false) } - onChange={handleSliderChange} // Smooth UI update - onChangeCommitted={handleSliderCommit} // Final change on slider release + onChange={handleSliderChange} + onChangeCommitted={handleSliderCommit} ref={sliderRef} {...getComponentProps(props, "slider")} /> From fa57350c3a69875a7ef901ccce6500d513dc2bf6 Mon Sep 17 00:00:00 2001 From: Lanny W Date: Tue, 15 Oct 2024 23:57:44 -0500 Subject: [PATCH 23/39] modify icon only dropdownview style --- .../core/src/plugins/SchemaIO/components/DropdownView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/packages/core/src/plugins/SchemaIO/components/DropdownView.tsx b/app/packages/core/src/plugins/SchemaIO/components/DropdownView.tsx index a14068f17d..9e9894169e 100644 --- a/app/packages/core/src/plugins/SchemaIO/components/DropdownView.tsx +++ b/app/packages/core/src/plugins/SchemaIO/components/DropdownView.tsx @@ -149,7 +149,7 @@ export default function DropdownView(props: ViewPropsType) { autoFocus={autoFocus(props)} defaultValue={computedDefaultValue} size="small" - fullWidth + fullWidth={!icon} displayEmpty title={compact ? description : undefined} renderValue={(value) => { From f377f137b31ce127be35496ee58fae03bf6c1c61 Mon Sep 17 00:00:00 2001 From: Lanny W Date: Thu, 17 Oct 2024 15:13:08 -0500 Subject: [PATCH 24/39] gridView change --- .../plugins/SchemaIO/components/GridView.tsx | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/app/packages/core/src/plugins/SchemaIO/components/GridView.tsx b/app/packages/core/src/plugins/SchemaIO/components/GridView.tsx index 04644549bb..a62179a2b6 100644 --- a/app/packages/core/src/plugins/SchemaIO/components/GridView.tsx +++ b/app/packages/core/src/plugins/SchemaIO/components/GridView.tsx @@ -1,5 +1,4 @@ import { Box, BoxProps } from "@mui/material"; -import React from "react"; import { HeaderView } from "."; import { getAdjustedLayoutWidth, @@ -21,6 +20,7 @@ export default function GridView(props: ViewPropsType) { const propertiesAsArray = Object.entries(properties).map(([id, property]) => { return { id, ...property }; }); + const height = props?.layout?.height as number; const parsedGap = parseGap(gap); const width = getAdjustedLayoutWidth( @@ -29,11 +29,19 @@ export default function GridView(props: ViewPropsType) { ) as number; const baseGridProps: BoxProps = { - sx: { gap: parsedGap, ...getGridSx(view) }, + sx: { + width: "100%", + boxSizing: "border-box", + gap: parsedGap, + ...getGridSx(view), + }, }; return ( - + {propertiesAsArray.map((property) => { @@ -48,7 +56,8 @@ export default function GridView(props: ViewPropsType) { orientation === "vertical" ? spaceToHeight(space, height) : undefined, - width: "100%", + width: "100%", // Ensure each child takes full width + boxSizing: "border-box", // Include borders in width calculation }, key: id, }; From 58946433a00eea0e5bd229506978f3dd9c295dcc Mon Sep 17 00:00:00 2001 From: Br2850 Date: Tue, 15 Oct 2024 12:28:39 -0600 Subject: [PATCH 25/39] Update PillBadge.tsx --- app/packages/components/src/components/PillBadge/PillBadge.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/app/packages/components/src/components/PillBadge/PillBadge.tsx b/app/packages/components/src/components/PillBadge/PillBadge.tsx index 362bc80b7a..5ae58e74d4 100644 --- a/app/packages/components/src/components/PillBadge/PillBadge.tsx +++ b/app/packages/components/src/components/PillBadge/PillBadge.tsx @@ -52,6 +52,7 @@ const PillBadge = ({ const chipStyle: { [key: string]: string | number } = { color: COLORS[chipColor || "default"] || COLORS.default, + fontSize: 14, fontWeight: 500, paddingLeft: 1, }; From ca1bccd80f75b7026a9fee2407a26e7a777b7076 Mon Sep 17 00:00:00 2001 From: manivoxel51 Date: Tue, 15 Oct 2024 18:16:16 -0700 Subject: [PATCH 26/39] adds disabled state for various variants of the button view --- .../SchemaIO/components/ButtonView.tsx | 42 ++++++++++++++++--- .../core/src/plugins/SchemaIO/utils/style.ts | 11 +++++ fiftyone/operators/types.py | 1 - 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/app/packages/core/src/plugins/SchemaIO/components/ButtonView.tsx b/app/packages/core/src/plugins/SchemaIO/components/ButtonView.tsx index ae4b7b2bec..3b47b82134 100644 --- a/app/packages/core/src/plugins/SchemaIO/components/ButtonView.tsx +++ b/app/packages/core/src/plugins/SchemaIO/components/ButtonView.tsx @@ -1,10 +1,10 @@ +import React from "react"; import { MuiIconFont } from "@fiftyone/components"; import { usePanelEvent } from "@fiftyone/operators"; import { usePanelId } from "@fiftyone/spaces"; import { isNullish } from "@fiftyone/utilities"; import { Box, ButtonProps, Typography } from "@mui/material"; -import React from "react"; -import { getColorByCode, getComponentProps } from "../utils"; +import { getColorByCode, getComponentProps, getDisabledColors } from "../utils"; import { ViewPropsType } from "../utils/types"; import Button from "./Button"; import TooltipProvider from "./TooltipProvider"; @@ -22,6 +22,7 @@ export default function ButtonView(props: ViewPropsType) { params = {}, prompt, title, + disabled = false, } = view; const panelId = usePanelId(); const handleClick = usePanelEvent(); @@ -37,8 +38,12 @@ export default function ButtonView(props: ViewPropsType) { return ( - +