Date: Tue, 12 Nov 2024 15:29:52 +0100
Subject: [PATCH 06/16] update notification for commit graph
---
src/plus/gk/account/subscriptionService.ts | 17 +++------
.../components/feature-gate-plus-state.ts | 38 ++++++++++++++-----
2 files changed, 35 insertions(+), 20 deletions(-)
diff --git a/src/plus/gk/account/subscriptionService.ts b/src/plus/gk/account/subscriptionService.ts
index 51afbafddbf06..4d6879d05cbd6 100644
--- a/src/plus/gk/account/subscriptionService.ts
+++ b/src/plus/gk/account/subscriptionService.ts
@@ -708,19 +708,14 @@ export class SubscriptionService implements Disposable {
this.changeSubscription(subscription);
setTimeout(async () => {
- const confirm: MessageItem = { title: 'Continue' };
- const learn: MessageItem = { title: 'See Pro Features' };
- const result = await window.showInformationMessage(
- `You can now preview local Pro features for ${
+ await window.showInformationMessage(
+ `You can now preview the Commit Graph on privately-hosted repos for ${
days < 1 ? '1 day' : pluralize('day', days)
- }, or [start your free ${proTrialLengthInDays}-day Pro trial](command:gitlens.plus.signUp "Start Pro Trial") for full access to Pro features.`,
- confirm,
- learn,
+ }, or [start your free ${proTrialLengthInDays}-day Pro trial](command:gitlens.plus.signUp "Start Pro Trial") for full access to all [GitLens Pro](${
+ urls.gitlensProVsCommunity
+ }) features.`,
+ { title: 'Continue' },
);
-
- if (result === learn) {
- void this.learnAboutPro({ source: 'notification', detail: { action: 'preview-started' } }, source);
- }
}, 1);
}
diff --git a/src/webviews/apps/plus/shared/components/feature-gate-plus-state.ts b/src/webviews/apps/plus/shared/components/feature-gate-plus-state.ts
index cd413791d30af..3bcc4ffcf00e4 100644
--- a/src/webviews/apps/plus/shared/components/feature-gate-plus-state.ts
+++ b/src/webviews/apps/plus/shared/components/feature-gate-plus-state.ts
@@ -122,13 +122,34 @@ export class GlFeatureGatePlusState extends LitElement {
case SubscriptionState.Community:
case SubscriptionState.ProPreviewExpired:
- return html`
- ${feature !== 'graph'
- ? html`
- Use on privately-hosted repos requires
- GitLens Pro.
-
`
- : nothing}
+ if (feature === 'graph' && this.state === SubscriptionState.Community) {
+ return html`
+ Continue
+
+ Continuing gives you 3 days to preview
+ ${this.featureWithArticleIfNeeded
+ ? `${this.featureWithArticleIfNeeded} and other `
+ : ''}local
+ Pro features.
+ ${appearance !== 'alert' ? html`
` : ''} For full access to Pro features
+ start your free ${proTrialLengthInDays}-day Pro trial
+ or
+ sign in.
+
+ `;
+ }
+
+ return html`
+ Use on privately-hosted repos requires
+ GitLens Pro.
+
Get ${proTrialLengthInDays} days of GitLens Pro for free - no credit card required. Or
sign in.
-
- `;
+ `;
case SubscriptionState.ProTrialExpired:
return html`
From d35902a4e7eb1fc9ef7c1ae21afe81eeae5c9ac0 Mon Sep 17 00:00:00 2001
From: Sergio
Date: Tue, 12 Nov 2024 16:28:15 +0100
Subject: [PATCH 07/16] change 7 to 14 days
---
README.md | 2 +-
package.json | 8 ++++----
src/constants.subscription.ts | 2 +-
3 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/README.md b/README.md
index fe76ad68ecda5..54965230a5843 100644
--- a/README.md
+++ b/README.md
@@ -293,7 +293,7 @@ Yes. All features are free to use on all repos, **except** for `Pro` features, w
While GitLens offers a remarkable set of free features, a subset of `Pro` features tailored for professional developers and teams, require a trial or paid plan for use on privately-hosted repos — use on local or publicly-hosted repos is free for everyone. Additionally `Preview` features may require a paid plan in the future and some, if cloud-backed, require a GitKraken account.
-Preview `Pro` features instantly for free for 3 days without an account, or start a free GitLens Pro trial to get an additional 7 days and gain access to `Pro` features to experience the full power of GitLens.
+Preview `Pro` features instantly for free for 3 days without an account, or start a free GitLens Pro trial to get an additional 14 days and gain access to `Pro` features to experience the full power of GitLens.
## Are `Pro` and `Preview` features free to use?
diff --git a/package.json b/package.json
index 5f58fdbaeed5f..9448e8328ed82 100644
--- a/package.json
+++ b/package.json
@@ -19420,12 +19420,12 @@
},
{
"view": "gitlens.views.launchpad",
- "contents": "[Continue](command:gitlens.plus.reactivateProTrial?%7B%22source%22%3A%22launchpad-view%22%7D)\n\nReactivate your Pro trial and experience Launchpad and all the new Pro features — free for another 7 days!",
+ "contents": "[Continue](command:gitlens.plus.reactivateProTrial?%7B%22source%22%3A%22launchpad-view%22%7D)\n\nReactivate your Pro trial and experience Launchpad and all the new Pro features — free for another 14 days!",
"when": "!gitlens:launchpad:connect && gitlens:plus:required && gitlens:plus:state == 5"
},
{
"view": "gitlens.views.scm.grouped",
- "contents": "[Continue](command:gitlens.plus.reactivateProTrial?%7B%22source%22%3A%22launchpad-view%22%7D)\n\nReactivate your Pro trial and experience Launchpad and all the new Pro features — free for another 7 days!",
+ "contents": "[Continue](command:gitlens.plus.reactivateProTrial?%7B%22source%22%3A%22launchpad-view%22%7D)\n\nReactivate your Pro trial and experience Launchpad and all the new Pro features — free for another 14 days!",
"when": "!gitlens:launchpad:connect && gitlens:plus:required && gitlens:plus:state == 5 && gitlens:views:scm:grouped:view == launchpad"
},
{
@@ -19527,12 +19527,12 @@
},
{
"view": "gitlens.views.worktrees",
- "contents": "[Continue](command:gitlens.plus.reactivateProTrial?%7B%22source%22%3A%22worktrees%22%7D)\n\nReactivate your Pro trial and experience Worktrees and all the new Pro features — free for another 7 days!",
+ "contents": "[Continue](command:gitlens.plus.reactivateProTrial?%7B%22source%22%3A%22worktrees%22%7D)\n\nReactivate your Pro trial and experience Worktrees and all the new Pro features — free for another 14 days!",
"when": "gitlens:plus:required && gitlens:plus:state == 5"
},
{
"view": "gitlens.views.scm.grouped",
- "contents": "[Continue](command:gitlens.plus.reactivateProTrial?%7B%22source%22%3A%22worktrees%22%7D)\n\nReactivate your Pro trial and experience Worktrees and all the new Pro features — free for another 7 days!",
+ "contents": "[Continue](command:gitlens.plus.reactivateProTrial?%7B%22source%22%3A%22worktrees%22%7D)\n\nReactivate your Pro trial and experience Worktrees and all the new Pro features — free for another 14 days!",
"when": "gitlens:plus:required && gitlens:plus:state == 5 && gitlens:views:scm:grouped:view == worktrees"
},
{
diff --git a/src/constants.subscription.ts b/src/constants.subscription.ts
index a7fc5e5349e36..1dcef837048aa 100644
--- a/src/constants.subscription.ts
+++ b/src/constants.subscription.ts
@@ -1,5 +1,5 @@
export const proPreviewLengthInDays = 3;
-export const proTrialLengthInDays = 7;
+export const proTrialLengthInDays = 14;
export type PromoKeys = 'gitlens16' | 'pro50';
From ca96bc2d6d8c0cdbaafc4f1202a300d6c59baae0 Mon Sep 17 00:00:00 2001
From: Sergio
Date: Wed, 13 Nov 2024 11:58:53 +0100
Subject: [PATCH 08/16] apply patch
---
src/commands/quickCommand.steps.ts | 4 +--
src/plus/gk/account/subscriptionService.ts | 36 ++++++----------------
src/quickpicks/items/directive.ts | 12 +++++---
3 files changed, 20 insertions(+), 32 deletions(-)
diff --git a/src/commands/quickCommand.steps.ts b/src/commands/quickCommand.steps.ts
index 72729c473ebfc..01168508eb927 100644
--- a/src/commands/quickCommand.steps.ts
+++ b/src/commands/quickCommand.steps.ts
@@ -2637,9 +2637,9 @@ export async function* ensureAccessStep<
const promo = getApplicablePromo(access.subscription.current.state, 'gate');
const detail = promo?.quickpick.detail;
- placeholder = 'Pro feature — requires a trial or paid plan for use on privately-hosted repos';
+ placeholder = 'Pro feature — requires a trial or GitLens Pro for use on privately-hosted repos';
if (isSubscriptionPaidPlan(access.subscription.required) && access.subscription.current.account != null) {
- placeholder = 'Pro feature — requires a paid plan for use on privately-hosted repos';
+ placeholder = 'Pro feature — requires GitLens Pro for use on privately-hosted repos';
directives.push(
createDirectiveQuickPickItem(Directive.RequiresPaidSubscription, true, { detail: detail }),
createQuickPickSeparator(),
diff --git a/src/plus/gk/account/subscriptionService.ts b/src/plus/gk/account/subscriptionService.ts
index 4d6879d05cbd6..0ad5f9894ea9a 100644
--- a/src/plus/gk/account/subscriptionService.ts
+++ b/src/plus/gk/account/subscriptionService.ts
@@ -316,42 +316,26 @@ export class SubscriptionService implements Disposable {
if (account?.verified === false) {
const verify: MessageItem = { title: 'Resend Email' };
- const learn: MessageItem | undefined = isSubscriptionPaid(this._subscription)
- ? { title: 'See Pro Features' }
- : undefined;
const confirm: MessageItem = { title: 'Continue', isCloseAffordance: true };
const result = await window.showInformationMessage(
- isSubscriptionPaid(this._subscription)
- ? `You are now on the ${actual.name} plan. \n\nYou must first verify your email. Once verified, you will have full access to Pro features.`
- : `Welcome to GitLens.`,
- {
- modal: true,
- detail: isSubscriptionPaid(this._subscription)
- ? `Your ${
- isSubscriptionPaid(this._subscription) ? 'plan' : 'trial'
- } also includes access to the GitKraken DevEx platform, unleashing powerful Git visualization & productivity capabilities everywhere you work: IDE, desktop, browser, and terminal.`
- : `Verify the email we just sent you to start your Pro trial.`,
- },
- ...([verify, learn, confirm].filter(Boolean) as MessageItem[]),
+ 'Welcome to GitLens',
+ { modal: true, detail: 'Verify the email we just sent you to start your Pro trial.' },
+ verify,
+ confirm,
);
if (result === verify) {
void this.resendVerification(source);
- } else if (learn && result === learn) {
- void this.learnAboutPro({ source: 'prompt', detail: { action: 'trial-started-verify-email' } }, source);
}
} else if (isSubscriptionPaid(this._subscription)) {
- const learn: MessageItem = { title: 'See Pro Features' };
+ const learn: MessageItem = { title: 'Learn More' };
const confirm: MessageItem = { title: 'Continue', isCloseAffordance: true };
const result = await window.showInformationMessage(
`You are now on the ${actual.name} plan and have full access to Pro features.`,
- {
- modal: true,
- detail: 'Your plan also includes access to the GitKraken DevEx platform, unleashing powerful Git visualization & productivity capabilities everywhere you work: IDE, desktop, browser, and terminal.',
- },
- learn,
+ { modal: true },
confirm,
+ learn,
);
if (result === learn) {
@@ -360,7 +344,7 @@ export class SubscriptionService implements Disposable {
} else if (isSubscriptionTrial(this._subscription)) {
const days = getSubscriptionTimeRemaining(this._subscription, 'days') ?? 0;
- const learn: MessageItem = { title: 'See Pro Features' };
+ const learn: MessageItem = { title: 'Learn More' };
const confirm: MessageItem = { title: 'Continue', isCloseAffordance: true };
const result = await window.showInformationMessage(
`Welcome to your ${effective.name} Trial.\n\nYou now have full access to Pro features for ${
@@ -379,13 +363,13 @@ export class SubscriptionService implements Disposable {
}
} else {
const upgrade: MessageItem = { title: 'Upgrade to Pro' };
- const learn: MessageItem = { title: 'See Pro Features' };
+ const learn: MessageItem = { title: 'Community vs. Pro' };
const confirm: MessageItem = { title: 'Continue', isCloseAffordance: true };
const result = await window.showInformationMessage(
`You are now on the ${actual.name} plan.`,
{
modal: true,
- detail: 'You only have access to Pro features on publicly-hosted repos. For full access to Pro features, please upgrade to a paid plan.\nA paid plan also includes access to the GitKraken DevEx platform, unleashing powerful Git visualization & productivity capabilities everywhere you work: IDE, desktop, browser, and terminal.',
+ detail: 'You only have access to Pro features on publicly-hosted repos. For full access to Pro features, please upgrade to a paid plan.',
},
upgrade,
learn,
diff --git a/src/quickpicks/items/directive.ts b/src/quickpicks/items/directive.ts
index 412e02525f4f3..773560aa02344 100644
--- a/src/quickpicks/items/directive.ts
+++ b/src/quickpicks/items/directive.ts
@@ -1,5 +1,6 @@
import type { QuickPickItem, ThemeIcon, Uri } from 'vscode';
import { proPreviewLengthInDays, proTrialLengthInDays } from '../../constants.subscription';
+import { pluralize } from '../../system/string';
export enum Directive {
Back,
@@ -64,8 +65,11 @@ export function createDirectiveQuickPickItem(
detail = `Continuing gives you ${proPreviewLengthInDays} days to preview this and other local Pro features`;
break;
case Directive.StartProTrial:
- label = 'Start Pro Trial';
- detail = `Start your free ${proTrialLengthInDays}-day GitLens Pro trial - no credit card required.`;
+ label = 'Try GitLens Pro';
+ detail = `Get ${pluralize(
+ 'day',
+ proTrialLengthInDays,
+ )} of GitLens Pro trial for free - no credit card required.`;
break;
case Directive.RequiresVerification:
label = 'Resend Email';
@@ -74,9 +78,9 @@ export function createDirectiveQuickPickItem(
case Directive.RequiresPaidSubscription:
label = 'Upgrade to Pro';
if (detail != null) {
- description ??= ' - access Launchpad and all Pro features with GitLens Pro';
+ description ??= ' \u2014\u00a0\u00a0 GitLens Pro is required to use this feature';
} else {
- detail = 'Upgrading to a paid plan is required to use this Pro feature';
+ detail = 'Upgrading to GitLens Pro is required to use this feature';
}
break;
}
From f152efb8daea545fed0e3143c4cf2ea81bfc924f Mon Sep 17 00:00:00 2001
From: Sergio
Date: Tue, 29 Oct 2024 15:26:01 +0100
Subject: [PATCH 09/16] deprecate current 3 day preview trial & add feature
preview trial
---
package.json | 9 ---------
src/commands/quickCommand.steps.ts | 6 +++---
src/commands/quickWizard.base.ts | 3 ++-
src/constants.commands.ts | 1 +
src/plus/gk/account/subscriptionService.ts | 7 +++++++
src/plus/utils.ts | 9 +++++----
src/webviews/apps/plus/graph/GraphWrapper.tsx | 1 +
.../shared/components/feature-gate-plus-state.ts | 14 ++++++++++----
.../apps/shared/components/feature-gate.ts | 4 ++++
9 files changed, 33 insertions(+), 21 deletions(-)
diff --git a/package.json b/package.json
index 9448e8328ed82..4b16f76472ca1 100644
--- a/package.json
+++ b/package.json
@@ -5726,11 +5726,6 @@
"title": "Sign Up for GitKraken...",
"category": "GitLens"
},
- {
- "command": "gitlens.plus.startPreviewTrial",
- "title": "Preview Pro",
- "category": "GitLens"
- },
{
"command": "gitlens.plus.reactivateProTrial",
"title": "Reactivate Pro Trial",
@@ -10276,10 +10271,6 @@
"command": "gitlens.plus.signUp",
"when": "!gitlens:plus"
},
- {
- "command": "gitlens.plus.startPreviewTrial",
- "when": "!gitlens:plus"
- },
{
"command": "gitlens.plus.reactivateProTrial",
"when": "gitlens:plus:state == 5"
diff --git a/src/commands/quickCommand.steps.ts b/src/commands/quickCommand.steps.ts
index 01168508eb927..39ca0e450b0b4 100644
--- a/src/commands/quickCommand.steps.ts
+++ b/src/commands/quickCommand.steps.ts
@@ -44,7 +44,7 @@ import type { GitWorktree, WorktreeQuickPickItem } from '../git/models/worktree'
import { createWorktreeQuickPickItem, getWorktreesByBranch, sortWorktrees } from '../git/models/worktree';
import { remoteUrlRegex } from '../git/parsers/remoteParser';
import { getApplicablePromo } from '../plus/gk/account/promos';
-import { isSubscriptionPaidPlan, isSubscriptionPreviewTrialExpired } from '../plus/gk/account/subscription';
+import { isSubscriptionPaidPlan } from '../plus/gk/account/subscription';
import type { LaunchpadCommandArgs } from '../plus/launchpad/launchpad';
import {
CommitApplyFileChangesCommandQuickPickItem,
@@ -2645,7 +2645,7 @@ export async function* ensureAccessStep<
createQuickPickSeparator(),
createDirectiveQuickPickItem(Directive.Cancel),
);
- } else if (
+ } /*else if (
access.subscription.current.account == null &&
!isSubscriptionPreviewTrialExpired(access.subscription.current)
) {
@@ -2654,7 +2654,7 @@ export async function* ensureAccessStep<
createQuickPickSeparator(),
createDirectiveQuickPickItem(Directive.Cancel),
);
- } else {
+ }*/ else {
directives.push(
createDirectiveQuickPickItem(Directive.StartProTrial, true),
createDirectiveQuickPickItem(Directive.SignIn),
diff --git a/src/commands/quickWizard.base.ts b/src/commands/quickWizard.base.ts
index c0fbbe2ced9a4..392faba0f9fe9 100644
--- a/src/commands/quickWizard.base.ts
+++ b/src/commands/quickWizard.base.ts
@@ -704,6 +704,7 @@ export abstract class QuickWizardCommandBase extends Command {
return;
}
+ /*
case Directive.StartPreview:
await Container.instance.subscription.startPreviewTrial({
source: 'quick-wizard',
@@ -714,7 +715,7 @@ export abstract class QuickWizardCommandBase extends Command {
});
resolve(await rootStep.command?.retry());
return;
-
+ */
case Directive.RequiresVerification: {
const result = await Container.instance.subscription.resendVerification({
source: 'quick-wizard',
diff --git a/src/constants.commands.ts b/src/constants.commands.ts
index 1e9424933e90d..cafae2cf499a2 100644
--- a/src/constants.commands.ts
+++ b/src/constants.commands.ts
@@ -150,6 +150,7 @@ export const enum Commands {
PlusShowPlans = 'gitlens.plus.showPlans',
PlusSignUp = 'gitlens.plus.signUp',
PlusStartPreviewTrial = 'gitlens.plus.startPreviewTrial',
+ PlusStartFeaturePreviewTrial = 'gitlens.plus.startFeaturePreviewTrial',
PlusUpgrade = 'gitlens.plus.upgrade',
PlusValidate = 'gitlens.plus.validate',
PlusSimulateSubscription = 'gitlens.plus.simulateSubscription',
diff --git a/src/plus/gk/account/subscriptionService.ts b/src/plus/gk/account/subscriptionService.ts
index 0ad5f9894ea9a..39ec190317b5a 100644
--- a/src/plus/gk/account/subscriptionService.ts
+++ b/src/plus/gk/account/subscriptionService.ts
@@ -242,6 +242,9 @@ export class SubscriptionService implements Disposable {
registerCommand(Commands.PlusManage, (src?: Source) => this.manage(src)),
registerCommand(Commands.PlusShowPlans, (src?: Source) => this.showPlans(src)),
registerCommand(Commands.PlusStartPreviewTrial, (src?: Source) => this.startPreviewTrial(src)),
+ registerCommand(Commands.PlusStartFeaturePreviewTrial, (src?: Source) =>
+ this.startFeaturePreviewTrial(src),
+ ),
registerCommand(Commands.PlusReactivateProTrial, (src?: Source) => this.reactivateProTrial(src)),
registerCommand(Commands.PlusResendVerification, (src?: Source) => this.resendVerification(src)),
registerCommand(Commands.PlusUpgrade, (src?: Source) => this.upgrade(src)),
@@ -703,6 +706,10 @@ export class SubscriptionService implements Disposable {
}, 1);
}
+ @gate(() => '')
+ @log()
+ async startFeaturePreviewTrial(_: Source | undefined): Promise {}
+
@log()
async upgrade(source: Source | undefined): Promise {
const scope = getLogScope();
diff --git a/src/plus/utils.ts b/src/plus/utils.ts
index f1fd1fffafbc2..7989fd1519d6d 100644
--- a/src/plus/utils.ts
+++ b/src/plus/utils.ts
@@ -5,13 +5,13 @@ import { proTrialLengthInDays } from '../constants.subscription';
import type { Source } from '../constants.telemetry';
import type { Container } from '../container';
import { openUrl } from '../system/vscode/utils';
-import { isSubscriptionPaidPlan, isSubscriptionPreviewTrialExpired } from './gk/account/subscription';
+import { isSubscriptionPaidPlan } from './gk/account/subscription';
export async function ensurePaidPlan(
container: Container,
title: string,
source: Source,
- options?: { allowPreview?: boolean },
+ _?: { allowPreview?: boolean },
): Promise {
while (true) {
const subscription = await container.subscription.getSubscription();
@@ -37,7 +37,7 @@ export async function ensurePaidPlan(
const plan = subscription.plan.effective.id;
if (isSubscriptionPaidPlan(plan)) break;
- if (options?.allowPreview && subscription.account == null && !isSubscriptionPreviewTrialExpired(subscription)) {
+ /*if (options?.allowPreview && subscription.account == null && !isSubscriptionPreviewTrialExpired(subscription)) {
const startTrial = { title: 'Continue' };
const cancel = { title: 'Cancel', isCloseAffordance: true };
const result = await window.showWarningMessage(
@@ -51,7 +51,8 @@ export async function ensurePaidPlan(
void container.subscription.startPreviewTrial(source);
break;
- } else if (subscription.account == null) {
+ } else */
+ if (subscription.account == null) {
const signUp = { title: 'Start Pro Trial' };
const signIn = { title: 'Sign In' };
const cancel = { title: 'Cancel', isCloseAffordance: true };
diff --git a/src/webviews/apps/plus/graph/GraphWrapper.tsx b/src/webviews/apps/plus/graph/GraphWrapper.tsx
index 9f95da0833fca..7b5fbacec75f0 100644
--- a/src/webviews/apps/plus/graph/GraphWrapper.tsx
+++ b/src/webviews/apps/plus/graph/GraphWrapper.tsx
@@ -1530,6 +1530,7 @@ export function GraphWrapper({
Continue
- Continuing gives you 3 days to preview
+ Continuing gives you ${proPreviewLengthInDays - consumedDays}
+ day${proPreviewLengthInDays - consumedDays !== 1 ? 's' : ''} to preview
${this.featureWithArticleIfNeeded
? `${this.featureWithArticleIfNeeded} and other `
: ''}local
diff --git a/src/webviews/apps/shared/components/feature-gate.ts b/src/webviews/apps/shared/components/feature-gate.ts
index 9bba2a5095de6..8715c3bdcb19c 100644
--- a/src/webviews/apps/shared/components/feature-gate.ts
+++ b/src/webviews/apps/shared/components/feature-gate.ts
@@ -90,6 +90,9 @@ export class GlFeatureGate extends LitElement {
}
`;
+ @property({ type: Boolean })
+ allowFeaturePreviewTrial?: boolean;
+
@property({ reflect: true })
appearance?: 'alert' | 'welcome';
@@ -126,6 +129,7 @@ export class GlFeatureGate extends LitElement {
.featureWithArticleIfNeeded=${this.featureWithArticleIfNeeded}
.source=${this.source}
.state=${this.state}
+ .allowFeaturePreviewTrial=${this.allowFeaturePreviewTrial}
>
`;
From d37e33ca2477c19ff06056f103efc9520bb85739 Mon Sep 17 00:00:00 2001
From: Sergio
Date: Tue, 5 Nov 2024 14:06:39 +0100
Subject: [PATCH 10/16] add 3 day local feature trial
---
src/constants.storage.ts | 7 +-
src/constants.telemetry.ts | 3 +-
src/plus/gk/account/subscriptionService.ts | 7 --
src/plus/webviews/graph/graphWebview.ts | 77 +++++++++++++++++++
src/plus/webviews/graph/protocol.ts | 12 +++
src/webviews/apps/plus/graph/GraphWrapper.tsx | 13 ++++
src/webviews/apps/plus/graph/graph.tsx | 7 +-
.../components/feature-gate-plus-state.ts | 26 ++++---
.../apps/shared/components/feature-gate.ts | 12 ++-
9 files changed, 143 insertions(+), 21 deletions(-)
diff --git a/src/constants.storage.ts b/src/constants.storage.ts
index faada399620b4..8eec9bd50e90a 100644
--- a/src/constants.storage.ts
+++ b/src/constants.storage.ts
@@ -1,7 +1,7 @@
import type { GraphBranchesVisibility, ViewShowBranchComparison } from './config';
import type { AIProviders } from './constants.ai';
import type { IntegrationId } from './constants.integrations';
-import type { TrackedUsage, TrackedUsageKeys } from './constants.telemetry';
+import type { Sources, TrackedUsage, TrackedUsageKeys } from './constants.telemetry';
import type { GroupableTreeViewTypes } from './constants.views';
import type { Environment } from './container';
import type { Subscription } from './plus/gk/account/subscription';
@@ -77,7 +77,10 @@ export type GlobalStorage = {
'launchpadView:groups:expanded': StoredLaunchpadGroup[];
'graph:searchMode': StoredGraphSearchMode;
'views:scm:grouped:welcome:dismissed': boolean;
-} & { [key in `confirm:ai:tos:${AIProviders}`]: boolean } & {
+} & { [key in `confirm:ai:tos:${AIProviders}`]: boolean }
+} & { [key in `plus:featurePreviewTrial:${Sources}:consumedDays`]: { startedOn: string; expiresOn: string }[] } & {
+ [key in `confirm:ai:tos:${AIProviders}`]: boolean;
+} & {
[key in `provider:authentication:skip:${string}`]: boolean;
} & { [key in `gk:${string}:checkin`]: Stored } & {
[key in `gk:${string}:organizations`]: Stored;
diff --git a/src/constants.telemetry.ts b/src/constants.telemetry.ts
index f9483f00af88d..71b2365e3d8d4 100644
--- a/src/constants.telemetry.ts
+++ b/src/constants.telemetry.ts
@@ -413,7 +413,8 @@ export type TelemetryEvents = {
| 'resend-verification'
| 'pricing'
| 'start-preview-trial'
- | 'upgrade';
+ | 'upgrade'
+ | `start-${Sources}-preview-trial`;
}
| {
action: 'visibility';
diff --git a/src/plus/gk/account/subscriptionService.ts b/src/plus/gk/account/subscriptionService.ts
index 39ec190317b5a..0ad5f9894ea9a 100644
--- a/src/plus/gk/account/subscriptionService.ts
+++ b/src/plus/gk/account/subscriptionService.ts
@@ -242,9 +242,6 @@ export class SubscriptionService implements Disposable {
registerCommand(Commands.PlusManage, (src?: Source) => this.manage(src)),
registerCommand(Commands.PlusShowPlans, (src?: Source) => this.showPlans(src)),
registerCommand(Commands.PlusStartPreviewTrial, (src?: Source) => this.startPreviewTrial(src)),
- registerCommand(Commands.PlusStartFeaturePreviewTrial, (src?: Source) =>
- this.startFeaturePreviewTrial(src),
- ),
registerCommand(Commands.PlusReactivateProTrial, (src?: Source) => this.reactivateProTrial(src)),
registerCommand(Commands.PlusResendVerification, (src?: Source) => this.resendVerification(src)),
registerCommand(Commands.PlusUpgrade, (src?: Source) => this.upgrade(src)),
@@ -706,10 +703,6 @@ export class SubscriptionService implements Disposable {
}, 1);
}
- @gate(() => '')
- @log()
- async startFeaturePreviewTrial(_: Source | undefined): Promise {}
-
@log()
async upgrade(source: Source | undefined): Promise {
const scope = getLogScope();
diff --git a/src/plus/webviews/graph/graphWebview.ts b/src/plus/webviews/graph/graphWebview.ts
index 4a69e82a51d0d..712b884169186 100644
--- a/src/plus/webviews/graph/graphWebview.ts
+++ b/src/plus/webviews/graph/graphWebview.ts
@@ -20,6 +20,7 @@ import type {
import { GlyphChars } from '../../../constants';
import { Commands } from '../../../constants.commands';
import type { StoredGraphFilters, StoredGraphRefType } from '../../../constants.storage';
+import { proPreviewLengthInDays, proTrialLengthInDays } from '../../../constants.subscription';
import type { GraphShownTelemetryContext, GraphTelemetryContext, TelemetryEvents } from '../../../constants.telemetry';
import type { Container } from '../../../container';
import { CancellationError } from '../../../errors';
@@ -97,6 +98,7 @@ import { getSearchQueryComparisonKey, parseSearchQuery } from '../../../git/sear
import { splitGitCommitMessage } from '../../../git/utils/commit-utils';
import { ReferencesQuickPickIncludes, showReferencePicker } from '../../../quickpicks/referencePicker';
import { showRepositoryPicker } from '../../../quickpicks/repositoryPicker';
+import { createFromDateDelta } from '../../../system/date';
import { gate } from '../../../system/decorators/gate';
import { debug, log } from '../../../system/decorators/log';
import type { Deferrable } from '../../../system/function';
@@ -136,6 +138,7 @@ import type {
DidGetCountParams,
DidGetRowHoverParams,
DidSearchParams,
+ DidSetFeaturePreviewTrialParams,
DoubleClickedParams,
GetMissingAvatarsParams,
GetMissingRefsMetadataParams,
@@ -206,6 +209,7 @@ import {
DidChangeWorkingTreeNotification,
DidFetchNotification,
DidSearchNotification,
+ DidSetFeaturePreviewTrialNotification,
DoubleClickedCommandType,
EnsureRowRequest,
GetCountsRequest,
@@ -294,6 +298,7 @@ export class GraphWebviewProvider implements WebviewProvider | null | undefined;
private _search: GitSearch | undefined;
@@ -681,11 +686,58 @@ export class GraphWebviewProvider implements WebviewProvider 0 && new Date(consumedDays[consumedDays.length - 1].expiresOn) > timestamp) {
+ return;
+ }
+
+ if (consumedDays.length >= proPreviewLengthInDays) {
+ void window.showInformationMessage(
+ `You have already used your ${proPreviewLengthInDays} days of previewing local Pro features.`,
+ );
+ return;
+ }
+
+ await this.container.storage.store(`plus:featurePreviewTrial:graph:consumedDays`, [
+ ...(consumedDays ?? []),
+ {
+ startedOn: timestamp.toISOString(),
+ expiresOn: createFromDateDelta(timestamp, { days: 1 }).toISOString(),
+ },
+ ]);
+
+ if (this.container.telemetry.enabled) {
+ this.container.telemetry.sendEvent(
+ 'subscription/action',
+ { action: `start-graph-preview-trial` },
+ { source: 'graph' },
+ );
+ }
+
+ void window.showInformationMessage(
+ `You can now preview local Pro features for 1 day${
+ consumedDays.length + 1 < proPreviewLengthInDays
+ ? `, up to ${proPreviewLengthInDays - (consumedDays.length + 1)} more days`
+ : ''
+ }, or [start your free ${proTrialLengthInDays}-day Pro trial](command:gitlens.plus.signUp "Start Pro Trial") for full access to Pro features.`,
+ );
+
+ void this.notifyDidSetFeaturePreviewTrial();
+ }
+
onWindowFocusChanged(focused: boolean): void {
this.isWindowFocused = focused;
}
@@ -1874,6 +1926,16 @@ export class GraphWebviewProvider implements WebviewProvider 0 &&
+ storedValue.length <= proPreviewLengthInDays &&
+ new Date(storedValue[storedValue.length - 1].expiresOn) > new Date(),
+ };
+ }
+
private updateIncludeOnlyRefs(
repoPath: string | undefined,
{ branchesVisibility, refs }: UpdateIncludedRefsParams,
diff --git a/src/plus/webviews/graph/protocol.ts b/src/plus/webviews/graph/protocol.ts
index f0cfd83c1b5e4..24c4970487b94 100644
--- a/src/plus/webviews/graph/protocol.ts
+++ b/src/plus/webviews/graph/protocol.ts
@@ -25,6 +25,7 @@ import type {
import type { Config, DateStyle, GraphBranchesVisibility } from '../../../config';
import type { SupportedCloudIntegrationIds } from '../../../constants.integrations';
import type { SearchQuery } from '../../../constants.search';
+import type { Source, Sources } from '../../../constants.telemetry';
import type { RepositoryVisibility } from '../../../git/gitProvider';
import type { GitTrackingState } from '../../../git/models/branch';
import type { GitGraphRowType } from '../../../git/models/graph';
@@ -129,6 +130,7 @@ export interface State extends WebviewState {
bottom: number;
};
theming?: { cssVariables: CssVariables; themeOpacityFactor: number };
+ graphPreviewTrial?: { consumedDays: { startedOn: string; expiresOn: string }[]; isActive: boolean };
}
export interface BranchState extends GitTrackingState {
@@ -380,6 +382,16 @@ export interface DidSearchParams {
export const SearchRequest = new IpcRequest(scope, 'search');
// NOTIFICATIONS
+export interface DidSetFeaturePreviewTrialParams {
+ feature: Sources;
+ consumedDays: { startedOn: string; expiresOn: string }[];
+ isActive: boolean;
+}
+
+export const DidSetFeaturePreviewTrialNotification = new IpcNotification(
+ scope,
+ 'featurePreviewTrial/didSet',
+);
export interface DidChangeRepoConnectionParams {
repositories?: GraphRepository[];
diff --git a/src/webviews/apps/plus/graph/GraphWrapper.tsx b/src/webviews/apps/plus/graph/GraphWrapper.tsx
index 7b5fbacec75f0..f536aafb54877 100644
--- a/src/webviews/apps/plus/graph/GraphWrapper.tsx
+++ b/src/webviews/apps/plus/graph/GraphWrapper.tsx
@@ -64,6 +64,7 @@ import {
DidChangeWorkingTreeNotification,
DidFetchNotification,
DidSearchNotification,
+ DidSetFeaturePreviewTrialNotification,
} from '../../../../plus/webviews/graph/protocol';
import { createCommandLink } from '../../../../system/commands';
import { filterMap, first, groupByFilterMap, join } from '../../../../system/iterable';
@@ -282,6 +283,8 @@ export function GraphWrapper({
const [windowFocused, setWindowFocused] = useState(state.windowFocused);
const [allowed, setAllowed] = useState(state.allowed ?? false);
const [subscription, setSubscription] = useState(state.subscription);
+ const [graphPreviewTrial, setGraphPreviewTrial] = useState(state.graphPreviewTrial);
+
// search state
const searchEl = useRef(null);
const [searchQuery, setSearchQuery] = useState(undefined);
@@ -318,6 +321,10 @@ export function GraphWrapper({
setStyleProps(state.theming);
}
break;
+ case DidSetFeaturePreviewTrialNotification:
+ setGraphPreviewTrial(state.graphPreviewTrial);
+ setAllowed(state.graphPreviewTrial?.isActive || allowed);
+ break;
case DidChangeAvatarsNotification:
setAvatars(state.avatars);
break;
@@ -1531,6 +1538,12 @@ export function GraphWrapper({
{
this.state.avatars = msg.params.avatars;
this.setState(this.state, DidChangeAvatarsNotification);
break;
-
+ case DidSetFeaturePreviewTrialNotification.is(msg):
+ this.state.graphPreviewTrial = { consumedDays: msg.params.consumedDays, isActive: msg.params.isActive };
+ this.state.allowed = msg.params.isActive || this.state.allowed;
+ this.setState(this.state, DidSetFeaturePreviewTrialNotification);
+ break;
case DidChangeBranchStateNotification.is(msg):
this.state.branchState = msg.params.branchState;
this.setState(this.state, DidChangeBranchStateNotification);
diff --git a/src/webviews/apps/plus/shared/components/feature-gate-plus-state.ts b/src/webviews/apps/plus/shared/components/feature-gate-plus-state.ts
index 341adbb4f2dad..06ebbbf70d082 100644
--- a/src/webviews/apps/plus/shared/components/feature-gate-plus-state.ts
+++ b/src/webviews/apps/plus/shared/components/feature-gate-plus-state.ts
@@ -3,7 +3,7 @@ import { customElement, property, query } from 'lit/decorators.js';
import { urls } from '../../../../../constants';
import { Commands } from '../../../../../constants.commands';
import { proPreviewLengthInDays, proTrialLengthInDays, SubscriptionState } from '../../../../../constants.subscription';
-import type { Source } from '../../../../../constants.telemetry';
+import type { Source, Sources } from '../../../../../constants.telemetry';
import type { Promo } from '../../../../../plus/gk/account/promos';
import { getApplicablePromo } from '../../../../../plus/gk/account/promos';
import { pluralize } from '../../../../../system/string';
@@ -73,6 +73,14 @@ export class GlFeatureGatePlusState extends LitElement {
@property({ type: Boolean })
allowFeaturePreviewTrial?: boolean;
+ @property({ type: Object })
+ featureInPreviewTrial?: {
+ [key in Sources]?: { consumedDays: { startedOn: string; expiresOn: string }[]; isActive: boolean };
+ };
+
+ @property({ type: String })
+ featurePreviewTrialCommandLink?: string;
+
@property({ type: String })
appearance?: 'alert' | 'welcome';
@@ -100,8 +108,10 @@ export class GlFeatureGatePlusState extends LitElement {
this.hidden = false;
const appearance = (this.appearance ?? 'alert') === 'alert' ? 'alert' : nothing;
const promo = this.state ? getApplicablePromo(this.state, 'gate') : undefined;
- const consumedDays = 0;
- //this.container.storage.get(`plus:featurePreviewTrial:${this.source?.source}:consumedDays`) ?? 0;
+ let consumedDaysCount = 0;
+ if (this.source?.source) {
+ consumedDaysCount = this.featureInPreviewTrial?.[this.source.source]?.consumedDays?.length ?? 0;
+ }
const feature = this.source?.source || '';
@@ -128,17 +138,15 @@ export class GlFeatureGatePlusState extends LitElement {
case SubscriptionState.Community:
case SubscriptionState.ProPreviewExpired:
if (this.allowFeaturePreviewTrial && this.state === SubscriptionState.Community) {
+ const daysLeft = proPreviewLengthInDays - consumedDaysCount;
return html`
- Continue
- Continuing gives you ${proPreviewLengthInDays - consumedDays}
- day${proPreviewLengthInDays - consumedDays !== 1 ? 's' : ''} to preview
+ Continuing gives you ${pluralize('day', daysLeft)} to preview
${this.featureWithArticleIfNeeded
- ? `${this.featureWithArticleIfNeeded} and other `
+ ? `${this.featureWithArticleIfNeeded} and other `
: ''}local
Pro features.
${appearance !== 'alert' ? html`
` : ''} For full access to Pro features
diff --git a/src/webviews/apps/shared/components/feature-gate.ts b/src/webviews/apps/shared/components/feature-gate.ts
index 8715c3bdcb19c..41d962730b646 100644
--- a/src/webviews/apps/shared/components/feature-gate.ts
+++ b/src/webviews/apps/shared/components/feature-gate.ts
@@ -1,7 +1,7 @@
import { css, html, LitElement } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import type { SubscriptionState } from '../../../../constants.subscription';
-import type { Source } from '../../../../constants.telemetry';
+import type { Source, Sources } from '../../../../constants.telemetry';
import { isSubscriptionStatePaidOrTrial } from '../../../../plus/gk/account/subscription';
import '../../plus/shared/components/feature-gate-plus-state';
@@ -93,6 +93,14 @@ export class GlFeatureGate extends LitElement {
@property({ type: Boolean })
allowFeaturePreviewTrial?: boolean;
+ @property({ type: Object })
+ featureInPreviewTrial?: {
+ [key in Sources]?: { consumedDays: { startedOn: string; expiresOn: string }[]; isActive: boolean };
+ };
+
+ @property({ type: String })
+ featurePreviewTrialCommandLink?: string;
+
@property({ reflect: true })
appearance?: 'alert' | 'welcome';
@@ -130,6 +138,8 @@ export class GlFeatureGate extends LitElement {
.source=${this.source}
.state=${this.state}
.allowFeaturePreviewTrial=${this.allowFeaturePreviewTrial}
+ .featureInPreviewTrial=${this.featureInPreviewTrial}
+ featurePreviewTrialCommandLink=${this.featurePreviewTrialCommandLink}
>
`;
From 6ca913d724405a3d4ad43cab97faf177e9c14296 Mon Sep 17 00:00:00 2001
From: Sergio
Date: Tue, 5 Nov 2024 15:50:21 +0100
Subject: [PATCH 11/16] improve messaging on modal
---
.../components/feature-gate-plus-state.ts | 22 +++++++++++++------
1 file changed, 15 insertions(+), 7 deletions(-)
diff --git a/src/webviews/apps/plus/shared/components/feature-gate-plus-state.ts b/src/webviews/apps/plus/shared/components/feature-gate-plus-state.ts
index 06ebbbf70d082..d70d3ef6ebefe 100644
--- a/src/webviews/apps/plus/shared/components/feature-gate-plus-state.ts
+++ b/src/webviews/apps/plus/shared/components/feature-gate-plus-state.ts
@@ -140,15 +140,23 @@ export class GlFeatureGatePlusState extends LitElement {
if (this.allowFeaturePreviewTrial && this.state === SubscriptionState.Community) {
const daysLeft = proPreviewLengthInDays - consumedDaysCount;
return html`
- Continue
+
+ Start ${proTrialLengthInDays}-day Pro Trial
+ or
+ Continue
+
+
Continuing gives you ${pluralize('day', daysLeft)} to preview
- ${this.featureWithArticleIfNeeded
- ? `${this.featureWithArticleIfNeeded} and other `
- : ''}local
- Pro features.
+ ${this.featureWithArticleIfNeeded ? `${this.featureWithArticleIfNeeded} on` : ''} private
+ repositories.
${appearance !== 'alert' ? html`
` : ''} For full access to Pro features
start your free ${proTrialLengthInDays}-day Pro trial
Date: Tue, 5 Nov 2024 16:23:33 +0100
Subject: [PATCH 12/16] get rid of unnecessary prop
---
src/webviews/apps/plus/graph/GraphWrapper.tsx | 1 -
.../components/feature-gate-plus-state.ts | 22 ++++++++++---------
.../apps/shared/components/feature-gate.ts | 4 ----
3 files changed, 12 insertions(+), 15 deletions(-)
diff --git a/src/webviews/apps/plus/graph/GraphWrapper.tsx b/src/webviews/apps/plus/graph/GraphWrapper.tsx
index f536aafb54877..43e7260ab3054 100644
--- a/src/webviews/apps/plus/graph/GraphWrapper.tsx
+++ b/src/webviews/apps/plus/graph/GraphWrapper.tsx
@@ -1537,7 +1537,6 @@ export function GraphWrapper({
0
+ ) {
return html`
- Continuing gives you ${pluralize('day', daysLeft)} to preview
- ${this.featureWithArticleIfNeeded ? `${this.featureWithArticleIfNeeded} on` : ''} private
- repositories.
+ Continuing gives you ${pluralize('day', proPreviewLengthInDays - consumedDaysCount)} to
+ preview ${this.featureWithArticleIfNeeded ? `${this.featureWithArticleIfNeeded} on` : ''}
+ private repositories.
${appearance !== 'alert' ? html`
` : ''} For full access to Pro features
start your free ${proTrialLengthInDays}-day Pro trial
From 46ad7caeb9f65602cade86b9b26bb0302cff2e20 Mon Sep 17 00:00:00 2001
From: Sergio
Date: Tue, 5 Nov 2024 16:30:02 +0100
Subject: [PATCH 13/16] remove notification
---
src/plus/webviews/graph/graphWebview.ts | 8 --------
1 file changed, 8 deletions(-)
diff --git a/src/plus/webviews/graph/graphWebview.ts b/src/plus/webviews/graph/graphWebview.ts
index 712b884169186..183ad9f95ce7c 100644
--- a/src/plus/webviews/graph/graphWebview.ts
+++ b/src/plus/webviews/graph/graphWebview.ts
@@ -727,14 +727,6 @@ export class GraphWebviewProvider implements WebviewProvider
Date: Tue, 5 Nov 2024 16:54:33 +0100
Subject: [PATCH 14/16] undo commented out blocks
---
src/commands/quickCommand.steps.ts | 6 +++---
src/commands/quickWizard.base.ts | 3 +--
src/constants.commands.ts | 1 -
src/plus/utils.ts | 9 ++++-----
4 files changed, 8 insertions(+), 11 deletions(-)
diff --git a/src/commands/quickCommand.steps.ts b/src/commands/quickCommand.steps.ts
index 39ca0e450b0b4..01168508eb927 100644
--- a/src/commands/quickCommand.steps.ts
+++ b/src/commands/quickCommand.steps.ts
@@ -44,7 +44,7 @@ import type { GitWorktree, WorktreeQuickPickItem } from '../git/models/worktree'
import { createWorktreeQuickPickItem, getWorktreesByBranch, sortWorktrees } from '../git/models/worktree';
import { remoteUrlRegex } from '../git/parsers/remoteParser';
import { getApplicablePromo } from '../plus/gk/account/promos';
-import { isSubscriptionPaidPlan } from '../plus/gk/account/subscription';
+import { isSubscriptionPaidPlan, isSubscriptionPreviewTrialExpired } from '../plus/gk/account/subscription';
import type { LaunchpadCommandArgs } from '../plus/launchpad/launchpad';
import {
CommitApplyFileChangesCommandQuickPickItem,
@@ -2645,7 +2645,7 @@ export async function* ensureAccessStep<
createQuickPickSeparator(),
createDirectiveQuickPickItem(Directive.Cancel),
);
- } /*else if (
+ } else if (
access.subscription.current.account == null &&
!isSubscriptionPreviewTrialExpired(access.subscription.current)
) {
@@ -2654,7 +2654,7 @@ export async function* ensureAccessStep<
createQuickPickSeparator(),
createDirectiveQuickPickItem(Directive.Cancel),
);
- }*/ else {
+ } else {
directives.push(
createDirectiveQuickPickItem(Directive.StartProTrial, true),
createDirectiveQuickPickItem(Directive.SignIn),
diff --git a/src/commands/quickWizard.base.ts b/src/commands/quickWizard.base.ts
index 392faba0f9fe9..c0fbbe2ced9a4 100644
--- a/src/commands/quickWizard.base.ts
+++ b/src/commands/quickWizard.base.ts
@@ -704,7 +704,6 @@ export abstract class QuickWizardCommandBase extends Command {
return;
}
- /*
case Directive.StartPreview:
await Container.instance.subscription.startPreviewTrial({
source: 'quick-wizard',
@@ -715,7 +714,7 @@ export abstract class QuickWizardCommandBase extends Command {
});
resolve(await rootStep.command?.retry());
return;
- */
+
case Directive.RequiresVerification: {
const result = await Container.instance.subscription.resendVerification({
source: 'quick-wizard',
diff --git a/src/constants.commands.ts b/src/constants.commands.ts
index cafae2cf499a2..1e9424933e90d 100644
--- a/src/constants.commands.ts
+++ b/src/constants.commands.ts
@@ -150,7 +150,6 @@ export const enum Commands {
PlusShowPlans = 'gitlens.plus.showPlans',
PlusSignUp = 'gitlens.plus.signUp',
PlusStartPreviewTrial = 'gitlens.plus.startPreviewTrial',
- PlusStartFeaturePreviewTrial = 'gitlens.plus.startFeaturePreviewTrial',
PlusUpgrade = 'gitlens.plus.upgrade',
PlusValidate = 'gitlens.plus.validate',
PlusSimulateSubscription = 'gitlens.plus.simulateSubscription',
diff --git a/src/plus/utils.ts b/src/plus/utils.ts
index 7989fd1519d6d..f1fd1fffafbc2 100644
--- a/src/plus/utils.ts
+++ b/src/plus/utils.ts
@@ -5,13 +5,13 @@ import { proTrialLengthInDays } from '../constants.subscription';
import type { Source } from '../constants.telemetry';
import type { Container } from '../container';
import { openUrl } from '../system/vscode/utils';
-import { isSubscriptionPaidPlan } from './gk/account/subscription';
+import { isSubscriptionPaidPlan, isSubscriptionPreviewTrialExpired } from './gk/account/subscription';
export async function ensurePaidPlan(
container: Container,
title: string,
source: Source,
- _?: { allowPreview?: boolean },
+ options?: { allowPreview?: boolean },
): Promise {
while (true) {
const subscription = await container.subscription.getSubscription();
@@ -37,7 +37,7 @@ export async function ensurePaidPlan(
const plan = subscription.plan.effective.id;
if (isSubscriptionPaidPlan(plan)) break;
- /*if (options?.allowPreview && subscription.account == null && !isSubscriptionPreviewTrialExpired(subscription)) {
+ if (options?.allowPreview && subscription.account == null && !isSubscriptionPreviewTrialExpired(subscription)) {
const startTrial = { title: 'Continue' };
const cancel = { title: 'Cancel', isCloseAffordance: true };
const result = await window.showWarningMessage(
@@ -51,8 +51,7 @@ export async function ensurePaidPlan(
void container.subscription.startPreviewTrial(source);
break;
- } else */
- if (subscription.account == null) {
+ } else if (subscription.account == null) {
const signUp = { title: 'Start Pro Trial' };
const signIn = { title: 'Sign In' };
const cancel = { title: 'Cancel', isCloseAffordance: true };
From d537ac56c380e8422e93abec29edb44e02c2453c Mon Sep 17 00:00:00 2001
From: Sergio
Date: Wed, 6 Nov 2024 11:50:37 +0100
Subject: [PATCH 15/16] Update modal messaging
---
.../components/feature-gate-plus-state.ts | 22 +++++--------------
1 file changed, 6 insertions(+), 16 deletions(-)
diff --git a/src/webviews/apps/plus/shared/components/feature-gate-plus-state.ts b/src/webviews/apps/plus/shared/components/feature-gate-plus-state.ts
index 7b5b1759f8071..cfb1b3dca556e 100644
--- a/src/webviews/apps/plus/shared/components/feature-gate-plus-state.ts
+++ b/src/webviews/apps/plus/shared/components/feature-gate-plus-state.ts
@@ -142,28 +142,18 @@ export class GlFeatureGatePlusState extends LitElement {
proPreviewLengthInDays - consumedDaysCount > 0
) {
return html`
-
- Start ${proTrialLengthInDays}-day Pro Trial
- or
- Continue
-
-
+ Continue
Continuing gives you ${pluralize('day', proPreviewLengthInDays - consumedDaysCount)} to
preview ${this.featureWithArticleIfNeeded ? `${this.featureWithArticleIfNeeded} on` : ''}
- private repositories.
- ${appearance !== 'alert' ? html`
` : ''} For full access to Pro features
+ privately-hosted repositories.
+ ${appearance !== 'alert' ? html`
` : ''} For full access to all GitLens Pro features,
start your free ${proTrialLengthInDays}-day Pro trial
- or
+ - no credit card required. Or
sign in.
From 21ad0733c9b12618287b0f0a5c7e92c63d9b5cca Mon Sep 17 00:00:00 2001
From: Sergio
Date: Fri, 8 Nov 2024 15:30:28 +0100
Subject: [PATCH 16/16] add tips for graph for each trial day
---
src/constants.storage.ts | 5 +-
src/plus/webviews/graph/graphWebview.ts | 3 +-
src/plus/webviews/graph/protocol.ts | 3 +-
.../apps/media/graph-commit-search.png | Bin 0 -> 92232 bytes
src/webviews/apps/media/graph-minimap.png | Bin 0 -> 98154 bytes
src/webviews/apps/plus/graph/GraphWrapper.tsx | 1 +
.../components/feature-gate-plus-state.ts | 96 ++++++++++++++----
.../apps/shared/components/feature-gate.ts | 16 ++-
8 files changed, 101 insertions(+), 23 deletions(-)
create mode 100644 src/webviews/apps/media/graph-commit-search.png
create mode 100644 src/webviews/apps/media/graph-minimap.png
diff --git a/src/constants.storage.ts b/src/constants.storage.ts
index 8eec9bd50e90a..ad8a0bcca327a 100644
--- a/src/constants.storage.ts
+++ b/src/constants.storage.ts
@@ -77,8 +77,9 @@ export type GlobalStorage = {
'launchpadView:groups:expanded': StoredLaunchpadGroup[];
'graph:searchMode': StoredGraphSearchMode;
'views:scm:grouped:welcome:dismissed': boolean;
-} & { [key in `confirm:ai:tos:${AIProviders}`]: boolean }
-} & { [key in `plus:featurePreviewTrial:${Sources}:consumedDays`]: { startedOn: string; expiresOn: string }[] } & {
+} & { [key in `confirm:ai:tos:${AIProviders}`]: boolean } & {
+ [key in `plus:featurePreviewTrial:${Sources}:consumedDays`]: { startedOn: string; expiresOn: string }[];
+} & {
[key in `confirm:ai:tos:${AIProviders}`]: boolean;
} & {
[key in `provider:authentication:skip:${string}`]: boolean;
diff --git a/src/plus/webviews/graph/graphWebview.ts b/src/plus/webviews/graph/graphWebview.ts
index 183ad9f95ce7c..3036f1b4b2514 100644
--- a/src/plus/webviews/graph/graphWebview.ts
+++ b/src/plus/webviews/graph/graphWebview.ts
@@ -20,7 +20,7 @@ import type {
import { GlyphChars } from '../../../constants';
import { Commands } from '../../../constants.commands';
import type { StoredGraphFilters, StoredGraphRefType } from '../../../constants.storage';
-import { proPreviewLengthInDays, proTrialLengthInDays } from '../../../constants.subscription';
+import { proPreviewLengthInDays } from '../../../constants.subscription';
import type { GraphShownTelemetryContext, GraphTelemetryContext, TelemetryEvents } from '../../../constants.telemetry';
import type { Container } from '../../../container';
import { CancellationError } from '../../../errors';
@@ -2574,6 +2574,7 @@ export class GraphWebviewProvider implements WebviewProvider_x?{aG((rf3?ZG;%}~-IEscO69nuX$cXx;&9TF-!LGM_#hzK
z;3>kSwBIA{=c48i4G=`^3Fh=HF21mF0%0UcN-xkIo~}KvU9KG-CLmzoC#@U;KMn&Y
zw-v<#J|lU{UC*Y|m%|M+GxIVJv#JAhb>-p3My)RLeO%Z*|L?=|T>r)-jZMGz|9u7g
zn}>zPd~-bC1RMH-O6+ZyFL^2)4&wgraWSK+I@~ct=H0LU&|ZsY7FdymoBi{#)KTu`
z(S!*ij=-v723DJbT72*B^3M@4m;+Z2T=_f3AV)xK-BTf9@=n&75qg
z7fbIO{Fob&)uQLY?9;~%>;K+)cp~}V(-IMitFkMP^8EK*@<91>Clq)JwJ=mbL!zNe
zoY~Sg|L=A3vk{Caa5I`)aj2%={ru+T`dYd3Kd*D$Ei8rmVs9pWELTKn=f{UlH@fd)
zUEjRUUaoeBeek_Gy%k_BsxoX=)u}dOM#rVXhq@AGtt=|ga4Fcf&RdM-2z_u^{8De;
zhdI~mm~Z)p4Rhk(Q^!n^-}kZBeA8rk^{GUBN$hF?+U9+cRIc^$r5x0-$v#OUz@K;?
zljwPY*Jt^7YW4^`nzWRJQi#sM{O1pb%?_FkZ@#ZX7)aO+=>6_4Q|B72vyrog6o(#0
zl9}T9Yk{*4)?1BOo~-pT!ITRNbKG@&ZVta55k9bM?MD%_GFs<2hjaaQI9%|m_kXx0
z5~xnm{Py0tbtU*L7re_Z;;}y=7qBsys2yoQ79|%;p%_iVzHzl2kPbX7nhxXWHY-s$
zrZ<_m_p=y1C|->LVz^H9V{ysv>p5{xUbl9t*m4veD9m&G}2&k66ZPOII7
zUyv-SjanD##?!pc{tN-*z8Owg?fH(SqVn}u9nF{3MnYC=y^)8;L~b)3pGUJ^D235*
z8h?QGo=UrWNBtmcSK?@eHQCPlJ(jDvUmO-VpG?
z3YJd#OSN&u_TP!vzk|;E?zo(kO|QUTGnV3oHfIKpRg}L;qm9N^fM<%q=eOl*tv*){
zLWwIM-4EtpjR|d0{R{wJAv6RHC+YQ9k8%$_kNsI9J49QEIi02G^-9QY@5BAsI0aqT
zvfv_7j7^$}|BWoPu?5ciW)!%!ONBRo7Co1LQU;#o=xmJT8Xk5$`NtX*@qD=}n)#(G
z2$P0{{Ta(;WlG+N(u<`ONV`1vJ(BI%&Mfh8-k_W(Dl6=HNYAQU{o@N_7$$KNL^f)>
zJ%p7PlQ-Y>-St>B3LMU7)Gh)<3QV5;kp8F%&*K%khZs8r%b#T$#pm;MXkppEqCh9J
z7V)22&Q3>a%C^IbRAAFtFYG>ZKz(VEoT(ng-L?yyn6I->5e6jLR%cUi5aa777@w2=
zhLUM;G!?Vm$N53J-y^#I8O`*5H_K_Y;%PTiJrw|~jMCrgZ!WWmRzvT4{nI&Ke>zCm
z>s>Af*A1sILvbkhChU@p6V+3&pb_e_L`;)DIF$U;J3~fWlnyOYHONq0j7UAu_KcBh
zY#h=x>5S>){S5|nzD0w{uAcOQ`^;^nAX
zze{}eXo)r3<@Asl65shA0$D9{Txdv%oBhv7mm`VOJ~-dgqp$^YJ>H+G;ZR7Ae!2K%
z_;b87g7LZOe538{BTO>?VNB#mJKzCWNGu#J`_VKCc!9g|y
z2NdSvuVy`HUTcv|MzV3Sl~Q!=8%fcxLw^71P4hh+pn2HL4|q&WVo)0H!IRji9`T>S
zKimH7ci2km7VvbpT;bh@OC`7+I+d^e{L4}MQ`zt+H@+|acLchX59!9*-1v#6oq<80
zkkDm%w!ZkDPYxg^Mh`NegaND9Cb!@b^1P0Qf}Sp+&F9J^wif>Fc9+%L*yrld^$4q6
za-gn^aG`!H&u1xD4Z~|C2;Fk3RKC>K3D`
zsd_6VraxYRY
zurBCzALxTT5y4EkNr%7i7pJrD0wW*u{XaoCWx~SPE9?($vg6UAy+K{aGe~ii)4+cR~0%t{`j|G4WWL0>)a3KLTkkN
zcr@aoJ#fL4_(4Mmu^J48;s6(s@Gc|E+wR{9l=s}a%GsEk=pVAQ}v((w820ad%>y6P{xEEhhACZJ_339q3f2A2BOEhqbXHa^_p+^nt}?Pxv0QErWV2azP>lM8Y@IV`kj
z++H2!uS|0~@C~d|a4``%p+u6HzoZN({?zpDw@!!5vDZl->H0phMz9}#ZlKJ-4MAHN
zSN97#bn?h=&eP0zccbZ?`No7}@h??m*XBQHBdV|f%l=i}pEN@U=dG_xKa+_MjpRkF
zeIxSFrB(99{eDVWf1;y{fTO`!f#7945}y_0^y>3ldsX(s#YWqy7i@`WlJvCF!*~++
zWm-Irt($LRix3aztn>YhPxu|Fp>vQ_Nn|8SuzHj^jj^JO_bX~x(`rRO<#4Ic`H)R(QR6+h#BQ@P+g
zaPZa?zoG>u)v76G>XKvqGT6NiJC@tBOG6q
zkGPS*t#@3FSBswcOA;{ed5Hmt_wIu0_$$Mp12RFUf=c}cxvL&H@&|)Mdmq$+C@n
ztIH-|CCTsZqpi`*;$V_Hh8o%@m!(LPN
zPvW|6cZ#F>-OL-OA_R8w@^`9^p}5)kGwnJSeJSnr-dS{>qB}EE@1K;ee+O4M;s}b4
zH*qHL;Cg#=AA1ypmqE?NHO_s!x%06&6F(zlOmfZZ9H*V>Rb0MR7-s@Sy#h?D9UkO79
zE=W}un!=>g>Ln{O{Z(V~QA15l{3cX*s7`3Q{*$0&iAu3t>`$q&MCjK%3DpbtnG0>R
zv8@+KIZOHtZ{8Mf<|w7Il!J9()~ogsPxn|X^aveN8!&afZ|@_GlNVZD`Dam^~xNps$_V0EpYBW#c&lHD=__5K8xGxB$P{2_3L$S>>upLOh5KFj(*r-|J
z@mfaKsuazoDhX@Q5eUZw(T=N&KCG{^s(TKB{KpWKU7yEO6O{x(Ufg8+!^*wqi50jXsOnp2g
z&~>#rDd*V<0Mn}4`d+=ggj
z6JYAwU)Yw*@7H668&FJm#R%Ki^VfV5q_=-NcD%|D?D7`xQQNq_=CR>HDJ$^^YS#A2
z5%gJd?n6`rJ&ECJ8&rjdPe{QCAc&JFf*Ej~jc;9ueHu(rD+kTXIv+ua(E*uhVHTs`
z`wCHW)Jt)}lOYOWt_mp_cQS51w>uwymP6kn4PZL`!v6wal3bugS`EhHP?wJ_LvBB#
z)uXLl?G$^uKie(P$n@=nalviwN#w!K^kiei5%Df-zL0rZRc^drx}Y&Ud9s1*y`Ikl
zwj0K)1#33R$~cG&ZRJL-Nnf{e29*7r?}3mfH*U7{tyOq2?rx=+l~E!RF%=KLQxp8I`FLLxvrg#?Lh#;7Mt2jasueY;t
zxTg2>2aX?2_~z1kb_QLaySM2IXnpBDF)7!cCu&k-v!SJW!xdzIn=KpZ_FAHlRtvZj
zYLMS`I@W?4*-=>M>*JfGS70{9Rf*_vM=czyD*|2pznQY-7fSq`0(`Y8FS1-WMPLdv
zH>gzENBB7{jI%hq+?QC~RJAfWB9}otXChZ-jg`&HxaTPJIjO~1Cn*Wc$9lbW@u%ib^(4iu^!3vphOXA^Sx
zg=+p={tu3>oJ}%qnLzT7KMX0R=IuyqPpuO!_}?9T`d+~M9z-FnZgHF^iQE@OEnlvE^CB{7@7pfi9y#VsRmIu;b_j)t|
z_mT9ez;fLa>o{oo&1W8CC(TFGTHJo~CtFfm_p8UdT+_MnNTOJ6^#YAj0d8)2BQds5
zSK!0~yP30%0h$08^JPn>VNrZ03N~?k*0KuNs`CSw>xaurk~OsM(EMN7)Dw9b^YXdY
z^Bj>_4}OZ6_B{lR)Q18^nC~#?MzF>jKR9W;oyDC`b~J1}Te0ogr>Vthl%aV+7Ppd0
z*Ggkt`G>bxQoM)pWQ_{^1O5Yo$)eX=RJov4jnPehh2RtZTI#&;wT9gOyJPOU$yo##
zB<=uJJ0EW%9~}j&QhdDdsj9iS5gEoF$}=1kD~*8cKW
zR-_AO8KDmcL5Dt{Xg%)|F&B(iZtK3;n5E)vXEyVnH|K(j!`U_z%%21cjeM%@w)v>z
zO~COA;Njs5^C)VJlE_aAYWO4-x;3TUKOP^@7e2?wf*_bUTen}2+C8mW<%B&%<#Xo}
zq4MSGT$Y3Cxw{vaza3F^oUzb<)AA*1?S_#WPnQS7dRJi)(Jok|$Gu{YD@I0e^A$o%#t6H
z4lvY-xRZC`fZ4U_VG9>icW|4s<&7
z3w$P-j44xV>iac;8wi)(hs?nJP~>Hi29CFS`BPGF2i)n=$qs(Y$DNShZAlGm?ayGD
z8)e5dN*DjDoaNYWrxyca~gYM}mV{lmc+JL$RXM@qKc
zpDyi5i;CqnIK={o{DhF8!a!Xb(zIQfvbB&kL^^cq@Clw?3RNU)>_=RwTE{qL1XiJ@mi^(e2
zI>p*I#(Pc)nL$Pp9XyRqcF
zf$Ushqsv@LYziEcUdA3O{=(GvOj-XE{H5!MEJNZvtiqrU7H$8vnx
zhQ!t@@4U$!f2Xqh7uF9EpN@G0zZ)1an0O65&$w%qkUePx%Nt%ArzzHzx$?eXD&n
zTXh}kzRy3YKizzuHRbppkpPho$qnTmBbYk_sEa}z@G<@IrNl?a6znFXj(HVd!=Ca?
z>2A-kwbzvrKSiX7`5t}FnoeSbq#X_lIlx6L&*cl98QbPBdkNKSQ1G?xp5M=B*;#3Q
z);pmeNo3RXa}Rht9c-Rl#_W{!OSS6=3UVpmJtY$V@85YU(5QZ5oA(Y}k~Kk*-ILK$iBB?Gbw!W)ls
z@$1O!8T)XA5!dDcCHdn3j8K|V+8+8r!yr;GtED%0f;R~bKNUtqU}g^^yeWAN!_=@T
zt#dBCB$=z=93;q<#jNIdK8I_WJcSBrDRUEtMBNl_Bn`2l=z5eM)g=`q*3q3td~*b;
zu0m=xA?DzqP*8H-do#@J?rAA(s5BZBW3=iKn9lCP)^7-EL_z!?ef*j7KgKAu_ZC&a
z_0F_B{>=u~-O!$J3Zuv&=jEAQ-J?YLc82bxOb%eZ-mB}75~($@nnnr*Yty`Q)=}E^
z43O1^+nau}A`0ddvKGd7iUv4xANA>VTm*CWYAb8nj4z32nZk5Q(jZFFCEmh?iT--@
zI)6SYXZ{d=@r#LNi4?bYNCq(7$ClP#?*i3jKst{-rUr+SihUJYaf@W6(C+DONs^_3%a?&nZUJ?h~!fXd{0Gy}@xdb@lli*{K7
zVGVA}k*C!xb8_JoyO|*ZWhtH9!o+BqS@u7?j^yv_fR!l_FMkSM1&|)@)B%1yP!E?J
zf$P5o%7)GXq7%<+BCTzfB$BZIInLwnamp`4D@T!}NruKF2Zys>Xu52TRo&G1;#Q7A
z5W=Dpu&x|imj6@k0)r~o4`YM&g<|S=O@TsClSUynUUU@-iI6OsXv?Dn^4`4b-im8+
zUc~>QjrR2Vz2>ORIYO>ci9(Ws<`h)j$_7d5?G@Lh;5Uq2q2Kg?t#)cJZSu^7A*{ET
z?f+rXK{k38^VMS#4kJBXTM?%f$;A
zl8Jd|&G(z2A@A$ujf;f_fY>Q6ee+Vom>m0ejDBF=Q@weMPi0C*4)zs9;#OrMAg-&o
z9bli`_pns-!pb!kYg4@%
z;=+dvqmZSFO{hd~QYzOsQ5t*3y6)RtcN^xyrG~-$o8m0VGIELL+CgoVLeuhsEK)%Yk#$4UN~qqXsEIS_83RQ>
zxDEn^x}(bdXQ{?$1^I=28tlKF8CqQkbC;7o6=F$5zfySoxn43D+`fxNIBf<=Gqc2I
ztNWVBte`+9lJYf!>U)}qED{NXJyAh41OKglEEhYqI@sa7bhebazR-Q)bn(qd8vCs)qiVx)rq_Y`(
z<__{BCr9*saZ1l={7nxT|3~&0_q}0Y!Tq3LK6^1^1O@0@6qKM1&?)?za4}<>>hur$2AI0#_PG-8
z7YTR$h*ARb%)UsyCj|q^c6}Fu|bq{^im$y53m&*CB8bTPeU!uvr{0j`?+E9$;sHZiTo9
z0AF3P;CDO}(^@2PzX2XiCFmrnN@B}l{@VAX?`@;_)4eNkr6@pyTJCSQ^8ezr*41!I
z%d~sedzfw#h)G&Q@1Otp)4;RvY*lpA6x7lXXljisog(|#~
zUg-yjRaRx!CVFSYs5Fqu26jY&ND=H8s14AAUZ186EWn&XRtpW`9)
z%5|bZ%6l2V`MFY0IL?cSz_PqiOeB(E0tod?tNmQP%Vw(Xu$sh!n>3_el?bFaO4LpI
z5tnw>ihht`u}Bt6=xV|Bh0|ukxVVTdBBm{ixkll9vm<9;6mhafvD{D$IWN-k>R=J0
z%jwP87M@SHeMA?O2^^ch?0&TLn$o6LI&(=q==9Hb){m@wR59y-bW%M-L&qx49d8=}
z(gXCVFp9-dNh>CEc2A_&(yXWK?#qN#VVM5)I)^^1dlK{wQo+9Sc)PyJ%F5q5&OB&y
z1P`w2tjF_e69YvD-+LwI3<~>`&QGq9x&5eRX!`Q$|00eY8we3jSu(i|=*a~&(H(NN
zh`4X1A6lFy1d5M{=NEgc^&SN&FIoB#+byIU31WqBt{^*#6?=MwunuwK;S%-TP-EPOV->j6*0XScDh{=QO
zyoVUlx0g9P?&}=_c0Q!fsI;VYQbjCRtH>
zW#j%?rtAH&>u|lHAWERvMtc!D3T9xzKz8*fA$z*Z{C&(QAKeN;=cxXrL7O`DM8^ju;h~f
zzsgP-2v4rP*qBYfext>Y=f`xhJYKHOXc-W}WWup0k21Q4JV8s-&Fy}7!`qX^pWpHJ
zrC#=zQF(-y7f@9InB_KaCo16Sp?Rr!#bRO!VC~lLmj3|*A4S^pJr><(b_&>$e_b2s
zzIIQ&g@FtepCR;`>@Bo5jE$P&CxNQ#EG%@-EA;A$w9C|`0NtW&ZI~v2-Y+oCa%KZs
zv>Yk11?}I4)~khL-Ig-z!)=cqLr(8?J4W2Mnvw+)^tKsSbVD4R2icl_OOP%Vb)e
z4*gOL1zY;hD^4t!1^kSvhX7hxr6FK{H_AIcXz1AXLA)wreA(~n^@a5gIPjzQ
zsL5Z+<|T}yEh~}ZEX{=Xb!Ga8O#vnmqZe>KK^FbDmqUG0>Z?d~656_|WXK{83xdR1
zhizBJe(~I$k$*io|K}j!e>Y!dpwQEgzY=Jn&4nb`2ZWAz3S?}0Gw1sydEY_mnP&i@Vz5I@JwAk_XQ-Mb6e!)OZvyX5Y9sB~}N
z!#TRvl%-duc?2WJ?eV52@xh)3D|hn}6Y0UHRS?oOWL}I%F`3gILtSg~|JWzG;X=AC@PcGap@+sbw*np{34)Ch9U!G*X&D2M)
zXhj!T_HDRjUM)?`!#=(`KC45$Hf`*ve*N{W*oN3atnSNumL?fa^8jQtY;|V}6MV{~
z91lPmB#Gnr#KXdj0|((ySiEGJrgZ#P$P_dwZ7CUxOG3$|Lx$Oj@h5mqVM;ynm!zq5
zFA@vV8ew?Wcw1I9*z`BRz;rn2^Tm!kk-c#PY2|t(3+RMH6O@TOQtYNr0c(k>JdxAz
zoP@@6IGO#^Fl$ZO31d3x5m+e+6A6K~mafT*4lBfM;F;%k$O=qO>|nM|1_vEakcBc9
z3o&T;5wBo*6VE?Qo0v@smMQjErTsD&m_qcMKqs*
z53`+x71A|nyI+97tU^$wA%^g>+V>Rh-doP{gRtjCRb2C7E%LcAvLa-uHdv%QQ7`CD
z)Iv1osQu32!H+9ua54@*o$J@M)jU~%kJ%)pQ?JuJF>45$phx~-Mc)?8QlrM^YCoh&
z%{pR?5Q*{%Fa(otV-e7W;-O=a
za$v(!X%pyD7-W09Iia^vLDsjS(tD0p#p#L^W)4}*jnt9+d&z3i
zkC}`fM=#v!Mg7)MHS4rB^jqGB(5IK6u+cW*$E^4X-Oeaap5EkVO5t-v=(YH{R-MF$
zbQC|u2+9TsyHOqznxlkFf^3Xrf>rNL>#n4D35?RaLeK{&-2xNBR1-+ltxui^vy3C1
zK+KVkSm;;y0T=;UMyp)|a$h_1g(tbN&^2Q1)C%w$tjdZ}s#mZ|QBg1^-wd7fyURC6
zI+7UpmH;t}PmlsjYte>CFkUcK@OCKIPU%#rmoVpbFA@iR9PgdANmrr-NRbRy6qYd@
zQAV_2Q@)^$w6a#P;#%iqGZ#-9iN8ecy-oK(sS%8JK$yBdNSG?q*C)mJBT8tX^RX-t
zZVunX1^sWvcOzvSrBm>@;!(gZpo!#KvDN%
z7wHw{)=#EyA=fB;ETSE=aGMz86T2wb!rKv1oe=OJ1Yu?S5L`E^Cw?+WE)&s76H}$d*GQJqmZju9p#B_AV
zr=r=rJvkBB_POg7;^b@u8dTwA#Zo(Jd=CAtvwB}!`JM7GN(1Sk3z^hu%F3DXc;#lCP6K!WFMkQQm9`Wpe>CXwyXNnh_N60-{*W7
zxTu>#Mj^lLE9owj_ZrR(K}60L9oy1npxc`KsUH
z)U#lY3{0h6>ZDZknSgw^+$MtM3l)rO3I7j6>NZL!izTV1X=IIxrNmlH_b<~Ln^zFR
z8W~$DG`Rj+T`{@b6PP{3MU&tRL|)zFw-TWWfUh!S=gUD|#F@NZuPgQ4N
z4yMeS`sxYgGD^$uEKD$)VRD2gHT8lh@$^FN=F{V+F%R}pjlt`WK1@}
zZgDFtu;hNWI21Xvge7{O|&`)2b{X&mF*
zk-*!xRA9T|V8Z4v!ZVTQFW@Sp_$x+(@u^h40i??W`Gl)MAhwQqT8xJE-~AzuUM!W{
z>1jYvwj1n-<&;*nTcJ+0OSg;ebS=_ZlhKuw7t(-`6nvx?5coL+QGv1Ls7pgb!bozA
zu}LGRZB4uAJKTfaqDWI`_Jb8xI6XiOZA_n8a
z=G0)Q#8$w~yEesyZ1uba<8G2Cn`ck5+;4VCKwtGA>#efg&f@Ua%*VoleEp62#OUoqg$xW
z`hrLkj>B)bt+-3l`pr&N*)3Jby{EvUTe5fwxZ#J(#y?#lP+pm*D5s$P;;5jB7D(&9IgCy%)z(unrgjix(AU>h%r*N6yIYD
zsRX~0{Q}&U>cz6~-Y&QW*>jpA^1BnQ45ujP32^aqWESC6a^hS+y6_twuuBA;83ob6
zoBXB8u~8>>cub*`t6wrjl^YL+L+JDr)|iCJ9wc}jgI79*j^6xv7)9DSxwyV&Sek6N
z94CXE4K*)}&bW(q_6G@m_KxNCWr*ZpH;5X=xb&90UP=s;1Nm8jJdGb8*XJ3}%Ihn@
zfdIOXySyBP#-l7vUCxckWHgaB**0f-&At9V!8)c9MS`u7X3K11f-@l%|9n|tXN|P)
zB@0{+<3eKRDeNrw+fmT*T3>nzNKc-j@rZQ?Q@m)rf(jSbfoevQCf%aMw&S;5Ao5^%
z6fuCF_@a=u;HEq`GxnTBzszSFTcfIIpKud
zQ8neM9$(GP*?Vbe=FRCuLSx$`TY>i&W9RRc@n@Sk*WrN|
z-gU6XodjEswh4rT-9Aly5Ki7b8Ql7%e~KI0X~WOojeKHDUa1T!ZQ=D>erK2z!g`q|qlz#>$Q+(Xf$ytYx}8k6p*W)a_mVGzWz%!M
z&WODG1GneY(>is`J_@aDaZStY@hu`ec{Y^vx{0)$$;Bz&^7mIEhZNOwM5Y#5YTYq6
zPD04?YTLkOFXrpbaRmzFk|<83kpKttwEq*X`Xh_4M;$F)tCXzN>gHY8d{pZr1qs-1
zeknUi&!Qy;f9T_iiIFLSkQBO&VlZ$wG02Y@;S;=P;mD_8X|WV)QQB&&sdCS(EOV-+
zN@WWb8LDIZa9P&Di2FL+VAa0QqNj4|kQ>k)TA49ORWD$II@E0D
zo#V9#_h~!;bip~zueNXu-G6iwrid~XSNfUDO@d_h^pcEXdHTJsMS@h;h|4{5)qXb2
z9s;?^)>SV3sG7+N(M<7P2D?VM;G8tDozZL+{gF
zcp?Q|)FF7!mfys^`OYlAAoD`$95GT*%1U&&XXcr}>9Kz-3RbvXwBEZ(z48#;IMVxv
zyOmc8%CrBlzCoi2`F`{_oA35mDapb2rh>Vlzvv_Nj57YVu>q1ZStc?IH(Gk?wJJjh
zr2`Aq8bXB_mFRl~U8`F37-t?+;>2K7tU;hLW?h*9o1{khUxYX)I`PqhKk0@vJkeP}
zQb2ApW9;uaEs7D&w+z3o}}4xYL@n>LO|C5Zpr
zCr`B1{fUa{x*6zMi)+9ePGeG|ZhpPrnrJsv!#8viboJPrgI>XFU?_g0PSc7vO?enw
zlb^P-_#bKtbuWXb5`jCpRQXq7Y`YvION_8g9;*_SY<`woTH5`^ukwJxSJkaCQ@x0y
z(sg8UwB&cXl^=lC^lq{!jEF^B0S$+O2tW4ibQvAcyQjs?X7|gU#XOcq!Wb}H*DMRz
z{ZjsS;of_b^
zI$hN8Pv#B$)(mIx5K#33=n*|oW+Vf!z;0R#u=JMzjOCVmQWM~u>5E@I_Z&k|7lE%{
z-kfgc%o4N9MiC``wwu}A0=?Xuaa8AK@wwdpi(QDn0zOiKazJ8irxEd}jM4gQHQi9UUY@Cw+z^xMNnjzvDH(g_5
z0{skdlFiT-GYb7TfNO;wXaDVFk*pOUMw{n|JB@qp&zg-ZuQ!Hv>7wa1J1nyF@5lh{
zj0gJGIhsK9%sExg=2rpQ#ddr1;#fq%b)kbWZ6bW;O|zG@9a~?4N7*_5_B~r*$FZoW
z2&wy3yYG#gff)dYUac6k`?fmBP*=RL`_-Gt6Fx|+3|#Y7AW~5+0Di%ab3zVjY~aK9
zH=n88glDf#H(5*Ec6c02R4gy|=MK9X6@j6QDG7mx$ynzYbHy%msp{TiBdniy=ehTn
zhXaOmX1qj=hdVqsX?5dr?eZkPck|lC^?%0mYnFMxPsp3!Bq%%
zqUlB_i)KkJ;XH|9-ai}m-+2a|k{Po$-bi7L;+mfM4heUTk}0|X=F%Y0W@8C-zp=dK
z&h1?PNO)RQh%ouex7})lgt&Y*jX6X-pBOR?bmm!}ZZNaFk(J%l0NZViJm+)SnxLy!qnW(P+Qu9lo3dmW
zVbSBVOc?LEsO$h}#!{s2QS|nklk|Je&2>iSYGtbi%f)i^#7NRaU6^>?JnhV4kJuF}
zp39=*^;jE&`LMj5bH^9AouXJ~vO~P9WLv*V5<$;HQQde}rLP_b=f-C~D0P5~RpI5a
z)kwYskmyvp*jU=v%FjaVu@3S{FZ-A-2RKX%%I&5(U8?GEG(2JiX;oEo1T|a>rOINF
zV+654rc?N`b^0(;%^A_(&5kn`1}S-FCAjl6$Y4w>A%vb*AbJAZpk#d_p>N?+JaNZxh%|9rR0-9TKMd~WD#5-yJWsJGwgsJ)`+
zE4SDmfEDf$^8MoB%+$uQk<%AuzX4!H}Tpa7xn&cO0Gwo7s4k}
z7FQKZVIk2<{tVb6R)rx^L0A1bE@wq&yM-1bCj&9$#j2I#9hN0=QXyzNJwbaa@(Hg{
zcl69loGE;hl#Hi|sh316b%An1Du^zh&o&7&c0u8j9dhQ{V0%P0%T+W+!&Wsgu@PZ^#!{
zPn-Z|Sh|w$Y8hNod&xCeZUkkXY{G#u>(72U@yCthD+=K@#f%|0#T?bSQsI5wh_@w^
z+dal5jKi4FxfyL%q~f1{k@!_~%hq$&;j#4D500Ja>v!O~x$sA9k6>%iWsZ251oc|u
z(b)Fz6on?4x5RZ|-eyZJ(!L*{tI(
z!2~C^KfgLBHngqq5TZ&4N#qD=XXmc~_9K>gb<&$W@7Q-6SDmI555$g~4_mWB^XL!y
zx3b@ABjIY-ir4RFm3*0cU+**4T=)9;%lLGgSSGXro4vPvC&`rK=_N%+wBOY7TE>35
zzbF#fZx`Qrs+tm9l9gh}pZBXADc55W^%L@E$T^LkaYGlU9(i`56JgKe#4xOZIdn)o
zUJ5MQ;f3-2)Tz316ihIM8OcE*LQm6UQXSM(LW&H|nJXzxq|Y?MH48x?=V4i@hC
z$vK!v@N8lzV|!VD5~YEs=Vz2MK
zI#b_HFwfZ;gnV)qeKK@F2v1f8hhv4vp`%@7qazF8DfhmlY+3F~)TOQtXc?#Dqlk}q
z=FUU~Qs?8qyTS{CP!$#v!}V6>xCmIXBZT=KmwdT-7^lf|)E_VK-osQ5h1}`iH{wl3
zUydoH)2M_3gC1}b6Nl#h`!}($=zcP;{_$>hI1o}Y}tPXUYK+lb5
zLE(=$xX5>Be#tpnh!sjoVS#pT*`-z)pr!_nRw4Tn@v?{5%=VbIG6wJlPu
z?)+jrm_*p^;hpq;Hb2ayp9V^|jl9nJ1O4sqrmD1{%MI&>_dY6jt9}09{QLH{Dl2?p
zH(DXOV=1r$LDc-3(V7TNb$N$F%FJ(1|AEh#pKne!kzd-iQ0L%X%|~x8OkGQMo(Chj
zhxnM0l?QpN`AI_ymwR0^te+o{e&3AY9hL!Y-vg=JNNFy!X-JK$x(hX7~-OYf~-67rG&5$CULpKZ((j7z3J^tf&?_CR6z#5o!X3kgp
z-TQeL@k0SK@p{pv7b(48md8E7zu#17?$iMeyFREMwSixsKYxEOxL6}KPgUBi%<2927m=j0Jf
zZ*2{1IpK@S)V%2We-M3+kle$~mNg`LWkp25QCF(^_6d
zZe6M|<8HmQAf>5!YHVzRkewmyWAJLH<8>K5;EK>uU`*PY^Vjz$btaKiD
zIqTEM^ah04K=8P=vtk{BQ}_8fEkcT*6B6DpM?r*D_3MKr5x;d~PC)1{sjZKlmy@OZ
zs!)Mk)eu^PYjzbv8UcoK!NJ|%2~-qCpTF9pTn>YEJ9eedTXM{z6K#M~WxQ9;*SH6M92eFhhEHOF-xQY_vN)HkW8aJC7GwN6{f;FA-IcStN{#1C
zm^R)v-dN{4lQmBKAtSil15(ZOIS7f>KyHSv8fW7eA#VufJkauINuD)OaDQwvDdaKw
zh8BHq5*Kj*Nc#enG>H
zw8S1YhG3VLs=`0wiYe&VQDXM%oo^B@$xAXV(usj7xm>P~NI0Pk+}h|Xp`B>6f{3$Q#`>le&*=TLaRU|zbLeRR=gRuJBKM#r>
zzoiDf2|jNNZkU%OZP4RDjW6wp#Nupl56nUu-tE71A0-R4*?%ZGf12O5EA*FNV+RO4
zEPJA}ACFLY5%mKhTb5-M)tkg#AB8^!-%q~nqvy3>)p@s<%q!@fi5ilOyYjOo>d&X9
z8!T1VD`ZgL7jL>r4?xl{9V{J{%4(2RB#ivyL)Z%=Ge2HB`7VnRqc
z_%xDS6wB+Ed>yF+f^gnDfl0D+^>Ll}xU&95gk_PS!^_D!h1e({!of;7H8=)zC&{q5i@Y)jx}kLhpq?#e7o1NM%t
zjtpv#{kw(uUdZ-ShJ^3iriI(2TSGq{iI^SOcy}Y0Bs&IWW-RNwH|Hq&5I4CP5_A!=
zM-jDY&p#1i9B-Iv1s><=ewLsLQa?rz?FVb`3%q$V
zezE)**^TE{Ey{+s=fKgx9Fp113E*{
z`@TT046z%a&uQ_QmVqYJudidee5N>te$M|6Hvs^M5P#LYQ^Fi)~K#;>u{f{DvR<
zD3@BDyiFj2TN=HTb^m06|NV$Ug}jaIlefE2@BKcBi$W-8nM~F@ME{vi*DgYH@E810
zo1XhW4^|s^d-;r`KeEVdOy+mZb;aq`cqlil8Cm)pa=H-q-x+;^KXogZlYdK6bSI2{|kRG>MCyRAT_K5r_c&
zC#vD72vKb3eYal|Eo)RTNAV5pn1_&W~ej~UfAbw@tMK*<``^(FfIV-2X-z3+`Wnb3y$)?HSvF6;|(xtM^adbJ;`w0WCh*U*|OS?Jp8^LS^swV
zDl=Tu898!MgqR7p2CgN&mr+WQiP@mF))qjjh}kSRrYS#A>Aicodojx9a@sO%?Eh2*
z1%O9-tl(|ayGzJN!8P`ufXF8Y{I2lZ6lJ~F<=4CG1*j_#g+_&z=1JEBD}!2L*gsI=Ow^8xCcm95o*Y~3MtI;jeQH20`#A*p_4r1+Ss=Fikq~
zQ6Pq>>q2R`Mwyya4dIk2kYHs5OsPeHe5eAXu1YpPydvu+6Z6XyDydfko`$Y^JQc9P
zmXj}5>T-3WOD3Xy;AbcQkS+gVOjX={7jjr_jAywedypL*N%(*b|IYv(*jJj$1XF`E
zJ+YV+@WL64=1BxH2&AX^y%h%|4C6mbRT8k7lEfFpGvFfvr`P;^)Bs#ct$!*1+6H~9
ztlOw9rJwAodM)-WZin-U4C=+-u4ew}<$zY_bTgMPr9kIRlA>+or71;v1DICzvo?{`
zixrdEx^5-l(nDpc1#Kuo$Ql6fB=QKb*8yZaazNZqe7w6v)H0+lKLE_-saZlE2R=cS
zz}qbsif(B|O)&L2Ohrx@4u-9$S9YX?%2htM*;rh7SK%5{kl+>Hw(WnGvO4nudIa7d|
zQo+3PGg%K)AfbD626oev;w~uh{{L
zJL7N6fA&=Ata?~KzA5Wov{?}^vUyDO{d}){z
zH}VvPqzPu8ghrT2ru;t*^N>gV=wE8^l_ua4e+4~VY0)lL&SC_tn$2C9Hb0B;QPj7;
zvBk&h0&fw4lG@;vKn@?1;Js&X+8n|2WsZ-V-r7I7lR!;%*F7U{J-7AXM>Q`MN5(Qh
zLajLadmjGhhk-t_NslS9Iu6!6=HC7FgW>(nF&$v{Pjo+CmWw4_u)ZP${}NVJ_0l8&
zUf{Nr*i|FsZ`-@3(8bp`z#Nd1&J|fdXjJpW*hb;3N;(2@Hz4+l%|+z0oKVa7e=bmt
zLP5e9o1w?G6j&eSfXRYKj(du1|E;}G)ky!&bGF^ipTN-nPi9Vr}->1t!P=U|Li4{Rn8ioA~l4u5)5NL
z3}bnch(tJy!In(@Qzs`JwVXtePziG307O%_1imqw@FF95(a|r>ImjhF4kzQi^WPyx
z55qZm`_|I3?a@T436CuOxwu0%Yf9s1Ub*#WzeY)!LI29-WG4C46ifM-qa}->pkYp@
z3E}XOWc3M$1-*fZOe&^@s?P?77Qdp2(^Ama+jz8CV7>H^4u)+dz!1Jr7rrH>u$F(Or@c
z>!lQH`HFqJCVH#)FAZsbBWg9vxqiU9tyD0!h(BeXNT)Kt@4x-r2H20e)0r3WRDVe5Mf>frV$skXwXNO-prN2k4KkxsKk0Q_Qhvqgj9bF0=@|@4
zgZe%U7o76g762A|lQk7=SxA*cDP_Tj0m(NEHD#t(CH^CcWNE&32)G7bM0c
z$TxLFO~V-ilQQ-=B+YLIo#@G?5`YS9+`~z`aqOr7W_a+V6f0Xb*c`C&kw1og+?Xb^^Xyyl7FerQ@sMV`HiYlm%gbBA^Wn=(XK?2OyDcRrIcK?!4mu_
zo=d@o*Df(BPDEczpvD40x5=bJ(eO)PI$feoTVs+B>mj|@stB2&RkiTrx{CA0q{%Q*
znpE|{ti*{sbY~*X@J&n>1H^Y;2AB=}q(?eaJ(66~=aZ{3I^qk>{*GlUiF#kX+2`_2
zxFQoVw9FOuy!M*D?3k~%mpo{I?bJMse&?9ECVX5L(Uyz8pg~x-
zGm6|Jz_H3=x=`+3;Ld4?jvLsz)Xr2UNK|k!%EHjyR&PRgo?`D6R_5J`7HSH1292eM
z?D#z5-K!3VA)V4ASst#558lo3;SJiu+X90gm<>{2U2w%RY}cC&OPG$={eQhRUn(>h
z#>RHymMGVzr>i2K_gI-oji0fkr8n1PHe5co4E4Q!5MOF=@ZY#R(rp;Y+uIUim$a{I
z*#I?{YHQzoSj=I@diNt@1sBAOk6nKAarX*Iy+f8_A#H*!Qu@gUZO_31l46K17*BBn
z3Nm-9p)#3>udn|BY3bt;dhd+-WJq1HNTTGtn`Tz8)cUBxKr6i$li^J~Jf;kJVV}A_P43_-o^?@p5|P-2l!g8Aiw(m=&5yd=V})WSVy&bnko=~6{sJrsaa(L8(40~!~8|dD7BIf
zeMQ`Q5uL3u0MdblarS8T2O;oyCfrQ*$VGWC3
zT$(_O6v7=OIjiX5lfy7zPAinFlD~LrXc>if5tAD~a&R8;##iG!_g&);VfaUDVnw!n
z&%N)3)?sxlWUt8Ikn&T*IHd^v2zUa%?KRqWo9dE2>mK7xn%4Y9Dih+=Jo=t(`wc(SkYNodt2Az|
z%ZcS~BQINx+<~++yfaiO!3Vn0RiYfFh~l*qCQJyXIP&1AhPnRglqQQ#NHvp}AF)n!
zDo=!luS11F72K7oA6Y#&Skk(_1^YdZj(RAJqd2LLCW*nd`VTpzXU
z7b#WNJoD~LRD2f?`4v*=w3Qk3*o_ln9|~6%c(U||9xZG%`vOQ#mab0N{15BwyK*~y
zANSm*k3y2`y|?i8Cks5=-4;Cl5zUX2x;3VZkmD{8FwjV|e57
z{{;iBFdk!PC6GUQX*}RIknAY)0;J<1UH$_WCZ^jDzz`
zu%X58BXGU@%vbQ?MPycw0ew3xge=-z(6oh@<@%KGv|Ilfb{ty|d28xn>=gcKJ^tv2O--CgwQx}cZgS2uQT~J1(Y=jkrQk;OIn4i=Wy^A6
z>IruwRiy?_OE&HV&pj0J$1xFe$>Q+WRSn7W?|!xvu0|{
z$)eZJld<0nWQEt7T-#k#28g=4dBn`^++RZGhA6A=EJ639<=20NR2STY7Lwg^9(i4Z
zH0TZ0NDrKnJD$g5iLTurXz&H8n)PdXfZaf*
z;?-<6sO}YuouI-za-3gLfTj`=%G;zox2d)IZ)oor>;1
zpXr}3W1Wna#IEJWK-F^|%j3&?MSTsrNcjI2%4C{5d9bzkxJ4||^ip1o{47v4CFnWp
zxQF0o6^;|~%o^yHnQd0VT$-dDRVB<+)3V{iYtm;4p{!S
z4~!P_r9STh)k)mVrbK5plgETf!NXtPPln|cibmE!I83td7N=Ih^@BE4IQ2P%$#m82
z;?l}FY_P7oEc7IJ%H5L{uX?AI?lQ&J^&G$*;JiEb
z=kfgh1Zw*O5;o#}KLhRKzec_`rfL|Fb@cvbUh?xNGU#J~Q{VP9;2DgqcGW8VO!1Ua
zCbQjda)3RP&Ml_eWGy%sXFkJ_=X(CAmM?2`x3i#%{U=H}ZCYwOG$$0PYw&vq><`(*
z57vCN!$im%`C&>`wKkpS+6Q8G>&ysEmlWp2u3qVV%N38~gckq39imMlTo+8KdTREn
z-P7|#^PxV5`foed;KR8V^SkA1<<~+`p2cHEY_v3fUHi5@Uj{YyZsMZS>X3_INOwxC
z|MBkYya=@>UtQd+ukD!3#7nDyZ5)8ASwwdngq2gA0B`J-zpJl!>>rewrxYJ;yrUKS3?RXkn@WpN-=~`QB
z=n$&cMWBlL(_a?l(t}M@m5JH~kGo~JbO%(T_QSzCMnR5uyE!(3Hs@+0vAoLEK3nZM
z+X5a+m=M6pL1ZZgUUj$Rv7z37J7W%{U0?148;#85S1P>7ivKb>DaYr7IdMjv-bE`{
z!a>%~KG7cp)GkBp7HfN6S9+48df$7n4Luswl!XQGzaKLbX&;83iZX-OcBS6^O%62c
zPFZ0UROt4(!K178zwCHc+Pi6{$Qg>s%Nc29%Z_*Me@Es~nGpPL=$0)zIjdJR(Xdx6
z6-IiLr%a29*L7Zl;r2k&-T5c;EaLu7A7)Gyx%ME{-BrBubaT*o@uznIYzAWXk@ls)
zFmkc~#oFomU{SvUY6k7mah_EnWqr!wn>b(LsbmO*`fCUkHTe1Jw$M0R9RGru>q7Y~
zP9Qb1=fCePQU4D!Thz`n(RwgjGeXAb-6Y~T83o+1419Q5#(eaY-dL(HmEG~@
z)m;s-VWm2_veIcGud>|%ztM{Ra21+Kwd?;eW0R^xZMh9Lg-B_+;+-%yWqURgh0J4KpSC+}|b0
z!^lj|W3k)YKR?}{$GKYCy!V&8m=NqrWPfy2J7|a>E=nB}A2xqTYzyZi8VmVI_D#gH
zuQRsc<>cR}gCFiAFl{&M>YN#6?KViA((E6!K+#zjeiC5B<@lbyw$Fb}+Wq|K=|9-?
zwAYe%VPE5aB@#f5PIN<0rK-f4+F+MSFj1;ybkcV9A-Y+BfcvLLp?K1^D`b0)xJ3>D
zmNEGpUGlSH_v-DN&ZjtAH^@H8#JtE>_Hl!sr(APe*TB6P)FVJvcg=572&0T$twPPJ
z$uJP=qw(;;M;ep|!hCaKFWj9aY9pfPp#OMyNJR1w`yg^`H(R1I+^OBCEaQC;a#HWH
zojQ>M$_KrNdi3}ma6K$Gw3;=v4JLPfSr_Kq4jgR6`0dgS8-?wO<4)4)h-?@(#cvsQ
zNBR9$!f%{8op5@J;~DJg_4}H#D~!Nl5rLbOrrc`zdCP3U!LZY*0zIa&Qd^TqgM|(h
zqN1vLx+BDzOt8?hDbUOFu`nObe{)%~Qw295@jQ6}8PYy!Q#>8*6=@q8RGx)j&-q*l
z3|}5b(m4RL7BQv5d|%erarflW_o=Kb2csZu*A%PEx6*eWS|||G`*fB=ZJe#yfGf@C
zChH<%-*r)yo0F`atD>~~K#DJ+psO8JR@oQ#ISy-DQ7FF7QFxrA)ynfI3(
zN>+U@$J1YABUdE}-U3rgLc!D&r_Bv#nQ=(~qup2!ddQ;=vItPiPU1OimRG#OT4NRc
z>4z_jh#AJopU~&1{lRr2RG?LoKABOkPav}zC>-K=5-lVN3P
zghNpzZhTS2!W-VKCeQE)IiajGohk#}r8cuvt^;t9eA=|~_4-y&$@CBCgh8WD?2ljW
zlW&9IQ8|^WsxGN43og_jlFE|&V@PVee$hfQ*e+A591D7AuK3z-PLidYtoZ{|K3Qv(
zljH1;B@^L}>|ZZ}Eo+SaP3!H4s-Gk97|F*E=H^Se;pKYOQM4m;&QVbIIhURhO|=$l
z+;RPsh2)HH;5Oy$<$AMFu9V=SHXKAg+oiuZh7=PtOi5zh?}buyR)LTO@N}s+E#0LB
zS5Z1z4En+oFs{_fg!^js&+PuB;99#aGb+twjmoma%$e>1NCIB<*4dR3zSa5fX|2PsPoOgxT|*6&mcgqMYoGA7JBD@ao@3_~|hMg_~be62!@o#Ata+YxloR4qQS
zl8H0;Nf+e=I4ZMCyhQKVohlt|^(JbJ;ybQO%z6d%Div=~S_kTr8+t{=sakIE`2C7h
zP_@hV;HxFo+BakKpoW)s+tFli3k7EnQ*AnCf{rc`FfFiMwf>wCh2~Vtsb(8KP
zBhL6=aeI(-PhOU}^iy37mRkKdUS7SI_|o3aDm1tRY{Y(P8g6sYS-;=+Y>&$6lw@_Y
z@V{Ppc@ZH5yBl4Qck}3KHw1Z3yOnnDiK(Huwo3Uu`eeqKejPRif8#`OZSD5D=jDs&
zP^x}9?N*-cx8#ZUVLheCZ@X7nqt~tvTlKKOe~LkVanM}zE=p7#f1ZEv1!mPZZA*iP
z!r5vaT5>#^H(0@oZ;MM*F>JKxSuEGi6SzH&E0uSqiK>e1R;_8R|NcaqOH0zyLD^$HI|3b6-Zh!{L*lk
z649;JYY!=Qw6G>yvnK=^vN@AR&dPIiUg&$6J*r~h(S_2E%X%BTOd3Ql_LEHL7g=7{
z1jR0NuJs7Z>!hK}Io9WyQzukuoapY93+G{gqjhSE$N6hBE{%}5`8+k0l#g$j2dz)c4Jm2_BcbIpJv1`og
z_IK%>>KXZa*JiH2&k^XD>W9H4H%}r)Mr~McTA%|}?Dp|7@P&=wlv6@L4dGxC5
z9!{SnNql_|)LPNF-HC32s+jADiTvdkFJzAKKkwHwtFew({6FXCJCC@DOr-R~4Vipy
zNaG|BFGrqW2J~JR+euF+K7!xMlpNCyF1E+Yq)|+Ugc7Jqloy7|TqbNO@pIOcQ>t~D
z4*4ABN-J-37vb)-RGX549M=$;7INykoKgd@M&A#ohOa_S)}arqsV)+iqXD=~2rJm-
zhc$9NzFybje(mEc&BOIPHm{~|kzxoD##2cf@3XoKSGOup3<59pD8{hz+^GMCMYyBc
zXsF9LMGC>e3aO*us`U1Ugt_o9h;k^!B>PTWyF(^)2_*cE?8?e)H|>^izojN5j>iNQ)$191A6s@Wo?E5|Y%5LLiq$hTPSvT0#n$KuOT!&?9sSBAu^4($rcW5D)k
z)$ywnGK}sG=55yrGZvo+sB-}y$C=*5OPb94NHOVFWo@)$)GXbW^1mGvNH6WGY|kZ}
z4HxE#{MOn&qAfJTqZjFavc=-Gmk(#vfLLbmswWpVf^d-@*CQ9mOEJS2Q+r!x6o2?vh?`H6S~7nHW1c}({54$mPDBth@+jfyCi=J&M#i6bF#-`^rKht&F1h?~hq+7wBtA^0(+f
zp*HkSS@w->NyPoWySv?);$S^He)T0V#6WT15*L!9IoW|4Y;s7_=e4E?&O3h9NuJ4>zGwS5n)|QCw2M+=U9q)W%S~I*OK_c0mxvQZtmt}{
z@|84u_ielfjsfN`VAa$Xaw?P9r(}E;96(m*dQ9uJ6n=@Y_5xd?|FmX}&CHTzT2zXD
zF)Em8LCA+k+ePz+jLWbj8I6ct<9q!JWoeI
zM2YJ255JLh5Rw}aG23+A8DaO1uJ2&~Zs~Z`sqW)iE=Rrj_?sqJ|Jtdn&G*y6S>B)q
z$UWM^-XfjYN!1S5
z*?e86@W#bBL
zZc*KMuSlMV+r1B^EUofkNHwC`RLRj}ki1W!3D*y=rI;BRW{`)2KV?&4gK>TY5B1Ep
z%tmNuE3MpE=MP)YjSe;LD-^j54lpXZigkm1;WLg_QyqWn=*y`%K?7=KYDGyOoRt4G
zXFcpl0{ug#v@3Y+6HF3|%eQ8`61G_P)vdQ~dd?230LB}?S*ujb+hB&^D&pDHapj--t&}ktaT1tG^USdr_vQp(3rsXDDFqFWocvk;NZMLl;p!1H
znm!H>kvJ8TMNi*h403fO`9&*|qjr8UqCD4?!B31Lf)$4ztayU2H9*hWG?+o_(d2jrWf3zRE>8
zuCTa~7)WEWc^!#>!9GF)-EN@&tfj}gsfO3h4W0;=tu_s2oMxdE007KP0~#vZWFHw8KU
zLTZT-Jf_OyY(YpPUj8e`xWBCW#C)k)^A%q$!^>R^$)3J5
zn~3lJX<7TFVVe^&{ASqL*q$&@h|x~fg%yeL|238
z12XZiyf&{4@tBR-3QE|fA)a_J*WW{T%3uEI0vd-uce>!M?%36@)RR_&feC}XzQ(E_
zq9~&8AFjIRe8UDKBpb1Jw;_?Dn4s22uLRLr$NDeqF%NwkbdUr#I6)Z#L9OW;?NY5>
z2`eY3voUUm*V8h
z@8XXX`E=MEJ>
zGbRJ!N^5)Oi&ubkMCH=B4qFdEIn+9h+S3KtPO0`OO)L=$ySBN0Gd8AGHrkVHq+dEA
zLYBbGaait9XVjoSc^!8=QMW?q?oW`$*&k6Jr_OG?s)@}WfUA(KrrBvQyy+BWDnb4Q
zEsa~3_1lVGV!PPl7Ne4Nmk-oPx5=+SgH>P8zVr4KxI-ODsqbW*0^IoXmB%Q}+V#^m
z__7F$^VPnsHe=OkDfr~}4;n{EW~L^7qYX=op?BV@&$R{P*^#B&P_83j-(0&e{{O2vCLE0NdAmez@PpKzLD%)>c6gnDb-8Vmf$I-tXD^Z
z7!Ip0O|G_DG9=d6syHc1$j{04=D33{V^_g)q8O4)nOD1O$P50fLk47g%gt`PC+0Q6
zfAX(o9CutRCK)`=d1fHtCd((a7dnx;OOV>GM^|#w
zxEUkPo|in^ZLajUC6nQCAeY;IJ409U8Lm20G*`to6lwgF2M!Heqln>i;g|CRU
z?%sX%g7uK<@ujp+h^X>xM|tgKk?}B^#8=?wy(9mOuk%PF(@c%jg+xyG_8kg4h1DkhPo&3k!ag~)A4g?p9N
zaqgENKXp`@7(I@-GQQcbKXi?%d>XKU1}8(k@Nn*)P8(VWoFJyf4=`d5DLbYaoTsA<
z8XI0~@3eMS0sZn61_}&gm+T}4BB8BdhAn1p7v)4jYq8-|Dcyajg{voeTmuI5ZtnC=
zDXR>rdN#<+k~Kw@3s07Gf$XUe@);K|oYB&nC$RW7Jy{-KdqtWNSqpN*KG*QEslHB~
zHhrD?ufkKO%{=v{Ng7eB;E)_Ym*hzu!ZQ-gHDcDT2g_Kv#egg_w#PH
z&|w)BL*_$X+r(!Cx{&}Wn?k>}hhGpOU2mOyK=D+rw
z>c7odFT`CQFa}HV#2tpyH(>ZZkH3|KOTS?!aTqTiKY6M8xrCb8s7V@~A1e|UVV6ap
zlwgY7W3DGxRqgk)@!f&Xct(FxhhMME$URFP27%V&iU;!@ETl?*r!&U~sgE6dGVvke
zN!i(SB|SQ$NVeyd{rk;0<;2yx2k21E>6_;wD()5c{=Yur1`o|}^3E>F2&eT*MNjr0
z$roW4-q(kxSp|d{vkn`3brX9mj<1%aLEUx>_Fk6{Ic{wfGmLOTG1WwfS(lEc({9~G
z<$9}}{GeFi47)PDVsCqcpIMfkf`o=d!KXZgxIwp8KZ5r{JXVX1Ud{_UBv}Pqx=;)v
zzR~FS6Ei_2k|uM`1(~PMk1c{jZPQRyRiyiU;6y@abt@BVgJgXfBO*wG>;%hs+fW2k
z@Hh<9)?C2yyb8>5+u!^WLke5y&LN**UvoOzg}d1~)wt6lCu7jm=}=0ai#~vYe6MH6
z>z#YG%0AbZ`ieLfju~`}gxk`@LKmijU|n{*MuusBX9gWyn_UC5R{#~nkd1Bk$97bi;tzxA{H`mNahcT{;|$=Bbc4tKOUuzj^6=3}k6{r9Km5g*
z++;TIb)F$Q*MDR9iBMh^q$Z7LtHY3tOUH&{6sdm)Oi}V2N-QFAlZ-=@QJ~5!0qm->8_#z*d{&Z7
zOvM)g_+3XOatSK5V}Mh@TMfbNV%QZRXJU<6#CGD}wgVkxI4>Vkx65LEMt>4UYRD1^
zT4Y~&pW9~+C_8-60PgIZxW!#MGP1
zD|v1c-5xak`ZD(ugSQI>e(L0zO|V0T|Zc-;@9#)Be}
zX`Ii{4A$vPg-X=JCz)bQqlYV*i=n2q!c!aANMkV5HPMUkLFm0zobT>JfVbIM9F{AlAUgDf6j%~Xf@^H-nG%1Q
z)Wy|h+e?JI`n1Mc)4t212!BR2WQgxEdZR}>Zb-}K_tZ{MLMq@wl5&s6D_1t{qcIsE
z(%vxI0S>m98+mM#$h)z%p06YXgfpNoHPFw2J`#VBHs2iacSUuZJkacoSdP^PM#_b)
z+b|r@IT-ppHQctJzy%cgiHF0mxXhQYXnkn2>G%#Z={O~%D$xe}*n0VO-_w5<
zcTO&+RHdmHGO^75Td7m?JK=BNbZsBWG-hO|2KWFTBE$&k5?
zErs%J7O6lF==wk`eIGWkVXOrhxY_gPndyGo@i#}j%X%q@$*<025E@;$+eoIx8yWMp
z?L?5I%3RXzy4bFM*sWt|R1>UNC+>$JNON70}AzzwWntmhN!;TvRceLA5&lQuRwFhMf^COQC8G
zi{ekwxx6mtBjR^qK~yNVtCjus<{Tx}+kMvtb$$&f2;01zoV=1N@ea|#B&5qk1=C!s
zBGe`CreY)+HolDOy!V0Y@`RVN2tsEO^WO|P$rwScBg2*k<
zoYrk?_eGCv3fXpa6HC%OArajq*{TWA03=-C?L}9(JLAhOiM=67{(HJuniNQ8RJj>G
zKx@51$MoxXm4#?xNh&lc)`OeXvG41TnLW^yrAUvbl#V9>&r~#wPn2%H3F7LXagosnllqA2^Hmi6>Cg)KSepXB~)3sUQL#pHmij)YYccn{p^C3kgiPzlI~X*jUn{WEfF=PZ1#7Cc2AcE1W^
z)~i>hN|L=dYEecE4-ZS?>n3FAgr^lL>-Y+L_wv~HITzF7JE(TYbiih6(m$avrm!-Q
zr1Pds%~;A_wz*k6%~ug&CF_3CdsTs}Y+TEasp@uMMwtS1qUrk|3vBPcRy;MLW4`9L
z6VR0e2wQQ;y#olxQRPk1DgUsS8SwZ%y3FiK4bhUqfH=I%O*
zrop3}B?$d~qL%I)cTFDG-@}qOTz>;x3vm3d!8FinJ^D8#{yYGXr|AULS-q}3E;(r>
zLsrXKW|k&K+{6x50rRKkz>5VwGmGu*BKuj8cK*X%{V8BLXepMq06h)ne*cchwBV2w
z-mu#S)EXHdy$QKMDdx1YrlRiB0}ANY{}q#nUow$m)v~hF6M7Enku1e@`$90V|8lC@
zYNBPrjk?vB
zSh(Y?lf`a#=W%EGKWV}t|Np+BDx+q=W>N|d
zk7p*<8l4jM+My4RMWd4IqxFd8Z)SGrn&fHM_ax6*Lwo!V>}9c
zx*gcE1IOyrqosz?e5puRRfJwy#wGOA6k(qh*`NwDtn^6{-ZGycl_UHABuZps3~tYd
zFLzWr!VqQhX-pT~U3R0)^O$f3p7Y@5PVj^+W=fT8^GKaAZN62<&gy2M?eqViL}Uff
zWhp@az>g_x#uOp(8uet(l=In2j7icT^uyV&jQ^9ToXI*u9FcdHs>Bh?nq=oB04Vc`
zmAddbmkd5Jb?vSH8>U31+KMZeWu`<6i+L+KL?BIT2E<~N`^uRBQddeeG2ekD&|V>h
zMZfu5Bi?6-&3q*TiC`uT5HLys;76lm{#OfSAz{Wq!i*e^h#Rvw0(hh%Fs^b}{`CZL
zp4UAt0_aioRx@9tZ_+u;LRO4}NDF`fky^Qi+)}4ci()4K6i7@m=7RtwkeW&b5=v8?
z<7r&l|C(z23mRzWNd#|~S5zkfai5VG5+QQ{^~LG;81xzvzefg@4uqq_K&Ck*{SG(}
ztOEasskaWRD(bqorMtW1Al*ne9=fHw1q37ok&-@iiXh!0NJy82bf+NQB_ZALEuP==
zzVEmHTI{{Q=oqlc6ep=Dy;CkjAO>+xZ_GZbUme!~wWd7W2p
z4t_4RawbiS0a%aHXVV3WR!kCD+8tljGUs?LIuVI-KeB{_)c*7`qk1*qkmF*Ks(tcP
z>1X-hkdM^~#M|
z3qUB97B;02Z7}kEp^Rw)y(}ONl_vtrszI}7CUE?9MZEh{;CFjrcyWJwY2OIYWt_m-
zr{`KB6$tP4Z-tKN-<^cWyS)OLm6OfEnxPDw&IJH-I4IYY7B8lI8MgOM^m2jo$D3(Z
zrjudJ3t37yJL+7S*JL
z$E6loS$(r`JQZ9IU{fW(8YZ)SG4^4(!b~zv!Y^!Vj2`dKz&5kc5)(YLrHEJgn@Q>t
zpDccbl{9pE!PsETG=67QzW3*XYVRX-U0(y!?FlfdiAdXjiIj)EfT;mLjCR!?c%<*v
z#x(%5#H&W^IPyc7G>;<>MVe;Kt%Wib>UeJxRZ+r@|3epC#fth2!Uk-ZD
zJJs*rKA9_H$fwU!XF~ubLj5VEOknMcL-{9gR~5n5*oYN<>jnSb=3YFArW1eLw9$>o
z*5RZ}7zMGq`LIdLJ^2ZKQr=u%T;urX6-bra00y~fpD;gkwE?q6x99eYO*$DQ^b9te
zUl7$P`2Tl@@RBH=?4%0FdLXDhcbu6zz0)G2o
z>+7#}a)?uWqhj9=!W9_W+^Fa=weWxiu(a1A0BS3JyjFF9L)RC~-Sq%Vrsriz1uoq|
z06Y4c@H+YE{!-FoJ3(I7p8l(0on!9pa^x5%@$LS6eVm#-G-S=U
z3N9Gsaow@q^PXy%MuPJDOmJVBOep%B5Yp{)z^NDjd6C*uGArMJXdn2CRiC`POhX4V
zJ5}x@!tN1DLIpwe&&MgUPX!{WX%AXAVR6G&1b@o+`JBz7>}=EtC~GL*1BGKgUT
zwW6!6@>5aSkBo(>!91aiwT^$30t5pd;4|^Bz^-=4;_4DCLL=sqkST`Z^XRnsi)a3j
zZ^Y}vy$30jtROSd!)CbLk}{8G+)5${ERfkAqxOm3
z8Ed56!dedZWvWCUA%9UDhrthqkxQs%!eEI)LMK>QZ-_pXw_cDFDJ!%;{QHID@PcUa
zb{5AHYx0=d?@Tl1?H7>QxHSR)^9g8!uB-xq(lXMcABWFu^lN3nbhJ;^Kd~Dq72kTV
zzU~MLWtIBGWu`V5k_zBjBn}UyE}HRo0us1=LeBW(ZP!~yY7I9kp#>m-Z2-8`iq|IC
zDq7YCB=pDy-P=4p%U^*afzDUy)RjGz<&oZN*fkvCmL%UY&gwLti3-=zUOD0at*ldNdg>DJY0c{|
zgTZL&L#ELJ7X$!;?$tJaalHL2wAErsGC8H=HQ9e{&~@C!kjjDNx{Z&|l<#fkDDXr8
zDwi3lZMn)S{GmrZiOr}kx}364TU@dh?7dc8>8Q~N7y2gH4$XipB3|h_-3Iq}F69)|
zOkM?R+)@(W#n?M7;P)+XS*8D?9@(|s-Y9-Pl_c`cdA4%Z5c?;WR#DCVa)4BQarEj)
zjKj-jt0wyGH6-d2T_By%=;j%Smg;>WH=uT%YN=nQ&qK8FyJ9&PRcFsk3gla#S>|xv9>nS(9uVG`P?$N`iNu;6T6sY^;4|hla#J
zg18U1xZWX7Crw@Wsp1=Ii9@7uj|cxEk_?n_#|(}H1T*RgntBvqY(NyHS#30GSnw72B>ciFONRJKDr
zo)?x~3PjQKHTK`Vo1TtIM4h|{3phn2iQHKQXHf5Zkm^o8?)eQ=LH{*;7SLe2@tKdR
zaO;}ZcW?3vu0qRV)!Idh*WH*byNlw|M2lf(2U@DS>`_EjuWFZkWrV#z_>}~dD}gx+
z@t8rA$(C4ahd1gY66$Z@hb*gkGi^x2`jjyM)&)Mq=_@!%$mL(#wGLrs;<^=skX?!X
zd2XjqL;`94N{G1@1tc|m^ai^Ox7fVracvQXF*rp^1pEUvyBaW=^UTALm4=p&KU2(@
zA^eq+^ufW9h?sS{H=hHmT!OI|yd0fh-L~xqGrz9GhgIStl`w3tn6;+qx^sMgcxj+LN2kMv;k4=6A?j?(q6OaS=#gt${{hdUMLn1-D16yMw5B=nfg$)Q!N>y
zoWX$zCt)>Ccp(hzg)huDn_X(@!EIgemWt(EU>rs#B3z%cXm;pbuX9W?vCw_5#!)c6
z17bbU(Km#jcT$RY?vv`f^qo$sOBOO$!r{Ri$-+c+!nrVhF9UZYj7?zCA+EL|eSJT(jP18ZIVljmmzf!RRgW&(<4N4DlRG
zk)|^~0FR*#qZ;$`$s$4=Ut)qJS#0XaDQ0#MFa@g0m=5XH!1Os3HXPfFPpOkiwn-z>
z4eLB_$ltDF4ajeI9*O?+8|yEiw8v|dHkeg!aC=4EW@sZ*$-7PEwSc$HH?R$PjR_IY
znOp=>69;`L82UAFE7Py8(~;Q3HjW@S15x_ab2Rge+RJ#UAhe?}e_H38wx&FmPkUtP
zQrf@C6fOMzV1;l#d{#@>=&ONGV_wDhLa%Z$LRZA_sD{N0IipE==%pz}_ganTK?Skb
z{t`qOd(c2TGvEC-_`t#OWl8}HY03R~2U$BJC
z^_f%11U%!RuX4f<@<*zR4h%NOW0RmrC!9~$CsWg**hPZSe-Ao6aLe8rN+USBhnaM=
z!sht)knWW(ssW(wK1=6MaGJP}=ZdogFGc@z;;tyh(7C{JW
zNErf}f)04kfaOaEVw=Z^*zj~n$(~S{>mGI^;+Mt;ls_LIZs)E)0K@+aS4nKVQiI=M
z)}7^ZC`<)sBdZ_+jaKsm=z0-DGEAIPa9CCrbrG8~xKFsV65Ah@xOFt?Po%UF+Xwh=
z0)r~cP-E9_BJxNq50TW)NC<#xvfW4TmX*C%>|MzN>;kn+5qtUAY5KOSwI~o3C(;0{
z&^4f_n3?NnJQ-l)Z(0dNp&t7IUjAAlGRn|jnO;jJkhdYe?y4?b;`Iw|QKoz&aB$}B
zAK^}R^5fp1z)l0x-!>^ybvywP-OF7u2Nr+&`81wQD4&b0n!9DX>>o8BN&Kf8c{vS0
zfiW-(Q4uBQ*7FqKe;p{r&==_m4`XD`Gv%4+U!lX#-bIg_n|{=nzRmycwL2jzx>n;%
zqbh53QCdO&X1wm}Xf%{yIw@XbSUVGZCwHAIacVF`9->VrFG?nBTpdw2J9{dF*bf3P
z;y5gBoGPiBHWHPm2EP1{DcEpsuSdM!jtVgH0JJNvoH3z1<-gt
z?{vDriyVYVptk)ge|AsjwlpS~BaTTSppw0c+F66rW#UMb4YGknEhy$l@ya%$4;Gv2
z4>6kMC0_&X=m$zfrW@8-_I-%wXOdf~_znT9`W=)p`Ck`w4Y)|utZRHZ^34ScXMUBp
zAk#+cR`I&3IfLe&$MJa(CISW2BbH;gTlG?k(IqhN|w(
zXsslG9tVq+Osw4?;uzpSf1`XqYWF7PU@(wWizj)DTl@ZMQZrq^>5C^S>L2-q5BpWU
zHCnwjqS+9XD+-J7*lCb9mD_72=^^p{AK%_`sU8=R4-6h0)NT7Al`9C+_!i=HX(zz+T!*_!RK1+OzzW`v70KiaPr&IS$P4@A8yl(^M+WwX>qL+2}Et@j&EfHI4`
zQ#~#(qrc(r`Zj~jg#(uj54*|+bnh&39_7qZwy_bi#nu}*jjIdV-~CE)s~{yr8?oPn
zz(xF%!92?&J5R8`IYTJPPM$1^&k~^=F?jJ$B)PiM$L8~nqdbF%L(Na@#x@)w!M-+j
z{HHGuR>vO*eo#2a2nWu>iW>eJ8pH&Z+hfcn+8Z`0ZJCT)oE$E=hIO>dzJhx!h@B7~
zOO=ToLX%-+`gptiSTm_$i!M(fjj0(kLYki`wAB8UD1m-IPK_3li7M%VN;+NeQVvyc
z(>fWG8K)N`bcCGBRE~L9-%UlN^p2V?;)Hg?l?wOk6;`HF$n$0CA}|Pya3Zr`H{Nac
zJX|mkRrH|U?ZOo%S}@pZT1A~`T