Skip to content
Closed
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
2 changes: 1 addition & 1 deletion frontend/javascripts/dashboard/dashboard_view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ class DashboardView extends PureComponent<PropsWithRouter, State> {
) : null;

return (
<NmlUploadZoneContainer onImport={this.uploadNmls} isUpdateAllowed>
<NmlUploadZoneContainer onImport={this.uploadNmls} isEditingAllowed>
{whatsNextBanner}
<div className="container propagate-flex-height" style={{ minHeight: "66vh" }}>
{pricingPlanWarnings}
Expand Down
15 changes: 12 additions & 3 deletions frontend/javascripts/navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,14 @@ import messages from "messages";
import { PricingEnforcedSpan } from "components/pricing_enforcers";
import type { ItemType, MenuItemType, SubMenuType } from "antd/es/menu/interface";
import type { MenuClickEventHandler } from "rc-menu/lib/interface";
import constants from "oxalis/constants";
import constants, { type AnnotationMutexStateEnum } from "oxalis/constants";
import { MaintenanceBanner, UpgradeVersionBanner } from "banners";
import { getAntdTheme, getSystemColorTheme } from "theme";
import { formatUserName } from "oxalis/model/accessors/user_accessor";
import { isAnnotationOwner as isAnnotationOwnerAccessor } from "oxalis/model/accessors/annotation_accessor";
import {
isAnnotationEditingAllowed,
isAnnotationOwner as isAnnotationOwnerAccessor,
} from "oxalis/model/accessors/annotation_accessor";

const { Header } = Layout;

Expand All @@ -84,6 +87,7 @@ type StateProps = {
hasOrganizations: boolean;
othersMayEdit: boolean;
allowUpdate: boolean;
annotationMutexState: AnnotationMutexStateEnum;
isLockedByOwner: boolean;
isAnnotationOwner: boolean;
annotationOwnerName: string;
Expand Down Expand Up @@ -811,6 +815,7 @@ function Navbar({
othersMayEdit,
blockedByUser,
allowUpdate,
annotationMutexState,
annotationOwnerName,
isLockedByOwner,
navbarHeight,
Expand Down Expand Up @@ -877,7 +882,10 @@ function Navbar({
menuItems.push(getTimeTrackingMenu(collapseAllNavItems));
}

if (othersMayEdit && !allowUpdate && !isLockedByOwner) {
if (
othersMayEdit &&
!isAnnotationEditingAllowed(allowUpdate, isLockedByOwner, annotationMutexState)
) {
trailingNavItems.push(
<AnnotationLockedByUserTag
key="locked-by-user-tag"
Expand Down Expand Up @@ -1005,6 +1013,7 @@ const mapStateToProps = (state: OxalisState): StateProps => ({
othersMayEdit: state.tracing.othersMayEdit,
blockedByUser: state.tracing.blockedByUser,
allowUpdate: state.tracing.restrictions.allowUpdate,
annotationMutexState: state.tracing.annotationMutexState,
isLockedByOwner: state.tracing.isLockedByOwner,
annotationOwnerName: formatUserName(state.activeUser, state.tracing.owner),
isAnnotationOwner: isAnnotationOwnerAccessor(state),
Expand Down
7 changes: 7 additions & 0 deletions frontend/javascripts/oxalis/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,13 @@ export const NODE_ID_REF_REGEX = /#([0-9]+)/g;
export const POSITION_REF_REGEX = /#\(([0-9]+,[0-9]+,[0-9]+)\)/g;
const VIEWPORT_WIDTH = 376;

export enum AnnotationMutexStateEnum {
NOT_NEEDED = "NOT_NEEDED",
ACQUIRED_FROM_BEGINNING = "ACQUIRED_FROM_BEGINNING",
PENDING = "PENDING",
ACQUIRED = "ACQUIRED",
}

// ARBITRARY_CAM_DISTANCE has to be calculated such that with cam
// angle 45°, the plane of width Constants.VIEWPORT_WIDTH fits exactly in the
// viewport.
Expand Down
2 changes: 2 additions & 0 deletions frontend/javascripts/oxalis/default_state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Constants, {
TDViewDisplayModeEnum,
InterpolationModeEnum,
UnitLong,
AnnotationMutexStateEnum,
} from "oxalis/constants";
import type {
APIAllowedMode,
Expand Down Expand Up @@ -48,6 +49,7 @@ const initialAnnotationInfo = {
},
annotationType: "View" as APIAnnotationType,
meshes: [],
annotationMutexState: AnnotationMutexStateEnum.NOT_NEEDED,
};

const defaultState: OxalisState = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { OxalisState, Tracing } from "oxalis/store";
import { getVolumeTracingById } from "./volumetracing_accessor";
import type { APIAnnotationInfo } from "types/api_flow_types";
import type { EmptyObject } from "types/globals";
import { AnnotationMutexStateEnum } from "oxalis/constants";

export function mayEditAnnotationProperties(state: OxalisState) {
const { owner, restrictions } = state.tracing;
Expand All @@ -13,7 +14,30 @@ export function mayEditAnnotationProperties(state: OxalisState) {
restrictions.allowSave &&
activeUser &&
owner?.id === activeUser.id &&
!state.tracing.isLockedByOwner
!state.tracing.isLockedByOwner &&
!(state.tracing.annotationMutexState === AnnotationMutexStateEnum.PENDING)
);
}

export function isAnnotationEditingAllowedByFullState(state: OxalisState) {
return isAnnotationEditingAllowed(
state.tracing.restrictions.allowUpdate,
state.tracing.isLockedByOwner,
state.tracing.annotationMutexState,
);
}

export function isAnnotationEditingAllowed(
allowUpdate: boolean,
isLockedByOwner: boolean,
annotationMutexState: AnnotationMutexStateEnum,
) {
return (
allowUpdate &&
!isLockedByOwner &&
[AnnotationMutexStateEnum.PENDING, AnnotationMutexStateEnum.ACQUIRED].includes(
annotationMutexState,
)
);
}

Expand Down
10 changes: 8 additions & 2 deletions frontend/javascripts/oxalis/model/actions/annotation_actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import type {
UserBoundingBoxWithoutId,
UserBoundingBoxWithoutIdMaybe,
} from "oxalis/store";
import type { Vector3 } from "oxalis/constants";
import type { AnnotationMutexStateEnum, Vector3 } from "oxalis/constants";
import _ from "lodash";
import type { Dispatch } from "redux";
import Deferred from "libs/async/deferred";
Expand All @@ -25,6 +25,7 @@ type SetAnnotationVisibilityAction = ReturnType<typeof setAnnotationVisibilityAc
export type EditAnnotationLayerAction = ReturnType<typeof editAnnotationLayerAction>;
type SetAnnotationDescriptionAction = ReturnType<typeof setAnnotationDescriptionAction>;
type SetAnnotationAllowUpdateAction = ReturnType<typeof setAnnotationAllowUpdateAction>;
type SetAnnotationMutexStateAction = ReturnType<typeof setAnnotationMutexStateAction>;
type SetBlockedByUserAction = ReturnType<typeof setBlockedByUserAction>;
type SetUserBoundingBoxesAction = ReturnType<typeof setUserBoundingBoxesAction>;
type FinishedResizingUserBoundingBoxAction = ReturnType<
Expand Down Expand Up @@ -58,6 +59,7 @@ export type AnnotationActionTypes =
| EditAnnotationLayerAction
| SetAnnotationDescriptionAction
| SetAnnotationAllowUpdateAction
| SetAnnotationMutexStateAction
| SetBlockedByUserAction
| SetUserBoundingBoxesAction
| ChangeUserBoundingBoxAction
Expand Down Expand Up @@ -134,7 +136,11 @@ export const setAnnotationAllowUpdateAction = (allowUpdate: boolean) =>
type: "SET_ANNOTATION_ALLOW_UPDATE",
allowUpdate,
}) as const;

export const setAnnotationMutexStateAction = (mutexState: AnnotationMutexStateEnum) =>
({
type: "SET_ANNOTATION_MUTEX_STATE",
mutexState,
}) as const;
export const setBlockedByUserAction = (blockedByUser: APIUserCompact | null | undefined) =>
({
type: "SET_BLOCKED_BY_USER",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,12 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState {
allowUpdate,
});
}
case "SET_ANNOTATION_MUTEX_STATE": {
const { mutexState } = action;
return updateKey(state, "tracing", {
annotationMutexState: mutexState,
});
}

case "SET_BLOCKED_BY_USER": {
const { blockedByUser } = action;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import {
getNodeKey,
} from "oxalis/view/right-border-tabs/tree_hierarchy_view_helpers";
import type { MetadataEntryProto } from "types/api_flow_types";
import { isAnnotationEditingAllowedByFullState } from "../accessors/annotation_accessor";

function SkeletonTracingReducer(state: OxalisState, action: Action): OxalisState {
switch (action.type) {
Expand Down Expand Up @@ -590,11 +591,10 @@ function SkeletonTracingReducer(state: OxalisState, action: Action): OxalisState
}

/**
* ATTENTION: The following actions are only executed if allowUpdate is true!
* ATTENTION: The following actions are only executed if isAnnotationEditingAllowed is true!
*/
const { restrictions } = state.tracing;
const { allowUpdate } = restrictions;
if (!allowUpdate) return state;
if (!isAnnotationEditingAllowedByFullState(state)) return state;

switch (action.type) {
case "CREATE_NODE": {
Expand Down
5 changes: 3 additions & 2 deletions frontend/javascripts/oxalis/model/reducers/ui_reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
getPreviousTool,
} from "oxalis/model/reducers/reducer_helpers";
import { hideBrushReducer } from "oxalis/model/reducers/volumetracing_reducer_helpers";
import { isAnnotationEditingAllowedByFullState } from "../accessors/annotation_accessor";

function UiReducer(state: OxalisState, action: Action): OxalisState {
switch (action.type) {
Expand Down Expand Up @@ -54,15 +55,15 @@ function UiReducer(state: OxalisState, action: Action): OxalisState {
}

case "SET_TOOL": {
if (!state.tracing.restrictions.allowUpdate) {
if (!isAnnotationEditingAllowedByFullState(state)) {
return state;
}

return setToolReducer(state, action.tool);
}

case "CYCLE_TOOL": {
if (!state.tracing.restrictions.allowUpdate) {
if (!isAnnotationEditingAllowedByFullState(state)) {
return state;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { setDirectionReducer } from "oxalis/model/reducers/flycam_reducer";
import { updateKey } from "oxalis/model/helpers/deep_update";
import { mapGroupsToGenerator } from "../accessors/skeletontracing_accessor";
import { getMaximumSegmentIdForLayer } from "../accessors/dataset_accessor";
import { isAnnotationEditingAllowedByFullState } from "../accessors/annotation_accessor";

export function updateVolumeTracing(
state: OxalisState,
Expand Down Expand Up @@ -114,9 +115,10 @@ export function addToLayerReducer(
volumeTracing: VolumeTracing,
position: Vector3,
) {
const { allowUpdate } = state.tracing.restrictions;

if (!allowUpdate || isVolumeAnnotationDisallowedForZoom(state.uiInformation.activeTool, state)) {
if (
!isAnnotationEditingAllowedByFullState(state) ||
isVolumeAnnotationDisallowedForZoom(state.uiInformation.activeTool, state)
) {
return state;
}

Expand Down
32 changes: 14 additions & 18 deletions frontend/javascripts/oxalis/model/sagas/annotation_saga.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import _ from "lodash";
import type { Action } from "oxalis/model/actions/actions";
import {
type EditAnnotationLayerAction,
setAnnotationAllowUpdateAction,
setAnnotationMutexStateAction,
setBlockedByUserAction,
type SetOthersMayEditForAnnotationAction,
} from "oxalis/model/actions/annotation_actions";
Expand Down Expand Up @@ -37,7 +37,7 @@ import { getActiveMagIndexForLayer } from "oxalis/model/accessors/flycam_accesso
import { Model } from "oxalis/singletons";
import Store from "oxalis/store";
import Toast from "libs/toast";
import constants, { MappingStatusEnum } from "oxalis/constants";
import constants, { AnnotationMutexStateEnum, MappingStatusEnum } from "oxalis/constants";
import messages from "messages";
import type { APIUserCompact } from "types/api_flow_types";
import { Button } from "antd";
Expand Down Expand Up @@ -240,7 +240,6 @@ export function* acquireAnnotationMutexMaybe(): Saga<void> {
const MUTEX_NOT_ACQUIRED_KEY = "MutexCouldNotBeAcquired";
const MUTEX_ACQUIRED_KEY = "AnnotationMutexAcquired";
let isInitialRequest = true;
let doesHaveMutexFromBeginning = false;
let doesHaveMutex = false;
let shallTryAcquireMutex = othersMayEdit;

Expand Down Expand Up @@ -271,7 +270,7 @@ export function* acquireAnnotationMutexMaybe(): Saga<void> {
function* tryAcquireMutexContinuously(): Saga<void> {
while (shallTryAcquireMutex) {
if (isInitialRequest) {
yield* put(setAnnotationAllowUpdateAction(false));
yield* put(setAnnotationMutexStateAction(AnnotationMutexStateEnum.PENDING));
}
try {
const { canEdit, blockedByUser } = yield* retry(
Expand All @@ -280,32 +279,29 @@ export function* acquireAnnotationMutexMaybe(): Saga<void> {
acquireAnnotationMutex,
annotationId,
);
if (canEdit !== doesHaveMutex || isInitialRequest) {
doesHaveMutex = canEdit;
onMutexStateChanged(canEdit, blockedByUser);
}
if (isInitialRequest && canEdit) {
doesHaveMutexFromBeginning = true;
// Only set allow update to true in case the first try to get the mutex succeeded.
yield* put(setAnnotationAllowUpdateAction(true));
}
if (!canEdit || !doesHaveMutexFromBeginning) {
// If the mutex could not be acquired anymore or the user does not have it from the beginning, set allow update to false.
doesHaveMutexFromBeginning = false;
yield* put(setAnnotationAllowUpdateAction(false));
yield* put(
setAnnotationMutexStateAction(AnnotationMutexStateEnum.ACQUIRED_FROM_BEGINNING),
);
} else if (!isInitialRequest && canEdit) {
yield* put(setAnnotationMutexStateAction(AnnotationMutexStateEnum.ACQUIRED));
}
if (canEdit) {
yield* put(setBlockedByUserAction(activeUser));
} else {
yield* put(setBlockedByUserAction(blockedByUser));
}
if (canEdit !== doesHaveMutex || isInitialRequest) {
doesHaveMutex = canEdit;
onMutexStateChanged(canEdit, blockedByUser);
}
} catch (error) {
const wasCanceled = yield* cancelled();
if (!wasCanceled) {
console.error("Error while trying to acquire mutex.", error);
yield* put(setBlockedByUserAction(undefined));
yield* put(setAnnotationAllowUpdateAction(false));
doesHaveMutexFromBeginning = false;
yield* put(setAnnotationMutexStateAction(AnnotationMutexStateEnum.PENDING));
if (doesHaveMutex || isInitialRequest) {
onMutexStateChanged(false, null);
doesHaveMutex = false;
Expand All @@ -329,7 +325,7 @@ export function* acquireAnnotationMutexMaybe(): Saga<void> {
runningTryAcquireMutexContinuouslySaga = yield* fork(tryAcquireMutexContinuously);
} else {
// othersMayEdit was turned off. The user editing it should be able to edit the annotation.
yield* put(setAnnotationAllowUpdateAction(true));
yield* put(setAnnotationMutexStateAction(AnnotationMutexStateEnum.NOT_NEEDED));
}
}
yield* takeEvery("SET_OTHERS_MAY_EDIT_FOR_ANNOTATION", reactToOthersMayEditChanges);
Expand Down
13 changes: 7 additions & 6 deletions frontend/javascripts/oxalis/model/sagas/proofread_saga.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ import { takeEveryUnlessBusy } from "./saga_helpers";
import type { Action } from "../actions/actions";
import { isBigInt, isNumberMap, SoftError } from "libs/utils";
import { getCurrentMag } from "../accessors/flycam_accessor";
import { isAnnotationEditingAllowedByFullState } from "../accessors/annotation_accessor";

function runSagaAndCatchSoftError<T>(saga: (...args: any[]) => Saga<T>) {
return function* (...args: any[]) {
Expand Down Expand Up @@ -319,8 +320,8 @@ function* handleSkeletonProofreadingAction(action: Action): Saga<void> {
return;
}

const allowUpdate = yield* select((state) => state.tracing.restrictions.allowUpdate);
if (!allowUpdate) return;
const allowEditing = yield* select((state) => isAnnotationEditingAllowedByFullState(state));
if (!allowEditing) return;

const { sourceNodeId, targetNodeId } = action;
const skeletonTracing = yield* select((state) => enforceSkeletonTracing(state.tracing));
Expand Down Expand Up @@ -691,8 +692,8 @@ function* handleProofreadMergeOrMinCut(action: Action) {
return;
}

const allowUpdate = yield* select((state) => state.tracing.restrictions.allowUpdate);
if (!allowUpdate) return;
const allowEditing = yield* select((state) => isAnnotationEditingAllowedByFullState(state));
if (!allowEditing) return;

const preparation = yield* call(prepareSplitOrMerge, false);
if (!preparation) {
Expand Down Expand Up @@ -884,8 +885,8 @@ function* handleProofreadCutFromNeighbors(action: Action) {
// This action does not depend on the active agglomerate. Instead, it
// only depends on the rightclicked agglomerate.

const allowUpdate = yield* select((state) => state.tracing.restrictions.allowUpdate);
if (!allowUpdate) return;
const allowEditing = yield* select((state) => isAnnotationEditingAllowedByFullState(state));
if (!allowEditing) return;

const preparation = yield* call(prepareSplitOrMerge, false);
if (!preparation) {
Expand Down
Loading