Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: LEAP-1351: LSF customization for bulk annotation #6203

Open
wants to merge 16 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 30 additions & 13 deletions web/libs/editor/src/components/App/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,15 @@ import "../../tags/visual";
*/
import { Space } from "../../common/Space/Space";
import { Button } from "../../common/Button/Button";
import { Block, cn, Elem } from "../../utils/bem";
import { FF_DEV_1170, FF_DEV_3873, FF_LSDV_4620_3_ML, FF_SIMPLE_INIT, isFF } from "../../utils/feature-flags";
import { Block, Elem } from "../../utils/bem";
import {
FF_BULK_ANNOTATION,
FF_DEV_1170,
FF_DEV_3873,
FF_LSDV_4620_3_ML,
FF_SIMPLE_INIT,
isFF,
} from "../../utils/feature-flags";
import { sanitizeHtml } from "../../utils/html";
import { reactCleaner } from "../../utils/reactCleaner";
import { guidGenerator } from "../../utils/unique";
Expand Down Expand Up @@ -220,6 +227,7 @@ class App extends Component {
</Block>
);

const isBulkMode = isFF(FF_BULK_ANNOTATION) && store.hasInterface("annotation:bulk");
const outlinerEnabled = isFF(FF_DEV_1170);
const newUIEnabled = isFF(FF_DEV_3873);

Expand Down Expand Up @@ -261,24 +269,33 @@ class App extends Component {
>
{outlinerEnabled ? (
newUIEnabled ? (
<SideTabsPanels
panelsHidden={viewingAll}
currentEntity={as.selectedHistory ?? as.selected}
regions={as.selected.regionStore}
showComments={store.hasInterface("annotations:comments")}
focusTab={store.commentStore.tooltipMessage ? "comments" : null}
>
{mainContent}
{store.hasInterface("topbar") && <BottomBar store={store} />}
</SideTabsPanels>
) : (
!isBulkMode ? (
<SideTabsPanels
panelsHidden={viewingAll}
currentEntity={as.selectedHistory ?? as.selected}
regions={as.selected.regionStore}
showComments={store.hasInterface("annotations:comments")}
focusTab={store.commentStore.tooltipMessage ? "comments" : null}
>
{mainContent}
{store.hasInterface("topbar") && <BottomBar store={store} />}
</SideTabsPanels>
) : (
<>
{mainContent}
{store.hasInterface("topbar") && <BottomBar store={store} />}
</>
)
) : !isBulkMode ? (
<SidePanels
panelsHidden={viewingAll}
currentEntity={as.selectedHistory ?? as.selected}
regions={as.selected.regionStore}
>
{mainContent}
</SidePanels>
) : (
<>{mainContent}</>
)
) : (
<>
Expand Down
4 changes: 3 additions & 1 deletion web/libs/editor/src/components/BottomBar/Actions.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { IconInfoOutline, LsSettingsAlt } from "../../assets/icons";
import { Button } from "../../common/Button/Button";
import { Elem } from "../../utils/bem";
import { FF_BULK_ANNOTATION } from "../../utils/feature-flags";
import { EditingHistory } from "./HistoryActions";
import { DynamicPreannotationsToggle } from "../AnnotationTab/DynamicPreannotationsToggle";
import { AutoAcceptToggle } from "../AnnotationTab/AutoAcceptToggle";
Expand All @@ -12,6 +13,7 @@ export const Actions = ({ store }) => {
const entity = annotationStore.selected;
const isPrediction = entity?.type === "prediction";
const isViewAll = annotationStore.viewingAll === true;
const isBulkMode = isFF(FF_BULK_ANNOTATION) && store.hasInterface("annotation:bulk");

return (
<Elem name="section">
Expand Down Expand Up @@ -47,7 +49,7 @@ export const Actions = ({ store }) => {
/>
</Tooltip>

{store.hasInterface("ground-truth") && <GroundTruth entity={entity} />}
{store.hasInterface("ground-truth") && !isBulkMode && <GroundTruth entity={entity} />}

{!isViewAll && (
<Elem name="section">
Expand Down
10 changes: 7 additions & 3 deletions web/libs/editor/src/components/BottomBar/BottomBar.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { observer } from "mobx-react";
import { Block, Elem } from "../../utils/bem";
import { Actions } from "./Actions";
import { Controls } from "./Controls";
import { Controls, CustomControls } from "./Controls";
import "./BottomBar.scss";
import { FF_DEV_3873, isFF } from "../../utils/feature-flags";
import { FF_BULK_ANNOTATION, FF_DEV_3873, isFF } from "../../utils/feature-flags";

export const BottomBar = observer(({ store }) => {
const annotationStore = store.annotationStore;
Expand All @@ -20,7 +20,11 @@ export const BottomBar = observer(({ store }) => {
<Elem name="group">
{store.hasInterface("controls") && (store.hasInterface("review") || !isPrediction) && (
<Elem name="section" mod={{ flat: true }}>
<Controls annotation={entity} />
{isFF(FF_BULK_ANNOTATION) && store.hasInterface("controls:custom") ? (
Gondragos marked this conversation as resolved.
Show resolved Hide resolved
<CustomControls annotation={entity} />
) : (
<Controls annotation={entity} />
)}
</Elem>
)}
</Elem>
Expand Down
39 changes: 39 additions & 0 deletions web/libs/editor/src/components/BottomBar/Controls.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,45 @@ const controlsInjector = inject(({ store }) => {
};
});

const CustomControl = observer(({ button }) => {
const look = button.disabled ? "disabled" : button.look ?? "primary";
const [waiting, setWaiting] = useState(false);
const clickHandler = useCallback(
async (e) => {
setWaiting(true);
await button.onClick(e, button);
setWaiting(false);
},
[button, button.onClick],
);
return (
<ButtonTooltip key={button.key} title={button.tooltip}>
<Button
aria-label={button.ariaLabel}
disabled={button.disabled}
look={look}
onClick={clickHandler}
waiting={waiting}
>
{button.title}
</Button>
</ButtonTooltip>
);
});

export const CustomControls = controlsInjector(
observer(({ store }) => {
const buttons = store.controlButtons;
return (
<Block name="controls">
{buttons.map((button) => (
<CustomControl button={button} />
))}
</Block>
);
}),
);

export const Controls = controlsInjector(
observer(({ store, history, annotation }) => {
const isReview = store.hasInterface("review") || annotation.canBeReviewed;
Expand Down
12 changes: 7 additions & 5 deletions web/libs/editor/src/components/TopBar/Actions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { IconCopy, IconInfo, IconViewAll, LsSettings, LsTrash } from "../../asse
import { Button } from "../../common/Button/Button";
import { Tooltip } from "../../common/Tooltip/Tooltip";
import { Elem } from "../../utils/bem";
import { FF_BULK_ANNOTATION, isFF } from "../../utils/feature-flags";
import { GroundTruth } from "../CurrentEntity/GroundTruth";
import { EditingHistory } from "./HistoryActions";
import { confirm } from "../../common/Modal/Modal";
Expand All @@ -13,14 +14,15 @@ export const Actions = ({ store }) => {
const saved = !entity.userGenerate || entity.sentUserGenerate;
const isPrediction = entity?.type === "prediction";
const isViewAll = annotationStore.viewingAll;
const isBulkMode = isFF(FF_BULK_ANNOTATION) && store.hasInterface("annotation:bulk");

const onToggleVisibility = useCallback(() => {
annotationStore.toggleViewingAllAnnotations();
}, [annotationStore]);

return (
<Elem name="section">
{store.hasInterface("annotations:view-all") && (
{store.hasInterface("annotations:view-all") && !isBulkMode && (
<Tooltip title="View all annotations">
<Button
icon={<IconViewAll />}
Expand All @@ -37,11 +39,11 @@ export const Actions = ({ store }) => {
</Tooltip>
)}

{!isViewAll && store.hasInterface("ground-truth") && <GroundTruth entity={entity} />}
{!isViewAll && !isBulkMode && store.hasInterface("ground-truth") && <GroundTruth entity={entity} />}

{!isPrediction && !isViewAll && store.hasInterface("edit-history") && <EditingHistory entity={entity} />}

{!isViewAll && store.hasInterface("annotations:delete") && (
{!isViewAll && !isBulkMode && store.hasInterface("annotations:delete") && (
<Tooltip title="Delete annotation">
<Button
icon={<LsTrash />}
Expand All @@ -66,7 +68,7 @@ export const Actions = ({ store }) => {
</Tooltip>
)}

{!isViewAll && store.hasInterface("annotations:add-new") && saved && (
{!isViewAll && !isBulkMode && store.hasInterface("annotations:add-new") && saved && (
<Tooltip title={`Create copy of current ${entity.type}`}>
<Button
icon={<IconCopy style={{ width: 36, height: 36 }} />}
Expand Down Expand Up @@ -106,7 +108,7 @@ export const Actions = ({ store }) => {
}}
/>

{store.description && store.hasInterface("instruction") && (
{store.description && store.hasInterface("instruction") && !isBulkMode && (
<Button
icon={<IconInfo style={{ width: 16, height: 16 }} />}
primary={store.showingDescription}
Expand Down
39 changes: 39 additions & 0 deletions web/libs/editor/src/components/TopBar/Controls.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,45 @@ const controlsInjector = inject(({ store }) => {
};
});

const CustomControl = observer(({ button }) => {
const look = button.disabled ? "disabled" : button.look ?? "primary";
const [waiting, setWaiting] = useState(false);
const clickHandler = useCallback(
async (e) => {
setWaiting(true);
await button.onClick(e, button);
setWaiting(false);
},
[button, button.onClick],
);
return (
<ButtonTooltip key={button.key} title={button.tooltip}>
<Button
aria-label={button.ariaLabel}
disabled={button.disabled}
look={look}
onClick={clickHandler}
waiting={waiting}
>
{button.title}
</Button>
</ButtonTooltip>
);
});

export const CustomControls = controlsInjector(
observer(({ store }) => {
const buttons = store.controlButtons;
return (
<Block name="controls">
{buttons.map((button) => (
<CustomControl button={button} />
))}
</Block>
);
}),
);

export const Controls = controlsInjector(
observer(({ store, history, annotation }) => {
const isReview = store.hasInterface("review");
Expand Down
18 changes: 13 additions & 5 deletions web/libs/editor/src/components/TopBar/TopBar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import { IconViewAll, LsPlus } from "../../assets/icons";
import { Button } from "../../common/Button/Button";
import { Tooltip } from "../../common/Tooltip/Tooltip";
import { Block, Elem } from "../../utils/bem";
import { FF_DEV_3873, isFF } from "../../utils/feature-flags";
import { FF_BULK_ANNOTATION, FF_DEV_3873, isFF } from "../../utils/feature-flags";
import { AnnotationsCarousel } from "../AnnotationsCarousel/AnnotationsCarousel";
import { DynamicPreannotationsToggle } from "../AnnotationTab/DynamicPreannotationsToggle";
import { CustomControls } from "../BottomBar/Controls";
import { Actions } from "./Actions";
import { Annotations } from "./Annotations";
import { Controls } from "./Controls";
Expand All @@ -20,6 +21,9 @@ export const TopBar = observer(({ store }) => {
const isPrediction = entity?.type === "prediction";

const isViewAll = annotationStore?.viewingAll === true;
const isBulkMode = isFF(FF_BULK_ANNOTATION) && store.hasInterface("annotation:bulk");

if (isFF(FF_DEV_3873) && isBulkMode) return null;

return store ? (
<Block name="topbar" mod={{ newLabelingUI: isFF(FF_DEV_3873) }}>
Expand Down Expand Up @@ -50,7 +54,7 @@ export const TopBar = observer(({ store }) => {
icon={<LsPlus />}
className={"topbar__button"}
type="text"
aria-label="View All"
aria-label="Create an annotation"
onClick={(event) => {
event.preventDefault();
const created = store.annotationStore.createAnnotation();
Expand All @@ -77,8 +81,8 @@ export const TopBar = observer(({ store }) => {
) : (
<>
<Elem name="group">
<CurrentTask store={store} />
{!isViewAll && (
{!isBulkMode && <CurrentTask store={store} />}
{!isViewAll && !isBulkMode && (
<Annotations store={store} annotationStore={store.annotationStore} commentStore={store.commentStore} />
)}
<Actions store={store} />
Expand All @@ -91,7 +95,11 @@ export const TopBar = observer(({ store }) => {
)}
{!isViewAll && store.hasInterface("controls") && (store.hasInterface("review") || !isPrediction) && (
<Elem name="section" mod={{ flat: true }} style={{ width: 320, boxSizing: "border-box" }}>
<Controls annotation={entity} />
{isFF(FF_BULK_ANNOTATION) && store.hasInterface("controls:custom") ? (
<CustomControls annotation={entity} />
) : (
<Controls annotation={entity} />
)}
</Elem>
)}
</Elem>
Expand Down
23 changes: 23 additions & 0 deletions web/libs/editor/src/core/CustomTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,30 @@ const CSSColor = types.custom<any, string>({
},
});

// This type is using to store a raw callback function in mobx-state-tree model.
// It won't be serialized right. However, it is useful to allow passing a callback function as a prop
// and be able to get a reaction on its change
//
// /!\ Avoid using this type in case you need serialization of the model it contains
const RawCallback = types.custom<Function, Function>({
Gondragos marked this conversation as resolved.
Show resolved Hide resolved
name: "rawCallback",
fromSnapshot(value: Function) {
return value;
},
toSnapshot(value: Function) {
return value;
},
getValidationMessage(value: Function) {
if (this.isTargetType(value)) return "";
return `Value ${value} is not a function.`;
},
isTargetType(value: any) {
return typeof value === "function";
},
});

export const customTypes = {
range: Range,
color: CSSColor,
rawCallback: RawCallback,
};
8 changes: 7 additions & 1 deletion web/libs/editor/src/core/Tree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { IAnyComplexType, IAnyStateTreeNode } from "mobx-state-tree/dist/in

import Registry from "./Registry";
import { parseValue } from "../utils/data";
import { FF_DEV_3391, isFF } from "../utils/feature-flags";
import { FF_BULK_ANNOTATION, FF_DEV_3391, isFF } from "../utils/feature-flags";
import { guidGenerator } from "../utils/unique";

interface ConfigNodeBaseProps {
Expand Down Expand Up @@ -234,6 +234,12 @@ function renderItem(ref: IAnyStateTreeNode, annotation: IAnnotation, includeKey
const typeName = type.name;
const View = Registry.getViewByModel(typeName);

const isBulkMode = isFF(FF_BULK_ANNOTATION) && annotation?.store?.hasInterface("annotation:bulk");
const isNotIndependentTag = el.isIndependent !== true;
if (isBulkMode && isNotIndependentTag) {
return null;
}

if (!View) {
throw new Error(`No view for model: ${typeName}`);
}
Expand Down
Loading
Loading