Browse Source

updates home view content

- adds popover with features message
- adds ability to toggle views layout
- rearranges GL+ features section content
- swaps plus and integrations sections
- moves restore GL+ to GL+ features banner
- adds side bar close button
main
Keith Daulton 2 years ago
parent
commit
4726c9b458
13 changed files with 806 additions and 281 deletions
  1. +3
    -1
      src/commands/setViewsLayout.ts
  2. +2
    -0
      src/storage.ts
  3. +23
    -12
      src/webviews/apps/home/components/header-card.ts
  4. +225
    -0
      src/webviews/apps/home/components/plus-banner.ts
  5. +44
    -120
      src/webviews/apps/home/components/plus-content.ts
  6. +91
    -115
      src/webviews/apps/home/home.html
  7. +195
    -24
      src/webviews/apps/home/home.scss
  8. +45
    -6
      src/webviews/apps/home/home.ts
  9. BIN
      src/webviews/apps/media/gitlens-backdrop-opacity.png
  10. +107
    -0
      src/webviews/apps/shared/components/overlays/pop-over.ts
  11. +0
    -1
      src/webviews/apps/shared/components/styles/base.ts
  12. +57
    -2
      src/webviews/home/homeWebviewView.ts
  13. +14
    -0
      src/webviews/home/protocol.ts

+ 3
- 1
src/commands/setViewsLayout.ts View File

@ -5,7 +5,7 @@ import type { Container } from '../container';
import { command, executeCommand, executeCoreCommand } from '../system/command';
import { Command } from './base';
enum ViewsLayout {
export enum ViewsLayout {
GitLens = 'gitlens',
SourceControl = 'scm',
}
@ -47,6 +47,8 @@ export class SetViewsLayoutCommand extends Command {
layout = pick.layout;
}
void this.container.storage.store('views:layout', layout);
switch (layout) {
case ViewsLayout.GitLens:
try {

+ 2
- 0
src/storage.ts View File

@ -160,6 +160,7 @@ export interface GlobalStorage {
// Keep the pre-release version separate from the released version
preVersion?: string;
views: {
layout?: StoredViewsLayout;
welcome: {
visible?: boolean;
};
@ -212,6 +213,7 @@ export interface WorkspaceStorage {
};
}
export type StoredViewsLayout = 'gitlens' | 'scm';
export interface Stored<T, SchemaVersion extends number = 1> {
v: SchemaVersion;
data: T;

+ 23
- 12
src/webviews/apps/home/components/header-card.ts View File

@ -3,6 +3,7 @@ import { SubscriptionState } from '../../../../subscription';
import { pluralize } from '../../../../system/string';
import { numberConverter } from '../../shared/components/converters/number-converter';
import '../../shared/components/code-icon';
import '../../shared/components/overlays/pop-over';
const template = html<HeaderCard>`
<div class="header-card__media"><img class="header-card__image" src="${x => x.image}" alt="GitLens Logo" /></div>
@ -11,11 +12,13 @@ const template = html`
${when(x => x.name !== '', html<HeaderCard>`${x => x.name}`)}
</h1>
<p class="header-card__account">
<span
class="status"
title="Can access GitLens+ features on ${x => (x.isPro ? 'any repo' : 'local & public repos')}"
><span class="repo-access${x => (x.isPro ? ' is-pro' : '')}"></span>${x => x.planName}</span
>
<span class="status">
<span class="repo-access${x => (x.isPro ? ' is-pro' : '')}"></span>${x => x.planName}
<pop-over>
You have access to GitLens+ features on ${x => (x.isPro ? 'any repo' : 'local & public repos')}, and all
other GitLens features on any repo.
</pop-over>
</span>
<span class="account-actions">
${when(
x => !x.hasAccount,
@ -52,12 +55,6 @@ const template = html`
</div>
<span class="actions">
${when(
x => x.state === SubscriptionState.Free,
html<HeaderCard>`<a class="action is-primary" href="command:gitlens.plus.startPreviewTrial"
>Start Pro Trial</a
>`,
)}
${when(
x => x.state === SubscriptionState.FreePreviewTrialExpired,
html<HeaderCard>`<a class="action is-primary" href="command:gitlens.plus.loginOrSignUp"
>Extend Pro Trial</a
@ -99,9 +96,12 @@ const styles = css`
:host {
position: relative;
display: grid;
/*
padding: 1rem 1rem 1.2rem;
background-color: var(--card-background);
border-radius: 0.4rem;
*/
padding: 1rem 0 1.2rem;
gap: 0 0.8rem;
grid-template-columns: 3.4rem auto;
grid-auto-flow: column;
@ -143,6 +143,7 @@ const styles = css`
}
.header-card__account {
position: relative;
margin: 0;
display: flex;
flex-direction: row;
@ -176,8 +177,10 @@ const styles = css`
position: absolute;
bottom: 0;
left: 0;
/*
border-bottom-left-radius: 0.4rem;
border-bottom-right-radius: 0.4rem;
*/
}
.brand {
@ -185,12 +188,20 @@ const styles = css`
}
.status {
color: var(--color-foreground--65);
cursor: help;
}
.status pop-over {
top: 1.6em;
left: 0;
}
.status:not(:hover) pop-over {
display: none;
}
.repo-access {
font-size: 1.1em;
margin-right: 0.2rem;
cursor: help;
}
.repo-access:not(.is-pro) {
filter: grayscale(1) brightness(0.7);

+ 225
- 0
src/webviews/apps/home/components/plus-banner.ts View File

@ -0,0 +1,225 @@
import { attr, css, customElement, FASTElement, html, volatile, when } from '@microsoft/fast-element';
import { SubscriptionState } from '../../../../subscription';
import { pluralize } from '../../../../system/string';
import { numberConverter } from '../../shared/components/converters/number-converter';
import '../../shared/components/code-icon';
const template = html<PlusBanner>`
${when(
x => x.state === SubscriptionState.Free,
html<PlusBanner>`
<h3>
<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 GitLens+ features on private repos</vscode-button
>
</p>
<p class="mb-0">
${when(
x => x.plus,
html<PlusBanner>`<a class="minimal" href="command:gitlens.plus.hide">Hide GitLens+ features</a>`,
)}
${when(
x => !x.plus,
html<PlusBanner>`<a href="command:gitlens.plus.restore">Restore GitLens+ features</a>`,
)}
</p>
`,
)}
${when(
x => x.state === SubscriptionState.Paid,
html<PlusBanner>`
<h3>Welcome to ${x => x.planName}!</h3>
<p class="mb-0">
You have access to
<a title="Learn more about GitLens+ features" href="command:gitlens.plus.learn">GitLens+ features</a>
on any repo.
</p>
`,
)}
${when(
x => x.state === SubscriptionState.FreeInPreviewTrial,
html<PlusBanner>`
<h3>GitLens Pro Trial</h3>
<p>
You have ${x => x.daysRemaining} left in your 3-day GitLens Pro trial. Don't worry if you need more
time, you can extend your trial for an additional free 7-days of
<a title="Learn more about GitLens+ features" href="command:gitlens.plus.learn">GitLens+ features</a> 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.FreePlusInTrial,
html<PlusBanner>`
<h3>GitLens Pro Trial</h3>
<p class="mb-1">
You have ${x => x.daysRemaining} left in your GitLens Pro trial. Once your trial ends, you'll continue
to have access to
<a title="Learn more about GitLens+ features" href="command:gitlens.plus.learn">GitLens+ features</a> on
local and public repos, while upgrading to GitLens Pro gives you access on private repos.
</p>
`,
)}
${when(
x => x.state === SubscriptionState.FreePreviewTrialExpired,
html<PlusBanner>`
<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<PlusBanner>`
<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<PlusBanner>`
<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>
`,
)}
`;
const styles = css`
* {
box-sizing: border-box;
}
:host {
display: block;
text-align: center;
}
a {
color: var(--vscode-textLink-foreground);
text-decoration: none;
}
a:focus {
outline-color: var(--focus-border);
}
a:hover {
text-decoration: underline;
}
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;
}
.minimal {
color: var(--color-foreground--50);
font-size: 1rem;
position: relative;
top: -0.2rem;
}
`;
@customElement({ name: 'plus-banner', template: template, styles: styles })
export class PlusBanner extends FASTElement {
@attr({ converter: numberConverter })
days = 0;
@attr({ converter: numberConverter })
state: SubscriptionState = SubscriptionState.Free;
@attr
plan = '';
@attr
visibility: 'local' | 'public' | 'mixed' | 'private' = 'public';
@attr({ mode: 'boolean' })
plus = true;
get daysRemaining() {
if (this.days < 1) {
return 'less than one day';
}
return pluralize('day', this.days);
}
get isFree() {
return ['local', 'public'].includes(this.visibility);
}
@volatile
get planName() {
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 `${this.plan} (Unverified)`;
default:
return this.plan;
}
}
fireAction(command: string) {
this.$emit('action', command);
}
}

+ 44
- 120
src/webviews/apps/home/components/plus-content.ts View File

@ -5,116 +5,26 @@ import { numberConverter } from '../../shared/components/converters/number-conve
import '../../shared/components/code-icon';
const template = html<PlusContent>`
${when(
x => x.state === SubscriptionState.Free,
html<PlusContent>`
<p>All-new, powerful, additional features that enhance your GitLens experience.</p>
<p>
GitLens+ features 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-1">
<vscode-button @click="${x => x.fireAction('command:gitlens.plus.startPreviewTrial')}"
>Try GitLens+ features on private repos</vscode-button
>
</p>
<p class="mb-1">
<a class="minimal" href="command:gitlens.plus.hide">Hide GitLens+ features</a>
</p>
`,
)}
${when(
x => x.state === SubscriptionState.Paid,
html<PlusContent>`
<h3>Welcome to ${x => x.planName}!</h3>
<p>
You have access to
<a title="Learn more about GitLens+ features" href="command:gitlens.plus.learn">GitLens+ features</a>
on any repo.
</p>
`,
)}
${when(
x => x.state === SubscriptionState.FreeInPreviewTrial,
html<PlusContent>`
<h3>GitLens Pro Trial</h3>
<p>
You have ${x => x.daysRemaining} left in your 3-day
<a title="Learn more about GitLens+ features" href="command:gitlens.plus.learn">GitLens Pro trial</a>.
Don't worry if you need more time, you can extend your trial for an additional free 7-days of GitLens+
features on private repos.
</p>
<p>
<vscode-button @click="${x => x.fireAction('command:gitlens.plus.purchase')}"
>Upgrade to Pro</vscode-button
>
</p>
`,
)}
${when(
x => x.state === SubscriptionState.FreePlusInTrial,
html<PlusContent>`
<h3>GitLens Pro Trial</h3>
<p>
You have ${x => x.daysRemaining} left in your
<a title="Learn more about GitLens+ features" href="command:gitlens.plus.learn">GitLens Pro trial</a>.
Once your trial ends, you'll continue to have access to GitLens+ features on local and public repos,
while upgrading to GitLens Pro gives you access on private repos.
</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>
<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>
<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>
<vscode-button @click="${x => x.fireAction('command:gitlens.plus.validate')}"
>Refresh Verification Status</vscode-button
>
</p>
`,
)}
<p class="mb-0"><code-icon icon="info"></code-icon> All other GitLens features can always be used on any repo</p>
<div class="icon"><code-icon icon="info"></code-icon></div>
<div class="content">
${when(
x => x.state === SubscriptionState.Free,
html<PlusContent>`
<p class="mb-1">
<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>
`,
)}
${when(
x => x.state !== SubscriptionState.Free,
html<PlusContent>` <p class="mb-0">All other GitLens features can always be used on any repo</p> `,
)}
</div>
`;
const styles = css`
@ -123,8 +33,12 @@ const styles = css`
}
:host {
display: block;
text-align: center;
display: flex;
flex-direction: row;
padding: 0.8rem 1.2rem;
background-color: var(--color-alert-neutralBackground);
border-left: 0.3rem solid var(--color-foreground--50);
color: var(--color-alert-foreground);
}
a {
@ -142,19 +56,29 @@ const styles = css`
margin-top: 0;
}
.icon {
display: none;
flex: none;
margin-right: 0.4rem;
}
.icon code-icon {
font-size: 2.4rem;
margin-top: 0.2rem;
}
.content {
font-size: 1.2rem;
line-height: 1.2;
text-align: left;
}
.mb-1 {
margin-bottom: 0.4rem;
margin-bottom: 0.8rem;
}
.mb-0 {
margin-bottom: 0;
}
.minimal {
color: var(--color-foreground--50);
font-size: 1rem;
position: relative;
top: -0.2rem;
}
`;
@customElement({ name: 'plus-content', template: template, styles: styles })

+ 91
- 115
src/webviews/apps/home/home.html View File

@ -81,7 +81,7 @@
--video-banner-play: url(#{webroot}/media/play-button-dark.webp);
}
.gl-plus-banner {
background-image: url(#{webroot}/media/gitlens-backdrop.webp);
background-image: url(#{webroot}/media/gitlens-backdrop-opacity.webp);
}
</style>
<div class="stepped-sections">
@ -97,16 +97,11 @@
<ul class="icon-list mb-0">
<li>
<code-icon icon="circle-filled"></code-icon>
<a href="command:gitlens.showWelcomePage?%22quick-setup%22">Quick Setup</a> &mdash;
<a href="command:gitlens.showWelcomePage?%22quick-setup%22">Quick Setup</a> &mdash; quickly
personalize GitLens to your needs.
</li>
<li>
<code-icon icon="circle-filled"></code-icon>
<a href="command:gitlens.getStarted">Feature Walkthrough</a> &mdash; learn about the rich
features GitLens provides.
</li>
<li>
<code-icon icon="circle-filled"></code-icon>
<a href="command:gitlens.showSettingsPage">Interactive Settings editor</a> &mdash; fine-tune
your GitLens experience.
</li>
@ -117,7 +112,7 @@
<span slot="description">always free and accessible</span>
<p>
GitLens is deeply integrated into many areas and aspects of VS Code, especially editors and
views.
views. Learn more in the <a href="command:gitlens.getStarted">Feature Walkthrough</a>.
</p>
<card-section no-heading id="no-repo" aria-hidden="true">
<div class="centered">
@ -132,100 +127,98 @@
</div>
</card-section>
<div class="activitybar-banner">
<ul class="icon-list">
<li>
<code-icon icon="circle-filled"></code-icon> Find many features by opening the
<a href="command:workbench.view.scm">Source Control Side Bar</a>
</li>
<li>
<code-icon icon="circle-filled"></code-icon> Your central hub for the latest feature
updates and support information
</li>
</ul>
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 83 76">
<rect x="42.5" y=".5" width="40" height="75" rx="16.5" stroke="url(#a)" />
<mask
id="b"
class="svg-mask-alpha"
maskUnits="userSpaceOnUse"
x="45"
y="3"
width="35"
height="70"
>
<rect
x="45"
y="3"
width="35"
height="70"
rx="14"
fill="#333"
class="svg-activity-bar-bg"
/>
</mask>
<g mask="url(#b)">
<rect
x="45"
y="3"
width="35"
height="70"
rx="14"
fill="#333"
class="svg-activity-bar-bg"
/>
<path fill="#fff" d="M45 38h1v27h-1z" class="svg-activity-bar-active" />
</g>
<g clip-path="url(#c)" fill="#fff" class="svg-activity-bar-fg">
<path
d="m64.078 54.996-2.138-2.138.752-.751 2.137 2.138-.751.751ZM60.73 58.079V52.92h1.062v5.158h-1.063Z"
/>
<div class="activitybar-banner__content">
<p>
Find many features by opening the
<a href="command:workbench.view.scm">Source Control Side Bar</a>.
</p>
<p>Click on the icon to the left to set the location of your GitLens views.</p>
</div>
<div class="activitybar-banner__media">
<a
href="command:gitlens.setViewsLayout?%7B%22layout%22%3A%22scm%22%7D"
class="activitybar-banner__nav-item"
title="Move views to the Source Control side bar"
aria-label="Move views to the Source Control side bar"
></a>
<a
href="command:gitlens.setViewsLayout?%7B%22layout%22%3A%22gitlens%22%7D"
class="activitybar-banner__nav-item"
title="Move views to the GitLens side bar"
aria-label="Move views to the GitLens side bar"
></a>
<svg class="svg" fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 68 91">
<rect x=".5" y=".5" width="40" height="90" rx="16.5" stroke="url(#a)" />
<rect x="3" y="3" width="35" height="85" rx="14" fill="#333" class="svg__bar" />
<g clip-path="url(#b)" class="svg__icon" data-gitlens-layout="gitlens">
<path
d="m22.08 63-2.14-2.14.75-.75 2.14 2.13-.75.76ZM18.73 66.08v-5.16h1.06v5.16h-1.06Z"
/>
<path
d="M19.26 69.17a1.6 1.6 0 1 0 0-3.2 1.6 1.6 0 0 0 0 3.2ZM23.51 65.27a1.6 1.6 0 1 0 0-3.19 1.6 1.6 0 0 0 0 3.2ZM19.26 61.02a1.6 1.6 0 1 0 0-3.19 1.6 1.6 0 0 0 0 3.2Z"
/>
<path
d="M20.5 55.89a7.61 7.61 0 1 0 0 15.22 7.61 7.61 0 0 0 0-15.22ZM12 63.5a8.5 8.5 0 1 1 17 0 8.5 8.5 0 0 1-17 0Z"
/>
</g>
<path
d="M61.26 61.167a1.594 1.594 0 1 0 0-3.188 1.594 1.594 0 0 0 0 3.188ZM65.51 57.27a1.594 1.594 0 1 0 0-3.187 1.594 1.594 0 0 0 0 3.188ZM61.26 53.02a1.594 1.594 0 1 0 0-3.187 1.594 1.594 0 0 0 0 3.188Z"
fill="#fff"
d="M3 52h1v23H3z"
class="svg__indicator"
data-gitlens-layout="gitlens"
/>
<g clip-path="url(#c)" class="svg__icon" data-gitlens-layout="scm">
<path
d="M26.76 25.17a2.8 2.8 0 1 0-3.6 2.67 2.24 2.24 0 0 1-2.01 1.25h-2.24c-.83 0-1.63.31-2.24.87v-5.41a2.8 2.8 0 1 0-1.13 0v6.84a2.83 2.83 0 1 0 1.37.07 2.24 2.24 0 0 1 2-1.25h2.24a3.36 3.36 0 0 0 3.17-2.28 2.8 2.8 0 0 0 2.44-2.76ZM14.42 21.8a1.68 1.68 0 1 1 3.37 0 1.68 1.68 0 0 1-3.37 0Zm3.37 12.33a1.68 1.68 0 1 1-3.37 0 1.68 1.68 0 0 1 3.37 0Zm6.16-7.28a1.68 1.68 0 1 1 0-3.37 1.68 1.68 0 0 1 0 3.37Z"
/>
</g>
<path fill="#fff" d="M3 16h1v24H3z" class="svg__indicator" data-gitlens-layout="scm" />
<path
d="M62.5 47.885a7.615 7.615 0 1 0 0 15.23 7.615 7.615 0 0 0 0-15.23ZM54 55.5a8.5 8.5 0 1 1 17 0 8.5 8.5 0 0 1-17 0Z"
d="M67.67 64a2.67 2.67 0 1 0-5.34 0 2.67 2.67 0 0 0 5.34 0ZM49 64l5 2.89V61.1L49 64Zm16-.5H53.5v1H65v-1Z"
fill="red"
class="svg__arrow"
data-gitlens-layout="gitlens"
/>
</g>
<g clip-path="url(#d)">
<path
d="M68.755 17.166a2.804 2.804 0 0 0-5.406-1.053 2.803 2.803 0 0 0 1.802 3.724 2.241 2.241 0 0 1-2.001 1.253h-2.243a3.342 3.342 0 0 0-2.241.873V16.55a2.803 2.803 0 1 0-1.121 0v6.838a2.832 2.832 0 1 0 1.362.074 2.242 2.242 0 0 1 2.001-1.25h2.242a3.362 3.362 0 0 0 3.168-2.28 2.803 2.803 0 0 0 2.437-2.765Zm-12.331-3.363a1.682 1.682 0 1 1 3.363 0 1.682 1.682 0 0 1-3.363 0Zm3.363 12.331a1.682 1.682 0 1 1-3.364 0 1.682 1.682 0 0 1 3.364 0Zm6.166-7.286a1.682 1.682 0 1 1 0-3.364 1.682 1.682 0 0 1 0 3.364Z"
fill="#858585"
class="svg-activity-bar-inactive"
d="M67.67 28a2.67 2.67 0 1 0-5.34 0 2.67 2.67 0 0 0 5.34 0ZM49 28l5 2.89V25.1L49 28Zm16-.5H53.5v1H65v-1Z"
fill="#007FD5"
class="svg__arrow"
data-gitlens-layout="scm"
/>
</g>
<path
d="M.333 54a2.667 2.667 0 1 1 5.334 0 2.667 2.667 0 0 1-5.334 0ZM34 54l-5 2.887v-5.774L34 54Zm-31-.5h26.5v1H3v-1Z"
fill="#fff"
class="svg-fg"
/>
<path
d="M.333 22a2.667 2.667 0 1 1 5.334 0 2.667 2.667 0 0 1-5.334 0ZM34 22l-5 2.887v-5.774L34 22Zm-31-.5h26.5v1H3v-1Z"
fill="#007FD5"
class="svg-link"
/>
<defs>
<clipPath id="c">
<path fill="#fff" transform="translate(54 47)" d="M0 0h17v17H0z" class="svg-link" />
</clipPath>
<clipPath id="d">
<path fill="#fff" transform="translate(53 11)" d="M0 0h18v18H0z" class="svg-link" />
</clipPath>
<linearGradient
id="a"
x1="62.5"
y1="-19.365"
x2="62.5"
y2="76"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#333" class="svg-activity-bar-stop" />
<stop offset="1" stop-color="#333" stop-opacity="0" class="svg-activity-bar-stop" />
<stop offset="1" stop-color="#333" stop-opacity="0" class="svg-activity-bar-stop" />
</linearGradient>
</defs>
</svg>
<defs>
<clipPath id="b">
<path fill="#fff" transform="translate(12 55)" d="M0 0h17v17H0z" />
</clipPath>
<clipPath id="c">
<path fill="#fff" transform="translate(11 19)" d="M0 0h18v18H0z" />
</clipPath>
<linearGradient
id="a"
x1="20.5"
y1="-23.19"
x2="20.5"
y2="91"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#333" class="svg__outline" />
<stop offset="1" stop-color="#333" stop-opacity="0" class="svg__outline" />
<stop offset="1" stop-color="#333" stop-opacity="0" class="svg__outline" />
</linearGradient>
</defs>
</svg>
</div>
</div>
</stepped-section>
<stepped-section id="plus">
<span slot="heading">GitLens+ Features</span>
<span slot="description">want even more from GitLens?</span>
<card-section class="gl-plus-banner mb-1" no-heading>
<div class="centered plus-banner-text">
<plus-banner id="plus-banner"></plus-banner>
</div>
</card-section>
<plus-content id="plus-content"></plus-content>
</stepped-section>
<stepped-section id="integrations">
<span slot="heading">Integrations</span>
<p>GitLens provides issue and pull request auto-linking with many Git hosting services.</p>
@ -234,20 +227,6 @@
issues and pull requests, pull requests associated with branches and commits, and avatars.
</p>
</stepped-section>
<stepped-section id="plus">
<span slot="heading">Want even more from GitLens?</span>
<card-section class="gl-plus-banner mb-0" no-heading>
<div class="centered plus-banner-text">
<p class="type-tight">
<span class="logo"
><span class="foreground">Git</span>Lens+
<span class="foreground">Features</span></span
>
</p>
<plus-content id="plus-content"></plus-content>
</div>
</card-section>
</stepped-section>
</div>
<!-- check for gitlens+ -->
<div id="plus-sections">
@ -327,14 +306,11 @@
</card-section>
</div>
<div class="button-container">
<vscode-button id="restore-plus" appearance="secondary" data-action="command:gitlens.plus.restore"
>Restore GitLens+ features</vscode-button
<vscode-button appearance="secondary" data-action="command:workbench.action.toggleSidebarVisibility"
>Close</vscode-button
>
<vscode-button
id="restore-welcome"
appearance="secondary"
data-action="command:gitlens.home.restoreWelcome"
>Restore</vscode-button
<a id="restore-welcome" class="link-minimal" href="command:gitlens.home.restoreWelcome"
>Restore Home view state</a
>
</div>
</main>

+ 195
- 24
src/webviews/apps/home/home.scss View File

@ -14,6 +14,7 @@
--progress-bar-color: var(--color-background--lighten-15);
--card-background: var(--color-background--lighten-075);
--card-hover-background: var(--color-background--lighten-10);
--popover-bg: var(--color-background--lighten-15);
}
.vscode-high-contrast-light,
@ -21,6 +22,7 @@
--progress-bar-color: var(--color-background--darken-15);
--card-background: var(--color-background--darken-075);
--card-hover-background: var(--color-background--darken-10);
--popover-bg: var(--color-background--darken-15);
}
* {
@ -125,11 +127,21 @@ body {
&__header {
flex: none;
padding: 0 2rem;
position: relative;
}
&__main {
flex: 1;
overflow: auto;
padding: 2rem 2rem 0.4rem;
background: linear-gradient(var(--color-view-background) 33%, var(--color-view-background)),
linear-gradient(var(--color-view-background), var(--color-view-background) 66%) 0 100%,
linear-gradient(to bottom, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0)),
linear-gradient(to top, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0)) 0 100%;
background-color: var(--color-view-background);
background-repeat: no-repeat;
background-attachment: local, local, scroll, scroll;
background-size: 100% 12px, 100% 12px, 100% 6px, 100% 6px;
}
&__nav {
flex: none;
@ -138,6 +150,43 @@ body {
}
}
.popover {
background-color: var(--color-background--lighten-15);
position: absolute;
top: 100%;
left: 5.2rem;
transform: translateY(0.8rem);
max-width: 30rem;
padding: 0.8rem 1.2rem 1.2rem;
z-index: 10;
display: flex;
flex-direction: column;
gap: 0.4rem;
&__top {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
opacity: 0.5;
}
&__heading {
font-weight: 600;
}
&__caret {
position: absolute;
bottom: 100%;
width: 0;
height: 0;
border-left: 0.8rem solid transparent;
border-right: 0.8rem solid transparent;
border-bottom: 0.8rem solid var(--color-background--lighten-15);
}
}
h3 {
border: none;
color: var(--color-view-header-foreground);
@ -178,6 +227,11 @@ ul {
padding-left: 1.2em;
}
.unlist {
list-style: none;
padding-left: 0;
}
.icon-list {
list-style: none;
padding-left: 0;
@ -277,6 +331,12 @@ ul {
}
}
.gl-plus-banner {
background-color: transparent;
background-position: left -30vw center;
background-size: 80vw;
}
.plus-banner-text {
text-shadow: 0.1rem 0.1rem 0 var(--color-background), 0.1rem 0.1rem 0.2rem var(--color-background);
}
@ -292,10 +352,29 @@ ul {
opacity: 0.6;
}
.alert {
padding: 0.8rem 1.2rem;
line-height: 1.2;
margin-bottom: 1.2rem;
background-color: var(--color-alert-neutralBackground);
border-left: 0.3rem solid var(--color-alert-neutralBorder);
color: var(--color-alert-foreground);
&__title {
font-size: 1.4rem;
margin: 0;
}
&__description {
font-size: 1.2rem;
margin: 0.4rem 0 0;
}
}
.activitybar-banner {
display: flex;
flex-direction: row;
justify-content: space-between;
flex-direction: row-reverse;
justify-content: flex-end;
align-items: stretch;
gap: 1.6rem;
@ -306,10 +385,51 @@ ul {
gap: clamp(0.1rem, 2vw, 1.2rem);
margin-bottom: 0;
}
svg {
&__content {
// padding-top: 1.6rem;
display: flex;
flex-direction: column;
justify-content: center;
> :last-child {
margin-bottom: 0;
}
}
&__media {
position: relative;
flex: none;
max-width: 10rem;
height: auto;
width: 9.2rem;
}
&__nav {
position: absolute;
top: 0;
left: 0.4rem;
width: 4.8rem;
height: 12.3rem;
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
gap: 1.6rem;
&-item {
position: absolute;
left: 0.5rem;
width: 4.6rem;
height: 3.2rem;
// background-color: #ff000066;
&:first-of-type {
top: 2.2rem;
}
&:last-of-type {
top: 7rem;
}
}
}
#no-repo[aria-hidden='false'] ~ & {
@ -363,8 +483,19 @@ ul {
}
}
.link-minimal {
color: var(--color-foreground--50);
font-size: 1rem;
text-align: center;
position: relative;
top: 0.6rem;
&:hover {
color: var(--color-foreground--50);
}
}
vscode-button {
align-self: center;
max-width: 300px;
width: 100%;
@ -373,8 +504,11 @@ vscode-button {
}
}
@media (min-width: 640px) {
vscode-button {
.link-minimal,
vscode-button {
align-self: center;
@media (min-width: 640px) {
align-self: flex-start;
}
}
@ -402,28 +536,65 @@ vscode-button {
}
.svg {
&-activity-bar-bg {
fill: var(--vscode-activityBar-background);
width: 100%;
height: auto;
&__outline {
transition: all ease 250ms;
.vscode-light &,
.vscode-high-contrast-light & {
stop-color: var(--color-background--darken-15);
}
.vscode-dark &,
.vscode-high-contrast & {
stop-color: var(--color-background--lighten-15);
}
}
&-activity-bar-stop {
stop-color: var(--vscode-activityBar-background);
&:hover &__outline,
.activitybar-banner__nav-item:focus ~ & &__outline,
.activitybar-banner__nav-item:hover ~ & &__outline {
.vscode-light &,
.vscode-high-contrast-light & {
stop-color: var(--color-background--darken-50);
}
.vscode-dark &,
.vscode-high-contrast & {
stop-color: var(--color-background--lighten-50);
}
}
&-activity-bar-fg {
fill: var(--vscode-activityBar-foreground);
&__bar {
fill: var(--vscode-activityBar-background);
}
&-fg {
fill: var(--vscode-foreground);
&__indicator {
fill: transparent;
&.is-active {
fill: var(--vscode-activityBar-activeBorder);
}
}
&-activity-bar-inactive {
&__icon {
transition: all ease 100ms;
fill: var(--vscode-activityBar-inactiveForeground);
&.is-active {
fill: var(--vscode-activityBar-foreground);
}
}
&-activity-bar-active {
fill: var(--vscode-activityBar-activeBorder);
}
&-link {
fill: var(--vscode-textLink-foreground);
&__arrow {
fill: transparent;
&.is-active {
fill: var(--vscode-textLink-foreground);
}
}
&-mask-alpha {
mask-type: alpha;
.activitybar-banner__nav-item:first-of-type:focus ~ & &__icon:last-of-type,
.activitybar-banner__nav-item:first-of-type:hover ~ & &__icon:last-of-type,
.activitybar-banner__nav-item:last-of-type:focus ~ & &__icon:first-of-type,
.activitybar-banner__nav-item:last-of-type:hover ~ & &__icon:first-of-type {
fill: var(--vscode-activityBar-foreground);
}
}

+ 45
- 6
src/webviews/apps/home/home.ts View File

@ -6,7 +6,9 @@ import { getSubscriptionTimeRemaining, SubscriptionState } from '../../../subscr
import type { State } from '../../home/protocol';
import {
CompleteStepCommandType,
DidChangeConfigurationType,
DidChangeExtensionEnabledType,
DidChangeLayoutType,
DidChangeSubscriptionNotificationType,
DismissSectionCommandType,
} from '../../home/protocol';
@ -15,11 +17,13 @@ import { ExecuteCommandType, onIpc } from '../../protocol';
import { App } from '../shared/appBase';
import { DOM } from '../shared/dom';
import type { CardSection } from './components/card-section';
import type { PlusContent } from './components/plus-content';
import type { PlusBanner } from './components/plus-banner';
import type { SteppedSection } from './components/stepped-section';
import '../shared/components/code-icon';
import '../shared/components/overlays/pop-over';
import './components/card-section';
import './components/stepped-section';
import './components/plus-banner';
import './components/plus-content';
import './components/header-card';
@ -47,7 +51,7 @@ export class HomeApp extends App {
DOM.on('[data-action]', 'click', (e, target: HTMLElement) => this.onDataActionClicked(e, target)),
);
disposables.push(
DOM.on<PlusContent, string>('plus-content', 'action', (e, target: HTMLElement) =>
DOM.on<PlusBanner, string>('plus-banner', 'action', (e, target: HTMLElement) =>
this.onPlusActionClicked(e, target),
),
);
@ -87,6 +91,22 @@ export class HomeApp extends App {
this.updateNoRepo();
});
break;
case DidChangeConfigurationType.method:
this.log(`${this.appName}.onMessageReceived(${msg.id}): name=${msg.method}`);
onIpc(DidChangeConfigurationType, msg, params => {
this.state.plusEnabled = params.plusEnabled;
this.updatePlusContent();
});
break;
case DidChangeLayoutType.method:
this.log(`${this.appName}.onMessageReceived(${msg.id}): name=${msg.method}`);
onIpc(DidChangeLayoutType, msg, params => {
this.state.layout = params.layout;
this.updateLayout();
});
break;
default:
super.onMessageReceived?.(e);
@ -183,10 +203,29 @@ export class HomeApp extends App {
}
}
private updateLayout() {
const { layout } = this.state;
const $els = [...document.querySelectorAll('[data-gitlens-layout]')];
$els.forEach(el => {
const attr = el.getAttribute('data-gitlens-layout');
el.classList.toggle('is-active', attr === layout);
});
}
private updatePlusContent(days = this.getDaysRemaining()) {
const { subscription, visibility } = this.state;
const { subscription, visibility, plusEnabled } = this.state;
let $plusContent = document.getElementById('plus-banner');
if ($plusContent) {
$plusContent.setAttribute('days', days.toString());
$plusContent.setAttribute('state', subscription.state.toString());
$plusContent.setAttribute('visibility', visibility);
$plusContent.setAttribute('plan', subscription.plan.effective.name);
$plusContent.setAttribute('plus', plusEnabled.toString());
}
const $plusContent = document.getElementById('plus-content');
$plusContent = document.getElementById('plus-content');
if ($plusContent) {
$plusContent.setAttribute('days', days.toString());
$plusContent.setAttribute('state', subscription.state.toString());
@ -235,10 +274,10 @@ export class HomeApp extends App {
}
private updateState() {
const { completedSteps, dismissedSections, plusEnabled } = this.state;
const { completedSteps, dismissedSections } = this.state;
this.updateNoRepo();
document.getElementById('restore-plus')?.classList.toggle('hide', plusEnabled);
this.updateLayout();
const showRestoreWelcome = completedSteps?.length || dismissedSections?.length;
document.getElementById('restore-welcome')?.classList.toggle('hide', !showRestoreWelcome);

BIN
src/webviews/apps/media/gitlens-backdrop-opacity.png View File

Before After
Width: 290  |  Height: 290  |  Size: 56 KiB

+ 107
- 0
src/webviews/apps/shared/components/overlays/pop-over.ts View File

@ -0,0 +1,107 @@
import {
attr,
css,
customElement,
FASTElement,
html,
observable,
slotted,
volatile,
when,
} from '@microsoft/fast-element';
import { hasNodes } from '../helpers/slots';
import { elementBase } from '../styles/base';
const template = html<PopOver>`
<template>
${when(
x => x.hasTopNodes,
html<PopOver>`
<div class="top">
<slot ${slotted('typeNodes')} name="type"></slot>
<slot ${slotted('actionsNodes')} name="actions"></slot>
</div>
`,
)}
${when(
x => x.hasHeadingNodes,
html<PopOver>`<div class="heading"><slot ${slotted('headingNodes')} name="heading"></slot></div>`,
)}
<div class="content"><slot></slot></div>
${when(x => x.caret, html<PopOver>`<span class="caret"></span>`)}
</template>
`;
const styles = css`
${elementBase}
:host {
position: absolute;
width: var(--popover-width, 100%);
max-width: var(--popover-max-width, 30rem);
padding: 1.2rem 1.2rem 1.2rem;
/* update with a standardized z-index */
z-index: 10;
background-color: var(--popover-bg);
display: flex;
flex-direction: column;
gap: 0.4rem;
}
:host([caret]) {
transform: translateY(0.8rem);
}
.top {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
opacity: 0.5;
margin-top: -0.4rem;
}
.heading {
font-weight: 600;
}
.caret {
position: absolute;
bottom: 100%;
width: 0;
height: 0;
border-left: 0.8rem solid transparent;
border-right: 0.8rem solid transparent;
border-bottom: 0.8rem solid var(--popover-bg);
}
`;
@customElement({ name: 'pop-over', template: template, styles: styles })
export class PopOver extends FASTElement {
@attr({ mode: 'boolean' })
open = false;
@attr({ mode: 'boolean' })
caret = true;
@observable
typeNodes?: Node[];
@observable
actionsNodes?: Node[];
@observable
headingNodes?: Node[];
@volatile
get hasTopNodes() {
return hasNodes(this.typeNodes, this.actionsNodes);
}
@volatile
get hasHeadingNodes() {
return hasNodes(this.headingNodes);
}
}

+ 0
- 1
src/webviews/apps/shared/components/styles/base.ts View File

@ -9,7 +9,6 @@ export const elementBase = css`
:host *::after {
box-sizing: inherit;
}
[hidden] {
display: none !important;
}

+ 57
- 2
src/webviews/home/homeWebviewView.ts View File

@ -1,6 +1,7 @@
import type { Disposable } from 'vscode';
import type { ConfigurationChangeEvent, Disposable } from 'vscode';
import { window } from 'vscode';
import { getAvatarUriFromGravatarEmail } from '../../avatars';
import { ViewsLayout } from '../../commands/setViewsLayout';
import { configuration } from '../../configuration';
import { ContextKeys, CoreCommands } from '../../constants';
import type { Container } from '../../container';
@ -8,6 +9,7 @@ import { getContext, onDidChangeContext } from '../../context';
import type { RepositoriesVisibility } from '../../git/gitProviderService';
import type { SubscriptionChangeEvent } from '../../plus/subscription/subscriptionService';
import { ensurePlusFeaturesEnabled } from '../../plus/subscription/utils';
import type { StorageChangeEvent } from '../../storage';
import type { Subscription } from '../../subscription';
import { executeCoreCommand, registerCommand } from '../../system/command';
import type { Deferrable } from '../../system/function';
@ -19,7 +21,9 @@ import type { CompleteStepParams, DismissSectionParams, State } from './protocol
import {
CompletedActions,
CompleteStepCommandType,
DidChangeConfigurationType,
DidChangeExtensionEnabledType,
DidChangeLayoutType,
DidChangeSubscriptionNotificationType,
DismissSectionCommandType,
} from './protocol';
@ -34,6 +38,12 @@ export class HomeWebviewView extends WebviewViewBase {
if (key !== ContextKeys.Disabled) return;
this.notifyExtensionEnabled();
}),
configuration.onDidChange(e => {
this.onConfigurationChanged(e);
}, this),
this.container.storage.onDidChange(e => {
this.onStorageChanged(e);
}),
);
}
@ -46,6 +56,22 @@ export class HomeWebviewView extends WebviewViewBase {
void this.notifyDidChangeData(e.current);
}
private onConfigurationChanged(e: ConfigurationChangeEvent) {
if (!configuration.changed(e, 'plusFeatures.enabled')) {
return;
}
this.notifyDidChangeConfiguration();
}
private onStorageChanged(e: StorageChangeEvent) {
if (e.key !== 'views:layout') {
return;
}
this.notifyDidChangeLayout();
}
protected override onVisibilityChanged(visible: boolean): void {
if (!visible) {
this._validateSubscriptionDebounced?.cancel();
@ -178,11 +204,12 @@ export class HomeWebviewView extends WebviewViewBase {
webroot: this.getWebRoot(),
subscription: subscriptionState.subscription,
completedActions: subscriptionState.completedActions,
plusEnabled: configuration.get('plusFeatures.enabled'),
plusEnabled: this.getPlusEnabled(),
visibility: await this.getRepoVisibility(),
completedSteps: steps,
dismissedSections: sections,
avatar: subscriptionState.avatar,
layout: this.getLayout(),
};
}
@ -206,6 +233,34 @@ export class HomeWebviewView extends WebviewViewBase {
});
}
private getPlusEnabled() {
return configuration.get('plusFeatures.enabled');
}
private notifyDidChangeConfiguration() {
if (!this.isReady) return;
void this.notify(DidChangeConfigurationType, {
plusEnabled: this.getPlusEnabled(),
});
}
private getLayout() {
const layout = this.container.storage.get('views:layout');
if (layout == null) {
return ViewsLayout.GitLens;
}
return layout as ViewsLayout;
}
private notifyDidChangeLayout() {
if (!this.isReady) return;
void this.notify(DidChangeLayoutType, {
layout: this.getLayout(),
});
}
private _validateSubscriptionDebounced: Deferrable<HomeWebviewView['validateSubscription']> | undefined = undefined;
private async validateSubscription(): Promise<void> {

+ 14
- 0
src/webviews/home/protocol.ts View File

@ -1,3 +1,4 @@
import type { ViewsLayout } from '../../commands/setViewsLayout';
import type { RepositoriesVisibility } from '../../git/gitProviderService';
import type { Subscription } from '../../subscription';
import { IpcCommandType, IpcNotificationType } from '../protocol';
@ -17,6 +18,7 @@ export interface State {
plusEnabled: boolean;
visibility: RepositoriesVisibility;
avatar?: string;
layout: ViewsLayout;
}
export interface CompleteStepParams {
@ -45,3 +47,15 @@ export interface DidChangeExtensionEnabledParams {
export const DidChangeExtensionEnabledType = new IpcNotificationType<DidChangeExtensionEnabledParams>(
'extensionEnabled/didChange',
);
export interface DidChangeConfigurationParams {
plusEnabled: boolean;
}
export const DidChangeConfigurationType = new IpcNotificationType<DidChangeConfigurationParams>(
'configuration/didChange',
);
export interface DidChangeLayoutParams {
layout: ViewsLayout;
}
export const DidChangeLayoutType = new IpcNotificationType<DidChangeLayoutParams>('layout/didChange');

||||||
x
 
000:0
Loading…
Cancel
Save