дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 03:05:40 +02:00
perf(github): load the pull requests integration after start-up
Actions, the tool window and the timeline editor stay registered at start-up; their implementations, the connection and review-mode tracking load on idle or on first use. Takes ~60 KB gzip off the initial bundle.
Цей коміт міститься в:
1 джерело
eb1015c19a
коміт
b91025a48e
4 files changed
+183
-121
No files matched your search
@@ -10,69 +10,48 @@
|
||||
*/
|
||||
import "./ui/palette.css";
|
||||
import { registerActions, type Action } from "@/platform/actions";
|
||||
import { getContext, setContext } from "@/platform/context";
|
||||
import { useProject } from "@/platform/project";
|
||||
import { setContext } from "@/platform/context";
|
||||
import { lazyComponent } from "@/ui";
|
||||
import {
|
||||
maybeEditorService,
|
||||
registerCloneSource,
|
||||
registerEditorKind,
|
||||
registerOverlay,
|
||||
registerToolWindow,
|
||||
} from "@/workbench/contributions";
|
||||
import { activateReviewDiff, toggleReviewDiffViewed } from "./diff";
|
||||
import { reloadList } from "./data/list";
|
||||
import { checkoutPullRequest, clearDiffCache, copyUrl, openInBrowser, openInWorktree } from "./data/local";
|
||||
import { deleteMergedBranch } from "./data/operations";
|
||||
import { cachedDetails, loadPullRequestData, reloadPullRequest } from "./data/pullRequest";
|
||||
import { refreshConnection, resetConnection, useGhRepository } from "./data/repository";
|
||||
import { showSubmitReview } from "./details/SubmitReviewPopup";
|
||||
import { activateReviewMode } from "./reviewMode";
|
||||
import { useGithubShell } from "./shell";
|
||||
import { githubHandler, handlerContextKey } from "./toolwindow/handlers";
|
||||
import {
|
||||
openCreatePullRequest,
|
||||
openPullRequest,
|
||||
selectOpenedPullRequest,
|
||||
showPullRequests,
|
||||
WINDOW,
|
||||
} from "./toolwindow/open";
|
||||
import {
|
||||
changeRepository,
|
||||
PullRequestsHeaderActions,
|
||||
PullRequestsTabs,
|
||||
PullRequestsToolWindow,
|
||||
usePullRequestsHeaderMenu,
|
||||
} from "./toolwindow/PullRequestsToolWindow";
|
||||
import { contextPullRequest, resetTabs } from "./toolwindow/store";
|
||||
import {
|
||||
activeTimelinePullRequest,
|
||||
openTimeline,
|
||||
TIMELINE_KIND,
|
||||
timelineIcon,
|
||||
timelineTitle,
|
||||
} from "./toolwindow/timelineInput";
|
||||
import { GithubDialogHost, numberDialog } from "./ui/dialogs";
|
||||
import { GithubDialogHost } from "./ui/dialogs";
|
||||
|
||||
const WINDOW = "pullRequests";
|
||||
const TIMELINE_KIND = "github.timeline";
|
||||
const WHEN = "projectOpen";
|
||||
const CONNECTED = "projectOpen && githubConnected";
|
||||
const IN_WINDOW = `activeToolWindow == '${WINDOW}'`;
|
||||
|
||||
/** The pull request an action applies to: its argument, the timeline editor, the active tab or the list selection. */
|
||||
function targetPullRequest(arg: unknown): number | null {
|
||||
if (typeof arg === "number") return arg;
|
||||
return activeTimelinePullRequest() ?? contextPullRequest();
|
||||
/** The pull requests UI and data load after start-up (or on first use); registrations stay here. */
|
||||
type Runtime = typeof import("./runtime");
|
||||
let runtime: Runtime | null = null;
|
||||
let loading: Promise<Runtime> | null = null;
|
||||
let dispose: (() => void) | null = null;
|
||||
let active = false;
|
||||
|
||||
function load(): Promise<Runtime> {
|
||||
return (loading ??= import("./runtime").then(
|
||||
(m) => {
|
||||
runtime = m;
|
||||
if (active && !dispose) dispose = m.activate();
|
||||
return m;
|
||||
},
|
||||
(e: unknown) => {
|
||||
loading = null; // A failed chunk load may succeed later.
|
||||
throw e;
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
async function withPullRequest(arg: unknown, run: (number: number) => unknown) {
|
||||
const n = targetPullRequest(arg);
|
||||
if (n !== null) await run(n);
|
||||
}
|
||||
type ActionSpec = Omit<Action, "run"> & { run?: Action["run"] };
|
||||
|
||||
async function prUrl(number: number): Promise<string> {
|
||||
return cachedDetails(number)?.url ?? (await loadPullRequestData(number, "details")).url;
|
||||
}
|
||||
|
||||
function actions(): Action[] {
|
||||
function actions(): ActionSpec[] {
|
||||
return [
|
||||
// Git › GitHub
|
||||
{
|
||||
@@ -83,7 +62,6 @@ function actions(): Action[] {
|
||||
icon: "toolwindow-pull-requests",
|
||||
keywords: ["github", "pr", "review", "merge request", "code review"],
|
||||
when: WHEN,
|
||||
run: () => void showPullRequests(),
|
||||
},
|
||||
{
|
||||
// Github.Create.Pull.Request
|
||||
@@ -94,7 +72,6 @@ function actions(): Action[] {
|
||||
keywords: ["github", "pr", "new pull request", "start pull request", "merge request", "code review"],
|
||||
when: WHEN,
|
||||
keyWhen: IN_WINDOW,
|
||||
run: () => openCreatePullRequest(),
|
||||
},
|
||||
{
|
||||
id: "github.checkoutPullRequest",
|
||||
@@ -104,11 +81,6 @@ function actions(): Action[] {
|
||||
icon: "branch",
|
||||
keywords: ["github", "pr"],
|
||||
when: WHEN,
|
||||
run: async ({ arg }) => {
|
||||
if (!(await showPullRequests(false))) return;
|
||||
const n = targetPullRequest(arg) ?? (await numberDialog("Checkout Pull Request"));
|
||||
if (n) await checkoutPullRequest(n);
|
||||
},
|
||||
},
|
||||
{
|
||||
// Github.Open.In.Browser
|
||||
@@ -117,12 +89,6 @@ function actions(): Action[] {
|
||||
category: "GitHub",
|
||||
icon: "external-link",
|
||||
when: WHEN,
|
||||
run: async () => {
|
||||
if (!(await showPullRequests(false))) return;
|
||||
const n = targetPullRequest(undefined);
|
||||
const url = n !== null ? await prUrl(n) : useGhRepository.getState().connection?.repository.url;
|
||||
if (url) await openInBrowser(url);
|
||||
},
|
||||
},
|
||||
{
|
||||
// Github.PullRequest.List.Reload
|
||||
@@ -133,10 +99,6 @@ function actions(): Action[] {
|
||||
keywords: ["refresh list", "reload"],
|
||||
when: WHEN,
|
||||
keyWhen: `${IN_WINDOW} && githubPrTab == 'list'`,
|
||||
run: async () => {
|
||||
await refreshConnection();
|
||||
if (useGhRepository.getState().connection) await reloadList();
|
||||
},
|
||||
},
|
||||
// Tool window
|
||||
{
|
||||
@@ -145,8 +107,6 @@ function actions(): Action[] {
|
||||
title: "Change Repository or Account…",
|
||||
category: "GitHub",
|
||||
when: WHEN,
|
||||
// Not connected yet: the tool window offers the repository selector, or explains how to log in.
|
||||
run: () => (useGhRepository.getState().connection ? changeRepository() : void showPullRequests()),
|
||||
},
|
||||
{
|
||||
// pull.request.select.action
|
||||
@@ -155,7 +115,6 @@ function actions(): Action[] {
|
||||
category: "GitHub",
|
||||
icon: "locate",
|
||||
when: `${CONNECTED} && githubTimelineActive`,
|
||||
run: () => selectOpenedPullRequest(),
|
||||
},
|
||||
// A pull request
|
||||
{
|
||||
@@ -165,7 +124,6 @@ function actions(): Action[] {
|
||||
description: "View pull request information and timeline",
|
||||
category: "GitHub",
|
||||
when: CONNECTED,
|
||||
run: ({ arg }) => withPullRequest(arg, (n) => openPullRequest(n)),
|
||||
},
|
||||
{
|
||||
// Github.PullRequest.Timeline.Show
|
||||
@@ -175,7 +133,6 @@ function actions(): Action[] {
|
||||
category: "GitHub",
|
||||
icon: "github-timeline",
|
||||
when: CONNECTED,
|
||||
run: ({ arg }) => withPullRequest(arg, (n) => openTimeline(n, { focus: true })),
|
||||
},
|
||||
{
|
||||
// Github.PullRequest.Details.Reload
|
||||
@@ -185,7 +142,6 @@ function actions(): Action[] {
|
||||
icon: "refresh",
|
||||
when: CONNECTED,
|
||||
keyWhen: `${IN_WINDOW} && githubPrTab == 'pr'`,
|
||||
run: ({ arg }) => withPullRequest(arg, reloadPullRequest),
|
||||
},
|
||||
{
|
||||
// Github.PullRequest.Timeline.Update
|
||||
@@ -196,7 +152,6 @@ function actions(): Action[] {
|
||||
icon: "refresh",
|
||||
when: `${CONNECTED} && githubTimelineActive`,
|
||||
keyWhen: "editorAreaFocus",
|
||||
run: () => withPullRequest(undefined, reloadPullRequest),
|
||||
},
|
||||
{
|
||||
// Github.PullRequest.Open.Link
|
||||
@@ -206,7 +161,6 @@ function actions(): Action[] {
|
||||
icon: "external-link",
|
||||
keywords: ["open pull request in github"],
|
||||
when: CONNECTED,
|
||||
run: ({ arg }) => withPullRequest(arg, async (n) => openInBrowser(await prUrl(n))),
|
||||
},
|
||||
{
|
||||
// Github.PullRequest.Copy.Link
|
||||
@@ -215,7 +169,6 @@ function actions(): Action[] {
|
||||
category: "GitHub",
|
||||
icon: "copy",
|
||||
when: CONNECTED,
|
||||
run: ({ arg }) => withPullRequest(arg, async (n) => copyUrl(await prUrl(n))),
|
||||
},
|
||||
{
|
||||
// Github.PullRequest.CheckoutBranch
|
||||
@@ -225,7 +178,6 @@ function actions(): Action[] {
|
||||
category: "GitHub",
|
||||
icon: "branch",
|
||||
when: CONNECTED,
|
||||
run: ({ arg }) => withPullRequest(arg, checkoutPullRequest),
|
||||
},
|
||||
{
|
||||
// Github.PullRequest.OpenInWorktree
|
||||
@@ -234,7 +186,6 @@ function actions(): Action[] {
|
||||
description: "Fetch and open the pull request branch in a Git worktree",
|
||||
category: "GitHub",
|
||||
when: CONNECTED,
|
||||
run: ({ arg }) => withPullRequest(arg, openInWorktree),
|
||||
},
|
||||
{
|
||||
// GitHub.Timeline.DeleteMergedBranch
|
||||
@@ -245,11 +196,6 @@ function actions(): Action[] {
|
||||
icon: "trash",
|
||||
hidden: true,
|
||||
when: CONNECTED,
|
||||
run: ({ arg }) =>
|
||||
withPullRequest(arg, async (n) => {
|
||||
const ref = (await loadPullRequestData(n, "details")).headRef;
|
||||
if (ref) await deleteMergedBranch(n, ref.id);
|
||||
}),
|
||||
},
|
||||
// Implemented by other parts (`provideGithubHandler`)
|
||||
{
|
||||
@@ -259,12 +205,6 @@ function actions(): Action[] {
|
||||
category: "GitHub",
|
||||
// The details panel's popup; without it (the branch popup in review mode) one of its own.
|
||||
when: `${CONNECTED} && (${handlerContextKey("submitReview")} || githubCurrentBranchPullRequest)`,
|
||||
run: ({ arg }) =>
|
||||
withPullRequest(arg, (n) => {
|
||||
const handler = githubHandler("submitReview");
|
||||
if (handler) handler(n);
|
||||
else void showSubmitReview(n);
|
||||
}),
|
||||
},
|
||||
{
|
||||
// Github.PullRequest.Changed.MarkViewed.Toggle
|
||||
@@ -274,9 +214,6 @@ function actions(): Action[] {
|
||||
category: "GitHub",
|
||||
// The file of the focused review diff, else the details panel's selection.
|
||||
when: `${CONNECTED} && (${handlerContextKey("toggleViewed")} || githubReviewDiff)`,
|
||||
run: () => {
|
||||
if (!(getContext().diffEditorFocus && toggleReviewDiffViewed())) githubHandler("toggleViewed")?.();
|
||||
},
|
||||
},
|
||||
{
|
||||
// Github.Pull.Request.Show.In.Toolwindow
|
||||
@@ -308,7 +245,13 @@ function actions(): Action[] {
|
||||
];
|
||||
}
|
||||
|
||||
const lazyView = (name: "PullRequestsToolWindow" | "PullRequestsTabs" | "PullRequestsHeaderActions") =>
|
||||
lazyComponent<{ id: string }>(() => import("./toolwindow/PullRequestsToolWindow").then((m) => m[name]));
|
||||
|
||||
const useHeaderMenu = () => useGithubShell((s) => s.headerMenu);
|
||||
|
||||
export function activate(): () => void {
|
||||
active = true;
|
||||
setContext("githubPrTab", "list");
|
||||
const disposers = [
|
||||
registerCloneSource({
|
||||
@@ -325,39 +268,28 @@ export function activate(): () => void {
|
||||
anchor: "left",
|
||||
order: 2,
|
||||
stripe: "onUse",
|
||||
component: PullRequestsToolWindow,
|
||||
headerContent: PullRequestsTabs,
|
||||
headerActions: PullRequestsHeaderActions,
|
||||
headerMenu: usePullRequestsHeaderMenu,
|
||||
component: lazyView("PullRequestsToolWindow"),
|
||||
headerContent: lazyView("PullRequestsTabs"),
|
||||
headerActions: lazyView("PullRequestsHeaderActions"),
|
||||
headerMenu: useHeaderMenu,
|
||||
}),
|
||||
registerEditorKind({
|
||||
kind: TIMELINE_KIND,
|
||||
component: lazyComponent(() => import("./timeline/TimelineEditor").then((m) => m.TimelineEditor)),
|
||||
title: timelineTitle,
|
||||
icon: timelineIcon,
|
||||
title: (input) =>
|
||||
runtime?.timeline.title(input) ?? `#${(input.data as { number?: number } | undefined)?.number ?? "?"}`,
|
||||
icon: (input) => runtime?.timeline.icon(input) ?? "github-timeline",
|
||||
}),
|
||||
registerOverlay({ id: "github.dialogs", component: GithubDialogHost }),
|
||||
registerActions(actions()),
|
||||
useGhRepository.subscribe((s, prev) => {
|
||||
setContext("githubConnected", !!s.connection);
|
||||
setContext("githubTimelineActive", activeTimelinePullRequest() !== null);
|
||||
const a = s.connection?.coordinates;
|
||||
const b = prev.connection?.coordinates;
|
||||
if (a?.owner !== b?.owner || a?.name !== b?.name) {
|
||||
resetTabs();
|
||||
clearDiffCache();
|
||||
}
|
||||
}),
|
||||
useProject.subscribe((s, prev) => {
|
||||
if (s.project?.root !== prev.project?.root) resetConnection();
|
||||
}),
|
||||
activateReviewDiff(),
|
||||
activateReviewMode(),
|
||||
registerActions(actions().map((a) => ({ ...a, run: a.run ?? (async (c) => (await load()).runs[a.id]!(c)) }))),
|
||||
];
|
||||
const editors = maybeEditorService();
|
||||
const stopEditors = editors?.onActiveInputChange?.(() =>
|
||||
setContext("githubTimelineActive", activeTimelinePullRequest() !== null),
|
||||
);
|
||||
if (stopEditors) disposers.push(stopEditors);
|
||||
return () => disposers.reverse().forEach((d) => d());
|
||||
// Review mode follows the current branch: start once the window is up.
|
||||
const idle = typeof requestIdleCallback === "function" ? requestIdleCallback : (f: () => void) => setTimeout(f, 200);
|
||||
idle(() => void load().catch((e: unknown) => console.error("could not load GitHub", e)));
|
||||
return () => {
|
||||
active = false;
|
||||
dispose?.();
|
||||
dispose = null;
|
||||
disposers.reverse().forEach((d) => d());
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* The GitHub integration behind `index.ts`'s registrations, loaded after start-up or on
|
||||
* first use: action implementations, the connection and review-mode tracking, review in
|
||||
* the diff viewer.
|
||||
*/
|
||||
import type { ActionContext } from "@/platform/actions";
|
||||
import { getContext, setContext } from "@/platform/context";
|
||||
import { useProject } from "@/platform/project";
|
||||
import { maybeEditorService, type EditorInput } from "@/workbench/contributions";
|
||||
import { activateReviewDiff, toggleReviewDiffViewed } from "./diff";
|
||||
import { reloadList } from "./data/list";
|
||||
import { checkoutPullRequest, clearDiffCache, copyUrl, openInBrowser, openInWorktree } from "./data/local";
|
||||
import { deleteMergedBranch } from "./data/operations";
|
||||
import { cachedDetails, loadPullRequestData, reloadPullRequest } from "./data/pullRequest";
|
||||
import { refreshConnection, resetConnection, useGhRepository } from "./data/repository";
|
||||
import { showSubmitReview } from "./details/SubmitReviewPopup";
|
||||
import { activateReviewMode } from "./reviewMode";
|
||||
import { useGithubShell } from "./shell";
|
||||
import { githubHandler } from "./toolwindow/handlers";
|
||||
import { openCreatePullRequest, openPullRequest, selectOpenedPullRequest, showPullRequests } from "./toolwindow/open";
|
||||
import { changeRepository, headerMenu } from "./toolwindow/PullRequestsToolWindow";
|
||||
import { contextPullRequest, resetTabs } from "./toolwindow/store";
|
||||
import { activeTimelinePullRequest, openTimeline, timelineIcon, timelineTitle } from "./toolwindow/timelineInput";
|
||||
import { numberDialog } from "./ui/dialogs";
|
||||
|
||||
/** The pull request an action applies to: its argument, the timeline editor, the active tab or the list selection. */
|
||||
function targetPullRequest(arg: unknown): number | null {
|
||||
if (typeof arg === "number") return arg;
|
||||
return activeTimelinePullRequest() ?? contextPullRequest();
|
||||
}
|
||||
|
||||
async function withPullRequest(arg: unknown, run: (number: number) => unknown) {
|
||||
const n = targetPullRequest(arg);
|
||||
if (n !== null) await run(n);
|
||||
}
|
||||
|
||||
async function prUrl(number: number): Promise<string> {
|
||||
return cachedDetails(number)?.url ?? (await loadPullRequestData(number, "details")).url;
|
||||
}
|
||||
|
||||
type Run = (c: ActionContext) => void | Promise<void>;
|
||||
|
||||
/** Implementations of the actions `index.ts` registers, by id. */
|
||||
export const runs: Record<string, Run> = {
|
||||
"github.pullRequests": () => void showPullRequests(),
|
||||
"github.createPullRequest": () => openCreatePullRequest(),
|
||||
"github.checkoutPullRequest": async ({ arg }) => {
|
||||
if (!(await showPullRequests(false))) return;
|
||||
const n = targetPullRequest(arg) ?? (await numberDialog("Checkout Pull Request"));
|
||||
if (n) await checkoutPullRequest(n);
|
||||
},
|
||||
"github.openInBrowser": async () => {
|
||||
if (!(await showPullRequests(false))) return;
|
||||
const n = targetPullRequest(undefined);
|
||||
const url = n !== null ? await prUrl(n) : useGhRepository.getState().connection?.repository.url;
|
||||
if (url) await openInBrowser(url);
|
||||
},
|
||||
"github.refresh": async () => {
|
||||
await refreshConnection();
|
||||
if (useGhRepository.getState().connection) await reloadList();
|
||||
},
|
||||
// Not connected yet: the tool window offers the repository selector, or explains how to log in.
|
||||
"github.changeRepository": () =>
|
||||
useGhRepository.getState().connection ? changeRepository() : void showPullRequests(),
|
||||
"github.selectOpenedPullRequest": () => selectOpenedPullRequest(),
|
||||
"github.pullRequest.show": ({ arg }) => withPullRequest(arg, (n) => openPullRequest(n)),
|
||||
"github.pullRequest.showTimeline": ({ arg }) => withPullRequest(arg, (n) => openTimeline(n, { focus: true })),
|
||||
"github.pullRequest.refresh": ({ arg }) => withPullRequest(arg, reloadPullRequest),
|
||||
"github.pullRequest.timeline.refresh": () => withPullRequest(undefined, reloadPullRequest),
|
||||
"github.pullRequest.openInBrowser": ({ arg }) => withPullRequest(arg, async (n) => openInBrowser(await prUrl(n))),
|
||||
"github.pullRequest.copyUrl": ({ arg }) => withPullRequest(arg, async (n) => copyUrl(await prUrl(n))),
|
||||
"github.pullRequest.checkoutBranch": ({ arg }) => withPullRequest(arg, checkoutPullRequest),
|
||||
"github.pullRequest.openInWorktree": ({ arg }) => withPullRequest(arg, openInWorktree),
|
||||
"github.pullRequest.deleteMergedBranch": ({ arg }) =>
|
||||
withPullRequest(arg, async (n) => {
|
||||
const ref = (await loadPullRequestData(n, "details")).headRef;
|
||||
if (ref) await deleteMergedBranch(n, ref.id);
|
||||
}),
|
||||
// The details panel's popup; without it (the branch popup in review mode) one of its own.
|
||||
"github.pullRequest.submitReview": ({ arg }) =>
|
||||
withPullRequest(arg, (n) => {
|
||||
const handler = githubHandler("submitReview");
|
||||
if (handler) handler(n);
|
||||
else void showSubmitReview(n);
|
||||
}),
|
||||
// The file of the focused review diff, else the details panel's selection.
|
||||
"github.pullRequest.toggleViewed": () => {
|
||||
if (!(getContext().diffEditorFocus && toggleReviewDiffViewed())) githubHandler("toggleViewed")?.();
|
||||
},
|
||||
};
|
||||
|
||||
/** The timeline tab's title and icon once the details are known. */
|
||||
export const timeline = {
|
||||
title: (input: EditorInput) => timelineTitle(input),
|
||||
icon: (input: EditorInput) => timelineIcon(input),
|
||||
};
|
||||
|
||||
export function activate(): () => void {
|
||||
const sync = () => {
|
||||
const s = useGhRepository.getState();
|
||||
setContext("githubConnected", !!s.connection);
|
||||
setContext("githubTimelineActive", activeTimelinePullRequest() !== null);
|
||||
useGithubShell.setState({ headerMenu: headerMenu(s) });
|
||||
};
|
||||
sync();
|
||||
const disposers = [
|
||||
useGhRepository.subscribe((s, prev) => {
|
||||
sync();
|
||||
const a = s.connection?.coordinates;
|
||||
const b = prev.connection?.coordinates;
|
||||
if (a?.owner !== b?.owner || a?.name !== b?.name) {
|
||||
resetTabs();
|
||||
clearDiffCache();
|
||||
}
|
||||
}),
|
||||
useProject.subscribe((s, prev) => {
|
||||
if (s.project?.root !== prev.project?.root) resetConnection();
|
||||
}),
|
||||
activateReviewDiff(),
|
||||
activateReviewMode(),
|
||||
];
|
||||
const stopEditors = maybeEditorService()?.onActiveInputChange?.(() =>
|
||||
setContext("githubTimelineActive", activeTimelinePullRequest() !== null),
|
||||
);
|
||||
if (stopEditors) disposers.push(stopEditors);
|
||||
return () => disposers.reverse().forEach((d) => d());
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
/** State `index.ts`'s registrations read before `runtime.ts` has loaded. */
|
||||
import { create } from "zustand";
|
||||
import type { MenuItem } from "@/ui";
|
||||
|
||||
export const useGithubShell = create<{ headerMenu: MenuItem[] }>(() => ({ headerMenu: [] }));
|
||||
@@ -280,10 +280,8 @@ export function PullRequestsHeaderActions() {
|
||||
}
|
||||
|
||||
/** The "⋮" menu's own items: Change Repository or Account… (connected, several GitHub remotes). */
|
||||
export function usePullRequestsHeaderMenu(): MenuItem[] {
|
||||
const connected = useGhRepository((s) => !!s.connection);
|
||||
const several = useGhRepository((s) => s.remotes.length > 1);
|
||||
return connected && several ? CHANGE_REPOSITORY : NONE;
|
||||
export function headerMenu(s: Pick<ReturnType<typeof useGhRepository.getState>, "connection" | "remotes">): MenuItem[] {
|
||||
return s.connection && s.remotes.length > 1 ? CHANGE_REPOSITORY : NONE;
|
||||
}
|
||||
|
||||
const NONE: MenuItem[] = [];
|
||||
|
||||
Reference in new issue
Заблокувати користувача