mirror of
https://github.com/microsoft/vscode.git
synced 2025-12-20 02:08:47 +00:00
SCM - consistently render date on the right in the repositories view (#280870)
SCM - scaffold artifact timestamp
This commit is contained in:
@@ -4,16 +4,13 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { LogOutputChannel, SourceControlArtifactProvider, SourceControlArtifactGroup, SourceControlArtifact, Event, EventEmitter, ThemeIcon, l10n, workspace, Uri, Disposable } from 'vscode';
|
import { LogOutputChannel, SourceControlArtifactProvider, SourceControlArtifactGroup, SourceControlArtifact, Event, EventEmitter, ThemeIcon, l10n, workspace, Uri, Disposable } from 'vscode';
|
||||||
import { dispose, filterEvent, fromNow, getStashDescription, IDisposable } from './util';
|
import { dispose, filterEvent, IDisposable } from './util';
|
||||||
import { Repository } from './repository';
|
import { Repository } from './repository';
|
||||||
import { Ref, RefType } from './api/git';
|
import { Ref, RefType } from './api/git';
|
||||||
import { OperationKind } from './operation';
|
import { OperationKind } from './operation';
|
||||||
|
|
||||||
function getArtifactDescription(ref: Ref, shortCommitLength: number): string {
|
function getArtifactDescription(ref: Ref, shortCommitLength: number): string {
|
||||||
const segments: string[] = [];
|
const segments: string[] = [];
|
||||||
if (ref.commitDetails?.commitDate) {
|
|
||||||
segments.push(fromNow(ref.commitDetails.commitDate));
|
|
||||||
}
|
|
||||||
if (ref.commit) {
|
if (ref.commit) {
|
||||||
segments.push(ref.commit.substring(0, shortCommitLength));
|
segments.push(ref.commit.substring(0, shortCommitLength));
|
||||||
}
|
}
|
||||||
@@ -130,7 +127,8 @@ export class GitArtifactProvider implements SourceControlArtifactProvider, IDisp
|
|||||||
description: getArtifactDescription(r, shortCommitLength),
|
description: getArtifactDescription(r, shortCommitLength),
|
||||||
icon: this.repository.HEAD?.type === RefType.Head && r.name === this.repository.HEAD?.name
|
icon: this.repository.HEAD?.type === RefType.Head && r.name === this.repository.HEAD?.name
|
||||||
? new ThemeIcon('target')
|
? new ThemeIcon('target')
|
||||||
: new ThemeIcon('git-branch')
|
: new ThemeIcon('git-branch'),
|
||||||
|
timestamp: r.commitDetails?.commitDate?.getTime()
|
||||||
}));
|
}));
|
||||||
} else if (group === 'tags') {
|
} else if (group === 'tags') {
|
||||||
const refs = await this.repository
|
const refs = await this.repository
|
||||||
@@ -142,7 +140,8 @@ export class GitArtifactProvider implements SourceControlArtifactProvider, IDisp
|
|||||||
description: getArtifactDescription(r, shortCommitLength),
|
description: getArtifactDescription(r, shortCommitLength),
|
||||||
icon: this.repository.HEAD?.type === RefType.Tag && r.name === this.repository.HEAD?.name
|
icon: this.repository.HEAD?.type === RefType.Tag && r.name === this.repository.HEAD?.name
|
||||||
? new ThemeIcon('target')
|
? new ThemeIcon('target')
|
||||||
: new ThemeIcon('tag')
|
: new ThemeIcon('tag'),
|
||||||
|
timestamp: r.commitDetails?.commitDate?.getTime()
|
||||||
}));
|
}));
|
||||||
} else if (group === 'stashes') {
|
} else if (group === 'stashes') {
|
||||||
const stashes = await this.repository.getStashes();
|
const stashes = await this.repository.getStashes();
|
||||||
@@ -150,8 +149,9 @@ export class GitArtifactProvider implements SourceControlArtifactProvider, IDisp
|
|||||||
return stashes.map(s => ({
|
return stashes.map(s => ({
|
||||||
id: `stash@{${s.index}}`,
|
id: `stash@{${s.index}}`,
|
||||||
name: s.description,
|
name: s.description,
|
||||||
description: getStashDescription(s),
|
description: s.branchName,
|
||||||
icon: new ThemeIcon('git-stash')
|
icon: new ThemeIcon('git-stash'),
|
||||||
|
timestamp: s.commitDate?.getTime()
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -1765,6 +1765,7 @@ export interface SCMArtifactDto {
|
|||||||
readonly name: string;
|
readonly name: string;
|
||||||
readonly description?: string;
|
readonly description?: string;
|
||||||
readonly icon?: UriComponents | { light: UriComponents; dark: UriComponents } | ThemeIcon;
|
readonly icon?: UriComponents | { light: UriComponents; dark: UriComponents } | ThemeIcon;
|
||||||
|
readonly timestamp?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MainThreadSCMShape extends IDisposable {
|
export interface MainThreadSCMShape extends IDisposable {
|
||||||
|
|||||||
@@ -585,6 +585,33 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.scm-repositories-view .scm-artifact .timestamp-container {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: 2px;
|
||||||
|
margin-right: 4px;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scm-repositories-view .scm-artifact .timestamp-container.duplicate {
|
||||||
|
height: 22px;
|
||||||
|
min-width: 6px;
|
||||||
|
border-left: 1px solid currentColor;
|
||||||
|
opacity: 0.25;
|
||||||
|
|
||||||
|
.timestamp {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scm-repositories-view .monaco-list .monaco-list-row:hover .scm-artifact .timestamp-container.duplicate {
|
||||||
|
border-left: 0;
|
||||||
|
opacity: 0.5;
|
||||||
|
|
||||||
|
.timestamp {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* History item hover */
|
/* History item hover */
|
||||||
|
|
||||||
.monaco-hover.history-item-hover .history-item-hover-container > .rendered-markdown:first-child > p {
|
.monaco-hover.history-item-hover .history-item-hover-container > .rendered-markdown:first-child > p {
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import { IAsyncDataTreeViewState, ITreeCompressionDelegate } from '../../../../b
|
|||||||
import { Codicon } from '../../../../base/common/codicons.js';
|
import { Codicon } from '../../../../base/common/codicons.js';
|
||||||
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
|
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
|
||||||
import { IActionViewItemProvider } from '../../../../base/browser/ui/actionbar/actionbar.js';
|
import { IActionViewItemProvider } from '../../../../base/browser/ui/actionbar/actionbar.js';
|
||||||
|
import { fromNow } from '../../../../base/common/date.js';
|
||||||
|
|
||||||
type TreeElement = ISCMRepository | SCMArtifactGroupTreeElement | SCMArtifactTreeElement | IResourceNode<SCMArtifactTreeElement, SCMArtifactGroupTreeElement>;
|
type TreeElement = ISCMRepository | SCMArtifactGroupTreeElement | SCMArtifactTreeElement | IResourceNode<SCMArtifactTreeElement, SCMArtifactGroupTreeElement>;
|
||||||
|
|
||||||
@@ -136,6 +137,8 @@ class ArtifactGroupRenderer implements ICompressibleTreeRenderer<SCMArtifactGrou
|
|||||||
interface ArtifactTemplate {
|
interface ArtifactTemplate {
|
||||||
readonly icon: HTMLElement;
|
readonly icon: HTMLElement;
|
||||||
readonly label: IconLabel;
|
readonly label: IconLabel;
|
||||||
|
readonly timestampContainer: HTMLElement;
|
||||||
|
readonly timestamp: HTMLElement;
|
||||||
readonly actionBar: WorkbenchToolBar;
|
readonly actionBar: WorkbenchToolBar;
|
||||||
readonly elementDisposables: DisposableStore;
|
readonly elementDisposables: DisposableStore;
|
||||||
readonly templateDisposable: IDisposable;
|
readonly templateDisposable: IDisposable;
|
||||||
@@ -162,10 +165,13 @@ class ArtifactRenderer implements ICompressibleTreeRenderer<SCMArtifactTreeEleme
|
|||||||
const icon = append(element, $('.icon'));
|
const icon = append(element, $('.icon'));
|
||||||
const label = new IconLabel(element, { supportIcons: false });
|
const label = new IconLabel(element, { supportIcons: false });
|
||||||
|
|
||||||
|
const timestampContainer = append(element, $('.timestamp-container'));
|
||||||
|
const timestamp = append(timestampContainer, $('.timestamp'));
|
||||||
|
|
||||||
const actionsContainer = append(element, $('.actions'));
|
const actionsContainer = append(element, $('.actions'));
|
||||||
const actionBar = new WorkbenchToolBar(actionsContainer, { actionViewItemProvider: this.actionViewItemProvider }, this._menuService, this._contextKeyService, this._contextMenuService, this._keybindingService, this._commandService, this._telemetryService);
|
const actionBar = new WorkbenchToolBar(actionsContainer, { actionViewItemProvider: this.actionViewItemProvider }, this._menuService, this._contextKeyService, this._contextMenuService, this._keybindingService, this._commandService, this._telemetryService);
|
||||||
|
|
||||||
return { icon, label, actionBar, elementDisposables: new DisposableStore(), templateDisposable: combinedDisposable(label, actionBar) };
|
return { icon, label, timestampContainer, timestamp, actionBar, elementDisposables: new DisposableStore(), templateDisposable: combinedDisposable(label, actionBar) };
|
||||||
}
|
}
|
||||||
|
|
||||||
renderElement(nodeOrElement: ITreeNode<SCMArtifactTreeElement | IResourceNode<SCMArtifactTreeElement, SCMArtifactGroupTreeElement>, FuzzyScore>, index: number, templateData: ArtifactTemplate): void {
|
renderElement(nodeOrElement: ITreeNode<SCMArtifactTreeElement | IResourceNode<SCMArtifactTreeElement, SCMArtifactGroupTreeElement>, FuzzyScore>, index: number, templateData: ArtifactTemplate): void {
|
||||||
@@ -186,10 +192,18 @@ class ArtifactRenderer implements ICompressibleTreeRenderer<SCMArtifactTreeEleme
|
|||||||
? artifact.name.split('/').pop() ?? artifact.name
|
? artifact.name.split('/').pop() ?? artifact.name
|
||||||
: artifact.name;
|
: artifact.name;
|
||||||
templateData.label.setLabel(artifactLabel, artifact.description);
|
templateData.label.setLabel(artifactLabel, artifact.description);
|
||||||
|
|
||||||
|
templateData.timestamp.textContent = artifact.timestamp ? fromNow(artifact.timestamp) : '';
|
||||||
|
templateData.timestampContainer.classList.toggle('duplicate', artifactOrFolder.hideTimestamp);
|
||||||
|
templateData.timestampContainer.style.display = '';
|
||||||
} else if (isSCMArtifactNode(artifactOrFolder)) {
|
} else if (isSCMArtifactNode(artifactOrFolder)) {
|
||||||
// Folder
|
// Folder
|
||||||
templateData.icon.className = `icon ${ThemeIcon.asClassName(Codicon.folder)}`;
|
templateData.icon.className = `icon ${ThemeIcon.asClassName(Codicon.folder)}`;
|
||||||
templateData.label.setLabel(basename(artifactOrFolder.uri));
|
templateData.label.setLabel(basename(artifactOrFolder.uri));
|
||||||
|
|
||||||
|
templateData.timestamp.textContent = '';
|
||||||
|
templateData.timestampContainer.classList.remove('duplicate');
|
||||||
|
templateData.timestampContainer.style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
@@ -211,10 +225,18 @@ class ArtifactRenderer implements ICompressibleTreeRenderer<SCMArtifactTreeEleme
|
|||||||
: '';
|
: '';
|
||||||
|
|
||||||
templateData.label.setLabel(artifact.name, artifact.description);
|
templateData.label.setLabel(artifact.name, artifact.description);
|
||||||
|
|
||||||
|
templateData.timestamp.textContent = artifact.timestamp ? fromNow(artifact.timestamp) : '';
|
||||||
|
templateData.timestampContainer.classList.toggle('duplicate', artifactOrFolder.hideTimestamp);
|
||||||
|
templateData.timestampContainer.style.display = '';
|
||||||
} else if (isSCMArtifactNode(artifactOrFolder)) {
|
} else if (isSCMArtifactNode(artifactOrFolder)) {
|
||||||
// Folder
|
// Folder
|
||||||
templateData.icon.className = `icon ${ThemeIcon.asClassName(Codicon.folder)}`;
|
templateData.icon.className = `icon ${ThemeIcon.asClassName(Codicon.folder)}`;
|
||||||
templateData.label.setLabel(artifactOrFolder.uri.fsPath.substring(1));
|
templateData.label.setLabel(artifactOrFolder.uri.fsPath.substring(1));
|
||||||
|
|
||||||
|
templateData.timestamp.textContent = '';
|
||||||
|
templateData.timestampContainer.classList.remove('duplicate');
|
||||||
|
templateData.timestampContainer.style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
@@ -300,13 +322,29 @@ class RepositoryTreeDataSource extends Disposable implements IAsyncDataSource<IS
|
|||||||
if (inputOrElement.artifactGroup.supportsFolders) {
|
if (inputOrElement.artifactGroup.supportsFolders) {
|
||||||
// Resource tree for artifacts
|
// Resource tree for artifacts
|
||||||
const artifactsTree = new ResourceTree<SCMArtifactTreeElement, SCMArtifactGroupTreeElement>(inputOrElement);
|
const artifactsTree = new ResourceTree<SCMArtifactTreeElement, SCMArtifactGroupTreeElement>(inputOrElement);
|
||||||
for (const artifact of artifacts) {
|
for (let index = 0; index < artifacts.length; index++) {
|
||||||
artifactsTree.add(URI.from({
|
const artifact = artifacts[index];
|
||||||
scheme: 'scm-artifact', path: artifact.name
|
const artifactUri = URI.from({ scheme: 'scm-artifact', path: artifact.name });
|
||||||
}), {
|
const artifactBasename = artifact.id.lastIndexOf('/') > 0
|
||||||
|
? artifact.id.substring(0, artifact.id.lastIndexOf('/'))
|
||||||
|
: artifact.id;
|
||||||
|
|
||||||
|
const prevArtifact = index > 0 ? artifacts[index - 1] : undefined;
|
||||||
|
const prevArtifactBasename = prevArtifact && prevArtifact.id.lastIndexOf('/') > 0
|
||||||
|
? prevArtifact.id.substring(0, prevArtifact.id.lastIndexOf('/'))
|
||||||
|
: prevArtifact?.id;
|
||||||
|
|
||||||
|
const hideTimestamp = index > 0 &&
|
||||||
|
artifact.timestamp !== undefined &&
|
||||||
|
prevArtifact?.timestamp !== undefined &&
|
||||||
|
artifactBasename === prevArtifactBasename &&
|
||||||
|
fromNow(prevArtifact.timestamp) === fromNow(artifact.timestamp);
|
||||||
|
|
||||||
|
artifactsTree.add(artifactUri, {
|
||||||
repository,
|
repository,
|
||||||
group: inputOrElement.artifactGroup,
|
group: inputOrElement.artifactGroup,
|
||||||
artifact,
|
artifact,
|
||||||
|
hideTimestamp,
|
||||||
type: 'artifact'
|
type: 'artifact'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -315,14 +353,16 @@ class RepositoryTreeDataSource extends Disposable implements IAsyncDataSource<IS
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Flat list of artifacts
|
// Flat list of artifacts
|
||||||
return artifacts.map(artifact => (
|
return artifacts.map((artifact, index, artifacts) => ({
|
||||||
{
|
|
||||||
repository,
|
repository,
|
||||||
group: inputOrElement.artifactGroup,
|
group: inputOrElement.artifactGroup,
|
||||||
artifact,
|
artifact,
|
||||||
|
hideTimestamp: index > 0 &&
|
||||||
|
artifact.timestamp !== undefined &&
|
||||||
|
artifacts[index - 1].timestamp !== undefined &&
|
||||||
|
fromNow(artifacts[index - 1].timestamp!) === fromNow(artifact.timestamp),
|
||||||
type: 'artifact'
|
type: 'artifact'
|
||||||
} satisfies SCMArtifactTreeElement
|
} satisfies SCMArtifactTreeElement));
|
||||||
));
|
|
||||||
} else if (isSCMArtifactNode(inputOrElement)) {
|
} else if (isSCMArtifactNode(inputOrElement)) {
|
||||||
return Iterable.map(inputOrElement.children,
|
return Iterable.map(inputOrElement.children,
|
||||||
node => node.element && node.childrenCount === 0 ? node.element : node);
|
node => node.element && node.childrenCount === 0 ? node.element : node);
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export interface ISCMArtifact {
|
|||||||
readonly name: string;
|
readonly name: string;
|
||||||
readonly description?: string;
|
readonly description?: string;
|
||||||
readonly icon?: URI | { light: URI; dark: URI } | ThemeIcon;
|
readonly icon?: URI | { light: URI; dark: URI } | ThemeIcon;
|
||||||
|
readonly timestamp?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SCMArtifactGroupTreeElement {
|
export interface SCMArtifactGroupTreeElement {
|
||||||
@@ -38,5 +39,6 @@ export interface SCMArtifactTreeElement {
|
|||||||
readonly repository: ISCMRepository;
|
readonly repository: ISCMRepository;
|
||||||
readonly group: ISCMArtifactGroup;
|
readonly group: ISCMArtifactGroup;
|
||||||
readonly artifact: ISCMArtifact;
|
readonly artifact: ISCMArtifact;
|
||||||
|
readonly hideTimestamp: boolean;
|
||||||
readonly type: 'artifact';
|
readonly type: 'artifact';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,5 +29,6 @@ declare module 'vscode' {
|
|||||||
readonly name: string;
|
readonly name: string;
|
||||||
readonly description?: string;
|
readonly description?: string;
|
||||||
readonly icon?: IconPath;
|
readonly icon?: IconPath;
|
||||||
|
readonly timestamp?: number;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user