feat: open Changes as a workspace tab (#2298)
Open the complete working comparison in a desktop workspace tab while preserving inline sidebar diffs when the tab is closed. Working and commit comparisons share one diff panel, and workspace layout is the sole owner of live tab persistence and identity. Refs #1520
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
import { useCallback, useEffect, useMemo } from "react";
|
||||
import {
|
||||
buildWorkspaceAttachmentScopeKey,
|
||||
useWorkspaceAttachmentsStore,
|
||||
} from "@/attachments/workspace-attachments-store";
|
||||
import {
|
||||
buildReviewDraftKey,
|
||||
buildReviewDraftScopeKey,
|
||||
useInlineReviewController,
|
||||
useResolvedDiffMode,
|
||||
useReviewAttachmentSnapshot,
|
||||
useSetDiffModeOverride,
|
||||
} from "@/review";
|
||||
import { useCheckoutDiffQuery } from "@/git/use-diff-query";
|
||||
import { useCheckoutStatusQuery } from "@/git/use-status-query";
|
||||
|
||||
interface UseWorkingDiffOptions {
|
||||
serverId: string;
|
||||
workspaceId?: string;
|
||||
cwd: string;
|
||||
ignoreWhitespace: boolean;
|
||||
enabled: boolean;
|
||||
queryScope?: string;
|
||||
}
|
||||
|
||||
export function useWorkingDiff({
|
||||
serverId,
|
||||
workspaceId,
|
||||
cwd,
|
||||
ignoreWhitespace,
|
||||
enabled,
|
||||
queryScope,
|
||||
}: UseWorkingDiffOptions) {
|
||||
const {
|
||||
status,
|
||||
isLoading: isStatusLoading,
|
||||
isError: isStatusError,
|
||||
error: statusError,
|
||||
} = useCheckoutStatusQuery({ serverId, cwd });
|
||||
const gitStatus = status && status.isGit ? status : null;
|
||||
const isGit = Boolean(gitStatus);
|
||||
const notGit = status !== null && !status.isGit && !status.error;
|
||||
const statusErrorMessage =
|
||||
status?.error?.message ??
|
||||
(isStatusError && statusError instanceof Error ? statusError.message : null);
|
||||
const baseRef = gitStatus?.baseRef ?? undefined;
|
||||
const hasUncommittedChanges = Boolean(gitStatus?.isDirty);
|
||||
const currentBranchName =
|
||||
gitStatus?.currentBranch && gitStatus.currentBranch !== "HEAD" ? gitStatus.currentBranch : null;
|
||||
|
||||
const reviewDraftScopeKey = useMemo(
|
||||
() =>
|
||||
buildReviewDraftScopeKey({
|
||||
serverId,
|
||||
workspaceId,
|
||||
cwd,
|
||||
baseRef,
|
||||
ignoreWhitespace,
|
||||
}),
|
||||
[baseRef, cwd, ignoreWhitespace, serverId, workspaceId],
|
||||
);
|
||||
const diffMode = useResolvedDiffMode({
|
||||
scopeKey: reviewDraftScopeKey,
|
||||
hasUncommittedChanges,
|
||||
});
|
||||
const setDiffModeOverride = useSetDiffModeOverride();
|
||||
const selectDiffMode = useCallback(
|
||||
(mode: "uncommitted" | "base") => {
|
||||
setDiffModeOverride({
|
||||
scopeKey: reviewDraftScopeKey,
|
||||
override: { serverId, cwd, mode, isDirtyAtSelection: hasUncommittedChanges },
|
||||
});
|
||||
},
|
||||
[cwd, hasUncommittedChanges, reviewDraftScopeKey, serverId, setDiffModeOverride],
|
||||
);
|
||||
const selectUncommitted = useCallback(() => selectDiffMode("uncommitted"), [selectDiffMode]);
|
||||
const selectBase = useCallback(() => selectDiffMode("base"), [selectDiffMode]);
|
||||
|
||||
const {
|
||||
files,
|
||||
payloadError: diffPayloadError,
|
||||
isLoading: isDiffLoading,
|
||||
} = useCheckoutDiffQuery({
|
||||
serverId,
|
||||
cwd,
|
||||
mode: diffMode,
|
||||
baseRef,
|
||||
ignoreWhitespace,
|
||||
enabled: enabled && isGit,
|
||||
queryScope,
|
||||
});
|
||||
const reviewDraftKey = useMemo(
|
||||
() =>
|
||||
buildReviewDraftKey({
|
||||
serverId,
|
||||
workspaceId,
|
||||
cwd,
|
||||
mode: diffMode,
|
||||
baseRef,
|
||||
ignoreWhitespace,
|
||||
}),
|
||||
[baseRef, cwd, diffMode, ignoreWhitespace, serverId, workspaceId],
|
||||
);
|
||||
const reviewActions = useInlineReviewController({ reviewDraftKey });
|
||||
const reviewAttachment = useReviewAttachmentSnapshot({
|
||||
key: reviewDraftKey,
|
||||
diffFiles: files,
|
||||
cwd,
|
||||
mode: diffMode,
|
||||
baseRef,
|
||||
});
|
||||
|
||||
return {
|
||||
status,
|
||||
isStatusLoading,
|
||||
isGit,
|
||||
notGit,
|
||||
statusErrorMessage,
|
||||
baseRef,
|
||||
currentBranchName,
|
||||
diffMode,
|
||||
selectUncommitted,
|
||||
selectBase,
|
||||
files,
|
||||
diffPayloadError,
|
||||
isDiffLoading,
|
||||
reviewActions,
|
||||
reviewAttachment,
|
||||
};
|
||||
}
|
||||
|
||||
export function usePublishWorkingDiffAttachment({
|
||||
serverId,
|
||||
workspaceId,
|
||||
cwd,
|
||||
attachment,
|
||||
enabled,
|
||||
}: {
|
||||
serverId: string;
|
||||
workspaceId?: string;
|
||||
cwd: string;
|
||||
attachment: ReturnType<typeof useWorkingDiff>["reviewAttachment"];
|
||||
enabled: boolean;
|
||||
}) {
|
||||
const scopeKey = useMemo(
|
||||
() => buildWorkspaceAttachmentScopeKey({ serverId, workspaceId, cwd }),
|
||||
[cwd, serverId, workspaceId],
|
||||
);
|
||||
const setWorkspaceAttachments = useWorkspaceAttachmentsStore(
|
||||
(state) => state.setWorkspaceAttachments,
|
||||
);
|
||||
const clearWorkspaceAttachments = useWorkspaceAttachmentsStore(
|
||||
(state) => state.clearWorkspaceAttachments,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
const attachments = attachment ? [attachment] : [];
|
||||
setWorkspaceAttachments({ scopeKey, attachments });
|
||||
return () => {
|
||||
const current = useWorkspaceAttachmentsStore.getState().attachmentsByScope[scopeKey];
|
||||
if (current === attachments) {
|
||||
clearWorkspaceAttachments({ scopeKey });
|
||||
}
|
||||
};
|
||||
}, [attachment, clearWorkspaceAttachments, enabled, scopeKey, setWorkspaceAttachments]);
|
||||
}
|
||||
Reference in New Issue
Block a user