diff --git a/src/storage.ts b/src/storage.ts index 450d44d..288852d 100644 --- a/src/storage.ts +++ b/src/storage.ts @@ -139,6 +139,9 @@ export interface GlobalStorage { sections: { dismissed?: string[]; }; + status: { + pinned?: boolean; + }; }; pendingWelcomeOnFocus?: boolean; pendingWhatsNewOnFocus?: boolean; diff --git a/src/webviews/apps/home/components/header-card.ts b/src/webviews/apps/home/components/header-card.ts index 492ecbe..02fbb8f 100644 --- a/src/webviews/apps/home/components/header-card.ts +++ b/src/webviews/apps/home/components/header-card.ts @@ -13,8 +13,23 @@ const template = html`

- ${x => x.planName} - + ${x => x.planName} + + ${when( + x => x.pinStatus, + html` + status update + + `, + )} You have access to GitLens+ features on ${x => (x.isPro ? 'any repo' : 'local & public repos')}, and all other GitLens features on any repo. @@ -188,6 +203,9 @@ const styles = css` } .status { color: var(--color-foreground--65); + } + + .status-label { cursor: help; } @@ -195,7 +213,7 @@ const styles = css` top: 1.6em; left: 0; } - .status:not(:hover) pop-over { + .status-label:not(:hover) + pop-over:not(.is-pinned) { display: none; } @@ -251,6 +269,10 @@ const styles = css` :host-context(.vscode-light) .action:hover { background-color: var(--color-background--darken-10); } + + pop-over .action { + margin-right: -0.2rem; + } `; @customElement({ name: 'header-card', template: template, styles: styles }) @@ -276,7 +298,11 @@ export class HeaderCard extends FASTElement { @attr plan = ''; + @attr({ attribute: 'pin-status', mode: 'boolean' }) + pinStatus = true; + progressNode!: HTMLElement; + statusNode!: HTMLElement; override attributeChangedCallback(name: string, oldValue: string, newValue: string): void { super.attributeChangedCallback(name, oldValue, newValue); @@ -348,4 +374,13 @@ export class HeaderCard extends FASTElement { updateProgressWidth() { this.progressNode.style.width = this.progress; } + + dismissStatus(e: MouseEvent) { + this.pinStatus = false; + this.$emit('dismiss-status'); + + window.requestAnimationFrame(() => { + this.statusNode?.focus(); + }); + } } diff --git a/src/webviews/apps/home/home.ts b/src/webviews/apps/home/home.ts index f3b1a65..d9707c9 100644 --- a/src/webviews/apps/home/home.ts +++ b/src/webviews/apps/home/home.ts @@ -11,12 +11,14 @@ import { DidChangeLayoutType, DidChangeSubscriptionNotificationType, DismissSectionCommandType, + DismissStatusCommandType, } from '../../home/protocol'; import type { IpcMessage } from '../../protocol'; import { ExecuteCommandType, onIpc } from '../../protocol'; import { App } from '../shared/appBase'; import { DOM } from '../shared/dom'; import type { CardSection } from './components/card-section'; +import type { HeaderCard } from './components/header-card'; import type { PlusBanner } from './components/plus-banner'; import type { SteppedSection } from './components/stepped-section'; import '../shared/components/code-icon'; @@ -65,6 +67,11 @@ export class HomeApp extends App { this.onCardDismissed(e, target), ), ); + disposables.push( + DOM.on('header-card', 'dismiss-status', (e, target: HTMLElement) => + this.onStatusDismissed(e, target), + ), + ); return disposables; } @@ -80,6 +87,7 @@ export class HomeApp extends App { this.state.subscription = params.subscription; this.state.completedActions = params.completedActions; this.state.avatar = params.avatar; + this.state.pinStatus = params.pinStatus; this.updateState(); }); break; @@ -129,6 +137,12 @@ export class HomeApp extends App { this.updateState(); } + private onStatusDismissed(e: CustomEvent, target: HTMLElement) { + this.state.pinStatus = false; + this.sendCommand(DismissStatusCommandType, undefined); + this.updateHeader(); + } + private onDataActionClicked(e: MouseEvent, target: HTMLElement) { const action = target.dataset.action; this.onActionClickedCore(action); @@ -165,9 +179,9 @@ export class HomeApp extends App { } private updateHeader(days = this.getDaysRemaining(), forceShowPlus = this.forceShowPlus()) { - const { subscription, completedSteps, avatar } = this.state; + const { subscription, completedSteps, avatar, pinStatus } = this.state; - const $headerContent = document.getElementById('header-card'); + const $headerContent = document.getElementById('header-card') as HeaderCard; if ($headerContent) { if (avatar) { $headerContent.setAttribute('image', avatar); @@ -191,6 +205,7 @@ export class HomeApp extends App { $headerContent.setAttribute('state', subscription.state.toString()); $headerContent.setAttribute('plan', subscription.plan.effective.name); $headerContent.setAttribute('days', days.toString()); + $headerContent.pinStatus = pinStatus; } } diff --git a/src/webviews/apps/shared/components/overlays/pop-over.ts b/src/webviews/apps/shared/components/overlays/pop-over.ts index 972cef5..6929ca0 100644 --- a/src/webviews/apps/shared/components/overlays/pop-over.ts +++ b/src/webviews/apps/shared/components/overlays/pop-over.ts @@ -14,19 +14,13 @@ import { elementBase } from '../styles/base'; const template = html` diff --git a/src/webviews/home/homeWebviewView.ts b/src/webviews/home/homeWebviewView.ts index 411a5ea..ba6c941 100644 --- a/src/webviews/home/homeWebviewView.ts +++ b/src/webviews/home/homeWebviewView.ts @@ -26,6 +26,7 @@ import { DidChangeLayoutType, DidChangeSubscriptionNotificationType, DismissSectionCommandType, + DismissStatusCommandType, } from './protocol'; export class HomeWebviewView extends WebviewViewBase { @@ -52,7 +53,8 @@ export class HomeWebviewView extends WebviewViewBase { return super.show(options); } - private onSubscriptionChanged(e: SubscriptionChangeEvent) { + private async onSubscriptionChanged(e: SubscriptionChangeEvent) { + await this.container.storage.store('home:status:pinned', true); void this.notifyDidChangeData(e.current); } @@ -133,6 +135,9 @@ export class HomeWebviewView extends WebviewViewBase { case DismissSectionCommandType.method: onIpc(DismissSectionCommandType, e, params => this.dismissSection(params)); break; + case DismissStatusCommandType.method: + onIpc(DismissStatusCommandType, e, _params => this.dismissPinStatus()); + break; } } @@ -158,6 +163,10 @@ export class HomeWebviewView extends WebviewViewBase { void this.container.storage.store('home:sections:dismissed', sections); } + private dismissPinStatus() { + void this.container.storage.store('home:status:pinned', false); + } + protected override async includeBootstrap(): Promise { return this.getState(); } @@ -194,6 +203,10 @@ export class HomeWebviewView extends WebviewViewBase { }; } + private getPinStatus() { + return this.container.storage.get('home:status:pinned') ?? true; + } + private async getState(subscription?: Subscription): Promise { const subscriptionState = await this.getSubscription(subscription); const steps = this.container.storage.get('home:steps:completed', []); @@ -210,14 +223,24 @@ export class HomeWebviewView extends WebviewViewBase { dismissedSections: sections, avatar: subscriptionState.avatar, layout: this.getLayout(), + pinStatus: this.getPinStatus(), }; } private notifyDidChangeData(subscription?: Subscription) { if (!this.isReady) return false; + const getSub = async () => { + const sub = await this.getSubscription(subscription); + + return { + ...sub, + pinStatus: this.getPinStatus(), + }; + }; + return window.withProgress({ location: { viewId: this.id } }, async () => - this.notify(DidChangeSubscriptionNotificationType, await this.getSubscription(subscription)), + this.notify(DidChangeSubscriptionNotificationType, await getSub()), ); } diff --git a/src/webviews/home/protocol.ts b/src/webviews/home/protocol.ts index 570d2c0..d7dd3a0 100644 --- a/src/webviews/home/protocol.ts +++ b/src/webviews/home/protocol.ts @@ -19,6 +19,7 @@ export interface State { visibility: RepositoriesVisibility; avatar?: string; layout: ViewsLayout; + pinStatus: boolean; } export interface CompleteStepParams { @@ -32,10 +33,13 @@ export interface DismissSectionParams { } export const DismissSectionCommandType = new IpcCommandType('home/section/dismiss'); +export const DismissStatusCommandType = new IpcCommandType('home/status/dismiss'); + export interface DidChangeSubscriptionParams { subscription: Subscription; completedActions: CompletedActions[]; avatar?: string; + pinStatus: boolean; } export const DidChangeSubscriptionNotificationType = new IpcNotificationType( 'subscription/didChange',