Skip to content

Commit

Permalink
rework operator exec button to allow prompt
Browse files Browse the repository at this point in the history
  • Loading branch information
ritch committed Jan 6, 2025
1 parent efb27b6 commit 5a2c696
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import TooltipProvider from "./TooltipProvider";
import { OperatorExecutionOption } from "@fiftyone/operators/src/state";
import {
ExecutionCallback,
ExecutionCancelCallback,
ExecutionErrorCallback,
} from "@fiftyone/operators/src/types-internal";
import { OperatorResult } from "@fiftyone/operators/src/operators";
Expand All @@ -30,6 +31,8 @@ export default function OperatorExecutionButtonView(props: ViewPropsType) {
on_error,
on_success,
on_option_selected,
on_cancel,
prompt,
} = view;
const panelId = usePanelId();
const variant = getVariant(props);
Expand Down Expand Up @@ -75,6 +78,13 @@ export default function OperatorExecutionButtonView(props: ViewPropsType) {
});
}
};
const handleOnCancel: ExecutionCancelCallback = () => {
if (on_cancel) {
triggerEvent(panelId, {
operator: on_cancel,
});
}
};
const handleOnOptionSelected = (option: OperatorExecutionOption) => {
if (on_option_selected) {
triggerEvent(panelId, {
Expand All @@ -86,6 +96,13 @@ export default function OperatorExecutionButtonView(props: ViewPropsType) {
}
};

const iconProps = prompt
? {}
: {
startIcon: icon_position === "left" ? Icon : undefined,
endIcon: icon_position === "right" ? Icon : undefined,
};

return (
<Box {...getComponentProps(props, "container")}>
<TooltipProvider title={title} {...getComponentProps(props, "tooltip")}>
Expand All @@ -94,11 +111,12 @@ export default function OperatorExecutionButtonView(props: ViewPropsType) {
onSuccess={handleOnSuccess}
onError={handleOnError}
onOptionSelected={handleOnOptionSelected}
prompt={prompt}
onCancel={handleOnCancel}
executionParams={computedParams}
variant={variant}
disabled={disabled}
startIcon={icon_position === "left" ? Icon : undefined}
endIcon={icon_position === "right" ? Icon : undefined}
{...iconProps}
title={description}
{...getComponentProps(props, "button", getButtonProps(props))}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import { Button } from "@mui/material";
import { OperatorExecutionTrigger } from "../OperatorExecutionTrigger";
import React from "react";
import React, { useCallback, useMemo } from "react";
import {
ExecutionCallback,
ExecutionErrorCallback,
OperatorExecutorOptions,
} from "../../types-internal";
import { OperatorExecutionOption } from "../../state";
import {
OperatorExecutionOption,
useOperatorExecutionOptions,
useOperatorExecutor,
usePromptOperatorInput,
} from "../../state";

/**
* Button which acts as a trigger for opening an `OperatorExecutionMenu`.
Expand All @@ -16,33 +22,89 @@ import { OperatorExecutionOption } from "../../state";
* @param executionParams Parameters to provide to the operator's execute call
* @param onOptionSelected Callback for execution option selection
* @param disabled If true, disables the button and context menu
* @param executorOptions Operator executor options
*/
export const OperatorExecutionButton = ({
operatorUri,
onSuccess,
onError,
onClick,
onCancel,
executionParams,
onOptionSelected,
prompt,
disabled,
children,
executorOptions,
...props
}: {
operatorUri: string;
onSuccess?: ExecutionCallback;
onError?: ExecutionErrorCallback;
onClick?: () => void;
onCancel?: () => void;
prompt?: boolean;
executionParams?: object;
onOptionSelected?: (option: OperatorExecutionOption) => void;
disabled?: boolean;
children: React.ReactNode;
executorOptions?: OperatorExecutorOptions;
}) => {
// Pass onSuccess and onError through to the operator executor.
// These will be invoked on operator completion.
const operatorHandlers = useMemo(() => {
return { onSuccess, onError };
}, [onSuccess, onError]);
const operator = useOperatorExecutor(operatorUri, operatorHandlers);
const promptForOperator = usePromptOperatorInput();

// This callback will be invoked when an execution target option is clicked
const onExecute = useCallback(
(options?: OperatorExecutorOptions) => {
const resolvedOptions = {
...executorOptions,
...options,
};

if (prompt) {
promptForOperator(operatorUri, executionParams, {
callback: (result, opts) => {
if (result?.error) {
onError?.(result, opts);
} else {
onSuccess?.(result, opts);
}
},
onCancel,
});
} else {
return operator.execute(executionParams ?? {}, resolvedOptions);
}
},
[executorOptions, operator, executionParams]
);

const { executionOptions } = useOperatorExecutionOptions({
operatorUri,
onExecute,
});

if (prompt) {
return (
<Button disabled={disabled} {...props} onClick={onExecute}>
{children}
</Button>
);
}

return (
<OperatorExecutionTrigger
operatorUri={operatorUri}
onClick={onClick}
onSuccess={onSuccess}
onError={onError}
onCancel={onCancel}
prompt={prompt}
executionParams={executionParams}
onOptionSelected={onOptionSelected}
disabled={disabled}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useCallback, useMemo, useRef, useState } from "react";
import { Box } from "@mui/material";
import { Box, Button } from "@mui/material";
import { OperatorExecutionMenu } from "../OperatorExecutionMenu";
import {
ExecutionCallback,
Expand All @@ -10,6 +10,7 @@ import {
OperatorExecutionOption,
useOperatorExecutionOptions,
useOperatorExecutor,
usePromptOperatorInput,
} from "../../state";

/**
Expand Down Expand Up @@ -38,12 +39,8 @@ import {
* @param disabled If true, context menu will never open
*/
export const OperatorExecutionTrigger = ({
operatorUri,
onClick,
onSuccess,
onError,
executionParams,
executorOptions,
executionOptions,
onOptionSelected,
disabled,
children,
Expand All @@ -54,40 +51,18 @@ export const OperatorExecutionTrigger = ({
onClick?: () => void;
onSuccess?: ExecutionCallback;
onError?: ExecutionErrorCallback;
onCancel?: () => void;
prompt?: boolean;
executionParams?: object;
executorOptions?: OperatorExecutorOptions;
executionOptions?: OperatorExecutionOption[];
onOptionSelected?: (option: OperatorExecutionOption) => void;
disabled?: boolean;
}) => {
const [isMenuOpen, setIsMenuOpen] = useState(false);
// Anchor to use for context menu
const containerRef = useRef(null);

// Pass onSuccess and onError through to the operator executor.
// These will be invoked on operator completion.
const operatorHandlers = useMemo(() => {
return { onSuccess, onError };
}, [onSuccess, onError]);
const operator = useOperatorExecutor(operatorUri, operatorHandlers);

// This callback will be invoked when an execution target option is clicked
const onExecute = useCallback(
(options?: OperatorExecutorOptions) => {
const resolvedOptions = {
...executorOptions,
...options,
};

return operator.execute(executionParams ?? {}, resolvedOptions);
},
[executorOptions, operator, executionParams]
);

const { executionOptions } = useOperatorExecutionOptions({
operatorUri,
onExecute,
});

// Click handler controls the state of the context menu.
const clickHandler = useCallback(() => {
if (disabled) {
Expand Down
1 change: 1 addition & 0 deletions app/packages/operators/src/types-internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export type ExecutionErrorCallback = (
error: OperatorResult,
options: ExecutionCallbackOptions
) => void;
export type ExecutionCancelCallback = () => void;

export type OperatorExecutorOptions = {
delegationTarget?: string;
Expand Down

0 comments on commit 5a2c696

Please sign in to comment.