From 5f4aaa8d790da39b4ac1a8a00c2eab2350e5ff88 Mon Sep 17 00:00:00 2001 From: Keith Daulton Date: Thu, 23 Jan 2025 19:02:44 -0500 Subject: [PATCH] Splits home overview state by active and inactive branches --- src/webviews/apps/home/home.ts | 17 +- .../apps/plus/home/components/active-work.ts | 32 +- .../apps/plus/home/components/overview.ts | 36 +- .../plus/home/components/overviewState.ts | 64 ++- src/webviews/home/homeWebview.ts | 385 +++++++++--------- src/webviews/home/protocol.ts | 58 ++- 6 files changed, 331 insertions(+), 261 deletions(-) diff --git a/src/webviews/apps/home/home.ts b/src/webviews/apps/home/home.ts index 6454ea7a9d31f..69fe3bebc6ae9 100644 --- a/src/webviews/apps/home/home.ts +++ b/src/webviews/apps/home/home.ts @@ -6,7 +6,12 @@ import { customElement, query } from 'lit/decorators.js'; import { when } from 'lit/directives/when.js'; import type { State } from '../../home/protocol'; import { DidFocusAccount } from '../../home/protocol'; -import { OverviewState, overviewStateContext } from '../plus/home/components/overviewState'; +import { + ActiveOverviewState, + activeOverviewStateContext, + InactiveOverviewState, + inactiveOverviewStateContext, +} from '../plus/home/components/overviewState'; import type { GLHomeHeader } from '../plus/shared/components/home-header'; import { GlApp } from '../shared/app'; import { scrollableBase } from '../shared/components/styles/lit/base.css'; @@ -27,8 +32,11 @@ import './components/repo-alerts'; export class GlHomeApp extends GlApp { static override styles = [homeBaseStyles, scrollableBase, homeStyles]; - @provide({ context: overviewStateContext }) - private _overviewState!: OverviewState; + @provide({ context: activeOverviewStateContext }) + private _activeOverviewState!: ActiveOverviewState; + + @provide({ context: inactiveOverviewStateContext }) + private _inactiveOverviewState!: InactiveOverviewState; @query('gl-home-header') private _header!: GLHomeHeader; @@ -36,7 +44,8 @@ export class GlHomeApp extends GlApp { private badgeSource = { source: 'home', detail: 'badge' }; protected override createStateProvider(state: State, ipc: HostIpc): HomeStateProvider { - this.disposables.push((this._overviewState = new OverviewState(ipc))); + this.disposables.push((this._activeOverviewState = new ActiveOverviewState(ipc))); + this.disposables.push((this._inactiveOverviewState = new InactiveOverviewState(ipc))); return new HomeStateProvider(this, state, ipc); } diff --git a/src/webviews/apps/plus/home/components/active-work.ts b/src/webviews/apps/plus/home/components/active-work.ts index 0796680c15bd9..5cef91673ad98 100644 --- a/src/webviews/apps/plus/home/components/active-work.ts +++ b/src/webviews/apps/plus/home/components/active-work.ts @@ -7,12 +7,12 @@ import { ifDefined } from 'lit/directives/if-defined.js'; import { when } from 'lit/directives/when.js'; import { createCommandLink } from '../../../../../system/commands'; import { createWebviewCommandLink } from '../../../../../system/webview'; -import type { GetOverviewBranch, OpenInGraphParams, State } from '../../../../home/protocol'; +import type { GetActiveOverviewResponse, GetOverviewBranch, OpenInGraphParams, State } from '../../../../home/protocol'; import { stateContext } from '../../../home/context'; import { linkStyles } from '../../shared/components/vscode.css'; import { branchCardStyles, GlBranchCardBase } from './branch-card'; -import type { Overview, OverviewState } from './overviewState'; -import { overviewStateContext } from './overviewState'; +import type { ActiveOverviewState } from './overviewState'; +import { activeOverviewStateContext } from './overviewState'; import '../../../shared/components/button'; import '../../../shared/components/code-icon'; import '../../../shared/components/skeleton-loader'; @@ -62,14 +62,14 @@ export class GlActiveWork extends SignalWatcher(LitElement) { @state() private _homeState!: State; - @consume({ context: overviewStateContext }) - private _overviewState!: OverviewState; + @consume({ context: activeOverviewStateContext }) + private _activeOverviewState!: ActiveOverviewState; override connectedCallback(): void { super.connectedCallback(); if (this._homeState.repositories.openCount > 0) { - this._overviewState.run(); + this._activeOverviewState.run(); } } @@ -82,7 +82,7 @@ export class GlActiveWork extends SignalWatcher(LitElement) { return nothing; } - return this._overviewState.render({ + return this._activeOverviewState.render({ pending: () => this.renderPending(), complete: overview => this.renderComplete(overview), error: () => html`Error`, @@ -99,16 +99,16 @@ export class GlActiveWork extends SignalWatcher(LitElement) { } private renderPending() { - if (this._overviewState.state == null) { + if (this._activeOverviewState.state == null) { return this.renderLoader(); } - return this.renderComplete(this._overviewState.state, true); + return this.renderComplete(this._activeOverviewState.state, true); } - private renderComplete(overview: Overview, isFetching = false) { + private renderComplete(overview: GetActiveOverviewResponse, isFetching = false) { const repo = overview?.repository; - const activeBranches = repo?.branches?.active; - if (!activeBranches) return html`None`; + const activeBranch = overview?.active; + if (!repo || !activeBranch) return html`None`; return html` @@ -138,7 +138,7 @@ export class GlActiveWork extends SignalWatcher(LitElement) { tooltip="Open in Commit Graph" href=${createCommandLink('gitlens.home.openInGraph', { type: 'repo', - repoPath: this._overviewState.state!.repository.path, + repoPath: this._activeOverviewState.state!.repository.path, } satisfies OpenInGraphParams)} > @@ -152,9 +152,7 @@ export class GlActiveWork extends SignalWatcher(LitElement) { > - ${activeBranches.map(branch => { - return this.renderRepoBranchCard(branch, repo.path, isFetching); - })} + ${this.renderRepoBranchCard(activeBranch, repo.path, isFetching)} `; } @@ -191,7 +189,7 @@ export class GlActiveWork extends SignalWatcher(LitElement) { } private onChange(_e: MouseEvent) { - void this._overviewState.changeRepository(); + void this._activeOverviewState.changeRepository(); } } diff --git a/src/webviews/apps/plus/home/components/overview.ts b/src/webviews/apps/plus/home/components/overview.ts index c8a707029913c..8b0e338a39ab1 100644 --- a/src/webviews/apps/plus/home/components/overview.ts +++ b/src/webviews/apps/plus/home/components/overview.ts @@ -3,19 +3,17 @@ import { SignalWatcher } from '@lit-labs/signals'; import { css, html, LitElement, nothing } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { when } from 'lit/directives/when.js'; -import type { GetOverviewResponse, OverviewRecentThreshold, State } from '../../../../home/protocol'; +import type { GetInactiveOverviewResponse, OverviewRecentThreshold, State } from '../../../../home/protocol'; import { SetOverviewFilter } from '../../../../home/protocol'; import { stateContext } from '../../../home/context'; import { ipcContext } from '../../../shared/context'; import type { HostIpc } from '../../../shared/ipc'; import { linkStyles } from '../../shared/components/vscode.css'; -import type { OverviewState } from './overviewState'; -import { overviewStateContext } from './overviewState'; +import type { InactiveOverviewState } from './overviewState'; +import { inactiveOverviewStateContext } from './overviewState'; import '../../../shared/components/skeleton-loader'; import './branch-threshold-filter'; -type Overview = GetOverviewResponse; - export const overviewTagName = 'gl-overview'; @customElement(overviewTagName) @@ -35,14 +33,14 @@ export class GlOverview extends SignalWatcher(LitElement) { @state() private _homeState!: State; - @consume({ context: overviewStateContext }) - private _overviewState!: OverviewState; + @consume({ context: inactiveOverviewStateContext }) + private _inactiveOverviewState!: InactiveOverviewState; override connectedCallback(): void { super.connectedCallback(); if (this._homeState.repositories.openCount > 0) { - this._overviewState.run(); + this._inactiveOverviewState.run(); } } @@ -55,7 +53,7 @@ export class GlOverview extends SignalWatcher(LitElement) { return nothing; } - return this._overviewState.render({ + return this._inactiveOverviewState.render({ pending: () => this.renderPending(), complete: summary => this.renderComplete(summary), error: () => html`Error`, @@ -72,26 +70,26 @@ export class GlOverview extends SignalWatcher(LitElement) { } private renderPending() { - if (this._overviewState.state == null) { + if (this._inactiveOverviewState.state == null) { return this.renderLoader(); } - return this.renderComplete(this._overviewState.state, true); + return this.renderComplete(this._inactiveOverviewState.state, true); } @consume({ context: ipcContext }) private readonly _ipc!: HostIpc; private onChangeRecentThresholdFilter(e: CustomEvent<{ threshold: OverviewRecentThreshold }>) { - if (!this._overviewState.filter.stale || !this._overviewState.filter.recent) { + if (!this._inactiveOverviewState.filter.stale || !this._inactiveOverviewState.filter.recent) { return; } this._ipc.sendCommand(SetOverviewFilter, { - stale: this._overviewState.filter.stale, - recent: { ...this._overviewState.filter.recent, threshold: e.detail.threshold }, + stale: this._inactiveOverviewState.filter.stale, + recent: { ...this._inactiveOverviewState.filter.recent, threshold: e.detail.threshold }, }); } - private renderComplete(overview: Overview, isFetching = false) { + private renderComplete(overview: GetInactiveOverviewResponse, isFetching = false) { if (overview == null) return nothing; const { repository } = overview; return html` @@ -99,7 +97,7 @@ export class GlOverview extends SignalWatcher(LitElement) { label="recent" .isFetching=${isFetching} .repo=${repository.path} - .branches=${repository.branches.recent} + .branches=${overview.recent} > ${when( - this._overviewState.filter.stale?.show === true, + this._inactiveOverviewState.filter.stale?.show === true && overview.stale, () => html` `, )} diff --git a/src/webviews/apps/plus/home/components/overviewState.ts b/src/webviews/apps/plus/home/components/overviewState.ts index 592a88a5634af..3705a54a56735 100644 --- a/src/webviews/apps/plus/home/components/overviewState.ts +++ b/src/webviews/apps/plus/home/components/overviewState.ts @@ -1,33 +1,38 @@ import { createContext } from '@lit/context'; import { signalObject } from 'signal-utils/object'; -import type { GetOverviewResponse, OverviewFilters } from '../../../../home/protocol'; +import type { + GetActiveOverviewResponse, + GetInactiveOverviewResponse, + OverviewFilters, +} from '../../../../home/protocol'; import { ChangeOverviewRepository, DidChangeOverviewFilter, DidChangeRepositories, DidChangeRepositoryWip, - GetOverview, + GetActiveOverview, + GetInactiveOverview, GetOverviewFilterState, } from '../../../../home/protocol'; import { AsyncComputedState } from '../../../shared/components/signal-utils'; import type { Disposable } from '../../../shared/events'; import type { HostIpc } from '../../../shared/ipc'; -export type Overview = GetOverviewResponse; +export type ActiveOverview = GetActiveOverviewResponse; +export type InactiveOverview = GetInactiveOverviewResponse; -export class OverviewState extends AsyncComputedState { +export class ActiveOverviewState extends AsyncComputedState { private readonly _disposable: Disposable | undefined; constructor( private readonly _ipc: HostIpc, options?: { runImmediately?: boolean; - initial?: Overview; + initial?: ActiveOverview; }, ) { super(async _abortSignal => { - const rsp: Overview = await this._ipc.sendRequest(GetOverview, {}); - + const rsp: ActiveOverview = await this._ipc.sendRequest(GetActiveOverview, {}); return rsp; }, options); @@ -39,6 +44,41 @@ export class OverviewState extends AsyncComputedState { case DidChangeRepositoryWip.is(msg): this.run(true); break; + } + }); + } + + dispose() { + this._disposable?.dispose(); + } + + async changeRepository(): Promise { + await this._ipc.sendRequest(ChangeOverviewRepository, undefined); + this.run(true); + } +} + +export class InactiveOverviewState extends AsyncComputedState { + private readonly _disposable: Disposable | undefined; + filter = signalObject>({}); + + constructor( + private readonly _ipc: HostIpc, + options?: { + runImmediately?: boolean; + initial?: InactiveOverview; + }, + ) { + super(async _abortSignal => { + const rsp: InactiveOverview = await this._ipc.sendRequest(GetInactiveOverview, {}); + return rsp; + }, options); + + this._disposable = this._ipc.onReceiveMessage(msg => { + switch (true) { + case DidChangeRepositories.is(msg): + this.run(true); + break; case DidChangeOverviewFilter.is(msg): this.filter.recent = msg.params.filter.recent; this.filter.stale = msg.params.filter.stale; @@ -55,13 +95,7 @@ export class OverviewState extends AsyncComputedState { dispose(): void { this._disposable?.dispose(); } - - filter = signalObject>({}); - - async changeRepository(): Promise { - await this._ipc.sendRequest(ChangeOverviewRepository, undefined); - this.run(true); - } } -export const overviewStateContext = createContext('overviewState'); +export const activeOverviewStateContext = createContext('activeOverviewState'); +export const inactiveOverviewStateContext = createContext('inactiveOverviewState'); diff --git a/src/webviews/home/homeWebview.ts b/src/webviews/home/homeWebview.ts index c8cca88a5128f..37649571c936a 100644 --- a/src/webviews/home/homeWebview.ts +++ b/src/webviews/home/homeWebview.ts @@ -70,9 +70,9 @@ import type { BranchRef, CollapseSectionParams, DidChangeRepositoriesParams, + GetActiveOverviewResponse, + GetInactiveOverviewResponse, GetOverviewBranch, - GetOverviewBranches, - GetOverviewResponse, IntegrationState, OpenInGraphParams, OverviewFilters, @@ -95,8 +95,9 @@ import { DidCompleteDiscoveringRepositories, DidFocusAccount, DismissWalkthroughSection, + GetActiveOverview, + GetInactiveOverview, GetLaunchpadSummary, - GetOverview, GetOverviewFilterState, OpenInGraphCommand, SetOverviewFilter, @@ -128,6 +129,13 @@ type LaunchpadItemInfo = Awaited>[' type PullRequestInfo = Awaited; type WipInfo = Awaited; +const thresholdValues: Record = { + OneDay: 1000 * 60 * 60 * 24 * 1, + OneWeek: 1000 * 60 * 60 * 24 * 7, + OneMonth: 1000 * 60 * 60 * 24 * 30, + OneYear: 1000 * 60 * 60 * 24 * 365, +}; + export class HomeWebviewProvider implements WebviewProvider { private readonly _disposable: Disposable; private _discovering: Promise | undefined; @@ -170,6 +178,7 @@ export class HomeWebviewProvider implements WebviewProvider { + private async getActiveBranchOverview(): Promise { if (this._discovering != null) { await this._discovering; } @@ -687,23 +699,96 @@ export class HomeWebviewProvider implements WebviewProvider this.getBranchOverviewType(branch, worktreesByBranch) === 'active', + )!; - const result: GetOverviewResponse = { - repository: { - ...formattedRepo, - branches: overviewBranches, + const isPro = await this.isSubscriptionPro(); + const [activeOverviewBranch] = getOverviewBranchesCore( + [activeBranch], + branchesAndWorktrees.worktreesByBranch, + isPro, + this.container, + { + isActive: true, + forceStatus: forceWip ? true : undefined, }, + ); + + // TODO: revisit invalidation + if (!forceRepo && forceWip) { + this._invalidateOverview = undefined; + } + + return { + repository: await this.formatRepository(repo), + active: activeOverviewBranch, }; + } - return result; + private async getInactiveBranchOverview(): Promise { + if (this._discovering != null) { + await this._discovering; + } + + const repo = this.getSelectedRepository(); + if (repo == null) return undefined; + + const forceRepo = this._invalidateOverview === 'repo'; + const branchesAndWorktrees = await this.getBranchesData(repo, forceRepo); + + const recentBranches = branchesAndWorktrees.branches.filter( + branch => this.getBranchOverviewType(branch, branchesAndWorktrees.worktreesByBranch) === 'recent', + ); + + let staleBranches: GitBranch[] | undefined; + if (this._overviewBranchFilter.stale.show) { + sortBranches(branchesAndWorktrees.branches, { + missingUpstream: true, + orderBy: 'date:asc', + }); + + for (const branch of branchesAndWorktrees.branches) { + if (staleBranches != null && staleBranches.length > this._overviewBranchFilter.stale.limit) { + break; + } + if (recentBranches.some(b => b.id === branch.id)) { + continue; + } + + if (this.getBranchOverviewType(branch, branchesAndWorktrees.worktreesByBranch) !== 'stale') { + continue; + } + + staleBranches ??= []; + staleBranches.push(branch); + } + } + + const isPro = await this.isSubscriptionPro(); + const recentOverviewBranches = getOverviewBranchesCore( + recentBranches, + branchesAndWorktrees.worktreesByBranch, + isPro, + this.container, + ); + const staleOverviewBranches = + staleBranches == null + ? undefined + : getOverviewBranchesCore(staleBranches, branchesAndWorktrees.worktreesByBranch, isPro, this.container); + + // TODO: revisit invalidation + if (!forceRepo) { + this._invalidateOverview = undefined; + } + + return { + repository: await this.formatRepository(repo), + recent: recentOverviewBranches, + stale: staleOverviewBranches, + }; } private async formatRepository(repo: Repository): Promise<{ @@ -1164,29 +1249,53 @@ export class HomeWebviewProvider implements WebviewProvider = { - OneDay: 1000 * 60 * 60 * 24 * 1, - OneWeek: 1000 * 60 * 60 * 24 * 7, - OneMonth: 1000 * 60 * 60 * 24 * 30, - OneYear: 1000 * 60 * 60 * 24 * 365, -}; + private getBranchOverviewType( + branch: GitBranch, + worktreesByBranch: Map, + ): 'active' | 'recent' | 'stale' | undefined { + if (branch.current || worktreesByBranch.get(branch.id)?.opened) { + return 'active'; + } + + const timestamp = branch.date?.getTime(); + if (timestamp != null) { + const now = Date.now(); -function getOverviewBranches( - branchesData: RepositoryBranchData, + const recentThreshold = now - thresholdValues[this._overviewBranchFilter.recent.threshold]; + if (timestamp > recentThreshold) { + return 'recent'; + } + + const staleThreshold = now - thresholdValues[this._overviewBranchFilter.stale.threshold]; + if (timestamp < staleThreshold) { + return 'stale'; + } + } + + if (branch.upstream?.missing) { + return 'stale'; + } + + return undefined; + } +} + +function getOverviewBranchesCore( + branches: GitBranch[], + worktreesByBranch: Map, + isPro: boolean, container: Container, - filters: OverviewFilters, - options?: { forceActive?: boolean; isPro?: boolean }, -): GetOverviewBranches | undefined { - const { branches, worktreesByBranch } = branchesData; - if (branches.length === 0) return undefined; - - const overviewBranches: GetOverviewBranches = { - active: [], - recent: [], - stale: [], - }; + options?: { + forceStatus?: boolean; + isActive?: boolean; + includeMergeTarget?: boolean; + }, +): GetOverviewBranch[] { + if (branches.length === 0) return []; + + const isActive = options?.isActive ?? false; + const forceOptions = options?.forceStatus ? { force: true } : undefined; let launchpadPromise: Promise | undefined; let repoStatusPromise: Promise | undefined; @@ -1197,173 +1306,72 @@ function getOverviewBranches( const contributorsPromises = new Map>(); const mergeTargetPromises = new Map>(); - const now = Date.now(); - const recentThreshold = now - thresholdValues[filters.recent.threshold]; - + const overviewBranches: GetOverviewBranch[] = []; for (const branch of branches) { const wt = worktreesByBranch.get(branch.id); - const worktree: GetOverviewBranch['worktree'] = wt ? { name: wt.name, uri: wt.uri.toString() } : undefined; const timestamp = branch.date?.getTime(); - if (branch.current || wt?.opened) { - const forceOptions = options?.forceActive ? { force: true } : undefined; - if (options?.isPro !== false) { - prPromises.set(branch.id, getPullRequestInfo(container, branch, launchpadPromise)); - autolinkPromises.set(branch.id, branch.getEnrichedAutolinks()); - issuePromises.set( - branch.id, - getAssociatedIssuesForBranch(container, branch).then(issues => issues.value), - ); - contributorsPromises.set( - branch.id, - container.git.branches(branch.repoPath).getBranchContributionsOverview(branch.ref), - ); - if (branch.current) { - mergeTargetPromises.set(branch.id, getBranchMergeTargetStatusInfo(container, branch)); - } - } - if (wt != null) { - statusPromises.set(branch.id, wt.getStatus(forceOptions)); - } else { - if (repoStatusPromise === undefined) { - repoStatusPromise = container.git.status(branch.repoPath).getStatus(); - } - statusPromises.set(branch.id, repoStatusPromise); + if (isPro === true) { + prPromises.set(branch.id, getPullRequestInfo(container, branch, launchpadPromise)); + autolinkPromises.set(branch.id, branch.getEnrichedAutolinks()); + issuePromises.set( + branch.id, + getAssociatedIssuesForBranch(container, branch).then(issues => issues.value), + ); + contributorsPromises.set( + branch.id, + container.git.branches(branch.repoPath).getBranchContributionsOverview(branch.ref), + ); + if (branch.current) { + mergeTargetPromises.set(branch.id, getBranchMergeTargetStatusInfo(container, branch)); } - - overviewBranches.active.push({ - reference: getReferenceFromBranch(branch), - repoPath: branch.repoPath, - id: branch.id, - name: branch.name, - opened: true, - timestamp: timestamp, - state: branch.state, - status: branch.status, - upstream: branch.upstream, - worktree: worktree, - }); - - continue; } - if (timestamp != null && timestamp > recentThreshold) { - if (options?.isPro !== false) { - prPromises.set(branch.id, getPullRequestInfo(container, branch, launchpadPromise)); - autolinkPromises.set(branch.id, branch.getEnrichedAutolinks()); - issuePromises.set( - branch.id, - getAssociatedIssuesForBranch(container, branch).then(issues => issues.value), - ); - contributorsPromises.set( - branch.id, - container.git.branches(branch.repoPath).getBranchContributionsOverview(branch.ref), - ); - } - - if (wt != null) { - statusPromises.set(branch.id, wt.getStatus()); + if (wt != null) { + statusPromises.set(branch.id, wt.getStatus(forceOptions)); + } else if (isActive === true) { + if (repoStatusPromise === undefined) { + repoStatusPromise = container.git.status(branch.repoPath).getStatus(); } - - overviewBranches.recent.push({ - reference: getReferenceFromBranch(branch), - repoPath: branch.repoPath, - id: branch.id, - name: branch.name, - opened: false, - timestamp: timestamp, - state: branch.state, - status: branch.status, - upstream: branch.upstream, - worktree: worktree, - }); - - continue; + statusPromises.set(branch.id, repoStatusPromise); } - } - if (filters?.stale?.show === true) { - const staleThreshold = now - thresholdValues[filters.stale.threshold]; - sortBranches(branches, { - missingUpstream: true, - orderBy: 'date:asc', + overviewBranches.push({ + reference: getReferenceFromBranch(branch), + repoPath: branch.repoPath, + id: branch.id, + name: branch.name, + opened: isActive, + timestamp: timestamp, + state: branch.state, + status: branch.status, + upstream: branch.upstream, + worktree: wt ? { name: wt.name, uri: wt.uri.toString() } : undefined, }); - for (const branch of branches) { - if (overviewBranches.stale.length > 9) break; - - if ( - overviewBranches.active.some(b => b.id === branch.id) || - overviewBranches.recent.some(b => b.id === branch.id) - ) { - continue; - } - - if (options?.isPro !== false) { - autolinkPromises.set(branch.id, branch.getEnrichedAutolinks()); - issuePromises.set( - branch.id, - getAssociatedIssuesForBranch(container, branch).then(issues => issues.value), - ); - } - - const timestamp = branch.date?.getTime(); - if (branch.upstream?.missing || (timestamp != null && timestamp < staleThreshold)) { - const wt = worktreesByBranch.get(branch.id); - const worktree: GetOverviewBranch['worktree'] = wt - ? { name: wt.name, uri: wt.uri.toString() } - : undefined; - - if (options?.isPro !== false) { - if (!branch.upstream?.missing) { - prPromises.set(branch.id, getPullRequestInfo(container, branch, launchpadPromise)); - } - - contributorsPromises.set( - branch.id, - container.git.branches(branch.repoPath).getBranchContributionsOverview(branch.ref), - ); - } - - if (wt != null) { - statusPromises.set(branch.id, wt.getStatus()); - } - - overviewBranches.stale.push({ - reference: getReferenceFromBranch(branch), - repoPath: branch.repoPath, - id: branch.id, - name: branch.name, - opened: false, - timestamp: timestamp, - state: branch.state, - status: branch.status, - upstream: branch.upstream, - worktree: worktree, - }); - - continue; - } - } } - enrichOverviewBranches( - overviewBranches, - prPromises, - autolinkPromises, - issuePromises, - statusPromises, - contributorsPromises, - mergeTargetPromises, - container, - ); + if (overviewBranches.length > 0) { + enrichOverviewBranchesCore( + overviewBranches, + isActive, + prPromises, + autolinkPromises, + issuePromises, + statusPromises, + contributorsPromises, + mergeTargetPromises, + container, + ); + } return overviewBranches; } // FIXME: support partial enrichment -function enrichOverviewBranches( - overviewBranches: GetOverviewBranches, +function enrichOverviewBranchesCore( + overviewBranches: GetOverviewBranch[], + isActive: boolean, prPromises: Map>, autolinkPromises: Map | undefined>>, issuePromises: Map>, @@ -1372,8 +1380,7 @@ function enrichOverviewBranches( mergeTargetPromises: Map>, container: Container, ) { - for (const branch of [...overviewBranches.active, ...overviewBranches.recent, ...overviewBranches.stale]) { - const isActive = overviewBranches.active.includes(branch); + for (const branch of overviewBranches) { branch.pr = prPromises.get(branch.id); const autolinks = autolinkPromises.get(branch.id); diff --git a/src/webviews/home/protocol.ts b/src/webviews/home/protocol.ts index 17c7f1b440719..a1a77471b50f3 100644 --- a/src/webviews/home/protocol.ts +++ b/src/webviews/home/protocol.ts @@ -49,7 +49,7 @@ export interface OverviewFilters { recent: { threshold: OverviewRecentThreshold; }; - stale: { threshold: OverviewStaleThreshold; show: boolean }; + stale: { threshold: OverviewStaleThreshold; show: boolean; limit: number }; } // REQUESTS @@ -189,27 +189,51 @@ export interface GetOverviewBranch { uri: string; }; } -export interface GetOverviewBranches { - active: GetOverviewBranch[]; - recent: GetOverviewBranch[]; - stale: GetOverviewBranch[]; + +export interface OverviewRepository { + name: string; + path: string; + provider?: { + name: string; + icon?: string; + url?: string; + }; +} + +export interface GetActiveOverviewRequest { + [key: string]: unknown; } -export type GetOverviewResponse = +// TODO: look at splitting off selected repo +export type GetActiveOverviewResponse = | { - repository: { - name: string; - path: string; - provider?: { - name: string; - icon?: string; - url?: string; - }; - branches: GetOverviewBranches; - }; + repository: OverviewRepository; + active: GetOverviewBranch; } | undefined; -export const GetOverview = new IpcRequest(scope, 'overview'); + +export const GetActiveOverview = new IpcRequest( + scope, + 'overview/active', +); + +export interface GetInactiveOverviewRequest { + [key: string]: unknown; +} + +// TODO: look at splitting off selected repo +export type GetInactiveOverviewResponse = + | { + repository: OverviewRepository; + recent: GetOverviewBranch[]; + stale?: GetOverviewBranch[]; + } + | undefined; + +export const GetInactiveOverview = new IpcRequest( + scope, + 'overview/inactive', +); export type GetOverviewFilterStateResponse = OverviewFilters; export const GetOverviewFilterState = new IpcRequest(scope, 'overviewFilter');