diff --git a/src/premium/webviews/timeline/timelineWebviewView.ts b/src/premium/webviews/timeline/timelineWebviewView.ts index 186599f..ccd833a 100644 --- a/src/premium/webviews/timeline/timelineWebviewView.ts +++ b/src/premium/webviews/timeline/timelineWebviewView.ts @@ -5,9 +5,10 @@ import { Commands } from '../../../constants'; import { Container } from '../../../container'; import { PremiumFeatures } from '../../../features'; import { GitUri } from '../../../git/gitUri'; +import { RepositoryChange, RepositoryChangeComparisonMode, RepositoryChangeEvent } from '../../../git/models'; import { createFromDateDelta } from '../../../system/date'; import { debug } from '../../../system/decorators/log'; -import { debounce } from '../../../system/function'; +import { debounce, Deferrable } from '../../../system/function'; import { hasVisibleTextEditor, isTextEditor } from '../../../system/utils'; import { IpcMessage, onIpc } from '../../../webviews/protocol'; import { WebviewViewBase } from '../../../webviews/webviewViewBase'; @@ -16,72 +17,92 @@ import { Commit, DidChangeStateNotificationType, OpenDataPointCommandType, + Period, State, UpdatePeriodCommandType, } from './protocol'; +interface Context { + editor: TextEditor | undefined; + period: Period | undefined; + etagRepository: number | undefined; + etagSubscription: number | undefined; +} + +const defaultPeriod: Period = '3|M'; + export class TimelineWebviewView extends WebviewViewBase { - private _editor: TextEditor | undefined; - private _period: `${number}|${'D' | 'M' | 'Y'}` = '3|M'; + private _bootstraping = true; + /** The context the webview has */ + private _context: Context; + /** The context the webview should have */ + private _pendingContext: Partial | undefined; constructor(container: Container) { super(container, 'gitlens.views.timeline', 'timeline.html', 'Visual File History'); - this.disposables.push(this.container.subscription.onDidChange(this.onSubscriptionChanged, this)); - } - - override dispose() { - super.dispose(); - this._disposableVisibility?.dispose(); - } + this._context = { + editor: undefined, + period: defaultPeriod, + etagRepository: 0, + etagSubscription: this.container.subscription.etag, + }; - protected override onReady() { - this.onActiveEditorChanged(window.activeTextEditor); + this.disposables.push( + this.container.subscription.onDidChange(this.onSubscriptionChanged, this), + window.onDidChangeActiveTextEditor(this.onActiveEditorChanged, this), + this.container.git.onDidChangeRepository(this.onRepositoryChanged, this), + ); } protected override async includeBootstrap(): Promise { - return this.getState(undefined); + this._bootstraping = true; + this.updatePendingEditor(window.activeTextEditor); + this._context = { ...this._context, ...this._pendingContext }; + return this.getState(this._context); } @debug({ args: false }) private onActiveEditorChanged(editor: TextEditor | undefined) { - if (!this.isReady || (this._editor === editor && editor != null)) return; - if (editor == null && hasVisibleTextEditor()) return; - if (editor != null && !isTextEditor(editor)) return; + if (!this.updatePendingEditor(editor)) return; - this._editor = editor; - void this.notifyDidChangeState(editor); + this.updateState(); } - private onSubscriptionChanged(_e: SubscriptionChangeEvent) { - void this.notifyDidChangeState(this._editor); + private onRepositoryChanged(e: RepositoryChangeEvent) { + if (!e.changed(RepositoryChange.Heads, RepositoryChange.Index, RepositoryChangeComparisonMode.Any)) { + return; + } + + if (this.updatePendingContext({ etagRepository: e.repository.etag })) { + this.updateState(); + } + } + + private onSubscriptionChanged(e: SubscriptionChangeEvent) { + if (this.updatePendingContext({ etagSubscription: e.etag })) { + this.updateState(); + } } - private _disposableVisibility: Disposable | undefined; protected override onVisibilityChanged(visible: boolean) { - if (visible) { - if (this._disposableVisibility == null) { - this._disposableVisibility = window.onDidChangeActiveTextEditor( - debounce(this.onActiveEditorChanged, 500), - this, - ); - } - this.onActiveEditorChanged(window.activeTextEditor); - } else { - this._disposableVisibility?.dispose(); - this._disposableVisibility = undefined; + if (!visible) return; - this._editor = undefined; + // Since this gets called even the first time the webview is shown, avoid sending an update, because the bootstrap has the data + if (this._bootstraping) { + this._bootstraping = false; + return; } + this.updateState(true); } protected override onMessageReceived(e: IpcMessage) { switch (e.method) { case OpenDataPointCommandType.method: onIpc(OpenDataPointCommandType, e, params => { - if (params.data == null || this._editor == null || !params.data.selected) return; + if (params.data == null || !params.data.selected || this._context.editor == null) return; - const repository = this.container.git.getRepository(this._editor.document.uri); + const repository = this.container.git.getRepository(this._context.editor.document.uri); if (repository == null) return; const commandArgs: ShowQuickCommitCommandArgs = { @@ -111,33 +132,35 @@ export class TimelineWebviewView extends WebviewViewBase { case UpdatePeriodCommandType.method: onIpc(UpdatePeriodCommandType, e, params => { - this._period = params.period; - void this.notifyDidChangeState(this._editor); + if (this.updatePendingContext({ period: params.period })) { + this.updateState(true); + } }); break; } } - @debug({ args: { 0: e => e?.document.uri.toString(true) } }) - private async getState(editor: TextEditor | undefined): Promise { + @debug({ args: false }) + private async getState(current: Context): Promise { const access = await this.container.git.access(PremiumFeatures.Timeline); + const period = current.period ?? defaultPeriod; + const dateFormat = this.container.config.defaultDateFormat ?? 'MMMM Do, YYYY h:mma'; - if (editor == null || !access.allowed) { + if (current.editor == null || !access.allowed) { return { - period: this._period, + period: period, title: 'There are no editors open that can provide file history information', dateFormat: dateFormat, access: access, }; } - const gitUri = await GitUri.fromUri(editor.document.uri); + const gitUri = await GitUri.fromUri(current.editor.document.uri); const repoPath = gitUri.repoPath!; const title = gitUri.relativePath; - // this.setTitle(`${this.title} \u2022 ${gitUri.fileName}`); this.description = gitUri.fileName; const [currentUser, log] = await Promise.all([ @@ -145,16 +168,16 @@ export class TimelineWebviewView extends WebviewViewBase { this.container.git.getLogForFile(repoPath, gitUri.fsPath, { limit: 0, ref: gitUri.sha, - since: this.getPeriodDate().toISOString(), + since: this.getPeriodDate(period).toISOString(), }), ]); if (log == null) { return { dataset: [], - period: this._period, - title: title, - uri: editor.document.uri.toString(), + period: period, + title: 'No commits found for the specified time period', + uri: current.editor.document.uri.toString(), dateFormat: dateFormat, access: access, }; @@ -181,17 +204,15 @@ export class TimelineWebviewView extends WebviewViewBase { return { dataset: dataset, - period: this._period, + period: period, title: title, - uri: editor.document.uri.toString(), + uri: current.editor.document.uri.toString(), dateFormat: dateFormat, access: access, }; } - private getPeriodDate(): Date { - const period = this._period ?? '3|M'; - + private getPeriodDate(period: Period): Date { const [number, unit] = period.split('|'); switch (unit) { @@ -206,13 +227,72 @@ export class TimelineWebviewView extends WebviewViewBase { } } - private async notifyDidChangeState(editor: TextEditor | undefined) { - if (!this.isReady) return false; + private updatePendingContext(context: Partial): boolean { + let changed = false; + for (const [key, value] of Object.entries(context)) { + if ((this._context as unknown as Record)[key] !== value) { + if (this._pendingContext == null) { + this._pendingContext = {}; + } - return window.withProgress({ location: { viewId: this.id } }, async () => - this.notify(DidChangeStateNotificationType, { - state: await this.getState(editor), - }), - ); + (this._pendingContext as Record)[key] = value; + changed = true; + } + } + + return changed; + } + + private updatePendingEditor(editor: TextEditor | undefined): boolean { + if (editor == null && hasVisibleTextEditor()) return false; + if (editor != null && !isTextEditor(editor)) return false; + + let etag; + if (editor != null) { + const repository = this.container.git.getRepository(editor.document.uri); + etag = repository?.etag ?? 0; + } else { + etag = 0; + } + + return this.updatePendingContext({ editor: editor, etagRepository: etag }); + } + + private _notifyDidChangeStateDebounced: Deferrable<() => void> | undefined = undefined; + + @debug() + private updateState(immediate: boolean = false) { + if (!this.isReady || !this.visible) return; + + this.updatePendingEditor(window.activeTextEditor); + + if (immediate) { + void this.notifyDidChangeState(); + return; + } + + if (this._notifyDidChangeStateDebounced == null) { + this._notifyDidChangeStateDebounced = debounce(this.notifyDidChangeState.bind(this), 500); + } + + this._notifyDidChangeStateDebounced(); + } + + @debug() + private async notifyDidChangeState() { + if (!this.isReady || !this.visible) return false; + + this._notifyDidChangeStateDebounced?.cancel(); + const context = { ...this._context, ...this._pendingContext }; + + return window.withProgress({ location: { viewId: this.id } }, async () => { + const success = await this.notify(DidChangeStateNotificationType, { + state: await this.getState(context), + }); + if (success) { + this._context = context; + this._pendingContext = undefined; + } + }); } } diff --git a/src/webviews/apps/premium/timeline/chart.scss b/src/webviews/apps/premium/timeline/chart.scss index d6ea5fc..cabf4c3 100644 --- a/src/webviews/apps/premium/timeline/chart.scss +++ b/src/webviews/apps/premium/timeline/chart.scss @@ -94,18 +94,18 @@ line { .vscode-dark & { - stroke: var(--color-background--lighten-075); + stroke: var(--color-background--lighten-05); &.bb-ygrid { - stroke: var(--color-background--lighten-15); + stroke: var(--color-background--lighten-05); } } .vscode-light & { - stroke: var(--color-background--darken-075); + stroke: var(--color-background--darken-05); &.bb-ygrid { - stroke: var(--color-background--darken-15); + stroke: var(--color-background--darken-05); } } } @@ -116,13 +116,13 @@ } } - .bb-xgrid { - stroke-dasharray: 2 2; - } + // .bb-xgrid { + // stroke-dasharray: 2 2; + // } - .bb-ygrid { - stroke-dasharray: 2 1; - } + // .bb-ygrid { + // stroke-dasharray: 2 1; + // } .bb-xgrid-focus { line { @@ -148,43 +148,21 @@ /*-- Point --*/ .bb-circle { - // opacity: 0.8 !important; - // stroke-width: 0; - // fill-opacity: 0.6; - &._expanded_ { - // stroke-width: 1px; - // stroke: white; - - // opacity: 1 !important; - - fill-opacity: 1; + opacity: 1 !important; + fill-opacity: 1 !important; + stroke-opacity: 1 !important; stroke-width: 3px; - stroke-opacity: 0.6; } } .bb-selected-circle { - // fill: white; - // stroke-width: 2px; - - // opacity: 1 !important; - - fill-opacity: 1; + opacity: 1 !important; + fill-opacity: 1 !important; + stroke-opacity: 1 !important; stroke-width: 3px; - stroke-opacity: 0.6; } - // .bb-circles { - // &.bb-focused { - // opacity: 1; - // } - - // &.bb-defocused { - // opacity: 0.3 !important; - // } - // } - // rect.bb-circle, // use.bb-circle { // &._expanded_ { @@ -207,15 +185,12 @@ /*-- Bar --*/ .bb-bar { stroke-width: 0; - // opacity: 0.8 !important; - // fill-opacity: 0.6; + opacity: 0.9 !important; + fill-opacity: 0.9 !important; &._expanded_ { - fill-opacity: 0.75; - - // opacity: 1 !important; - // stroke-width: 3px; - // stroke-opacity: 0.6; + opacity: 1 !important; + fill-opacity: 1 !important; } } @@ -243,7 +218,7 @@ .bb-target.bb-defocused, .bb-circles.bb-defocused { - opacity: 0.3 !important; + opacity: 0.2 !important; } .bb-target.bb-defocused .text-overlapping, .bb-circles.bb-defocused .text-overlapping { @@ -330,6 +305,7 @@ &:not(.bb-tooltip-name-additions, .bb-tooltip-name-deletions) { display: flex; flex-direction: column-reverse; + margin-bottom: -1px; td { &.name { diff --git a/src/webviews/apps/premium/timeline/chart.ts b/src/webviews/apps/premium/timeline/chart.ts index ad8f6e4..ee06719 100644 --- a/src/webviews/apps/premium/timeline/chart.ts +++ b/src/webviews/apps/premium/timeline/chart.ts @@ -1,10 +1,12 @@ 'use strict'; /*global*/ -import { bar, bb, bubble, Chart, ChartOptions, DataItem, selection, zoom } from 'billboard.js'; +import { bar, bb, bubble, Chart, ChartOptions, ChartTypes, DataItem, zoom } from 'billboard.js'; // import BubbleCompare from 'billboard.js/dist/plugin/billboardjs-plugin-bubblecompare'; +// import { scaleSqrt } from 'd3-scale'; import { Commit, State } from '../../../../premium/webviews/timeline/protocol'; import { formatDate, fromNow } from '../../shared/date'; import { Emitter, Event } from '../../shared/events'; +import { throttle } from '../../shared/utils'; export interface DataPointClickEvent { data: { @@ -19,27 +21,244 @@ export class TimelineChart { return this._onDidClickDataPoint.event; } - private readonly _chart: Chart; - private _commitsByTimestamp = new Map(); - private _authorsByIndex = new Map(); - private _indexByAuthors = new Map(); + private readonly $container: HTMLElement; + private _chart: Chart | undefined; + private _chartDimensions: { height: number; width: number }; + private readonly _resizeObserver: ResizeObserver; + private readonly _selector: string; + + private readonly _commitsByTimestamp = new Map(); + private readonly _authorsByIndex = new Map(); + private readonly _indexByAuthors = new Map(); private _dateFormat: string = undefined!; constructor(selector: string) { + this._selector = selector; + + const fn = throttle(() => { + const dimensions = this._chartDimensions; + this._chart?.resize({ + width: dimensions.width, + height: dimensions.height - 10, + }); + }, 100); + + this._resizeObserver = new ResizeObserver(entries => { + const size = entries[0].borderBoxSize[0]; + const dimensions = { + width: Math.floor(size.inlineSize), + height: Math.floor(size.blockSize), + }; + + if ( + this._chartDimensions.height === dimensions.height && + this._chartDimensions.width === dimensions.width + ) { + return; + } + + this._chartDimensions = dimensions; + fn(); + }); + + this.$container = document.querySelector(selector)!.parentElement!; + const rect = this.$container.getBoundingClientRect(); + this._chartDimensions = { height: Math.floor(rect.height), width: Math.floor(rect.width) }; + + this._resizeObserver.observe(this.$container); + } + + reset() { + this._chart?.unselect(); + this._chart?.unzoom(); + } + + updateChart(state: State) { + this._dateFormat = state.dateFormat; + + this._commitsByTimestamp.clear(); + this._authorsByIndex.clear(); + this._indexByAuthors.clear(); + + if (state?.dataset == null || state.dataset.length === 0) { + this._chart?.destroy(); + this._chart = undefined; + + const $overlay = document.getElementById('chart-empty-overlay') as HTMLDivElement; + $overlay?.classList.toggle('hidden', false); + + const $emptyMessage = $overlay.querySelector('[data-bind="empty"]') as HTMLHeadingElement; + $emptyMessage.textContent = state.title; + + return; + } + + const $overlay = document.getElementById('chart-empty-overlay') as HTMLDivElement; + $overlay?.classList.toggle('hidden', true); + + const xs: { [key: string]: string } = {}; + const colors: { [key: string]: string } = {}; + const names: { [key: string]: string } = {}; + const axes: { [key: string]: string } = {}; + const types: { [key: string]: ChartTypes } = {}; + const groups: string[][] = []; + const series: { [key: string]: any } = {}; + const group = []; + + let index = 0; + + let commit: Commit; + let author: string; + let date: string; + let additions: number; + let deletions: number; + + // // Get the min and max additions and deletions from the dataset + // let minChanges = Infinity; + // let maxChanges = -Infinity; + + // for (const commit of state.dataset) { + // const changes = commit.additions + commit.deletions; + // if (changes < minChanges) { + // minChanges = changes; + // } + // if (changes > maxChanges) { + // maxChanges = changes; + // } + // } + + // const bubbleScale = scaleSqrt([minChanges, maxChanges], [6, 100]); + + for (commit of state.dataset) { + ({ author, date, additions, deletions } = commit); + + if (!this._indexByAuthors.has(author)) { + this._indexByAuthors.set(author, index); + this._authorsByIndex.set(index, author); + index--; + } + + const x = 'time'; + if (series[x] == null) { + series[x] = []; + + series['additions'] = []; + series['deletions'] = []; + + xs['additions'] = x; + xs['deletions'] = x; + + axes['additions'] = 'y2'; + axes['deletions'] = 'y2'; + + names['additions'] = 'Additions'; + names['deletions'] = 'Deletions'; + + colors['additions'] = 'rgba(73, 190, 71, 1)'; + colors['deletions'] = 'rgba(195, 32, 45, 1)'; + + types['additions'] = bar(); + types['deletions'] = bar(); + + group.push(x); + groups.push(['additions', 'deletions']); + } + + const authorX = `${x}.${author}`; + if (series[authorX] == null) { + series[authorX] = []; + series[author] = []; + + xs[author] = authorX; + + axes[author] = 'y'; + + names[author] = author; + + types[author] = bubble(); + + group.push(author, authorX); + } + + series[x].push(date); + series['additions'].push(additions); + series['deletions'].push(deletions); + + series[authorX].push(date); + + const z = additions + deletions; //bubbleScale(additions + deletions); + series[author].push({ + y: this._indexByAuthors.get(author), + z: z, + }); + + this._commitsByTimestamp.set(date, commit); + } + + groups.push(group); + + const columns = Object.entries(series).map(([key, value]) => [key, ...value]); + + if (this._chart == null) { + const options = this.getChartOptions(); + + if (options.axis == null) { + options.axis = { y: { tick: {} } }; + } + if (options.axis.y == null) { + options.axis.y = { tick: {} }; + } + if (options.axis.y.tick == null) { + options.axis.y.tick = {}; + } + + options.axis.y.min = index - 2; + options.axis.y.tick.values = [...this._authorsByIndex.keys()]; + + options.data = { + ...options.data, + axes: axes, + colors: colors, + columns: columns, + groups: groups, + names: names, + types: types, + xs: xs, + }; + + this._chart = bb.generate(options); + } else { + this._chart.config('axis.y.tick.values', [...this._authorsByIndex.keys()], false); + this._chart.config('axis.y.min', index - 2, false); + this._chart.groups(groups); + + this._chart.load({ + axes: axes, + colors: colors, + columns: columns, + names: names, + types: types, + xs: xs, + unload: true, + }); + } + } + + private getChartOptions() { const config: ChartOptions = { - bindto: selector, + bindto: this._selector, data: { columns: [], types: { time: bubble(), additions: bar(), deletions: bar() }, xFormat: '%Y-%m-%dT%H:%M:%S.%LZ', xLocaltime: false, - selection: { - enabled: selection(), - draggable: false, - grouped: false, - multiple: false, - }, + // selection: { + // enabled: selection(), + // draggable: false, + // grouped: false, + // multiple: false, + // }, onclick: this.onDataPointClicked.bind(this), }, axis: { @@ -61,7 +280,7 @@ export class TimelineChart { y: { max: 0, padding: { - top: 50, + top: 75, bottom: 100, }, show: true, @@ -72,16 +291,16 @@ export class TimelineChart { }, y2: { label: { - text: 'Number of Lines Changed', + text: 'Lines changed', position: 'outer-middle', }, // min: 0, show: true, - tick: { - outer: true, - // culling: true, - // stepSize: 1, - }, + // tick: { + // outer: true, + // // culling: true, + // // stepSize: 1, + // }, }, }, bar: { @@ -90,7 +309,8 @@ export class TimelineChart { padding: 2, }, bubble: { - maxR: 50, + maxR: 100, + zerobased: true, }, grid: { focus: { @@ -98,7 +318,7 @@ export class TimelineChart { show: true, y: true, }, - front: true, + front: false, lines: { front: false, }, @@ -113,20 +333,12 @@ export class TimelineChart { show: true, padding: 10, }, - // point: { - // r: 6, - // focus: { - // expand: { - // enabled: true, - // r: 9, - // }, - // }, - // select: { - // r: 12, - // }, - // }, resize: { - auto: true, + auto: false, + }, + size: { + height: this._chartDimensions.height - 10, + width: this._chartDimensions.width, }, tooltip: { grouped: true, @@ -160,148 +372,20 @@ export class TimelineChart { }, // plugins: [ // new BubbleCompare({ - // minR: 3, - // maxR: 30, + // minR: 6, + // maxR: 100, // expandScale: 1.2, // }), // ], }; - this._chart = bb.generate(config); - } - - private onDataPointClicked(d: DataItem, _element: SVGElement) { - const commit = this._commitsByTimestamp.get(new Date(d.x).toISOString()); - if (commit == null) return; - - const selected = this._chart.selected(d.id) as unknown as DataItem[]; - this._onDidClickDataPoint.fire({ - data: { - id: commit.commit, - selected: selected?.[0]?.id === d.id, - }, - }); - } - - reset() { - this._chart.unselect(); - this._chart.unzoom(); - } - - updateChart(state: State) { - this._dateFormat = state.dateFormat; - - this._commitsByTimestamp.clear(); - this._authorsByIndex.clear(); - this._indexByAuthors.clear(); - - if (state?.dataset == null) { - this._chart.unload(); - - return; - } - - const xs: { [key: string]: string } = {}; - const colors: { [key: string]: string } = {}; - const names: { [key: string]: string } = {}; - const axes: { [key: string]: string } = {}; - const types: { [key: string]: string } = {}; - const groups: string[][] = []; - const series: { [key: string]: any } = {}; - const group = []; - - let index = 0; - - let commit: Commit; - let author: string; - let date: string; - let additions: number; - let deletions: number; - - for (commit of state.dataset) { - ({ author, date, additions, deletions } = commit); - - if (!this._indexByAuthors.has(author)) { - this._indexByAuthors.set(author, index); - this._authorsByIndex.set(index, author); - index--; - } - - const x = 'time'; - if (series[x] == null) { - series[x] = []; - - series['additions'] = []; - series['deletions'] = []; - - xs['additions'] = x; - xs['deletions'] = x; - - axes['additions'] = 'y2'; - axes['deletions'] = 'y2'; - - names['additions'] = 'Additions'; - names['deletions'] = 'Deletions'; - - colors['additions'] = 'rgba(73, 190, 71, 1)'; - colors['deletions'] = 'rgba(195, 32, 45, 1)'; - - types['additions'] = bar(); - types['deletions'] = bar(); - - group.push(x); - groups.push(['additions', 'deletions']); - } - - const authorX = `${x}.${author}`; - if (series[authorX] == null) { - series[authorX] = []; - series[author] = []; - - xs[author] = authorX; - - axes[author] = 'y'; - - names[author] = author; - - types[author] = bubble(); - - group.push(author, authorX); - } - - series[x].push(date); - series['additions'].push(additions); - series['deletions'].push(deletions); - - series[authorX].push(date); - series[author].push({ /*x: date,*/ y: this._indexByAuthors.get(author), z: additions + deletions }); - - this._commitsByTimestamp.set(date, commit); - } - - this._chart.config('axis.y.tick.values', [...this._authorsByIndex.keys()], false); - this._chart.config('axis.y.min', index - 2, false); - - groups.push(group); - this._chart.groups(groups); - - const columns = Object.entries(series).map(([key, value]) => [key, ...value]); - - this._chart.load({ - columns: columns, - xs: xs, - axes: axes, - names: names, - colors: colors, - types: types, - unload: true, - }); + return config; } private getTooltipName(name: string, ratio: number, id: string, index: number) { if (id === 'additions' || /*id === 'changes' ||*/ id === 'deletions') return name; - const date = new Date(this._chart.data(id)[0].values[index].x); + const date = new Date(this._chart!.data(id)[0].values[index].x); const commit = this._commitsByTimestamp.get(date.toISOString()); return commit?.commit.slice(0, 8) ?? '00000000'; } @@ -320,10 +404,23 @@ export class TimelineChart { return value === 0 ? undefined! : value; } - const date = new Date(this._chart.data(id)[0].values[index].x); + const date = new Date(this._chart!.data(id)[0].values[index].x); const commit = this._commitsByTimestamp.get(date.toISOString()); return commit?.message ?? '???'; } + + private onDataPointClicked(d: DataItem, _element: SVGElement) { + const commit = this._commitsByTimestamp.get(new Date(d.x).toISOString()); + if (commit == null) return; + + // const selected = this._chart!.selected(d.id) as unknown as DataItem[]; + this._onDidClickDataPoint.fire({ + data: { + id: commit.commit, + selected: true, //selected?.[0]?.id === d.id, + }, + }); + } } function capitalize(s: string): string { diff --git a/src/webviews/apps/premium/timeline/timeline.html b/src/webviews/apps/premium/timeline/timeline.html index 37b74b2..cd03579 100644 --- a/src/webviews/apps/premium/timeline/timeline.html +++ b/src/webviews/apps/premium/timeline/timeline.html @@ -2,33 +2,46 @@ +
-
-