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
75 changes: 47 additions & 28 deletions apps/lite/ui/src/operations/operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import { DiffSpec, InsertSide, RelativeTo } from "@gitbutler/but-sdk";
import { Operand, operandEquals, operandFileParent } from "#ui/operands.ts";
import { resolveDiffSpecs, useResolveDiffSpecs } from "#ui/operations/diff-specs.ts";
import { decodeBytes } from "#ui/api/bytes.ts";
import type { HeadInfoIndex } from "#ui/api/ref-info.ts";
import { segmentBottomRelativeTo } from "#ui/api/stack.ts";
import { useAppDispatch } from "#ui/store.ts";
import { useParams } from "@tanstack/react-router";
import { errorMessageForToast } from "#ui/errors.ts";
Expand Down Expand Up @@ -58,6 +60,9 @@ type Operation =
| ({ _tag: "MoveBranch" } & MoveBranchOperation);

type OperationWithLabel = { operation: Operation; label: string };
type OperationContext = {
headInfoIndex?: HeadInfoIndex;
};

const commitAmendOperation = (operation: CommitAmendOperation): Operation => ({
_tag: "CommitAmend",
Expand Down Expand Up @@ -423,11 +428,12 @@ const moveOperation = ({
source,
target,
side,
headInfoIndex,
}: {
source: Operand;
target: Operand;
side: InsertSide;
}): OperationWithLabel | null => {
} & OperationContext): OperationWithLabel | null => {
const branchMoveOperation = Match.value({ source, target, side }).pipe(
// This should support `relativeTo`:
// https://linear.app/gitbutler/issue/GB-1161/refsbranches-should-use-bytes-instead-of-strings
Expand Down Expand Up @@ -460,34 +466,41 @@ const moveOperation = ({
Match.when(
{
target: { _tag: "Branch" },
// We use the branch operand as the source/target for the branch
// contents. However, `RelativeTo` is interpreted to mean just the
// branch reference rather than the branch bucket, meaning `side:
// "below"` won't work as expected.
side: "above",
},
({ target }): RelativeTo | null => ({ type: "referenceBytes", subject: target.branchRef }),
),
Match.when(
{
target: { _tag: "Branch" },
side: "below",
},
({ target }): RelativeTo | null => {
const segment = headInfoIndex?.branchContextByRefBytes(target.branchRef)?.segment;
return segment ? segmentBottomRelativeTo(segment) : null;
},
),
Match.orElse((): RelativeTo | null => null),
);

if (!relativeTo) return null;

return Match.value({ source, sourceFileParent: operandFileParent(source) }).pipe(
Match.when(
{ source: { _tag: "Commit" } },
({ source }): OperationWithLabel => ({
operation: commitMoveOperation({
subjectCommitIds: [source.commitId],
relativeTo,
side,
}),
label: Match.value(side).pipe(
Match.when("above", () => "Move above"),
Match.when("below", () => "Move below"),
Match.exhaustive,
),
}),
Match.when({ source: { _tag: "Commit" } }, ({ source }): OperationWithLabel | null =>
relativeTo.type === "commit" && relativeTo.subject === source.commitId
? null
: {
operation: commitMoveOperation({
subjectCommitIds: [source.commitId],
relativeTo,
side,
}),
label: Match.value(side).pipe(
Match.when("above", () => "Move above"),
Match.when("below", () => "Move below"),
Match.exhaustive,
),
},
),
Match.when(
{ sourceFileParent: { _tag: "UncommittedChanges" } },
Expand Down Expand Up @@ -535,7 +548,11 @@ const isOperationSourceEnabled = (source: Operand): boolean =>

export type OperationsByType = Record<OperationType, OperationWithLabel | null>;

export const getOperations = (source: Operand, target: Operand): OperationsByType => {
export const getOperations = (
source: Operand,
target: Operand,
context: OperationContext = {},
): OperationsByType => {
if (operandEquals(source, target) || !isOperationSourceEnabled(source))
return {
into: null,
Expand All @@ -544,17 +561,19 @@ export const getOperations = (source: Operand, target: Operand): OperationsByTyp
};
return {
into: intoOperation({ source, target }),
above: moveOperation({ source, target, side: "above" }),
below: moveOperation({ source, target, side: "below" }),
above: moveOperation({ source, target, side: "above", ...context }),
below: moveOperation({ source, target, side: "below", ...context }),
};
};

export const getOperation = (x: {
source: Operand;
target: Operand;
operationType: OperationType;
}): OperationWithLabel | null => {
const { into, above, below } = getOperations(x.source, x.target);
export const getOperation = (
x: {
source: Operand;
target: Operand;
operationType: OperationType;
} & OperationContext,
): OperationWithLabel | null => {
const { into, above, below } = getOperations(x.source, x.target, x);
return Match.value(x.operationType).pipe(
Match.when("into", () => into),
Match.when("above", () => above),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ const TransferKeyboardOperationControls: FC<{

const target = selection;

const operations = getOperations(mode.source, target);
const operations = getOperations(mode.source, target, { headInfoIndex });
const operation = operations[mode.operationType];

const run = () => {
Expand Down
18 changes: 14 additions & 4 deletions apps/lite/ui/src/routes/project/$id/workspace/OperationTarget.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { operandEquals, type Operand } from "#ui/operands.ts";
import { getHeadInfoIndex } from "#ui/api/ref-info.ts";
import { headInfoQueryOptions } from "#ui/api/queries.ts";
import { parseDragData } from "./DragData.ts";
import styles from "./OperationTarget.module.css";
import {
Expand All @@ -19,6 +21,7 @@ import { mergeProps, Tooltip, useRender } from "@base-ui/react";
import { Match, pipe } from "effect";
import { FC, useEffect, useEffectEvent, useRef } from "react";
import { TooltipPopup } from "#ui/components/Tooltip.tsx";
import { useQuery } from "@tanstack/react-query";

type DropTargetParams = Parameters<typeof dropTargetForElements>[0];
type GetDataArgs = Parameters<NonNullable<DropTargetParams["getData"]>>[0];
Expand Down Expand Up @@ -51,12 +54,16 @@ const useOperationDropTarget = ({
const dispatch = useAppDispatch();
const { mutate: runOperation } = useRunOperation();
const dropRef = useRef<HTMLElement>(null);
const { data: headInfoIndex } = useQuery({
...headInfoQueryOptions(projectId),
select: getHeadInfoIndex,
});

const getData = useEffectEvent(({ input, element, source }: GetDataArgs) => {
const dragData = parseDragData(source.data);
if (!dragData) return {};

const { into, above, below } = getOperations(dragData.source, target);
const { into, above, below } = getOperations(dragData.source, target, { headInfoIndex });
return attachInstruction(
{},
{
Expand Down Expand Up @@ -120,6 +127,7 @@ const useOperationDropTarget = ({
source: dragData.source,
target,
operationType,
headInfoIndex,
})
: null;

Expand All @@ -132,9 +140,9 @@ const useOperationDropTarget = ({
runOperation(operation.operation);
},
});
}, [dispatch, projectId, runOperation, target]);
}, [dispatch, headInfoIndex, projectId, runOperation, target]);

return { dropRef };
return { dropRef, headInfoIndex };
};

export type OperationTargetOutline = "inside" | "outside";
Expand All @@ -149,7 +157,7 @@ export const OperationTarget: FC<
outline: OperationTargetOutline;
} & useRender.ComponentProps<"div">
> = ({ enabled, target, projectId, isSelected, isAbsorptionTarget, outline, render, ...props }) => {
const { dropRef } = useOperationDropTarget({ enabled, target, projectId });
const { dropRef, headInfoIndex } = useOperationDropTarget({ enabled, target, projectId });

const activeTargetOperationType = useAppSelector((state) => {
const outlineMode = selectProjectOutlineModeState(state, projectId);
Expand Down Expand Up @@ -203,6 +211,7 @@ export const OperationTarget: FC<
source: mode.source,
target: mode.target,
operationType: mode.operationType,
headInfoIndex,
})?.label
: undefined,
),
Expand All @@ -213,6 +222,7 @@ export const OperationTarget: FC<
source: mode.source,
target,
operationType: mode.operationType,
headInfoIndex,
})?.label,
),
Match.orElse(() => undefined),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,7 @@ const Stacks: FC<{
}> = ({ projectId, commitTarget }) => {
const navigationIndex = assert(use(NavigationIndexContext));
const { data: headInfo } = useQuery(headInfoQueryOptions(projectId));
const headInfoIndex = headInfo ? getHeadInfoIndex(headInfo) : undefined;
const selection = useOutlineSelection({ projectId, navigationIndex });
const outlineMode = useAppSelector((state) => selectProjectOutlineModeState(state, projectId));

Expand All @@ -520,6 +521,7 @@ const Stacks: FC<{
source: mode.source,
target: mode.target,
operationType: mode.operationType,
headInfoIndex,
})?.operation
: undefined,
),
Expand All @@ -529,6 +531,7 @@ const Stacks: FC<{
source: mode.source,
target: selection,
operationType: mode.operationType,
headInfoIndex,
})?.operation
: undefined,
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import { buildIndexByKey, type NavigationIndex } from "#ui/workspace/navigation-
import { reverse } from "effect/Array";
import { OperationControls } from "#ui/routes/project/$id/workspace/OperationControls.tsx";
import { WorkspacePageErrorBoundary } from "./WorkspacePageErrorBoundary.tsx";
import { getHeadInfoIndex, type HeadInfoIndex } from "#ui/api/ref-info.ts";

// This must be unique as to not collide with other IDs, and stable because it's
// stored in local storage.
Expand Down Expand Up @@ -185,8 +186,8 @@ const outlineNavigationItems = ({
];
};

const hasAnyOperation = (source: Operand, target: Operand) => {
const operations = getOperations(source, target);
const hasAnyOperation = (source: Operand, target: Operand, headInfoIndex?: HeadInfoIndex) => {
const operations = getOperations(source, target, { headInfoIndex });
return !!operations.into || !!operations.above || !!operations.below;
};

Expand All @@ -199,6 +200,7 @@ const useOutlineNavigationIndex = ({
}): NavigationIndex<Operand> => {
const { data: headInfo } = useQuery(headInfoQueryOptions(projectId));
const { data: worktreeChanges } = useQuery(changesInWorktreeQueryOptions(projectId));
const headInfoIndex = headInfo ? getHeadInfoIndex(headInfo) : undefined;

const outlineMode = useAppSelector((state) => selectProjectOutlineModeState(state, projectId));

Expand All @@ -219,7 +221,7 @@ const useOutlineNavigationIndex = ({
items.filter(
(operand) =>
operandContains(operand, activeMode.value.source) ||
hasAnyOperation(activeMode.value.source, operand),
hasAnyOperation(activeMode.value.source, operand, headInfoIndex),
),
RenameBranch: (x) =>
items.filter((operand) => operandEquals(operand, branchOperand(x.operand))),
Expand Down
Loading