Ver a proveniência

Adopts feature gate component in Focus view

main
Eric Amodio há 1 ano
ascendente
cometimento
5665be5db7
5 ficheiros alterados com 190 adições e 305 eliminações
  1. +11
    -45
      src/plus/webviews/focus/focusWebview.ts
  2. +6
    -22
      src/plus/webviews/focus/protocol.ts
  3. +130
    -165
      src/webviews/apps/plus/focus/focus.html
  4. +20
    -0
      src/webviews/apps/plus/focus/focus.scss
  5. +23
    -73
      src/webviews/apps/plus/focus/focus.ts

+ 11
- 45
src/plus/webviews/focus/focusWebview.ts Ver ficheiro

@ -22,8 +22,6 @@ import { RepositoryChange, RepositoryChangeComparisonMode } from '../../../git/m
import { getWorktreeForBranch } from '../../../git/models/worktree';
import { parseGitRemoteUrl } from '../../../git/parsers/remoteParser';
import type { RichRemoteProvider } from '../../../git/remotes/richRemoteProvider';
import type { Subscription } from '../../../subscription';
import { SubscriptionState } from '../../../subscription';
import { executeCommand, registerCommand } from '../../../system/command';
import { setContext } from '../../../system/context';
import type { IpcMessage } from '../../../webviews/protocol';
@ -31,12 +29,7 @@ import { onIpc } from '../../../webviews/protocol';
import type { WebviewController, WebviewProvider } from '../../../webviews/webviewController';
import type { SubscriptionChangeEvent } from '../../subscription/subscriptionService';
import type { OpenWorktreeParams, State, SwitchToBranchParams } from './protocol';
import {
DidChangeStateNotificationType,
DidChangeSubscriptionNotificationType,
OpenWorktreeCommandType,
SwitchToBranchCommandType,
} from './protocol';
import { DidChangeNotificationType, OpenWorktreeCommandType, SwitchToBranchCommandType } from './protocol';
interface RepoWithRichRemote {
repo: Repository;
@ -226,44 +219,19 @@ export class FocusWebviewProvider implements WebviewProvider {
});
}
private async onSubscriptionChanged(e: SubscriptionChangeEvent) {
private onSubscriptionChanged(e: SubscriptionChangeEvent) {
if (e.etag === this._etagSubscription) return;
this._etagSubscription = e.etag;
const access = await this.container.git.access(PlusFeatures.Focus);
const { subscription, isPlus } = await this.getSubscription(access.subscription.current);
if (isPlus) {
void this.notifyDidChangeState();
}
return this.host.notify(DidChangeSubscriptionNotificationType, {
subscription: subscription,
isPlus: isPlus,
});
}
private async getSubscription(subscription?: Subscription) {
const currentSubscription = subscription ?? (await this.container.subscription.getSubscription(true));
const isPlus = ![
SubscriptionState.Free,
SubscriptionState.FreePreviewTrialExpired,
SubscriptionState.FreePlusTrialExpired,
SubscriptionState.VerificationRequired,
].includes(currentSubscription.state);
return {
subscription: currentSubscription,
isPlus: isPlus,
};
void this.notifyDidChangeState();
}
private async getState(deferState = false): Promise<State> {
const { subscription, isPlus } = await this.getSubscription();
if (!isPlus) {
const access = await this.container.git.access(PlusFeatures.Focus);
if (access.allowed !== true) {
return {
timestamp: Date.now(),
isPlus: isPlus,
subscription: subscription,
access: access,
};
}
@ -275,8 +243,7 @@ export class FocusWebviewProvider implements WebviewProvider {
if (deferState || !hasConnectedRepos) {
return {
timestamp: Date.now(),
isPlus: isPlus,
subscription: subscription,
access: access,
repos: (hasConnectedRepos ? connectedRepos : githubRepos).map(r => serializeRepoWithRichRemote(r)),
};
}
@ -299,8 +266,7 @@ export class FocusWebviewProvider implements WebviewProvider {
return {
timestamp: Date.now(),
isPlus: isPlus,
subscription: subscription,
access: access,
pullRequests: serializedPrs,
issues: serializedIssues,
repos: connectedRepos.map(r => serializeRepoWithRichRemote(r)),
@ -310,7 +276,7 @@ export class FocusWebviewProvider implements WebviewProvider {
async includeBootstrap(): Promise<State> {
if (this._bootstrapping) {
const state = await this.getState(true);
if (state.isPlus) {
if (state.access.allowed === true) {
void this.notifyDidChangeState();
}
return state;
@ -460,11 +426,11 @@ export class FocusWebviewProvider implements WebviewProvider {
}
private async notifyDidChangeState() {
if (!this.host.visible) return;
// if (!this.host.visible) return;
const state = await this.getState();
this._bootstrapping = false;
void this.host.notify(DidChangeStateNotificationType, { state: state });
void this.host.notify(DidChangeNotificationType, { state: state });
}
}

+ 6
- 22
src/plus/webviews/focus/protocol.ts Ver ficheiro

@ -1,18 +1,16 @@
import type { FeatureAccess } from '../../../features';
import type { IssueShape } from '../../../git/models/issue';
import type { PullRequestShape } from '../../../git/models/pullRequest';
import type { Subscription } from '../../../subscription';
import { IpcCommandType, IpcNotificationType } from '../../../webviews/protocol';
export type State = {
export interface State {
timestamp: number;
isPlus: boolean;
subscription: Subscription;
access: FeatureAccess;
pullRequests?: PullRequestResult[];
issues?: IssueResult[];
repos?: RepoWithRichProvider[];
[key: string]: unknown;
};
}
export interface SearchResultBase {
reasons: string[];
@ -41,30 +39,16 @@ export interface RepoWithRichProvider {
export interface OpenWorktreeParams {
pullRequest: PullRequestShape;
}
export const OpenWorktreeCommandType = new IpcCommandType<OpenWorktreeParams>('focus/pr/openWorktree');
export interface SwitchToBranchParams {
pullRequest: PullRequestShape;
}
export const SwitchToBranchCommandType = new IpcCommandType<SwitchToBranchParams>('focus/pr/switchToBranch');
// Notifications
export interface DidChangeStateNotificationParams {
export interface DidChangeParams {
state: State;
}
export const DidChangeStateNotificationType = new IpcNotificationType<DidChangeStateNotificationParams>(
'focus/state/didChange',
true,
);
export interface DidChangeSubscriptionParams {
subscription: Subscription;
isPlus: boolean;
}
export const DidChangeSubscriptionNotificationType = new IpcNotificationType<DidChangeSubscriptionParams>(
'graph/subscription/didChange',
);
export const DidChangeNotificationType = new IpcNotificationType<DidChangeParams>('focus/didChange', true);

+ 130
- 165
src/webviews/apps/plus/focus/focus.html Ver ficheiro

@ -4,178 +4,143 @@
<meta charset="utf-8" />
</head>
<body class="preload app" data-placement="#{placement}">
<header class="app__header" id="header">
<span class="badge">Preview</span>
<span class="app__header-group">
<account-badge id="account-badge"></account-badge>
<a
href="https://github.com/gitkraken/vscode-gitlens/discussions/2535"
title="Focus View Feedback"
aria-label="Focus View Feedback"
class="feedback-button"
><code-icon icon="feedback"></code-icon
></a>
</span>
</header>
<body class="preload" data-placement="#{placement}">
<gk-feature-gate id="subscription-gate" class="scrollable"
><p slot="feature">
Helps you focus on what's important by providing you with a comprehensive list of all your pull requests
and issues on your GitHub repos.
</p></gk-feature-gate
>
<gk-feature-gate id="connection-gate" class="scrollable" visible="false">
<h3>No GitHub remotes are connected</h3>
<p>
This enables access to Pull Requests and Issues in the Focus View as well as provide additional
information inside hovers and the Commit Details view, such as auto-linked issues and pull requests and
avatars.
</p>
<gk-button appearance="alert" href="command:gitlens.connectRemoteProvider">Connect to GitHub</gk-button>
</gk-feature-gate>
<div class="app">
<header class="app__header" id="header">
<span class="badge">Preview</span>
<span class="app__header-group">
<account-badge id="account-badge"></account-badge>
<a
href="https://github.com/gitkraken/vscode-gitlens/discussions/2535"
title="Focus View Feedback"
aria-label="Focus View Feedback"
class="feedback-button"
><code-icon icon="feedback"></code-icon
></a>
</span>
</header>
<div class="app__content" id="content">
<div class="app__cover"></div>
<main class="app__main">
<section class="focus-section app__section">
<header class="focus-section__header">
<h2>My Pull Requests</h2>
<nav class="tab-filter" id="pr-filter">
<button class="tab-filter__tab is-active" type="button" data-tab="">All</button>
<button class="tab-filter__tab" type="button" data-tab="authored">Opened by Me</button>
<button class="tab-filter__tab" type="button" data-tab="assigned">Assigned to Me</button>
<button class="tab-filter__tab" type="button" data-tab="review-requested">
Needs my Review
</button>
<button class="tab-filter__tab" type="button" data-tab="mentioned">Mentions Me</button>
</nav>
</header>
<div class="focus-section__content">
<table-container id="pull-requests">
<table-row slot="head">
<table-cell class="data-status" header="column" pinned title="PR status"
><code-icon icon="git-pull-request"></code-icon
></table-cell>
<table-cell class="data-time" header="column" pinned title="Last updated">
<code-icon icon="gl-clock"></code-icon>
</table-cell>
<table-cell class="data-body" header="column" pinned>Pull Request</table-cell>
<!-- <table-cell class="data-author" header="column" pinned>Author</table-cell>
<div class="app__content" id="content">
<div class="app__cover"></div>
<main class="app__main">
<section class="focus-section app__section">
<header class="focus-section__header">
<h2>My Pull Requests</h2>
<nav class="tab-filter" id="pr-filter">
<button class="tab-filter__tab is-active" type="button" data-tab="">All</button>
<button class="tab-filter__tab" type="button" data-tab="authored">Opened by Me</button>
<button class="tab-filter__tab" type="button" data-tab="assigned">
Assigned to Me
</button>
<button class="tab-filter__tab" type="button" data-tab="review-requested">
Needs my Review
</button>
<button class="tab-filter__tab" type="button" data-tab="mentioned">Mentions Me</button>
</nav>
</header>
<div class="focus-section__content">
<table-container id="pull-requests">
<table-row slot="head">
<table-cell class="data-status" header="column" pinned title="PR status"
><code-icon icon="git-pull-request"></code-icon
></table-cell>
<table-cell class="data-time" header="column" pinned title="Last updated">
<code-icon icon="gl-clock"></code-icon>
</table-cell>
<table-cell class="data-body" header="column" pinned>Pull Request</table-cell>
<!-- <table-cell class="data-author" header="column" pinned>Author</table-cell>
<table-cell class="data-assigned" header="column" pinned>Assigned</table-cell> -->
<table-cell class="data-participants" header="column" pinned title="Participants">
<code-icon icon="organization"></code-icon>
</table-cell>
<table-cell class="data-comments" header="column" pinned title="Comments">
<code-icon icon="comment-discussion"></code-icon>
</table-cell>
<table-cell class="data-stats" header="column" pinned title="Change stats">
<code-icon icon="add"></code-icon>
<code-icon icon="dash"></code-icon>
</table-cell>
<table-cell class="data-actions" header="column" pinned
><code-icon icon="blank" title="actions"></code-icon
></table-cell>
</table-row>
</table-container>
<div class="alert" id="loading-pull-requests">
<span class="alert__content"
><code-icon modifier="spin" icon="loading"></code-icon> Loading</span
>
<table-cell class="data-participants" header="column" pinned title="Participants">
<code-icon icon="organization"></code-icon>
</table-cell>
<table-cell class="data-comments" header="column" pinned title="Comments">
<code-icon icon="comment-discussion"></code-icon>
</table-cell>
<table-cell class="data-stats" header="column" pinned title="Change stats">
<code-icon icon="add"></code-icon>
<code-icon icon="dash"></code-icon>
</table-cell>
<table-cell class="data-actions" header="column" pinned
><code-icon icon="blank" title="actions"></code-icon
></table-cell>
</table-row>
</table-container>
<div class="alert" id="loading-pull-requests">
<span class="alert__content"
><code-icon modifier="spin" icon="loading"></code-icon> Loading</span
>
</div>
<div class="alert" id="no-pull-requests">
<span class="alert__content">No pull requests found</span>
</div>
</div>
<div class="alert" id="no-pull-requests">
<span class="alert__content">No pull requests found</span>
</div>
</div>
</section>
<section class="focus-section app__section">
<header class="focus-section__header">
<h2>My Issues</h2>
<nav class="tab-filter" id="issue-filter">
<button class="tab-filter__tab is-active" type="button" data-tab="">All</button>
<button class="tab-filter__tab" type="button" data-tab="authored">Opened by Me</button>
<button class="tab-filter__tab" type="button" data-tab="assigned">Assigned to Me</button>
<button class="tab-filter__tab" type="button" data-tab="mentioned">Mentions Me</button>
</nav>
</header>
<div class="focus-section__content">
<table-container id="issues">
<table-row slot="head">
<table-cell class="data-status" header="column" pinned title="PR status">
<code-icon icon="issues"></code-icon>
</table-cell>
<table-cell class="data-time" header="column" pinned title="Last updated">
<code-icon icon="gl-clock"></code-icon>
</table-cell>
<table-cell header="column" pinned>Title</table-cell>
<!-- <table-cell class="data-author" header="column" pinned>Author</table-cell>
</section>
<section class="focus-section app__section">
<header class="focus-section__header">
<h2>My Issues</h2>
<nav class="tab-filter" id="issue-filter">
<button class="tab-filter__tab is-active" type="button" data-tab="">All</button>
<button class="tab-filter__tab" type="button" data-tab="authored">Opened by Me</button>
<button class="tab-filter__tab" type="button" data-tab="assigned">
Assigned to Me
</button>
<button class="tab-filter__tab" type="button" data-tab="mentioned">Mentions Me</button>
</nav>
</header>
<div class="focus-section__content">
<table-container id="issues">
<table-row slot="head">
<table-cell class="data-status" header="column" pinned title="PR status">
<code-icon icon="issues"></code-icon>
</table-cell>
<table-cell class="data-time" header="column" pinned title="Last updated">
<code-icon icon="gl-clock"></code-icon>
</table-cell>
<table-cell header="column" pinned>Title</table-cell>
<!-- <table-cell class="data-author" header="column" pinned>Author</table-cell>
<table-cell class="data-assigned" header="column" pinned>Assigned</table-cell> -->
<table-cell class="data-participants" header="column" pinned title="Participants">
<code-icon icon="organization"></code-icon>
</table-cell>
<table-cell class="data-comments" header="column" pinned title="Comments">
<code-icon icon="comment-discussion"></code-icon>
</table-cell>
<table-cell class="data-checks" header="column" pinned title="Thumbs up">
<code-icon icon="thumbsup"></code-icon>
</table-cell>
<table-cell class="data-actions" header="column" pinned
><code-icon icon="blank" title="actions"></code-icon
></table-cell>
</table-row>
</table-container>
<div class="alert" id="loading-issues">
<span class="alert__content"
><code-icon modifier="spin" icon="loading"></code-icon> Loading</span
>
</div>
<div class="alert" id="no-issues" hidden>
<span class="alert__content">No issues found</span>
<table-cell class="data-participants" header="column" pinned title="Participants">
<code-icon icon="organization"></code-icon>
</table-cell>
<table-cell class="data-comments" header="column" pinned title="Comments">
<code-icon icon="comment-discussion"></code-icon>
</table-cell>
<table-cell class="data-checks" header="column" pinned title="Thumbs up">
<code-icon icon="thumbsup"></code-icon>
</table-cell>
<table-cell class="data-actions" header="column" pinned
><code-icon icon="blank" title="actions"></code-icon
></table-cell>
</table-row>
</table-container>
<div class="alert" id="loading-issues">
<span class="alert__content"
><code-icon modifier="spin" icon="loading"></code-icon> Loading</span
>
</div>
<div class="alert" id="no-issues" hidden>
<span class="alert__content">No issues found</span>
</div>
</div>
</div>
</section>
</main>
</div>
<div class="overlay" id="account-overlay" hidden>
<div class="overlay__content">
<h2>Focus View ✨</h2>
<p>
This GitLens+ feature provides you with a comprehensive list of all your most important work across
your connected GitHub repos:
</p>
<ul>
<li>
<strong>My Pull Requests:</strong> shows all GitHub PRs opened by you, assigned to you, or
awaiting your review.
</li>
<li>
<strong>My Issues:</strong> shows all issues created by you, assigned to you, or that mention
you.
</li>
</ul>
<plus-content id="plus-content"></plus-content>
</div>
</div>
<div class="overlay" id="connect-overlay" hidden>
<div class="overlay__content">
<h2>Focus View ✨</h2>
<p>
This GitLens+ feature provides you with a comprehensive list of all your most important work across
your connected GitHub repos:
</p>
<ul>
<li>
<strong>My Pull Requests:</strong> shows all GitHub PRs opened by you, assigned to you, or
awaiting your review.
</li>
<li>
<strong>My Issues:</strong> shows all issues created by you, assigned to you, or that mention
you.
</li>
</ul>
<div class="overlay__actions">
<hr class="divider" />
<h3>GitHub remotes are not connected</h3>
<p>
This enables access to Pull Requests and Issues in the Focus View as well as provide additional
information inside hovers and the Commit Details view, such as auto-linked issues and pull
requests and avatars.
</p>
<vscode-button data-action="command:gitlens.connectRemoteProvider">Connect to GitHub</vscode-button>
</div>
</section>
</main>
</div>
</div>
#{endOfBody}
<style nonce="#{cspNonce}">
:root {

+ 20
- 0
src/webviews/apps/plus/focus/focus.scss Ver ficheiro

@ -68,6 +68,22 @@ body {
visibility: hidden;
}
body[data-placement='editor'] {
background-color: var(--color-background);
[data-placement-hidden='editor'],
[data-placement-visible]:not([data-placement-visible='editor']) {
display: none !important;
}
}
body[data-placement='view'] {
[data-placement-hidden='view'],
[data-placement-visible]:not([data-placement-visible='view']) {
display: none !important;
}
}
[hidden] {
display: none !important;
}
@ -98,6 +114,10 @@ p {
margin-top: 0;
}
h3 {
margin-bottom: 0;
}
.tag {
display: inline-block;
padding: 0.1rem 0.2rem;

+ 23
- 73
src/webviews/apps/plus/focus/focus.ts Ver ficheiro

@ -1,20 +1,19 @@
import { provideVSCodeDesignSystem, vsCodeButton } from '@vscode/webview-ui-toolkit';
import type { PullRequestShape } from '../../../../git/models/pullRequest';
import type { State } from '../../../../plus/webviews/focus/protocol';
import {
DidChangeStateNotificationType,
DidChangeSubscriptionNotificationType,
DidChangeNotificationType,
OpenWorktreeCommandType,
SwitchToBranchCommandType,
} from '../../../../plus/webviews/focus/protocol';
import type { IpcMessage } from '../../../protocol';
import { ExecuteCommandType, onIpc } from '../../../protocol';
import { onIpc } from '../../../protocol';
import { App } from '../../shared/appBase';
import type { AccountBadge } from '../../shared/components/account/account-badge';
import type { FeatureGate } from '../../shared/components/feature-gate';
import { DOM } from '../../shared/dom';
import type { IssueRow } from './components/issue-row';
import type { PlusContent } from './components/plus-content';
import type { PullRequestRow } from './components/pull-request-row';
import '../../shared/components/button';
import '../../shared/components/code-icon';
import '../../shared/components/avatars/avatar-item';
import '../../shared/components/avatars/avatar-stack';
@ -23,8 +22,8 @@ import '../../shared/components/table/table-row';
import '../../shared/components/table/table-cell';
import '../../shared/components/account/account-badge';
import './components/issue-row';
import './components/plus-content';
import './components/pull-request-row';
import '../../shared/components/feature-gate';
import './focus.scss';
export class FocusApp extends App<State> {
@ -32,12 +31,10 @@ export class FocusApp extends App {
super('FocusApp');
}
_prFilter?: string;
_issueFilter?: string;
private _prFilter?: string;
private _issueFilter?: string;
override onInitialize() {
this.log(`${this.appName}.onInitialize`);
provideVSCodeDesignSystem().register(vsCodeButton());
this.renderContent();
}
@ -51,29 +48,15 @@ export class FocusApp extends App {
this.renderPullRequests();
}),
),
);
disposables.push(
DOM.on('#issue-filter [data-tab]', 'click', e =>
this.onSelectTab(e, val => {
this._issueFilter = val;
this.renderIssues();
}),
),
);
disposables.push(
DOM.on('[data-action]', 'click', (e, target: HTMLElement) => this.onDataActionClicked(e, target)),
);
disposables.push(
DOM.on<PlusContent, string>('plus-content', 'action', (e, target: HTMLElement) =>
this.onPlusActionClicked(e, target),
),
);
disposables.push(
DOM.on<PullRequestRow, PullRequestShape>('pull-request-row', 'open-worktree', (e, target: HTMLElement) =>
this.onOpenWorktree(e, target),
),
);
disposables.push(
DOM.on<PullRequestRow, PullRequestShape>('pull-request-row', 'switch-branch', (e, target: HTMLElement) =>
this.onSwitchBranch(e, target),
),
@ -92,47 +75,37 @@ export class FocusApp extends App {
this.sendCommand(OpenWorktreeCommandType, { pullRequest: e.detail });
}
private onDataActionClicked(_e: MouseEvent, target: HTMLElement) {
const action = target.dataset.action;
this.onActionClickedCore(action);
}
private onPlusActionClicked(e: CustomEvent<string>, _target: HTMLElement) {
this.onActionClickedCore(e.detail);
}
private onActionClickedCore(action?: string) {
if (action?.startsWith('command:')) {
this.sendCommand(ExecuteCommandType, { command: action.slice(8) });
}
}
protected override onMessageReceived(e: MessageEvent) {
const msg = e.data as IpcMessage;
this.log(`onMessageReceived(${msg.id}): name=${msg.method}`);
switch (msg.method) {
case DidChangeStateNotificationType.method:
onIpc(DidChangeStateNotificationType, msg, params => {
case DidChangeNotificationType.method:
onIpc(DidChangeNotificationType, msg, params => {
this.state = { ...this.state, ...params.state };
this.setState(this.state);
this.renderContent();
});
break;
case DidChangeSubscriptionNotificationType.method:
onIpc(DidChangeSubscriptionNotificationType, msg, params => {
this.state = { ...this.state, subscription: params.subscription, isPlus: params.isPlus };
this.setState(this.state);
this.renderContent();
});
break;
}
}
renderContent() {
this.renderAccountState();
let $gate = document.getElementById('subscription-gate')! as FeatureGate;
if ($gate != null) {
$gate.state = this.state.access.subscription.current.state;
$gate.visible = this.state.access.allowed !== true;
}
$gate = document.getElementById('connection-gate')! as FeatureGate;
if ($gate != null) {
$gate.visible = !(this.state.repos?.some(r => r.isConnected) ?? false);
}
if (this.state.isPlus) {
const badgeEl = document.getElementById('account-badge')! as AccountBadge;
badgeEl.subscription = this.state.access.subscription.current;
if (this.state.access.allowed === true) {
this.renderPullRequests();
this.renderIssues();
}
@ -203,29 +176,6 @@ export class FocusApp extends App {
}
}
renderAccountState() {
const content = document.getElementById('content')!;
const accountOverlay = document.getElementById('account-overlay')!;
const connectOverlay = document.getElementById('connect-overlay')!;
if (!this.state.isPlus) {
content.setAttribute('aria-hidden', 'true');
accountOverlay.removeAttribute('hidden');
connectOverlay.setAttribute('hidden', 'true');
} else if (this.state.repos != null && this.state.repos.some(repo => repo.isConnected) === false) {
content.setAttribute('aria-hidden', 'true');
accountOverlay.setAttribute('hidden', 'true');
connectOverlay.removeAttribute('hidden');
} else {
content.removeAttribute('aria-hidden');
accountOverlay.setAttribute('hidden', 'true');
connectOverlay.setAttribute('hidden', 'true');
}
const badgeEl = document.getElementById('account-badge')! as AccountBadge;
badgeEl.subscription = this.state.subscription;
}
onSelectTab(e: Event, callback?: (val?: string) => void) {
const thisEl = e.target as HTMLElement;
const tab = thisEl.dataset.tab!;

Carregando…
Cancelar
Guardar