diff --git a/contributions.json b/contributions.json
index 4f5738a34a7cc..9d38d8525dc7c 100644
--- a/contributions.json
+++ b/contributions.json
@@ -2905,6 +2905,123 @@
]
}
},
+ "gitlens.home.createPullRequest": {
+ "label": "Create Pull Request...",
+ "icon": "$(git-pull-request-create)",
+ "menus": {
+ "webview/context": [
+ {
+ "when": "webviewItem =~ /gitlens:home\\b(?=.*?\\b\\+.*gitlens\\.home\\.createPullRequest\\b)/",
+ "group": "30_gitlens_action",
+ "order": 1
+ }
+ ]
+ }
+ },
+ "gitlens.home.fetch": {
+ "label": "Fetch",
+ "icon": "$(gl-repo-fetch)",
+ "menus": {
+ "webview/context": [
+ {
+ "when": "webviewItem =~ /gitlens:home\\b(?=.*?\\b\\+.*gitlens\\.home\\.fetch\\b)/",
+ "group": "60_gitlens_action",
+ "order": 1
+ }
+ ]
+ }
+ },
+ "gitlens.home.openInGraph": {
+ "label": "Open in Commit Graph",
+ "icon": "$(gl-graph)",
+ "menus": {
+ "webview/context": [
+ {
+ "when": "webviewItem =~ /gitlens:home\\b(?=.*?\\b\\+.*gitlens\\.home\\.openInGraph\\b)/",
+ "group": "80_gitlens_action",
+ "order": 1
+ }
+ ]
+ }
+ },
+ "gitlens.home.openPullRequestChanges": {
+ "label": "Open Pull Request Changes",
+ "icon": "$(request-changes)",
+ "menus": {
+ "webview/context": [
+ {
+ "when": "webviewItem =~ /gitlens:home\\b(?=.*?\\b\\+.*gitlens\\.home\\.openPullRequestChanges\\b)/",
+ "group": "10_gitlens_action",
+ "order": 1
+ }
+ ]
+ }
+ },
+ "gitlens.home.openPullRequestOnRemote": {
+ "label": "Open Pull Request on Remote",
+ "icon": "$(globe)",
+ "menus": {
+ "webview/context": [
+ {
+ "when": "webviewItem =~ /gitlens:home\\b(?=.*?\\b\\+.*gitlens\\.home\\.openPullRequestOnRemote\\b)/",
+ "group": "20_gitlens_action",
+ "order": 1
+ }
+ ]
+ }
+ },
+ "gitlens.home.openWorktree": {
+ "label": "Open Worktree",
+ "icon": "$(browser)",
+ "menus": {
+ "webview/context": [
+ {
+ "when": "webviewItem =~ /gitlens:home\\b(?=.*?\\b\\+.*gitlens\\.home\\.openWorktree\\b)/",
+ "group": "40_gitlens_action",
+ "order": 1
+ }
+ ]
+ }
+ },
+ "gitlens.home.openWorktreeInNewWindow": {
+ "label": "Open Worktree in New Window",
+ "icon": "$(empty-window)",
+ "menus": {
+ "webview/context": [
+ {
+ "when": "webviewItem =~ /gitlens:home\\b(?=.*?\\b\\+.*gitlens\\.home\\.openWorktreeInNewWindow\\b)/",
+ "group": "40_gitlens_action",
+ "order": 1
+ }
+ ]
+ }
+ },
+ "gitlens.home.pull": {
+ "label": "Pull",
+ "icon": "$(gl-repo-pull)",
+ "menus": {
+ "webview/context": [
+ {
+ "when": "webviewItem =~ /gitlens:home\\b(?=.*?\\b\\+.*gitlens\\.home\\.pull\\b)/",
+ "group": "60_gitlens_action",
+ "order": 1
+ }
+ ]
+ }
+ },
+ "gitlens.home.switchToBranch": {
+ "label": "Switch to Branch...",
+ "icon": "$(gl-switch)",
+ "menus": {
+ "webview/context": [
+ {
+ "when": "webviewItem =~ /gitlens:home\\b(?=.*?\\b\\+.*gitlens\\.home\\.switchToBranch\\b)/",
+ "group": "50_gitlens_action",
+ "order": 1
+ }
+ ]
+ }
+ },
"gitlens.inviteToLiveShare": {
"label": "Invite to Live Share",
"icon": "$(live-share)",
diff --git a/package.json b/package.json
index 60b6a016e45be..d424193e65526 100644
--- a/package.json
+++ b/package.json
@@ -6880,6 +6880,51 @@
"icon": "$(discard)",
"enablement": "!operationInProgress"
},
+ {
+ "command": "gitlens.home.createPullRequest",
+ "title": "Create Pull Request...",
+ "icon": "$(git-pull-request-create)"
+ },
+ {
+ "command": "gitlens.home.fetch",
+ "title": "Fetch",
+ "icon": "$(gl-repo-fetch)"
+ },
+ {
+ "command": "gitlens.home.openInGraph",
+ "title": "Open in Commit Graph",
+ "icon": "$(gl-graph)"
+ },
+ {
+ "command": "gitlens.home.openPullRequestChanges",
+ "title": "Open Pull Request Changes",
+ "icon": "$(request-changes)"
+ },
+ {
+ "command": "gitlens.home.openPullRequestOnRemote",
+ "title": "Open Pull Request on Remote",
+ "icon": "$(globe)"
+ },
+ {
+ "command": "gitlens.home.openWorktree",
+ "title": "Open Worktree",
+ "icon": "$(browser)"
+ },
+ {
+ "command": "gitlens.home.openWorktreeInNewWindow",
+ "title": "Open Worktree in New Window",
+ "icon": "$(empty-window)"
+ },
+ {
+ "command": "gitlens.home.pull",
+ "title": "Pull",
+ "icon": "$(gl-repo-pull)"
+ },
+ {
+ "command": "gitlens.home.switchToBranch",
+ "title": "Switch to Branch...",
+ "icon": "$(gl-switch)"
+ },
{
"command": "gitlens.inviteToLiveShare",
"title": "Invite to Live Share",
@@ -10793,6 +10838,42 @@
"command": "gitlens.graph.undoCommit",
"when": "false"
},
+ {
+ "command": "gitlens.home.createPullRequest",
+ "when": "false"
+ },
+ {
+ "command": "gitlens.home.fetch",
+ "when": "false"
+ },
+ {
+ "command": "gitlens.home.openInGraph",
+ "when": "false"
+ },
+ {
+ "command": "gitlens.home.openPullRequestChanges",
+ "when": "false"
+ },
+ {
+ "command": "gitlens.home.openPullRequestOnRemote",
+ "when": "false"
+ },
+ {
+ "command": "gitlens.home.openWorktree",
+ "when": "false"
+ },
+ {
+ "command": "gitlens.home.openWorktreeInNewWindow",
+ "when": "false"
+ },
+ {
+ "command": "gitlens.home.pull",
+ "when": "false"
+ },
+ {
+ "command": "gitlens.home.switchToBranch",
+ "when": "false"
+ },
{
"command": "gitlens.inviteToLiveShare",
"when": "false"
@@ -18736,6 +18817,51 @@
"when": "webviewItem =~ /gitlens:graph:(columns|settings)\\b/",
"group": "3_columns@2"
},
+ {
+ "command": "gitlens.home.openPullRequestChanges",
+ "when": "webviewItem =~ /gitlens:home\\b(?=.*?\\b\\+.*gitlens\\.home\\.openPullRequestChanges\\b)/",
+ "group": "10_gitlens_action@1"
+ },
+ {
+ "command": "gitlens.home.openPullRequestOnRemote",
+ "when": "webviewItem =~ /gitlens:home\\b(?=.*?\\b\\+.*gitlens\\.home\\.openPullRequestOnRemote\\b)/",
+ "group": "20_gitlens_action@1"
+ },
+ {
+ "command": "gitlens.home.createPullRequest",
+ "when": "webviewItem =~ /gitlens:home\\b(?=.*?\\b\\+.*gitlens\\.home\\.createPullRequest\\b)/",
+ "group": "30_gitlens_action@1"
+ },
+ {
+ "command": "gitlens.home.openWorktree",
+ "when": "webviewItem =~ /gitlens:home\\b(?=.*?\\b\\+.*gitlens\\.home\\.openWorktree\\b)/",
+ "group": "40_gitlens_action@1"
+ },
+ {
+ "command": "gitlens.home.openWorktreeInNewWindow",
+ "when": "webviewItem =~ /gitlens:home\\b(?=.*?\\b\\+.*gitlens\\.home\\.openWorktreeInNewWindow\\b)/",
+ "group": "40_gitlens_action@1"
+ },
+ {
+ "command": "gitlens.home.switchToBranch",
+ "when": "webviewItem =~ /gitlens:home\\b(?=.*?\\b\\+.*gitlens\\.home\\.switchToBranch\\b)/",
+ "group": "50_gitlens_action@1"
+ },
+ {
+ "command": "gitlens.home.fetch",
+ "when": "webviewItem =~ /gitlens:home\\b(?=.*?\\b\\+.*gitlens\\.home\\.fetch\\b)/",
+ "group": "60_gitlens_action@1"
+ },
+ {
+ "command": "gitlens.home.pull",
+ "when": "webviewItem =~ /gitlens:home\\b(?=.*?\\b\\+.*gitlens\\.home\\.pull\\b)/",
+ "group": "60_gitlens_action@1"
+ },
+ {
+ "command": "gitlens.home.openInGraph",
+ "when": "webviewItem =~ /gitlens:home\\b(?=.*?\\b\\+.*gitlens\\.home\\.openInGraph\\b)/",
+ "group": "80_gitlens_action@1"
+ },
{
"command": "gitlens.graph.openPullRequestChanges",
"when": "webviewItem =~ /gitlens:pullrequest\\b(?=.*?\\b\\+refs\\b)/ && config.multiDiffEditor.experimental.enabled",
diff --git a/src/constants.commands.ts b/src/constants.commands.ts
index 79bc6912724b5..cd28f3b8e59b7 100644
--- a/src/constants.commands.ts
+++ b/src/constants.commands.ts
@@ -695,8 +695,10 @@ type HomeWebviewCommands = `home.${
| 'openPullRequestDetails'
| 'createPullRequest'
| 'openWorktree'
+ | 'openWorktreeInNewWindow'
| 'switchToBranch'
| 'fetch'
+ | 'pull'
| 'openInGraph'
| 'createBranch'
| 'mergeIntoCurrent'
diff --git a/src/webviews/apps/home/home.html b/src/webviews/apps/home/home.html
index 75db0cbb1bbd5..50bc7413ac406 100644
--- a/src/webviews/apps/home/home.html
+++ b/src/webviews/apps/home/home.html
@@ -19,7 +19,7 @@
diff --git a/src/webviews/apps/plus/home/components/active-work.ts b/src/webviews/apps/plus/home/components/active-work.ts
index 8ffd065b17bb5..9297650051f9b 100644
--- a/src/webviews/apps/plus/home/components/active-work.ts
+++ b/src/webviews/apps/plus/home/components/active-work.ts
@@ -9,14 +9,10 @@ import { createCommandLink } from '../../../../../system/commands';
import { createWebviewCommandLink } from '../../../../../system/webview';
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 { ActiveOverviewState } from './overviewState';
-import { activeOverviewStateContext } from './overviewState';
+import type { ActionList } from '../../../shared/components/actions/action-list';
import '../../../shared/components/button';
-import '../../../shared/components/code-icon';
-import '../../../shared/components/skeleton-loader';
import '../../../shared/components/card/card';
+import '../../../shared/components/code-icon';
import '../../../shared/components/commit/commit-stats';
import '../../../shared/components/menu/menu-item';
import '../../../shared/components/overlays/popover';
@@ -24,7 +20,12 @@ import '../../../shared/components/overlays/tooltip';
import '../../../shared/components/pills/tracking';
import '../../../shared/components/rich/issue-icon';
import '../../../shared/components/rich/pr-icon';
+import '../../../shared/components/skeleton-loader';
import '../../shared/components/merge-rebase-status';
+import { linkStyles } from '../../shared/components/vscode.css';
+import { branchCardStyles, GlBranchCardBase } from './branch-card';
+import type { ActiveOverviewState } from './overviewState';
+import { activeOverviewStateContext } from './overviewState';
export const activeWorkTagName = 'gl-active-work';
@@ -376,27 +377,27 @@ export class GlActiveBranchCard extends GlBranchCardBase {
>`;
}
- protected getBranchActions(): TemplateResult[] {
+ protected getBranchActions(): (typeof ActionList.ItemProps)[] {
return [];
}
- protected getPrActions(): TemplateResult[] {
+ protected getPrActions(): (typeof ActionList.ItemProps)[] {
return [
- html``,
- html``,
- html``,
+ {
+ label: 'Open Pull Request Changes',
+ icon: 'request-changes',
+ href: this.createCommandLink('gitlens.home.openPullRequestChanges'),
+ },
+ {
+ label: 'Compare Pull Request',
+ icon: 'git-compare',
+ href: this.createCommandLink('gitlens.home.openPullRequestComparison'),
+ },
+ {
+ label: 'Open Pull Request Details',
+ icon: 'eye',
+ href: this.createCommandLink('gitlens.home.openPullRequestDetails'),
+ },
];
}
}
diff --git a/src/webviews/apps/plus/home/components/branch-card.ts b/src/webviews/apps/plus/home/components/branch-card.ts
index 25719547fe40e..9fb395dc52deb 100644
--- a/src/webviews/apps/plus/home/components/branch-card.ts
+++ b/src/webviews/apps/plus/home/components/branch-card.ts
@@ -16,22 +16,22 @@ import { createCommandLink } from '../../../../../system/commands';
import { fromNow } from '../../../../../system/date';
import { interpolate, pluralize } from '../../../../../system/string';
import type { BranchRef, GetOverviewBranch, OpenInGraphParams } from '../../../../home/protocol';
+import '../../../shared/components/actions/action-list';
+import type { ActionList } from '../../../shared/components/actions/action-list';
+import '../../../shared/components/avatar/avatar';
+import '../../../shared/components/avatar/avatar-list';
+import '../../../shared/components/branch-icon';
import { renderBranchName } from '../../../shared/components/branch-name';
import type { GlCard } from '../../../shared/components/card/card';
-import { GlElement, observe } from '../../../shared/components/element';
-import { srOnlyStyles } from '../../../shared/components/styles/lit/a11y.css';
-import { linkStyles } from '../../shared/components/vscode.css';
import '../../../shared/components/code-icon';
-import '../../../shared/components/avatar/avatar';
-import '../../../shared/components/avatar/avatar-list';
import '../../../shared/components/commit/commit-stats';
+import { GlElement, observe } from '../../../shared/components/element';
import '../../../shared/components/formatted-date';
import '../../../shared/components/pills/tracking';
import '../../../shared/components/rich/issue-icon';
import '../../../shared/components/rich/pr-icon';
-import '../../../shared/components/actions/action-item';
-import '../../../shared/components/actions/action-nav';
-import '../../../shared/components/branch-icon';
+import { srOnlyStyles } from '../../../shared/components/styles/lit/a11y.css';
+import { linkStyles } from '../../shared/components/vscode.css';
import './merge-target-status';
export const branchCardStyles = css`
@@ -76,11 +76,6 @@ export const branchCardStyles = css`
font-size: 0.9em;
}
- /* :empty selector doesn't work with lit */
- .branch-item__actions:not(:has(*)) {
- display: none;
- }
-
.branch-item__icon {
color: var(--vscode-descriptionForeground);
flex: none;
@@ -412,6 +407,10 @@ export abstract class GlBranchCardBase extends GlElement {
this.attachFocusListener();
}
+ static get OpenContextMenuEvent(): CustomEvent<{ items: (typeof ActionList.ItemProps)[]; branchRefs: BranchRef }> {
+ throw new Error('type field OpenContextMenuEvent cannot be used as a value');
+ }
+
get branchRef(): BranchRef {
return {
repoPath: this.repo,
@@ -607,20 +606,38 @@ export abstract class GlBranchCardBase extends GlElement {
>`;
}
- protected abstract getBranchActions(): TemplateResult[];
- protected renderBranchActions(): TemplateResult | NothingType {
+ protected renderActions(actions: (typeof ActionList.ItemProps)[]) {
+ return html` {
+ const ev = new CustomEvent('open-actions-menu', {
+ detail: { items: e.detail.items, branchRefs: this.branchRef },
+ }) satisfies typeof GlBranchCard.OpenContextMenuEvent;
+ this.dispatchEvent(ev);
+ }}
+ @close-actions-menu=${() => {
+ const ev = new CustomEvent('close-actions-menu');
+ this.dispatchEvent(ev);
+ }}
+ .items=${actions}
+ >`;
+ }
+
+ protected abstract getBranchActions(): (typeof ActionList.ItemProps)[];
+ protected renderBranchActions() {
const actions = this.getBranchActions?.();
if (!actions?.length) return nothing;
- return html`${actions}`;
+ return this.renderActions(actions);
}
- protected abstract getPrActions(): TemplateResult[];
+ protected abstract getPrActions(): (typeof ActionList.ItemProps)[];
protected renderPrActions(): TemplateResult | NothingType {
const actions = this.getPrActions?.();
if (!actions?.length) return nothing;
- return html`${actions}`;
+ return this.renderActions(actions);
}
protected createCommandLink(command: Commands, args?: T | any): string {
@@ -847,66 +864,76 @@ export class GlBranchCard extends GlBranchCardBase {
`;
}
- protected getBranchActions(): TemplateResult[] {
- const actions = [];
+ protected getBranchActions(): (typeof ActionList.ItemProps)[] {
+ const actions: (typeof ActionList.ItemProps)[] = [];
if (this.branch.worktree) {
- actions.push(
- html``,
- );
+ actions.push({
+ label: 'Open Worktree',
+ icon: 'browser',
+ href: this.createCommandLink('gitlens.home.openWorktree'),
+ modifiers: [
+ {
+ key: 'alt',
+ label: 'Open Worktree in New Window',
+ href: this.createCommandLink('gitlens.home.openWorktreeInNewWindow'),
+ icon: 'empty-window',
+ },
+ ],
+ });
} else {
- actions.push(
- html``,
- );
+ actions.push({
+ label: 'Switch to Branch...',
+ icon: 'gl-switch',
+ href: this.createCommandLink('gitlens.home.switchToBranch'),
+ });
}
// branch actions
- actions.push(
- html``,
- );
- actions.push(
- html``,
- );
+ actions.push({
+ label: 'Fetch',
+ icon: 'repo-fetch',
+ href: this.createCommandLink('gitlens.home.fetch'),
+ modifiers: this.branch.upstream && [
+ {
+ key: 'alt',
+ label: 'Pull',
+ icon: 'repo-pull',
+ href: this.createCommandLink('gitlens.home.pull'),
+ },
+ ],
+ });
+ actions.push({
+ label: 'Open in Commit Graph',
+ icon: 'gl-graph',
+ href: createCommandLink('gitlens.home.openInGraph', {
+ ...this.branchRef,
+ type: 'branch',
+ } satisfies OpenInGraphParams),
+ });
return actions;
}
- protected getPrActions(): TemplateResult[] {
+ protected getPrActions(): (typeof ActionList.ItemProps)[] {
return [
- html``,
- html``,
- html``,
+ {
+ label: 'Open Pull Request Changes',
+ icon: 'request-changes',
+ href: this.createCommandLink('gitlens.home.openPullRequestChanges'),
+ },
+
+ {
+ label: 'Compare Pull Request',
+ icon: 'git-compare',
+ href: this.createCommandLink('gitlens.home.openPullRequestComparison'),
+ },
+
+ {
+ label: 'Open Pull Request Details',
+ icon: 'eye',
+ href: this.createCommandLink('gitlens.home.openPullRequestDetails'),
+ },
];
}
diff --git a/src/webviews/apps/plus/home/components/branch-section.ts b/src/webviews/apps/plus/home/components/branch-section.ts
index abbe694230490..fa692ae75a8dd 100644
--- a/src/webviews/apps/plus/home/components/branch-section.ts
+++ b/src/webviews/apps/plus/home/components/branch-section.ts
@@ -3,9 +3,22 @@ import { customElement, property, queryAll } from 'lit/decorators.js';
import { ifDefined } from 'lit/directives/if-defined.js';
import { when } from 'lit/directives/when.js';
import { debounce } from '../../../../../system/function';
-import type { GetOverviewBranch } from '../../../../home/protocol';
-import type { GlBranchCardBase } from './branch-card';
+import type { BranchRef, GetOverviewBranch } from '../../../../home/protocol';
+import '../../../shared/components/actions/action-item';
+import '../../../shared/components/actions/action-list';
+import type { ActionList } from '../../../shared/components/actions/action-list';
+import '../../../shared/components/actions/action-nav';
+import '../../../shared/components/avatar/avatar';
+import '../../../shared/components/avatar/avatar-list';
+import '../../../shared/components/card/card';
+import '../../../shared/components/code-icon';
+import '../../../shared/components/commit/commit-stats';
+import '../../../shared/components/formatted-date';
+import '../../../shared/components/pills/tracking';
import '../../../shared/components/progress';
+import '../../../shared/components/rich/issue-icon';
+import '../../../shared/components/rich/pr-icon';
+import type { GlBranchCard, GlBranchCardBase } from './branch-card';
@customElement('gl-section')
export class GlSection extends LitElement {
@@ -70,6 +83,10 @@ export class GlSection extends LitElement {
@customElement('gl-branch-section')
export class GlBranchSection extends LitElement {
+ static get OpenContextMenuEvent(): CustomEvent<{ items: (typeof ActionList.ItemProps)[]; branchRefs: BranchRef }> {
+ throw new Error('type field OpenContextMenuEvent cannot be used as a value');
+ }
+
@property({ type: String }) label!: string;
@property() repo!: string;
@property({ type: Array }) branches!: GetOverviewBranch[];
@@ -124,7 +141,31 @@ export class GlBranchSection extends LitElement {
() =>
this.branches.map(
branch =>
- html``,
+ html` {
+ const evt = new CustomEvent('branch-context-opened', {
+ detail: {
+ branchRefs: e.detail.branchRefs,
+ items: e.detail.items,
+ },
+ }) satisfies typeof GlBranchSection.OpenContextMenuEvent;
+ this.dispatchEvent(evt);
+ }}
+ @close-actions-menu=${(e: CustomEvent) => {
+ const evt = new CustomEvent<{
+ branch: GetOverviewBranch;
+ }>('branch-context-closed', {
+ detail: {
+ branch: branch,
+ },
+ });
+ this.dispatchEvent(evt);
+ console.log('closeVContext', { e: e }, branch);
+ }}
+ .repo=${this.repo}
+ .branch=${branch}
+ >`,
),
() => html`No ${this.label} branches
`,
)}
diff --git a/src/webviews/apps/plus/home/components/overview.ts b/src/webviews/apps/plus/home/components/overview.ts
index 8b0e338a39ab1..01f21eed63428 100644
--- a/src/webviews/apps/plus/home/components/overview.ts
+++ b/src/webviews/apps/plus/home/components/overview.ts
@@ -6,13 +6,14 @@ import { when } from 'lit/directives/when.js';
import type { GetInactiveOverviewResponse, OverviewRecentThreshold, State } from '../../../../home/protocol';
import { SetOverviewFilter } from '../../../../home/protocol';
import { stateContext } from '../../../home/context';
+import '../../../shared/components/skeleton-loader';
import { ipcContext } from '../../../shared/context';
import type { HostIpc } from '../../../shared/ipc';
import { linkStyles } from '../../shared/components/vscode.css';
+import type { GlBranchSection } from './branch-section';
+import './branch-threshold-filter';
import type { InactiveOverviewState } from './overviewState';
import { inactiveOverviewStateContext } from './overviewState';
-import '../../../shared/components/skeleton-loader';
-import './branch-threshold-filter';
export const overviewTagName = 'gl-overview';
@@ -89,6 +90,32 @@ export class GlOverview extends SignalWatcher(LitElement) {
});
}
+ // TODO: can be moved to a separate function (maybe for home scope only)
+ private applyContext(context: object) {
+ const prevContext = JSON.parse(document.body.getAttribute('data-vscode-context') ?? '{}');
+ document.body.setAttribute(
+ 'data-vscode-context',
+ JSON.stringify({
+ ...prevContext,
+ ...context,
+ }),
+ );
+ // clear context immediatelly after the contextmenu is opened to avoid randomly clicked contextmenu being filled
+ setTimeout(() => {
+ document.body.setAttribute('data-vscode-context', JSON.stringify(prevContext));
+ });
+ }
+
+ private handleBranchContext(e: typeof GlBranchSection.OpenContextMenuEvent) {
+ let context = 'gitlens:home';
+ e.detail.items.forEach(x => {
+ if (x.href) {
+ context += `+${x.href}`;
+ }
+ });
+ this.applyContext({ webviewItem: context, ...e.detail.branchRefs, type: 'branch' });
+ }
+
private renderComplete(overview: GetInactiveOverviewResponse, isFetching = false) {
if (overview == null) return nothing;
const { repository } = overview;
@@ -98,6 +125,7 @@ export class GlOverview extends SignalWatcher(LitElement) {
.isFetching=${isFetching}
.repo=${repository.path}
.branches=${overview.recent}
+ @branch-context-opened=${this.handleBranchContext}
>
-
-
-
-
- `;
+ private renderButtonContent(): TemplateResult {
+ return html`
+
+ `;
+ }
+
+ override render() {
+ if (this.selected) {
+ return this.renderButtonContent();
+ }
+ return html` ${this.renderButtonContent()} `;
}
override focus(options?: FocusOptions): void {
diff --git a/src/webviews/apps/shared/components/actions/action-list.ts b/src/webviews/apps/shared/components/actions/action-list.ts
new file mode 100644
index 0000000000000..cb541f19b4748
--- /dev/null
+++ b/src/webviews/apps/shared/components/actions/action-list.ts
@@ -0,0 +1,158 @@
+import { html, LitElement } from 'lit';
+import { customElement, property, state } from 'lit/decorators.js';
+import { ifDefined } from 'lit/directives/if-defined.js';
+import { when } from 'lit/directives/when.js';
+import { isMac } from '@env/platform';
+import './action-item';
+import './action-nav';
+
+interface ActionItemProps {
+ icon: string;
+ label: string;
+ href?: string;
+ modifiers?: { key: 'ctrl' | 'alt'; icon: string; label: string; href?: string }[];
+}
+
+@customElement('action-list')
+export class ActionList extends LitElement {
+ static get ItemProps(): ActionItemProps {
+ throw new Error('type field ItemProps cannot be used as a value');
+ }
+ static get OpenContextMenuEvent(): CustomEvent<{ items: ActionItemProps[] }> {
+ throw new Error('type field OpenContextMenuEvent cannot be used as a value');
+ }
+ private _slotSubscriptionsDisposer?: () => void;
+ @property({ type: Array })
+ private items: Array = [];
+
+ @property({ type: Number })
+ private limit: number = 3;
+
+ @state()
+ private modifier: 'ctrl' | 'alt' | undefined;
+
+ override connectedCallback(): void {
+ const handleKeydown = this.handleKeydown.bind(this);
+ const handleKeyup = this.handleKeyup.bind(this);
+ window.addEventListener('keydown', handleKeydown, false);
+ window.addEventListener('keyup', handleKeyup, false);
+ this._slotSubscriptionsDisposer = () => {
+ window.removeEventListener('keydown', handleKeydown, false);
+ window.removeEventListener('keyup', handleKeyup, false);
+ };
+ super.connectedCallback();
+ }
+
+ override disconnectedCallback() {
+ this._slotSubscriptionsDisposer?.();
+ super.disconnectedCallback();
+ }
+
+ /** is used to remove tooltip under the context menu */
+ @state()
+ private open = false;
+
+ private handleMoreActions(from: number, e: MouseEvent) {
+ if (e.button !== 0) {
+ return;
+ }
+ e.preventDefault();
+
+ this.open = true;
+ const event = new CustomEvent('open-actions-menu', {
+ detail: {
+ items: this.items
+ .slice(from)
+ .map((item): ActionItemProps[] => [item, ...(item.modifiers ?? [])])
+ .flat(),
+ },
+ }) satisfies typeof ActionList.OpenContextMenuEvent;
+ this.dispatchEvent(event);
+
+ const contextMenuEvent = new PointerEvent('contextmenu', {
+ bubbles: true,
+ cancelable: true,
+ composed: true,
+ view: window,
+ button: 2,
+ buttons: 2,
+ clientX: this.getBoundingClientRect().right,
+ clientY: this.getBoundingClientRect().bottom,
+ });
+ this.dispatchEvent(contextMenuEvent);
+ this.modifier = undefined;
+
+ const handleClick = () => {
+ this.open = false;
+ window.removeEventListener('keyup', handleClick);
+ window.removeEventListener('mousedown', handleClick);
+ window.removeEventListener('mousemove', handleClick);
+ window.removeEventListener('blur', handleClick);
+ };
+ setTimeout(() => {
+ window.addEventListener('keyup', handleClick);
+ window.addEventListener('mousedown', handleClick);
+ window.addEventListener('mousemove', handleClick);
+ window.addEventListener('blur', handleClick);
+ });
+ }
+
+ private renderMoreOptions(from: number) {
+ return html`
+
+
+ `;
+ }
+
+ override render() {
+ const hasMore = this.items.length > this.limit;
+ const splitValue = hasMore ? this.limit - 1 : this.items.length;
+ return html`
+
+ ${this.items.slice(0, splitValue).map(({ modifiers, ...originalProps }) => {
+ const { icon, label, href } = modifiers?.find(x => this.modifier === x.key) ?? originalProps;
+ return html` {
+ // finish event handling and clear modifier in next macrotask
+ setTimeout(() => {
+ this.modifier = undefined;
+ });
+ }}
+ label=${label}
+ href=${ifDefined(href)}
+ >`;
+ })}
+ ${when(hasMore, this.renderMoreOptions.bind(this, splitValue))}
+
+ `;
+ }
+
+ // TODO it would be fine to think about hover-to-focus behavior for all places that use this component
+ private handleKeydown(e: KeyboardEvent) {
+ if (this.modifier) {
+ return;
+ }
+ if (e.key === 'Alt') {
+ this.modifier = 'alt';
+ } else if ((isMac && e.key === 'Meta') || (!isMac && e.key === 'Control')) {
+ this.modifier = 'ctrl';
+ }
+ }
+
+ private handleKeyup(e: KeyboardEvent) {
+ if (!this.modifier) {
+ return;
+ }
+ if (e.key === 'Alt' || (isMac && e.key === 'Meta') || (!isMac && e.key === 'Control')) {
+ this.modifier = undefined;
+ }
+ }
+}
diff --git a/src/webviews/home/homeWebview.ts b/src/webviews/home/homeWebview.ts
index 3431fe6d5ec3b..02ee5c5c021ab 100644
--- a/src/webviews/home/homeWebview.ts
+++ b/src/webviews/home/homeWebview.ts
@@ -273,17 +273,6 @@ export class HomeWebviewProvider implements WebviewProvider b.id === ref.branchId);
+ if (branch == null) return;
+
+ void RepoActions.pull(repo!.repo, getReferenceFromBranch(branch));
+ }
+
private findBranch(ref: BranchRef): GitBranch | undefined {
const branches = this._repositoryBranches.get(ref.repoPath)?.branches;
return branches?.find(b => b.id === ref.branchId);