Files
openidea/src/features/github/details/state.ts
T
imacross 059fd8a69b feat(github): pull request tool window, timeline and review flow (#477)
Replaces the Pull Requests tool window with a full review workflow on top of gh's authentication.

- List with search, quick filters and paging; a tab per pull request and a New Pull Request tab.
- Details: commit picker, changes tree with viewed state, CI and reviewer status, merge, close and reopen.
- Timeline editor: comments, review threads, reactions and suggested changes.
- Review in the diff viewer and, for the checked-out branch, in the editor (review mode).
- New generic vcs extension points: registerDiffViewerProvider, lineInlays, reviewGutter, branch widget extensions; new vcs_merge_base and gh_api commands.
- CSP: img-src allows https://avatars.githubusercontent.com for avatars.

Fixed while testing against a real repository: remotes behind an SSH host alias, mergeability stuck on UNKNOWN, Apply Locally staying disabled after a checkout, relative links navigating the app's page, and review inlays wider than the pane.

Co-authored-by: imacross <[email protected]>
2026-10-31 07:50:43 +08:00

80 lines
3.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Client state of the details tab: the changes of single commits (REST `commits/{sha}`,
* cached per commit), the changes tree's grouping (`GithubPullRequestsProjectUISettings
* .changesGrouping`, kept in local storage) and the Submit Review popup's text, kept per
* pull request until the review is submitted (IntelliJ keeps it as the pending review's
* comment).
*/
import { useEffect } from "react";
import { create } from "zustand";
import { errorMessage } from "../api/client";
import { commitChangedFiles } from "../api/requests";
import type { GHCommitFile } from "../api/types";
import type { Loadable } from "../data/pullRequest";
import { useGhRepository } from "../data/repository";
const useCommitFiles = create<{ bySha: Record<string, Loadable<GHCommitFile[]>> }>(() => ({ bySha: {} }));
const EMPTY: Loadable<GHCommitFile[]> = { value: undefined, loading: false, error: null };
function loadCommitFiles(sha: string) {
const cur = useCommitFiles.getState().bySha[sha];
if (cur?.value || cur?.loading) return;
const put = (v: Loadable<GHCommitFile[]>) => useCommitFiles.setState((s) => ({ bySha: { ...s.bySha, [sha]: v } }));
put({ value: undefined, loading: true, error: null });
commitChangedFiles(sha).then(
(value) => put({ value, loading: false, error: null }),
(e: unknown) => put({ value: undefined, loading: false, error: errorMessage(e) }),
);
}
/** The files commit `sha` changes; nothing for null. */
export function useCommitFilesOf(sha: string | null): Loadable<GHCommitFile[]> {
useEffect(() => {
if (sha) loadCommitFiles(sha);
}, [sha]);
return useCommitFiles((s) => (sha ? (s.bySha[sha] ?? EMPTY) : EMPTY));
}
/** Drops the cached commit changes (Refresh). */
export function clearCommitFiles() {
useCommitFiles.setState({ bySha: {} });
}
useGhRepository.subscribe((s, prev) => {
if (s.connection?.coordinates !== prev.connection?.coordinates) clearCommitFiles();
});
// ---------------------------------------------------------------------------
const GROUPING_KEY = "openidea.github.changesGrouping.v1";
function loadGrouping(): boolean {
try {
return globalThis.localStorage?.getItem(GROUPING_KEY) !== "none";
} catch {
return true;
}
}
/** Group By › Directory of the changes tree (on by default). */
export const useChangesGrouping = create<{ byDirectory: boolean }>(() => ({ byDirectory: loadGrouping() }));
export function setGroupByDirectory(on: boolean) {
useChangesGrouping.setState({ byDirectory: on });
try {
globalThis.localStorage?.setItem(GROUPING_KEY, on ? "directory" : "none");
} catch {
/* storage unavailable */
}
}
// ---------------------------------------------------------------------------
/** The Submit Review popup's text per pull request. */
export const useReviewText = create<{ text: Record<number, string> }>(() => ({ text: {} }));
export function setReviewText(number: number, text: string) {
useReviewText.setState((s) => ({ text: { ...s.text, [number]: text } }));
}