Browse Source

Adds gating to the commit graph

main
Keith Daulton 2 years ago
parent
commit
d40056c1c1
6 changed files with 329 additions and 33 deletions
  1. +41
    -5
      src/plus/webviews/graph/graphWebview.ts
  2. +13
    -0
      src/plus/webviews/graph/protocol.ts
  3. +150
    -11
      src/webviews/apps/plus/graph/GraphWrapper.tsx
  4. +71
    -7
      src/webviews/apps/plus/graph/graph.scss
  5. +14
    -0
      src/webviews/apps/plus/graph/graph.tsx
  6. +40
    -10
      src/webviews/apps/shared/theme.ts

+ 41
- 5
src/plus/webviews/graph/graphWebview.ts View File

@ -7,6 +7,7 @@ import { configuration } from '../../../configuration';
import { Commands, ContextKeys } from '../../../constants'; import { Commands, ContextKeys } from '../../../constants';
import type { Container } from '../../../container'; import type { Container } from '../../../container';
import { setContext } from '../../../context'; import { setContext } from '../../../context';
import { PlusFeatures } from '../../../features';
import type { GitCommit } from '../../../git/models/commit'; import type { GitCommit } from '../../../git/models/commit';
import type { GitGraph } from '../../../git/models/graph'; import type { GitGraph } from '../../../git/models/graph';
import type { Repository, RepositoryChangeEvent } from '../../../git/models/repository'; import type { Repository, RepositoryChangeEvent } from '../../../git/models/repository';
@ -22,12 +23,14 @@ import { RepositoryFolderNode } from '../../../views/nodes/viewNode';
import type { IpcMessage } from '../../../webviews/protocol'; import type { IpcMessage } from '../../../webviews/protocol';
import { onIpc } from '../../../webviews/protocol'; import { onIpc } from '../../../webviews/protocol';
import { WebviewBase } from '../../../webviews/webviewBase'; import { WebviewBase } from '../../../webviews/webviewBase';
import type { SubscriptionChangeEvent } from '../../subscription/subscriptionService';
import { ensurePlusFeaturesEnabled } from '../../subscription/utils'; import { ensurePlusFeaturesEnabled } from '../../subscription/utils';
import type { GraphCompositeConfig, GraphRepository, State } from './protocol'; import type { GraphCompositeConfig, GraphRepository, State } from './protocol';
import { import {
DidChangeCommitsNotificationType, DidChangeCommitsNotificationType,
DidChangeGraphConfigurationNotificationType, DidChangeGraphConfigurationNotificationType,
DidChangeNotificationType, DidChangeNotificationType,
DidChangeSubscriptionNotificationType,
DismissPreviewCommandType, DismissPreviewCommandType,
GetMoreCommitsCommandType, GetMoreCommitsCommandType,
UpdateColumnCommandType, UpdateColumnCommandType,
@ -69,6 +72,7 @@ export class GraphWebview extends WebviewBase {
return this._selection; return this._selection;
} }
private _etagSubscription?: number;
private _etagRepository?: number; private _etagRepository?: number;
private _repositoryEventsDisposable: Disposable | undefined; private _repositoryEventsDisposable: Disposable | undefined;
private _repositoryGraph?: GitGraph; private _repositoryGraph?: GitGraph;
@ -88,12 +92,16 @@ export class GraphWebview extends WebviewBase {
'graphWebview', 'graphWebview',
Commands.ShowGraphPage, Commands.ShowGraphPage,
); );
this.disposables.push(configuration.onDidChange(this.onConfigurationChanged, this), {
dispose: () => {
this._statusBarItem?.dispose();
void this._repositoryEventsDisposable?.dispose();
this.disposables.push(
configuration.onDidChange(this.onConfigurationChanged, this),
{
dispose: () => {
this._statusBarItem?.dispose();
void this._repositoryEventsDisposable?.dispose();
},
}, },
});
this.container.subscription.onDidChange(this.onSubscriptionChanged, this),
);
this.onConfigurationChanged(); this.onConfigurationChanged();
} }
@ -232,6 +240,13 @@ export class GraphWebview extends WebviewBase {
this.updateState(); this.updateState();
} }
private onSubscriptionChanged(e: SubscriptionChangeEvent) {
if (e.etag === this._etagSubscription) return;
this._etagSubscription = e.etag;
void this.notifyDidChangeSubscription();
}
private onThemeChanged(theme: ColorTheme) { private onThemeChanged(theme: ColorTheme) {
if (this._theme != null) { if (this._theme != null) {
if ( if (
@ -341,6 +356,17 @@ export class GraphWebview extends WebviewBase {
} }
@debug() @debug()
private async notifyDidChangeSubscription() {
if (!this.isReady || !this.visible) return false;
const access = await this.container.git.access(PlusFeatures.Graph, this.repository?.path);
return this.notify(DidChangeSubscriptionNotificationType, {
subscription: access.subscription.current,
allowed: access.allowed,
});
}
@debug()
private async notifyDidChangeCommits() { private async notifyDidChangeCommits() {
if (!this.isReady || !this.visible) return false; if (!this.isReady || !this.visible) return false;
@ -385,6 +411,13 @@ export class GraphWebview extends WebviewBase {
// If we have a set of data refresh to the same set // If we have a set of data refresh to the same set
const limit = this._repositoryGraph?.paging?.limit ?? config.defaultItemLimit; const limit = this._repositoryGraph?.paging?.limit ?? config.defaultItemLimit;
// only check on private
const access = await this.container.git.access(PlusFeatures.Graph, this.repository?.path);
// TODO: probably not the right place to set this
if (this._etagSubscription == null) {
this._etagSubscription = this.container.subscription.etag;
}
const data = await this.container.git.getCommitsForGraph( const data = await this.container.git.getCommitsForGraph(
this.repository.path, this.repository.path,
this._panel!.webview.asWebviewUri, this._panel!.webview.asWebviewUri,
@ -396,6 +429,9 @@ export class GraphWebview extends WebviewBase {
previewBanner: this.previewBanner, previewBanner: this.previewBanner,
repositories: formatRepositories(this.container.git.openRepositories), repositories: formatRepositories(this.container.git.openRepositories),
selectedRepository: this.repository.path, selectedRepository: this.repository.path,
selectedVisibility: access.visibility,
subscription: access.subscription.current,
allowed: access.allowed,
rows: data.rows, rows: data.rows,
paging: { paging: {
startingCursor: data.paging?.startingCursor, startingCursor: data.paging?.startingCursor,

+ 13
- 0
src/plus/webviews/graph/protocol.ts View File

@ -1,10 +1,15 @@
import type { CommitType, GraphRow, Remote } from '@gitkraken/gitkraken-components'; import type { CommitType, GraphRow, Remote } from '@gitkraken/gitkraken-components';
import type { GraphColumnConfig, GraphConfig } from '../../../config'; import type { GraphColumnConfig, GraphConfig } from '../../../config';
import type { RepositoryVisibility } from '../../../git/gitProvider';
import type { Subscription } from '../../../subscription';
import { IpcCommandType, IpcNotificationType } from '../../../webviews/protocol'; import { IpcCommandType, IpcNotificationType } from '../../../webviews/protocol';
export interface State { export interface State {
repositories?: GraphRepository[]; repositories?: GraphRepository[];
selectedRepository?: string; selectedRepository?: string;
selectedVisibility?: RepositoryVisibility;
subscription?: Subscription;
allowed?: boolean;
rows?: GraphRow[]; rows?: GraphRow[];
paging?: GraphPaging; paging?: GraphPaging;
config?: GraphCompositeConfig; config?: GraphCompositeConfig;
@ -94,6 +99,14 @@ export const DidChangeGraphConfigurationNotificationType = new IpcNotificationTy
'graph/configuration/didChange', 'graph/configuration/didChange',
); );
export interface DidChangeSubscriptionParams {
subscription: Subscription;
allowed: boolean;
}
export const DidChangeSubscriptionNotificationType = new IpcNotificationType<DidChangeSubscriptionParams>(
'graph/subscription/didChange',
);
export interface DidChangeCommitsParams { export interface DidChangeCommitsParams {
rows: GraphRow[]; rows: GraphRow[];
paging?: GraphPaging; paging?: GraphPaging;

+ 150
- 11
src/webviews/apps/plus/graph/GraphWrapper.tsx View File

@ -14,6 +14,9 @@ import type {
GraphRepository, GraphRepository,
State, State,
} from '../../../../plus/webviews/graph/protocol'; } from '../../../../plus/webviews/graph/protocol';
import type { Subscription } from '../../../../subscription';
import { SubscriptionState } from '../../../../subscription';
import { fromNow } from '../../shared/date';
export interface GraphWrapperProps extends State { export interface GraphWrapperProps extends State {
nonce?: string; nonce?: string;
@ -114,6 +117,8 @@ export function GraphWrapper({
repositories = [], repositories = [],
rows = [], rows = [],
selectedRepository, selectedRepository,
subscription,
allowed,
config, config,
paging, paging,
onSelectRepository, onSelectRepository,
@ -139,7 +144,12 @@ export function GraphWrapper({
const [mainWidth, setMainWidth] = useState<number>(); const [mainWidth, setMainWidth] = useState<number>();
const [mainHeight, setMainHeight] = useState<number>(); const [mainHeight, setMainHeight] = useState<number>();
const mainRef = useRef<HTMLElement>(null); const mainRef = useRef<HTMLElement>(null);
const [showBanner, setShowBanner] = useState(previewBanner);
// banner
const [showPreview, setShowPreview] = useState(previewBanner);
// account
const [showAccount, setShowAccount] = useState(true);
const [isAllowed, setIsAllowed] = useState(allowed ?? false);
const [subscriptionSnapshot, setSubscriptionSnapshot] = useState<Subscription | undefined>(subscription);
// repo selection UI // repo selection UI
const [repoExpanded, setRepoExpanded] = useState(false); const [repoExpanded, setRepoExpanded] = useState(false);
@ -173,6 +183,8 @@ export function GraphWrapper({
setPagingState(state.paging); setPagingState(state.paging);
setIsLoading(false); setIsLoading(false);
setStyleProps(getStyleProps(state.mixedColumnColors)); setStyleProps(getStyleProps(state.mixedColumnColors));
setIsAllowed(state.allowed ?? false);
setSubscriptionSnapshot(state.subscription);
} }
useEffect(() => { useEffect(() => {
@ -207,15 +219,135 @@ export function GraphWrapper({
onSelectionChange?.(graphRows.map(r => r.sha)); onSelectionChange?.(graphRows.map(r => r.sha));
}; };
const handleDismissBanner = () => {
setShowBanner(false);
const handleDismissPreview = () => {
setShowPreview(false);
onDismissPreview?.(); onDismissPreview?.();
}; };
const handleDismissAccount = () => {
setShowAccount(false);
};
const renderAlertContent = () => {
if (subscriptionSnapshot == null) return;
let icon = 'account';
let modifier = '';
let content;
let actions;
switch (subscriptionSnapshot.state) {
case SubscriptionState.Free:
case SubscriptionState.Paid:
return;
case SubscriptionState.FreeInPreview:
icon = 'calendar';
modifier = 'neutral';
content = (
<>
<p className="alert__title">Trial Preview</p>
<p className="alert__message">
You're able to view the Commit Graph with any repository until your preview expires
{subscriptionSnapshot.previewTrial
? ` ${fromNow(new Date(subscriptionSnapshot.previewTrial.expiresOn))}`
: ''}
.
</p>
</>
);
break;
case SubscriptionState.FreePreviewExpired:
icon = 'warning';
modifier = 'warning';
content = (
<>
<p className="alert__title">Extend Your Trial</p>
<p className="alert__message">Sign in to extend your free trial an additional 7-days.</p>
</>
);
actions = (
<>
<a className="alert-action" href="command:gitlens.plus.loginOrSignUp">
Try for 7-days
</a>{' '}
<a className="alert-action" href="command:gitlens.plus.purchase">
View Plans
</a>
</>
);
break;
case SubscriptionState.FreePlusInTrial:
icon = 'calendar';
modifier = 'neutral';
content = (
<>
<p className="alert__title">Extended Trial</p>
<p className="alert__message">
You're able to view the Commit Graph with any repository until your trial expires
{subscriptionSnapshot.previewTrial
? ` ${fromNow(new Date(subscriptionSnapshot.previewTrial.expiresOn))}`
: ''}
.
</p>
</>
);
break;
case SubscriptionState.FreePlusTrialExpired:
icon = 'warning';
modifier = 'warning';
content = (
<>
<p className="alert__title">Trial Expired</p>
<p className="alert__message">
Upgrade your account to use the Commit Graph and other GitLens+ features on private repos.
</p>
<p>
<a className="alert-action" href="command:gitlens.plus.purchase">
Upgrade Your Account
</a>
</p>
</>
);
break;
case SubscriptionState.VerificationRequired:
icon = 'unverified';
modifier = 'warning';
content = (
<>
<p className="alert__title">Please verify your email</p>
<p className="alert__message">Please verify the email for the account you created.</p>
</>
);
actions = (
<>
<a className="alert-action" href="command:gitlens.plus.resendVerification">
Resend Verification Email
</a>
<a className="alert-action" href="command:gitlens.plus.validate">
Refresh Verification Status
</a>
</>
);
break;
}
return (
<div className={`alert${modifier !== '' ? ` alert--${modifier}` : ''}`}>
<span className={`alert__icon codicon codicon-${icon}`}></span>
<div className="alert__content">{content}</div>
{actions && <div className="alert__actions">{actions}</div>}
{isAllowed && (
<button className="alert__dismiss" type="button" onClick={() => handleDismissAccount()}>
<span className="codicon codicon-chrome-close"></span>
</button>
)}
</div>
);
};
return ( return (
<> <>
{showBanner && (
<section className="graph-app__banner">
<section className="graph-app__banners">
{showPreview && (
<div className="alert"> <div className="alert">
<span className="alert__icon codicon codicon-search"></span> <span className="alert__icon codicon codicon-search"></span>
<div className="alert__content"> <div className="alert__content">
@ -230,13 +362,20 @@ export function GraphWrapper({
. .
</p> </p>
</div> </div>
<button className="alert__action" type="button" onClick={() => handleDismissBanner()}>
<button className="alert__dismiss" type="button" onClick={() => handleDismissPreview()}>
<span className="codicon codicon-chrome-close"></span> <span className="codicon codicon-chrome-close"></span>
</button> </button>
</div> </div>
</section>
)}
<main ref={mainRef} id="main" className="graph-app__main">
)}
{showAccount && renderAlertContent()}
</section>
<main
ref={mainRef}
id="main"
className={`graph-app__main${!isAllowed ? ' is-gated' : ''}`}
aria-hidden={!isAllowed}
>
{!isAllowed && <div className="graph-app__cover"></div>}
{currentRepository !== undefined ? ( {currentRepository !== undefined ? (
<> <>
{mainWidth !== undefined && mainHeight !== undefined && ( {mainWidth !== undefined && mainHeight !== undefined && (
@ -263,7 +402,7 @@ export function GraphWrapper({
<p>No repository is selected</p> <p>No repository is selected</p>
)} )}
</main> </main>
<footer className="actionbar graph-app__footer">
<footer className={`actionbar graph-app__footer${!isAllowed ? ' is-gated' : ''}`} aria-hidden={!isAllowed}>
<div className="actionbar__group"> <div className="actionbar__group">
<div className="actioncombo"> <div className="actioncombo">
<button <button
@ -321,7 +460,7 @@ export function GraphWrapper({
)} )}
</div> </div>
</div> </div>
{graphList.length > 0 && (
{isAllowed && graphList.length > 0 && (
<span className="actionbar__details"> <span className="actionbar__details">
showing {graphList.length} item{graphList.length ? 's' : ''} showing {graphList.length} item{graphList.length ? 's' : ''}
</span> </span>

+ 71
- 7
src/webviews/apps/plus/graph/graph.scss View File

@ -142,9 +142,10 @@ a {
} }
.alert { .alert {
--alert-foreground: var(--vscode-input-foreground);
--alert-background: var(--vscode-inputValidation-infoBackground);
--alert-border-color: var(--vscode-inputValidation-infoBorder);
--alert-foreground: var(--color-alert-foreground);
--alert-background: var(--color-alert-infoBackground);
--alert-border-color: var(--color-alert-infoBorder);
--alert-hover-background: var(--color-alert-infoHoverBackground);
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: flex-start; justify-content: flex-start;
@ -160,7 +161,11 @@ a {
} }
&__content { &__content {
flex: 1;
padding-top: 0.24rem; padding-top: 0.24rem;
> :last-child {
margin-bottom: 0;
}
} }
&__title { &__title {
font-size: 1.3rem; font-size: 1.3rem;
@ -177,7 +182,11 @@ a {
margin-top: 0.25rem; margin-top: 0.25rem;
} }
&__action {
&__actions {
align-self: center;
}
&__dismiss {
border: 1px solid transparent; border: 1px solid transparent;
background-color: transparent; background-color: transparent;
color: inherit; color: inherit;
@ -185,8 +194,44 @@ a {
width: 2rem; width: 2rem;
height: 2rem; height: 2rem;
padding: 0; padding: 0;
cursor: pointer;
margin-left: auto;
}
&--warning {
--alert-background: var(--color-alert-warningBackground);
--alert-border-color: var(--color-alert-warningBorder);
--alert-hover-background: var(--color-alert-warningHoverBackground);
}
&--error {
--alert-background: var(--color-alert-errorBackground);
--alert-border-color: var(--color-alert-errorBorder);
--alert-hover-background: var(--color-alert-errorHoverBackground);
}
&--neutral {
--alert-background: var(--color-alert-neutralBackground);
--alert-border-color: var(--color-alert-neutralBorder);
--alert-hover-background: var(--color-alert-neutralHoverBackground);
}
}
.alert-action {
display: inline-block;
padding: 0.4rem 0.8rem;
font-family: inherit;
font-size: inherit;
line-height: 1.4;
text-align: center;
text-decoration: none;
user-select: none;
background: transparent;
color: var(--alert-foreground);
cursor: pointer;
border: 1px solid var(--alert-border-color);
border-radius: 0.2rem;
&:hover {
text-decoration: none;
color: var(--alert-foreground);
background-color: var(--alert-hover-background);
} }
} }
@ -307,9 +352,23 @@ a {
padding: 0 2px; padding: 0 2px;
} }
&__banner {
&__banners {
flex: none; flex: none;
padding: 0.5rem; padding: 0.5rem;
z-index: 2000;
> *:not(:first-child) {
margin-top: 0.5rem;
}
}
&__cover {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1999;
backdrop-filter: blur(4px) saturate(0.8);
} }
&__footer { &__footer {
@ -320,6 +379,11 @@ a {
flex: 1 1 auto; flex: 1 1 auto;
overflow: hidden; overflow: hidden;
} }
&__main.is-gated {
position: relative;
pointer-events: none;
}
} }
::-webkit-scrollbar-thumb { ::-webkit-scrollbar-thumb {

+ 14
- 0
src/webviews/apps/plus/graph/graph.tsx View File

@ -8,6 +8,7 @@ import {
DidChangeCommitsNotificationType, DidChangeCommitsNotificationType,
DidChangeGraphConfigurationNotificationType, DidChangeGraphConfigurationNotificationType,
DidChangeNotificationType, DidChangeNotificationType,
DidChangeSubscriptionNotificationType,
DismissPreviewCommandType, DismissPreviewCommandType,
GetMoreCommitsCommandType, GetMoreCommitsCommandType,
UpdateColumnCommandType, UpdateColumnCommandType,
@ -154,6 +155,19 @@ export class GraphApp extends App {
}); });
break; break;
case DidChangeSubscriptionNotificationType.method:
this.log(`${this.appName}.onMessageReceived(${msg.id}): name=${msg.method}`);
onIpc(DidChangeSubscriptionNotificationType, msg, params => {
this.setState({
...this.state,
subscription: params.subscription,
allowed: params.allowed,
});
this.refresh(this.state);
});
break;
default: default:
super.onMessageReceived?.(e); super.onMessageReceived?.(e);
} }

+ 40
- 10
src/webviews/apps/shared/theme.ts View File

@ -6,6 +6,10 @@ export function initializeAndWatchThemeColors(callback?: () => void) {
const body = document.body; const body = document.body;
const computedStyle = window.getComputedStyle(body); const computedStyle = window.getComputedStyle(body);
const isLightTheme =
body.classList.contains('vscode-light') || body.classList.contains('vscode-high-contrast-light');
const isHighContrastTheme = body.classList.contains('vscode-high-contrast');
const bodyStyle = body.style; const bodyStyle = body.style;
bodyStyle.setProperty('--font-family', computedStyle.getPropertyValue('--vscode-font-family').trim()); bodyStyle.setProperty('--font-family', computedStyle.getPropertyValue('--vscode-font-family').trim());
@ -42,17 +46,15 @@ export function initializeAndWatchThemeColors(callback?: () => void) {
color = computedStyle.getPropertyValue('--vscode-button-background').trim(); color = computedStyle.getPropertyValue('--vscode-button-background').trim();
bodyStyle.setProperty('--color-button-background', color); bodyStyle.setProperty('--color-button-background', color);
bodyStyle.setProperty('--color-button-background--darken-30', darken(color, 30)); bodyStyle.setProperty('--color-button-background--darken-30', darken(color, 30));
color = computedStyle.getPropertyValue('--vscode-button-secondaryBackground').trim();
bodyStyle.setProperty('--color-button-secondary-background', color);
bodyStyle.setProperty('--color-button-secondary-background--darken-30', darken(color, 30));
color = computedStyle.getPropertyValue('--vscode-button-background').trim();
bodyStyle.setProperty('--color-highlight', color); bodyStyle.setProperty('--color-highlight', color);
bodyStyle.setProperty('--color-highlight--75', opacity(color, 75)); bodyStyle.setProperty('--color-highlight--75', opacity(color, 75));
bodyStyle.setProperty('--color-highlight--50', opacity(color, 50)); bodyStyle.setProperty('--color-highlight--50', opacity(color, 50));
bodyStyle.setProperty('--color-highlight--25', opacity(color, 25)); bodyStyle.setProperty('--color-highlight--25', opacity(color, 25));
color = computedStyle.getPropertyValue('--vscode-button-secondaryBackground').trim();
bodyStyle.setProperty('--color-button-secondary-background', color);
bodyStyle.setProperty('--color-button-secondary-background--darken-30', darken(color, 30));
color = computedStyle.getPropertyValue('--vscode-button-foreground').trim(); color = computedStyle.getPropertyValue('--vscode-button-foreground').trim();
bodyStyle.setProperty('--color-button-foreground', color); bodyStyle.setProperty('--color-button-foreground', color);
@ -97,12 +99,40 @@ export function initializeAndWatchThemeColors(callback?: () => void) {
bodyStyle.setProperty('--color-hover-statusBarBackground', color); bodyStyle.setProperty('--color-hover-statusBarBackground', color);
// graph-specific colors // graph-specific colors
const isLightTheme =
body.className.includes('vscode-light') || body.className.includes('vscode-high-contrast-light');
color = computedStyle.getPropertyValue('--vscode-editor-background').trim();
bodyStyle.setProperty('--graph-panel-bg', isLightTheme ? darken(color, 5) : lighten(color, 5));
bodyStyle.setProperty(
'--graph-panel-bg',
isLightTheme ? darken(backgroundColor, 5) : lighten(backgroundColor, 5),
);
bodyStyle.setProperty('--graph-theme-opacity-factor', isLightTheme ? '0.5' : '1'); bodyStyle.setProperty('--graph-theme-opacity-factor', isLightTheme ? '0.5' : '1');
// alert colors
color = computedStyle.getPropertyValue('--vscode-inputValidation-infoBackground').trim();
bodyStyle.setProperty('--color-alert-infoHoverBackground', isLightTheme ? darken(color, 5) : lighten(color, 5));
bodyStyle.setProperty('--color-alert-infoBackground', color);
color = computedStyle.getPropertyValue('--vscode-inputValidation-warningBackground').trim();
bodyStyle.setProperty(
'--color-alert-warningHoverBackground',
isLightTheme ? darken(color, 5) : lighten(color, 5),
);
bodyStyle.setProperty('--color-alert-warningBackground', color);
color = computedStyle.getPropertyValue('--vscode-inputValidation-errorBackground').trim();
bodyStyle.setProperty(
'--color-alert-errorHoverBackground',
isLightTheme ? darken(color, 5) : lighten(color, 5),
);
bodyStyle.setProperty('--color-alert-errorBackground', color);
color = computedStyle.getPropertyValue('--vscode-input-background').trim();
bodyStyle.setProperty(
'--color-alert-neutralHoverBackground',
isLightTheme ? darken(color, 5) : lighten(color, 5),
);
bodyStyle.setProperty('--color-alert-neutralBackground', color);
bodyStyle.setProperty('--color-alert-infoBorder', 'var(--vscode-inputValidation-infoBorder)');
bodyStyle.setProperty('--color-alert-warningBorder', 'var(--vscode-inputValidation-warningBorder)');
bodyStyle.setProperty('--color-alert-errorBorder', 'var(--vscode-inputValidation-errorBorder)');
bodyStyle.setProperty('--color-alert-neutralBorder', 'var(--vscode-input-foreground)');
bodyStyle.setProperty('--color-alert-foreground', 'var(--vscode-input-foreground)');
callback?.(); callback?.();
}; };

Loading…
Cancel
Save