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 25 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d7331f8
feat: LEAP-1351: LSF customization for bulk annotation
Gondragos Aug 14, 2024
6593f92
feat: LEAP-1351: Add ability to customize bottom bar control buttons
Gondragos Aug 14, 2024
16c2bb7
tests: LEAP-1351: Add tests
Gondragos Aug 14, 2024
224514b
Refactoring
Gondragos Aug 14, 2024
9895956
Merge branch 'refs/heads/develop' into fb-leap-1351/bulk-annotation-lsf
Gondragos Aug 14, 2024
2b56bb6
biome fixes
Gondragos Aug 14, 2024
e8e756e
Refactoring
Gondragos Aug 14, 2024
d989d4a
Rename interface
Gondragos Aug 14, 2024
73277b6
Hide unnecessary elements for both modes with outliner
Gondragos Aug 14, 2024
0d0842b
Rename variable
Gondragos Aug 14, 2024
f74f9c0
Fix misprint
Gondragos Aug 14, 2024
f19a06f
Hide more things and update tests
Gondragos Aug 14, 2024
f5336ed
Refactor condition
Gondragos Aug 26, 2024
17cb58d
Merge branch 'develop' into fb-leap-1351/bulk-annotation-lsf
Gondragos Aug 26, 2024
6ea5746
Merge branch 'develop' into 'fb-leap-1351/bulk-annotation-lsf'
Gondragos Aug 29, 2024
8e51cea
Merge branch 'develop' into 'fb-leap-1351/bulk-annotation-lsf'
Gondragos Sep 2, 2024
62b486f
Merge branch 'develop' into fb-leap-1351/bulk-annotation-lsf
Gondragos Nov 20, 2024
f958929
Adjust types, small fixes, add ability to update button
Gondragos Nov 21, 2024
a1bb31d
Adjust tests
Gondragos Nov 21, 2024
d603916
Merge branch 'develop' into fb-leap-1351/bulk-annotation-lsf
Gondragos Nov 21, 2024
46427e0
Merge branch 'develop' into 'fb-leap-1351/bulk-annotation-lsf'
Gondragos Nov 22, 2024
1edc48a
Remove unused parts of code
Gondragos Nov 22, 2024
0c6e6e4
Change way of applying state to CustomButton
Gondragos Nov 22, 2024
5e95fbd
Refactoring
Gondragos Nov 22, 2024
bd5b0ed
Merge branch 'fb-leap-1351/bulk-annotation-lsf' of ssh://github.com/h…
Gondragos Nov 22, 2024
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
41 changes: 29 additions & 12 deletions web/libs/editor/src/components/App/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,14 @@ import "../../tags/visual";
import { Space } from "../../common/Space/Space";
import { Button } from "../../common/Button/Button";
import { Block, 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 {
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 @@ -219,6 +226,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 @@ -260,24 +268,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.styl";
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,
};
6 changes: 5 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,10 @@ function renderItem(ref: IAnyStateTreeNode, annotation: IAnnotation, includeKey
const typeName = type.name;
const View = Registry.getViewByModel(typeName);

if (isFF(FF_BULK_ANNOTATION) && annotation?.store?.hasInterface("annotation:bulk") && el.isIndependent !== true) {
Gondragos marked this conversation as resolved.
Show resolved Hide resolved
return null;
}

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