Преглед на файлове

Updates home view layout and content

main
Keith Daulton преди 2 години
родител
ревизия
b71742dea6
променени са 7 файла, в които са добавени 639 реда и са изтрити 363 реда
  1. +8
    -3
      src/webviews/apps/home/components/card-section.ts
  2. +236
    -0
      src/webviews/apps/home/components/header-card.ts
  3. +156
    -0
      src/webviews/apps/home/components/plus-content.ts
  4. +12
    -4
      src/webviews/apps/home/components/stepped-section.ts
  5. +62
    -69
      src/webviews/apps/home/home.html
  6. +48
    -148
      src/webviews/apps/home/home.scss
  7. +117
    -139
      src/webviews/apps/home/home.ts

+ 8
- 3
src/webviews/apps/home/components/card-section.ts Целия файл

@ -1,4 +1,4 @@
import { attr, css, customElement, FASTElement, html, volatile, when } from '@microsoft/fast-element';
import { attr, css, customElement, FASTElement, html, when } from '@microsoft/fast-element';
import { numberConverter } from '../../shared/components/converters/number-converter';
import '../../shared/components/codicon';
@ -38,11 +38,16 @@ const styles = css`
:host {
display: block;
padding: 1.2rem;
background-color: #aaaaaa10;
background-color: var(--card-background);
margin-bottom: 1rem;
border-radius: 0.4rem;
background-repeat: no-repeat;
background-size: cover;
transition: aspect-ratio linear 100ms, background-color linear 100ms;
}
:host(:hover) {
background-color: var(--card-hover-background);
}
header {
@ -104,7 +109,7 @@ export class CardSection extends FASTElement {
@attr({ mode: 'boolean' })
expanded = true;
handleDismiss(e: Event) {
handleDismiss(_e: Event) {
this.$emit('dismiss');
}
}

+ 236
- 0
src/webviews/apps/home/components/header-card.ts Целия файл

@ -0,0 +1,236 @@
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/codicon';
const template = html<HeaderCard>`
<div class="header-card__media"><img class="header-card__image" src="${x => x.image}" alt="GitLens Logo" /></div>
<h1 class="header-card__title">
${when(
x => x.name === '',
html<HeaderCard>`<span class="foreground">Git</span>Lens 12 <em>Git supercharged</em>`,
)}
${when(x => x.name !== '', html<HeaderCard>`<span class="foreground">${x => x.name}</span>`)}
</h1>
<p class="header-card__account">
<span class="status">${x => x.planName}</span>
<span>
${when(
x => x.state === SubscriptionState.Free,
html<HeaderCard>`
<a title="Sign in to GitLens+" href="command:gitlens.plus.loginOrSignUp">Sign In</a>
`,
)}
${when(
x => x.state === SubscriptionState.Paid,
html<HeaderCard>`
<a href="command:" aria-label="Manage Account" title="Manage Account"
><code-icon icon="account"></code-icon></a
>&nbsp;<a href="command:" aria-label="Sign Out" title="Sign Out"
><code-icon icon="sign-out"></code-icon
></a>
`,
)}
${when(
x => [SubscriptionState.FreeInPreviewTrial, SubscriptionState.FreePlusInTrial].includes(x.state),
html<HeaderCard>`${x => x.daysRemaining}`,
)}
${when(
x => x.state === SubscriptionState.FreePreviewTrialExpired,
html<HeaderCard>`<a href="command:gitlens.plus.loginOrSignUp">Extend Trial</a>`,
)}
${when(
x => x.state === SubscriptionState.FreePlusTrialExpired,
html<HeaderCard>`
<a href="command:gitlens.plus.purchase">Upgrade to Pro</a>&nbsp;<a
href="command:"
aria-label="Sign Out"
title="Sign Out"
><code-icon icon="sign-out"></code-icon
></a>
`,
)}
${when(
x => x.state === SubscriptionState.VerificationRequired,
html<HeaderCard>`
<a
href="command:gitlens.plus.resendVerification"
title="Resend Verification Email"
aria-label="Resend Verification Email"
>Verify</a
>&nbsp;<a
href="command:gitlens.plus.validate"
title="Refresh Verification Status"
aria-label="Refresh Verification Status"
><code-icon icon="sync"></code-icon
></a>
`,
)}
</span>
</p>
<div
class="progress header-card__progress"
role="progressbar"
aria-valuemax="${x => x.progressMax}"
aria-valuenow="${x => x.progressNow}"
aria-label="${x => x.progressNow} of ${x => x.progressMax} steps completed"
>
<div class="progress__indicator" style="width: ${x => x.progress};"></div>
</div>
`;
const styles = css`
* {
box-sizing: border-box;
}
:host {
position: relative;
display: grid;
padding: 1rem 1rem 1.2rem;
background-color: var(--card-background);
border-radius: 0.4rem;
gap: 0 0.8rem;
grid-template-columns: 3.4rem auto;
grid-auto-flow: column;
}
a {
color: var(--vscode-textLink-foreground);
text-decoration: none;
}
a:focus {
outline-color: var(--focus-border);
}
a:hover {
text-decoration: underline;
}
.header-card__media {
grid-column: 1;
grid-row: 1 / span 2;
}
.header-card__image {
aspect-ratio: 1 / 1;
border-radius: 50%;
}
.header-card__title {
font-size: var(--vscode-font-size);
color: var(--gitlens-brand-color-2);
margin: 0;
}
.header-card__title em {
font-weight: normal;
color: var(--color-view-foreground);
opacity: 0.4;
margin-left: 0.4rem;
}
.header-card__account {
margin: 0;
display: flex;
flex-direction: row;
justify-content: space-between;
flex-wrap: wrap;
}
.progress {
width: 100%;
overflow: hidden;
}
:host-context(.vscode-high-contrast) .progress,
:host-context(.vscode-dark) .progress {
background-color: var(--color-background--lighten-15);
}
:host-context(.vscode-high-contrast-light) .progress,
:host-context(.vscode-light) .progress {
background-color: var(--color-background--darken-15);
}
.progress__indicator {
height: 4px;
background-color: var(--vscode-progressBar-background);
}
.header-card__progress {
position: absolute;
bottom: 0;
left: 0;
border-bottom-left-radius: 0.4rem;
border-bottom-right-radius: 0.4rem;
}
.foreground {
color: var(--color-foreground);
}
.status {
color: var(--color-foreground--75);
}
`;
@customElement({ name: 'header-card', template: template, styles: styles })
export class HeaderCard extends FASTElement {
@attr
image = '';
@attr
name = '';
@attr({ converter: numberConverter })
days = 0;
@attr({ converter: numberConverter })
steps = 4;
@attr({ converter: numberConverter })
completed = 0;
@attr({ converter: numberConverter })
state: SubscriptionState = SubscriptionState.Free;
@attr
plan = '';
get daysRemaining() {
if (this.days < 1) {
return '<1 day';
}
return pluralize('day', this.days);
}
get progressNow() {
return this.completed + 1;
}
get progressMax() {
return this.steps + 1;
}
@volatile
get progress() {
return `${(this.progressNow / this.progressMax) * 100}%`;
}
@volatile
get planName() {
switch (this.state) {
case SubscriptionState.Free:
return 'GitLens+ (Local & Public Repos)';
case SubscriptionState.FreeInPreviewTrial:
case SubscriptionState.FreePlusInTrial:
return 'GitLens+ Pro Trial';
case SubscriptionState.FreePreviewTrialExpired:
return 'GitLens+ (Local & Public Repos)';
case SubscriptionState.FreePlusTrialExpired:
return 'GitLens+ (Local & Public Repos)';
case SubscriptionState.VerificationRequired:
return 'GitLens+ (Unverified)';
default:
return this.plan;
}
}
}

+ 156
- 0
src/webviews/apps/home/components/plus-content.ts Целия файл

@ -0,0 +1,156 @@
import { attr, css, customElement, FASTElement, html, 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/codicon';
const template = html<PlusContent>`
${when(
x => x.state === SubscriptionState.Free,
html<PlusContent>`
<p>Adds all-new, completely optional, features that enhance your GitLens experience.</p>
<p>These features are free for local and public repos with no account required.</p>
<p class="mb-1">
<vscode-button @click="${x => x.fireAction('command:gitlens.plus.startPreviewTrial')}"
>Try GitLens+ for private repositories</vscode-button
>
</p>
<p>
<a class="minimal" href="command:gitlens.plus.hide">Hide GitLens+ features</a>
</p>
`,
)}
${when(
x => x.state === SubscriptionState.Paid,
html<PlusContent>`
<p>GitLens+ adds all-new, completely optional, features that enhance your current GitLens experience.</p>
<p>These features are free for local and public repos with no account required.</p>
`,
)}
${when(
x => [SubscriptionState.FreeInPreviewTrial, SubscriptionState.FreePlusInTrial].includes(x.state),
html<PlusContent>`
<h3>GitLens+ Trial</h3>
<p>
You have ${x => x.daysRemaining} left in your
<a title="Learn more about GitLens+ features" href="command:gitlens.plus.learn">GitLens+ trial</a>. Once
your trial ends, you'll need a paid plan to continue to use GitLens+ features on this and other private
repos.
</p>
`,
)}
${when(
x => x.state === SubscriptionState.FreePreviewTrialExpired,
html<PlusContent>`
<h3>Extend Your GitLens+ Trial</h3>
<p>
Your free trial has ended, please sign in to extend your trial of GitLens+ features on private repos by
an additional 7-days.
</p>
<p class="mb-1">
<vscode-button @click="${x => x.fireAction('command:gitlens.plus.loginOrSignUp')}"
>Extend Trial</vscode-button
>
</p>
`,
)}
${when(
x => x.state === SubscriptionState.FreePlusTrialExpired,
html<PlusContent>`
<h3>GitLens+ Trial Expired</h3>
<p>
Your free trial has ended, please upgrade your account to continue to use GitLens+ features, including
the Commit Graph, on this and other private repos.
</p>
<p class="mb-1">
<vscode-button @click="${x => x.fireAction('command:gitlens.plus.purchase')}"
>Upgrade Your Account</vscode-button
>
</p>
`,
)}
${when(
x => x.state === SubscriptionState.VerificationRequired,
html<PlusContent>`
<h3>Please verify your email</h3>
<p class="alert__message">Please verify the email for the account you created.</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>
`,
)}
<p class="mb-0"><code-icon icon="info"></code-icon> All other GitLens features are always accessible</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;
}
p {
margin-top: 0;
}
.mb-1 {
margin-bottom: 0.4rem;
}
.mb-0 {
margin-bottom: 0;
}
.minimal {
color: var(--color-foreground--50);
}
`;
@customElement({ name: 'plus-content', template: template, styles: styles })
export class PlusContent extends FASTElement {
@attr({ converter: numberConverter })
days = 0;
@attr({ converter: numberConverter })
state: SubscriptionState = SubscriptionState.Free;
@attr
visibility: 'local' | 'public' | 'mixed' | 'private' = 'public';
get daysRemaining() {
if (this.days < 1) {
return 'less than one day';
}
return pluralize('day', this.days);
}
get isFree() {
return ['local', 'public'].includes(this.visibility);
}
fireAction(command: string) {
this.$emit('action', command);
}
}

+ 12
- 4
src/webviews/apps/home/components/stepped-section.ts Целия файл

@ -1,4 +1,4 @@
import { attr, css, customElement, FASTElement, html, volatile, when } from '@microsoft/fast-element';
import { attr, css, customElement, FASTElement, html } from '@microsoft/fast-element';
import { numberConverter } from '../../shared/components/converters/number-converter';
import '../../shared/components/codicon';
@ -63,6 +63,15 @@ const styles = css`
color: var(--vscode-textLink-foreground);
}
/*
.checkbox code-icon {
border-radius: 50%;
}
.heading:hover ~ .checkbox code-icon {
background-color: var(--vscode-textLink-foreground);
}
*/
:host(:not(:last-of-type)) .checkbox:after {
content: '';
position: absolute;
@ -86,8 +95,7 @@ const styles = css`
.description {
margin-left: 0.2rem;
text-transform: none;
/* color needs to come from some sort property */
color: #b68cd8;
color: var(--gitlens-brand-color-2);
opacity: 0.6;
font-style: italic;
}
@ -101,7 +109,7 @@ export class SteppedSection extends FASTElement {
@attr({ mode: 'boolean' })
completed = false;
handleClick(e: Event) {
handleClick(_e: Event) {
this.completed = !this.completed;
this.$emit('complete', this.completed);
}

+ 62
- 69
src/webviews/apps/home/home.html Целия файл

@ -5,20 +5,67 @@
</head>
<body class="home preload">
<a href="#main" class="sr-skip sr-only-focusable">skip to content</a>
<a href="#footer" class="sr-skip sr-only-focusable">skip to footer links</a>
<header class="home__header">
<div class="header-card">
<div class="header-card__logo"><img src="#{webroot}/media/gitlens-logo.webp" alt="GitLens Logo" /></div>
<h1 class="header-card__title"><span class="foreground">Git</span>Lens 12 <em>Git supercharged</em></h1>
<p class="header-card__account">
<span id="header-content"></span>
<span id="header-actions"></span>
</p>
<div class="progress header-card__progress" role="progressbar">
<div class="progress__indicator"></div>
<a href="#links" class="sr-skip sr-only-focusable">skip to links</a>
<a href="#main" class="sr-skip sr-only-focusable">skip to main content</a>
<div class="home__nav">
<nav class="inline-nav" id="links" tabindex="-1" aria-label="Help and Resources">
<div class="inline-nav__group">
<a
class="inline-nav__link inline-nav__link--text"
href="https://help.gitkraken.com/gitlens/gitlens-release-notes-current/"
aria-label="What's New"
title="What's New"
><span class="codicon codicon-rocket"></span> What's New</a
>
<a
class="inline-nav__link inline-nav__link--text"
href="https://help.gitkraken.com/gitlens/gitlens-home/"
aria-label="Help Center"
title="Help Center"
><span class="codicon codicon-question"></span>Help</a
>
<a
class="inline-nav__link inline-nav__link--text"
href="https://github.com/gitkraken/vscode-gitlens/issues"
aria-label="Feedback"
title="Feedback"
><span class="codicon codicon-feedback"></span>Feedback</a
>
</div>
</div>
<div class="inline-nav__group">
<a
class="inline-nav__link"
href="https://github.com/gitkraken/vscode-gitlens/discussions"
aria-label="GitHub Discussions"
title="GitHub Discussions"
><span class="codicon codicon-comment-discussion"></span
></a>
<a
class="inline-nav__link"
href="https://github.com/gitkraken/vscode-gitlens"
aria-label="GitHub Repo"
title="GitHub Repo"
><span class="codicon codicon-github"></span
></a>
<a
class="inline-nav__link"
href="https://twitter.com/gitlens"
aria-label="@gitlens on Twitter"
title="@gitlens on Twitter"
><span class="codicon codicon-twitter"></span
></a>
<a
class="inline-nav__link"
href="https://gitkraken.com/gitlens?utm_source=gitlens-extension&utm_medium=in-app-links&utm_campaign=gitlens-logo-links"
aria-label="GitLens Website"
title="GitLens Website"
><span class="codicon codicon-globe"></span
></a>
</div>
</nav>
</div>
<header class="home__header">
<header-card id="header-card" image="#{webroot}/media/gitlens-logo.webp"></header-card>
</header>
<main class="home__main" id="main" tabindex="-1">
<style>
@ -192,25 +239,9 @@
<card-section no-heading backdrop="#{webroot}/media/gitlens-backdrop.webp">
<div class="centered">
<p class="type-tight">
<span class="logo"><span class="foreground">Git</span>Lens+</span><br />
<em class="description">*optional</em>
<span class="logo"><span class="foreground">Git</span>Lens+</span>
</p>
<div id="plus-content">
<p>
GitLens+ adds all-new, completely optional, features that enhance your current
GitLens experience.
</p>
<p>These features are free for local and public repos with no account required.</p>
<!-- free -->
<p class="mb-1">
<vscode-button data-action="command:gitlens.plus.startPreviewTrial"
>Try GitLens+ with private repositories</vscode-button
>
</p>
<p class="mb-0">
<a href="command:gitlens.plus.hide">Hide GitLens+ features, cannot use them</a>
</p>
</div>
<plus-content id="plus-content"></plus-content>
</div>
</card-section>
</stepped-section>
@ -274,44 +305,6 @@
</div>
</main>
<footer class="home__footer" id="footer" tabindex="-1">
<nav class="inline-nav" aria-label="Help and Resources">
<a href="https://gitlens.amod.io/#whats-new" aria-label="What's new" title="What's new"
><span class="codicon codicon-rocket"></span
></a>
<a
href="https://github.com/gitkraken/vscode-gitlens#readme"
aria-label="GitHub Readme"
title="GitHub Readme"
><span class="codicon codicon-book"></span
></a>
<a
href="https://github.com/gitkraken/vscode-gitlens/discussions"
aria-label="GitHub Discussions"
title="GitHub Discussions"
><span class="codicon codicon-comment-discussion"></span
></a>
<a
href="https://github.com/gitkraken/vscode-gitlens/issues"
aria-label="GitHub Issues"
title="GitHub Issues"
><span class="codicon codicon-issues"></span
></a>
<a href="https://gitkraken.com/gitlens-support" aria-label="GitLens Support" title="GitLens Support"
><span class="codicon codicon-account"></span
></a>
<a
href="https://gitkraken.com/gitlens?utm_source=gitlens-extension&utm_medium=in-app-links&utm_campaign=gitlens-logo-links"
aria-label="GitLens Website"
title="GitLens Website"
><span class="codicon codicon-globe"></span
></a>
<a href="https://twitter.com/gitlens" aria-label="GitLens on Twitter" title="GitLens on Twitter"
><span class="codicon codicon-twitter"></span
></a>
</nav>
</footer>
#{endOfBody}
<style nonce="#{cspNonce}">
@font-face {

+ 48
- 148
src/webviews/apps/home/home.scss Целия файл

@ -9,6 +9,20 @@
--gitlens-brand-color-2: #a16dc4;
}
.vscode-high-contrast,
.vscode-dark {
--progress-bar-color: var(--color-background--lighten-15);
--card-background: var(--color-background--lighten-075);
--card-hover-background: var(--color-background--lighten-10);
}
.vscode-high-contrast-light,
.vscode-light {
--progress-bar-color: var(--color-background--darken-15);
--card-background: var(--color-background--darken-075);
--card-hover-background: var(--color-background--darken-10);
}
* {
box-sizing: border-box;
}
@ -75,39 +89,13 @@ body {
overflow: auto;
padding: 2rem 2rem 0.4rem;
}
&__footer {
&__nav {
flex: none;
padding: 0 2rem;
margin-bottom: 0.6rem;
}
}
.container {
display: grid;
font-size: 1.3em;
grid-template-rows: min-content auto min-content;
min-height: 100%;
}
.content {
--slots: 3;
display: grid;
// repeat needs to match the number of slots
grid-template-rows: repeat(var(--slots), min-content);
}
.notice {
font-size: 1.1rem;
border-bottom: 1px solid var(--divider-background);
padding-bottom: 1rem;
text-align: center;
}
section {
display: flex;
flex-direction: column;
padding: 0;
}
h3 {
border: none;
color: var(--color-view-header-foreground);
@ -148,10 +136,6 @@ ul {
padding-left: 1.2em;
}
.feature-desc {
margin-bottom: 1rem;
}
.button-container {
display: flex;
flex-direction: column;
@ -168,63 +152,6 @@ ul {
text-align: center;
}
.progress {
width: 100%;
.vscode-high-contrast &,
.vscode-dark & {
background-color: var(--color-background--lighten-15);
}
.vscode-high-contrast-light &,
.vscode-light & {
background-color: var(--color-background--darken-15);
}
&__indicator {
height: 4px;
}
}
.header-card {
position: relative;
display: grid;
padding: 1rem 1rem 1.2rem;
background-color: #aaaaaa10;
border-radius: 0.4rem;
gap: 0 0.8rem;
grid-template-columns: 3.4rem auto;
grid-auto-flow: column;
&__logo {
grid-column: 1;
grid-row: 1 / span 2;
}
&__title {
font-size: var(--vscode-font-size);
color: var(--gitlens-brand-color-2);
margin: 0;
em {
font-weight: normal;
color: var(--color-view-foreground);
opacity: 0.4;
}
}
&__account {
margin: 0;
display: flex;
flex-direction: row;
justify-content: space-between;
}
&__progress {
position: absolute;
bottom: 0;
left: 0;
border-bottom-left-radius: 0.4rem;
border-bottom-right-radius: 0.4rem;
}
}
.foreground {
color: var(--color-view-foreground);
}
@ -232,22 +159,48 @@ ul {
.inline-nav {
display: flex;
flex-direction: row;
gap: 0.4rem;
justify-content: space-between;
&__group {
display: flex;
flex-direction: row;
}
a {
&__link {
display: flex;
justify-content: center;
align-items: center;
width: 2.2rem;
height: 2.2rem;
// line-height: 2.2rem;
color: inherit;
border-radius: 0.3rem;
.codicon {
line-height: 1.6rem;
}
&:hover {
color: inherit;
text-decoration: none;
.vscode-dark & {
background-color: var(--color-background--lighten-10);
}
.vscode-light & {
background-color: var(--color-background--darken-10);
}
}
&--text {
flex: none;
padding: {
left: 0.4rem;
right: 0.4rem;
}
gap: 0.2rem;
min-width: 2.2rem;
width: fit-content;
}
}
}
@ -287,19 +240,22 @@ ul {
align-items: flex-end;
margin-bottom: 0.8rem;
background: no-repeat var(--video-banner-play) center center, no-repeat var(--video-banner-bg) left center;
background-color: var(--card-background);
background-size: clamp(2.9rem, 8%, 6rem), cover;
aspect-ratio: var(--video-banner-ratio, 354 / 54);
padding: 0.4rem 1.2rem;
color: inherit;
line-height: 1.2;
font-size: clamp(var(--vscode-font-size), 4vw, 2.4rem);
transition: aspect-ratio linear 100ms;
transition: aspect-ratio linear 100ms, background-color linear 100ms;
border-radius: 0.4rem;
@media (min-width: 564px) {
aspect-ratio: var(--video-banner-ratio, 354 / 40);
}
&:hover {
background-color: var(--card-hover-background);
text-decoration: none;
color: inherit;
}
@ -309,50 +265,6 @@ ul {
}
}
.image--preview {
border-radius: 8px;
box-shadow: 0px 0px 1px 0px rgba(0, 0, 0, 0.8), 0px 0px 12px 1px rgba(0, 0, 0, 0.5);
margin: 1rem 2rem 1rem 1rem;
}
.link--preview {
align-self: center;
max-width: 400px;
margin: 1rem 2rem 0 0;
}
.divider {
border-top: 1px solid var(--divider-background);
margin-top: 2rem;
}
.links {
display: grid;
grid-template-columns: repeat(3, auto);
grid-gap: 2rem;
justify-content: space-between;
margin: 1rem;
h4 {
margin: 0.5rem 0 1rem 0;
}
ul {
list-style: none;
margin: 0.5rem 0 0 0;
padding: 0;
white-space: nowrap;
}
@media screen and (max-width: 450px) {
grid-template-columns: repeat(2, auto);
}
@media screen and (max-width: 350px) {
grid-template-columns: 1fr;
}
}
vscode-button {
align-self: center;
max-width: 300px;
@ -363,22 +275,10 @@ vscode-button {
}
}
span.button-subaction {
align-self: center;
margin-top: 0.75rem;
}
@media (min-width: 640px) {
vscode-button {
align-self: flex-start;
}
span.button-subaction {
align-self: flex-start;
}
}
vscode-divider {
margin-top: 2rem;
}
@import '../shared/codicons';

+ 117
- 139
src/webviews/apps/home/home.ts Целия файл

@ -2,9 +2,7 @@
import './home.scss';
import { provideVSCodeDesignSystem, vsCodeButton } from '@vscode/webview-ui-toolkit';
import type { Disposable } from 'vscode';
// import { RepositoriesVisibility } from '../../../git/gitProviderService';
import { getSubscriptionTimeRemaining, isSubscriptionTrial, SubscriptionState } from '../../../subscription';
import { pluralize } from '../../../system/string';
import { getSubscriptionTimeRemaining, SubscriptionState } from '../../../subscription';
import type { State } from '../../home/protocol';
import {
CompleteStepCommandType,
@ -16,10 +14,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 { SteppedSection } from './components/stepped-section';
import '../shared/components/codicon';
import './components/card-section';
import './components/stepped-section';
import './components/plus-content';
import './components/header-card';
export class HomeApp extends App<State> {
private $steps!: SteppedSection[];
@ -41,7 +42,14 @@ export class HomeApp extends App {
protected override onBind(): Disposable[] {
const disposables = super.onBind?.() ?? [];
disposables.push(DOM.on('[data-action]', 'click', (e, target: HTMLElement) => this.onActionClicked(e, target)));
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<SteppedSection, boolean>('stepped-section', 'complete', (e, target: HTMLElement) =>
this.onStepComplete(e, target),
@ -78,172 +86,142 @@ export class HomeApp extends App {
private onStepComplete(e: CustomEvent<boolean>, target: HTMLElement) {
const id = target.id;
console.log('onStepComplete', id, e.detail);
this.sendCommand(CompleteStepCommandType, { id: id, completed: e.detail ?? false });
const isComplete = e.detail ?? false;
this.state.completedSteps = toggleArrayItem(this.state.completedSteps, id, isComplete);
this.sendCommand(CompleteStepCommandType, { id: id, completed: isComplete });
this.updateState();
}
private onCardDismissed(e: CustomEvent<undefined>, target: HTMLElement) {
const id = target.id;
console.log('onCardDismissed', id);
this.state.dismissedSections = toggleArrayItem(this.state.dismissedSections, id);
this.sendCommand(DismissSectionCommandType, { id: id });
target.remove();
this.updateState();
}
private onActionClicked(e: MouseEvent, target: HTMLElement) {
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) });
}
}
private updateState() {
const { subscription, completedSteps, dismissedSections, plusEnabled, visibility } = this.state;
// banner
document.getElementById('plus')?.classList.toggle('hide', !plusEnabled);
document.getElementById('restore-plus')?.classList.toggle('hide', plusEnabled);
document.getElementById('plus-sections')?.classList.toggle('hide', !plusEnabled);
const showRestoreWelcome = completedSteps?.length || dismissedSections?.length;
document.getElementById('restore-welcome')?.classList.toggle('hide', !showRestoreWelcome);
private getDaysRemaining() {
if (
![SubscriptionState.FreeInPreviewTrial, SubscriptionState.FreePlusInTrial].includes(
this.state.subscription.state,
)
) {
return 0;
}
// TODO: RepositoriesVisibility causes errors during the build
// const alwaysFree = [RepositoriesVisibility.Local, RepositoriesVisibility.Public].includes(visibility);
const alwaysFree = ['local', 'public'].includes(visibility);
const needsAccount = ['mixed', 'private'].includes(visibility);
return getSubscriptionTimeRemaining(this.state.subscription, 'days') ?? 0;
}
console.log('updateState', alwaysFree, needsAccount, this.state);
private updateHeader(days = this.getDaysRemaining()) {
const { subscription, completedSteps } = this.state;
let days = 0;
if ([SubscriptionState.FreeInPreviewTrial, SubscriptionState.FreePlusInTrial].includes(subscription.state)) {
days = getSubscriptionTimeRemaining(subscription, 'days') ?? 0;
const $headerContent = document.getElementById('header-card');
if ($headerContent) {
$headerContent.setAttribute('steps', this.$steps?.length.toString() ?? '');
$headerContent.setAttribute('completed', completedSteps?.length.toString() ?? '');
$headerContent.setAttribute('state', subscription.state.toString());
$headerContent.setAttribute('plan', subscription.plan.effective.name);
$headerContent.setAttribute('days', days.toString());
}
}
const timeRemaining = days < 1 ? 'less than one day' : pluralize('day', days);
const shortTimeRemaining = days < 1 ? '<1 day' : pluralize('day', days);
let plan = subscription.plan.effective.name;
let content;
let actions;
let forcePlus = false;
// switch (-1 as SubscriptionState) {
switch (subscription.state) {
case SubscriptionState.Free:
plan = 'Free';
break;
case SubscriptionState.Paid:
break;
case SubscriptionState.FreeInPreviewTrial:
case SubscriptionState.FreePlusInTrial: {
plan = 'Trial';
content = `
<h3>GitLens+ Trial</h3>
<p class="mb-0">
You have ${timeRemaining} left in your&nbsp;
<a title="Learn more about GitLens+ features" href="command:gitlens.plus.learn">
GitLens+ trial </a
>. Once your trial ends, you'll need a paid plan to continue to use GitLens+ features on this
and other private repos.
</p>
`;
actions = shortTimeRemaining;
break;
}
case SubscriptionState.FreePreviewTrialExpired:
forcePlus = true;
plan = 'Free Trial (0 days)';
content = `
<h3>Extend Your GitLens+ Trial</h3>
<p>
Your free trial has ended, please sign in to extend your trial of GitLens+ features on private
repos by an additional 7-days.
</p>
<p class="mb-1">
<vscode-button data-action="command:gitlens.plus.loginOrSignUp">Extend Trial</vscode-button>
</p>
`;
actions = `
<a href="command:gitlens.plus.loginOrSignUp">
Extend Trial
</a>
`;
break;
case SubscriptionState.FreePlusTrialExpired:
forcePlus = true;
plan = 'GitLens+ Trial (0 days)';
content = `
<h3>GitLens+ Trial Expired</h3>
<p>
Your free trial has ended, please upgrade your account to continue to use GitLens+ features,
including the Commit Graph, on this and other private repos.
</p>
<p class="mb-1">
<vscode-button data-action="command:gitlens.plus.purchase">Upgrade Your Account</vscode-button>
</p>
`;
actions = `
<a href="command:gitlens.plus.purchase">
Upgrade Your Account
</a>
`;
break;
case SubscriptionState.VerificationRequired:
forcePlus = true;
plan = 'Unverified';
content = `
<h3>Please verify your email</h3>
<p class="alert__message">Please verify the email for the account you created.</p>
<p class="mb-1">
<vscode-button data-action="command:gitlens.plus.resendVerification"
>Resend Verification Email</vscode-button
>
</p>
<p class="mb-1">
<vscode-button data-action="command:gitlens.plus.validate"
>Refresh Verification Status</vscode-button
>
</p>
`;
actions = `
<a href="command:gitlens.plus.resendVerification" title="Resend Verification Email" aria-label="Resend Verification Email">Verify</a>&nbsp;<a
href="command:gitlens.plus.validate"
title="Refresh Verification Status"
aria-label="Refresh Verification Status"
><span class="codicon codicon-sync"></span
></a>
`;
break;
}
private updatePlusContent(days = this.getDaysRemaining()) {
const { subscription, visibility } = this.state;
if (content) {
const $plusContent = document.getElementById('plus-content');
if ($plusContent) {
$plusContent.innerHTML = content;
}
const $plusContent = document.getElementById('plus-content');
if ($plusContent) {
$plusContent.setAttribute('days', days.toString());
$plusContent.setAttribute('state', subscription.state.toString());
$plusContent.setAttribute('visibility', visibility);
}
}
const $headerContent = document.getElementById('header-content');
if ($headerContent) {
$headerContent.innerHTML = plan ?? '';
}
const $headerActions = document.getElementById('header-actions');
if ($headerActions) {
$headerActions.innerHTML = actions ?? '';
private updateSteps() {
if (
this.$steps == null ||
this.$steps.length === 0 ||
this.state.completedSteps == null ||
this.state.completedSteps.length === 0
) {
return;
}
this.$steps?.forEach(el => {
const forceShowPlus = [
SubscriptionState.FreePreviewTrialExpired,
SubscriptionState.FreePlusTrialExpired,
SubscriptionState.VerificationRequired,
].includes(this.state.subscription.state);
this.$steps.forEach(el => {
el.setAttribute(
'completed',
(el.id === 'plus' && forcePlus) || completedSteps?.includes(el.id) !== true ? 'false' : 'true',
(el.id === 'plus' && forceShowPlus) || this.state.completedSteps?.includes(el.id) !== true
? 'false'
: 'true',
);
});
}
private updateSections() {
if (
this.$cards == null ||
this.$cards.length === 0 ||
this.state.dismissedSections == null ||
this.state.dismissedSections.length === 0
) {
return;
}
this.$cards?.forEach(el => {
if (dismissedSections?.includes(el.id)) {
el.remove();
this.state.dismissedSections.forEach(id => {
const found = this.$cards.findIndex(el => el.id === id);
if (found > -1) {
this.$cards[found].remove();
this.$cards.splice(found, 1);
}
});
}
private updateState() {
const { completedSteps, dismissedSections, plusEnabled } = this.state;
document.getElementById('restore-plus')?.classList.toggle('hide', plusEnabled);
const showRestoreWelcome = completedSteps?.length || dismissedSections?.length;
document.getElementById('restore-welcome')?.classList.toggle('hide', !showRestoreWelcome);
const days = this.getDaysRemaining();
this.updateHeader(days);
this.updatePlusContent(days);
this.updateSteps();
this.updateSections();
}
}
function toggleArrayItem(list: string[] = [], item: string, add = true) {
const hasStep = list.includes(item);
if (!hasStep && add) {
list.push(item);
} else if (hasStep && !add) {
list.splice(list.indexOf(item), 1);
}
return list;
}
new HomeApp();

Зареждане…
Отказ
Запис