瀏覽代碼

Updates focus view authorizations

- adds watchers for repository changes
- adds banner content for account actions
- adds banner for connecting to GitHub
main
Keith Daulton 1 年之前
committed by Keith Daulton
父節點
當前提交
c8a7097600
共有 6 個文件被更改,包括 335 次插入36 次删除
  1. +72
    -25
      src/plus/webviews/workspaces/workspacesWebview.ts
  2. +189
    -0
      src/webviews/apps/plus/workspaces/components/plus-content.ts
  3. +26
    -3
      src/webviews/apps/plus/workspaces/workspaces.html
  4. +4
    -0
      src/webviews/apps/plus/workspaces/workspaces.scss
  5. +43
    -7
      src/webviews/apps/plus/workspaces/workspaces.ts
  6. +1
    -1
      src/webviews/apps/shared/components/account/account-badge.ts

+ 72
- 25
src/plus/webviews/workspaces/workspacesWebview.ts 查看文件

@ -1,4 +1,4 @@
import type { Disposable } from 'vscode';
import { Disposable } from 'vscode';
import { Commands, ContextKeys } from '../../../constants';
import type { Container } from '../../../container';
import { setContext } from '../../../context';
@ -12,7 +12,8 @@ import {
serializePullRequest,
} from '../../../git/models/pullRequest';
import type { GitRemote } from '../../../git/models/remote';
import type { Repository } from '../../../git/models/repository';
import type { Repository, RepositoryChangeEvent } from '../../../git/models/repository';
import { RepositoryChange, RepositoryChangeComparisonMode } from '../../../git/models/repository';
import type { RichRemoteProvider } from '../../../git/remotes/richRemoteProvider';
import type { Subscription } from '../../../subscription';
import { SubscriptionState } from '../../../subscription';
@ -35,6 +36,8 @@ export class WorkspacesWebview extends WebviewBase {
private _pullRequests: SearchedPullRequest[] = [];
private _issues: SearchedIssue[] = [];
private _etagSubscription?: number;
private _repositoryEventsDisposable?: Disposable;
private _repos?: RepoWithRichRemote[];
constructor(container: Container) {
super(
@ -49,6 +52,7 @@ export class WorkspacesWebview extends WebviewBase {
);
this.disposables.push(this.container.subscription.onDidChange(this.onSubscriptionChanged, this));
this.disposables.push(this.container.git.onDidChangeRepositories(() => void this.refresh(true)));
}
protected override registerCommands(): Disposable[] {
@ -110,22 +114,33 @@ export class WorkspacesWebview extends WebviewBase {
private async getState(deferState = false): Promise<State> {
const { subscription, isPlus } = await this.getSubscription();
if (deferState || !isPlus) {
if (!isPlus) {
return {
isPlus: isPlus,
subscription: subscription,
};
}
const richRepos = await this.getRichRepos();
const allRichRepos = await this.getRichRepos();
const githubRepos = filterGithubRepos(allRichRepos);
const connectedRepos = filterUsableRepos(githubRepos);
const hasConnectedRepos = connectedRepos.length > 0;
const prs = await this.getMyPullRequests(richRepos);
if (deferState || !hasConnectedRepos) {
return {
isPlus: isPlus,
subscription: subscription,
repos: (hasConnectedRepos ? connectedRepos : githubRepos).map(r => serializeRepoWithRichRemote(r)),
};
}
const prs = await this.getMyPullRequests(connectedRepos);
const serializedPrs = prs.map(pr => ({
pullRequest: serializePullRequest(pr.pullRequest),
reasons: pr.reasons,
}));
const issues = await this.getMyIssues(richRepos);
const issues = await this.getMyIssues(connectedRepos);
const serializedIssues = issues.map(issue => ({
issue: serializeIssue(issue.issue),
reasons: issue.reasons,
@ -136,6 +151,7 @@ export class WorkspacesWebview extends WebviewBase {
subscription: subscription,
pullRequests: serializedPrs,
issues: serializedIssues,
repos: connectedRepos.map(r => serializeRepoWithRichRemote(r)),
};
}
@ -151,28 +167,44 @@ export class WorkspacesWebview extends WebviewBase {
return this.getState();
}
private async getRichRepos(): Promise<RepoWithRichRemote[]> {
const repos = [];
for (const repo of this.container.git.openRepositories) {
const richRemote = await repo.getRichRemote(true);
if (richRemote == null || repos.findIndex(repo => repo.remote === richRemote) > -1) {
continue;
}
private async getRichRepos(force?: boolean): Promise<RepoWithRichRemote[]> {
if (this._repos == null || force === true) {
const repos = [];
const disposables = [];
for (const repo of this.container.git.openRepositories) {
const richRemote = await repo.getRichRemote();
if (richRemote == null || repos.findIndex(repo => repo.remote === richRemote) > -1) {
continue;
}
repos.push({
repo: repo,
remote: richRemote,
isConnected: await richRemote.provider.isConnected(),
isGitHub: richRemote.provider.name === 'GitHub',
});
disposables.push(repo.onDidChange(this.onRepositoryChanged, this));
repos.push({
repo: repo,
remote: richRemote,
isConnected: await richRemote.provider.isConnected(),
isGitHub: richRemote.provider.name === 'GitHub',
});
}
if (this._repositoryEventsDisposable) {
this._repositoryEventsDisposable.dispose();
this._repositoryEventsDisposable = undefined;
}
this._repositoryEventsDisposable = Disposable.from(...disposables);
this._repos = repos;
}
return repos;
return this._repos;
}
private async onRepositoryChanged(e: RepositoryChangeEvent) {
if (e.changed(RepositoryChange.RemoteProviders, RepositoryChangeComparisonMode.Any)) {
await this.getRichRepos(true);
void this.notifyDidChangeState();
}
}
private async getMyPullRequests(richReposWithRemote?: RepoWithRichRemote[]): Promise<SearchedPullRequest[]> {
// if (this._pullRequests.length === 0) {
const richRepos = richReposWithRemote ?? (await this.getRichRepos());
private async getMyPullRequests(richRepos: RepoWithRichRemote[]): Promise<SearchedPullRequest[]> {
const allPrs = [];
for (const { remote } of richRepos) {
const prs = await this.container.git.getMyPullRequests(remote);
@ -223,9 +255,8 @@ export class WorkspacesWebview extends WebviewBase {
return this._pullRequests;
}
private async getMyIssues(richReposWithRemote?: RepoWithRichRemote[]): Promise<SearchedIssue[]> {
private async getMyIssues(richRepos: RepoWithRichRemote[]): Promise<SearchedIssue[]> {
// if (this._issues.length === 0) {
const richRepos = richReposWithRemote ?? (await this.getRichRepos());
const allIssues = [];
for (const { remote } of richRepos) {
const issues = await this.container.git.getMyIssues(remote);
@ -258,3 +289,19 @@ export class WorkspacesWebview extends WebviewBase {
void this.notify(DidChangeStateNotificationType, { state: state });
}
}
function filterGithubRepos(list: RepoWithRichRemote[]): RepoWithRichRemote[] {
return list.filter(entry => entry.isGitHub);
}
function filterUsableRepos(list: RepoWithRichRemote[]): RepoWithRichRemote[] {
return list.filter(entry => entry.isConnected && entry.isGitHub);
}
function serializeRepoWithRichRemote(entry: RepoWithRichRemote) {
return {
repo: entry.repo.path,
isGitHub: entry.isGitHub,
isConnected: entry.isConnected,
};
}

+ 189
- 0
src/webviews/apps/plus/workspaces/components/plus-content.ts 查看文件

@ -0,0 +1,189 @@
import { attr, css, customElement, FASTElement, html, observable, volatile, when } from '@microsoft/fast-element';
import type { Subscription } from '../../../../../subscription';
import { SubscriptionState } from '../../../../../subscription';
import { focusOutline, srOnly } from '../../../shared/components/styles/a11y';
import { elementBase } from '../../../shared/components/styles/base';
import '../../../shared/components/code-icon';
const template = html<PlusContent>`
<div class="main">
${when(
x => x.state === SubscriptionState.Free,
html<PlusContent>`
<!-- <h3>
GitLens+ features are
<a title="Learn more about GitLens+ features" href="command:gitlens.plus.learn"
>powerful, additional features</a
>
that enhance your GitLens experience.
</h3> -->
<p class="mb-1">
<vscode-button @click="${x => x.fireAction('command:gitlens.plus.startPreviewTrial')}"
>Try the Focus View</vscode-button
>
</p>
`,
)}
${when(
x => x.state === SubscriptionState.FreePreviewTrialExpired,
html<PlusContent>`
<h3>Extend Your GitLens Pro Trial</h3>
<p>
Your free 3-day GitLens Pro trial has ended, extend your trial to get an additional free 7-days of
GitLens+ features on private repos.
</p>
<p class="mb-1">
<vscode-button @click="${x => x.fireAction('command:gitlens.plus.loginOrSignUp')}"
>Extend Pro Trial</vscode-button
>
</p>
`,
)}
${when(
x => x.state === SubscriptionState.FreePlusTrialExpired,
html<PlusContent>`
<h3>GitLens Pro Trial Expired</h3>
<p>
Your GitLens Pro trial has ended, please upgrade to GitLens Pro to continue to use GitLens+ features
on private repos.
</p>
<p class="mb-1">
<vscode-button @click="${x => x.fireAction('command:gitlens.plus.purchase')}"
>Upgrade to Pro</vscode-button
>
</p>
`,
)}
${when(
x => x.state === SubscriptionState.VerificationRequired,
html<PlusContent>`
<h3>Please verify your email</h3>
<p class="alert__message">
Before you can also use GitLens+ features on private repos, please verify your email address.
</p>
<p class="mb-1">
<vscode-button @click="${x => x.fireAction('command:gitlens.plus.resendVerification')}"
>Resend Verification Email</vscode-button
>
</p>
<p class="mb-1">
<vscode-button @click="${x => x.fireAction('command:gitlens.plus.validate')}"
>Refresh Verification Status</vscode-button
>
</p>
`,
)}
</div>
<div class="secondary">
<p class="mb-1">
All other
<a title="Learn more about GitLens+ features" href="command:gitlens.plus.learn">GitLens+ features</a>
are free for local and public repos, no account required, while upgrading to GitLens Pro gives you access on
private repos.
</p>
<p class="mb-0">All other GitLens features can always be used on any repo.</p>
</div>
`;
const styles = css`
${elementBase}
:host {
display: block;
/* text-align: center; */
}
:host(:focus) {
outline: none;
}
a {
color: var(--vscode-textLink-foreground);
text-decoration: none;
}
a:hover {
color: var(--vscode-textLink-activeForeground);
text-decoration: underline;
}
a:focus {
${focusOutline}
}
h3,
p {
margin-top: 0;
}
h3 a {
color: inherit;
text-decoration: underline;
text-decoration-color: var(--color-foreground--50);
}
h3 a:hover {
text-decoration-color: inherit;
}
.mb-1 {
margin-bottom: 0.4rem;
}
.mb-0 {
margin-bottom: 0;
}
.main {
text-align: center;
margin: 3rem 0;
}
.secondary {
font-size: 1.4rem;
}
`;
@customElement({ name: 'plus-content', template: template, styles: styles })
export class PlusContent extends FASTElement {
@observable
subscription?: Subscription;
@volatile
get state(): SubscriptionState {
return this.subscription?.state ?? SubscriptionState.Free;
}
@volatile
get isPro() {
return ![
SubscriptionState.Free,
SubscriptionState.FreePreviewTrialExpired,
SubscriptionState.FreePlusTrialExpired,
SubscriptionState.VerificationRequired,
].includes(this.state);
}
@volatile
get planName() {
const label = this.subscription?.plan.effective.name;
switch (this.state) {
case SubscriptionState.Free:
case SubscriptionState.FreePreviewTrialExpired:
case SubscriptionState.FreePlusTrialExpired:
return 'GitLens Free';
case SubscriptionState.FreeInPreviewTrial:
case SubscriptionState.FreePlusInTrial:
return 'GitLens Pro (Trial)';
case SubscriptionState.VerificationRequired:
return `${label} (Unverified)`;
default:
return label;
}
}
fireAction(command: string) {
this.$emit('action', command);
}
}

+ 26
- 3
src/webviews/apps/plus/workspaces/workspaces.html 查看文件

@ -208,10 +208,33 @@
</menu-list> -->
</div>
<div class="overlay" id="overlay" hidden>
<div class="overlay" id="account-overlay" hidden>
<div class="overlay__content">
The Focus view helps you focus on the work that matters most. It shows you the pull requests and issues
that you are involved in, and hides everything else.
<h2>Focus View ✨</h2>
<p>
This GitLens+ feature gives you 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">
<p>Your GitHub remotes are not connected.</p>
<vscode-button data-action="command:gitlens.connectRemoteProvider">Connect Now</vscode-button>
</div>
</div>

+ 4
- 0
src/webviews/apps/plus/workspaces/workspaces.scss 查看文件

@ -92,6 +92,10 @@ code-icon {
font-size: inherit;
}
h2 {
margin-top: 0;
}
.tag {
display: inline-block;
padding: 0.1rem 0.2rem;

+ 43
- 7
src/webviews/apps/plus/workspaces/workspaces.ts 查看文件

@ -1,14 +1,16 @@
import { provideVSCodeDesignSystem, vsCodeButton } from '@vscode/webview-ui-toolkit';
import type { State } from '../../../../plus/webviews/workspaces/protocol';
import {
DidChangeStateNotificationType,
DidChangeSubscriptionNotificationType,
} from '../../../../plus/webviews/workspaces/protocol';
import type { IpcMessage } from '../../../protocol';
import { onIpc } from '../../../protocol';
import { ExecuteCommandType, onIpc } from '../../../protocol';
import { App } from '../../shared/appBase';
import type { AccountBadge } from '../../shared/components/account/account-badge';
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/code-icon';
import '../../shared/components/avatars/avatar-item';
@ -22,6 +24,7 @@ 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 './workspaces.scss';
@ -35,6 +38,7 @@ export class WorkspacesApp extends App {
override onInitialize() {
this.log(`${this.appName}.onInitialize`);
provideVSCodeDesignSystem().register(vsCodeButton());
this.renderContent();
console.log(this.state);
}
@ -58,9 +62,33 @@ export class WorkspacesApp extends App {
}),
),
);
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),
),
);
return disposables;
}
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}`);
@ -151,13 +179,21 @@ export class WorkspacesApp extends App {
renderAccountState() {
const content = document.getElementById('content')!;
const overlay = document.getElementById('overlay')!;
if (this.state.isPlus) {
content.removeAttribute('aria-hidden');
overlay.setAttribute('hidden', 'true');
} else {
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');
overlay.removeAttribute('hidden');
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;

+ 1
- 1
src/webviews/apps/shared/components/account/account-badge.ts 查看文件

@ -59,7 +59,7 @@ const styles = css`
text-align: left;
}
.badge:not(:hover) + .badge-popover {
.badge:not(:hover) ~ .badge-popover {
display: none;
}
`;

Loading…
取消
儲存