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 { dispose, filterEvent, fromNow, getStashDescription, IDisposable } from './util';
|
||||
import { dispose, filterEvent, IDisposable } from './util';
|
||||
import { Repository } from './repository';
|
||||
import { Ref, RefType } from './api/git';
|
||||
import { OperationKind } from './operation';
|
||||
|
||||
function getArtifactDescription(ref: Ref, shortCommitLength: number): string {
|
||||
const segments: string[] = [];
|
||||
if (ref.commitDetails?.commitDate) {
|
||||
segments.push(fromNow(ref.commitDetails.commitDate));
|
||||
}
|
||||
if (ref.commit) {
|
||||
segments.push(ref.commit.substring(0, shortCommitLength));
|
||||
}
|
||||
@@ -130,7 +127,8 @@ export class GitArtifactProvider implements SourceControlArtifactProvider, IDisp
|
||||
description: getArtifactDescription(r, shortCommitLength),
|
||||
icon: this.repository.HEAD?.type === RefType.Head && r.name === this.repository.HEAD?.name
|
||||
? new ThemeIcon('target')
|
||||
: new ThemeIcon('git-branch')
|
||||
: new ThemeIcon('git-branch'),
|
||||
timestamp: r.commitDetails?.commitDate?.getTime()
|
||||
}));
|
||||
} else if (group === 'tags') {
|
||||
const refs = await this.repository
|
||||
@@ -142,7 +140,8 @@ export class GitArtifactProvider implements SourceControlArtifactProvider, IDisp
|
||||
description: getArtifactDescription(r, shortCommitLength),
|
||||
icon: this.repository.HEAD?.type === RefType.Tag && r.name === this.repository.HEAD?.name
|
||||
? new ThemeIcon('target')
|
||||
: new ThemeIcon('tag')
|
||||
: new ThemeIcon('tag'),
|
||||
timestamp: r.commitDetails?.commitDate?.getTime()
|
||||
}));
|
||||
} else if (group === 'stashes') {
|
||||
const stashes = await this.repository.getStashes();
|
||||
@@ -150,8 +149,9 @@ export class GitArtifactProvider implements SourceControlArtifactProvider, IDisp
|
||||
return stashes.map(s => ({
|
||||
id: `stash@{${s.index}}`,
|
||||
name: s.description,
|
||||
description: getStashDescription(s),
|
||||
icon: new ThemeIcon('git-stash')
|
||||
description: s.branchName,
|
||||
icon: new ThemeIcon('git-stash'),
|
||||
timestamp: s.commitDate?.getTime()
|
||||
}));
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
@@ -1765,6 +1765,7 @@ export interface SCMArtifactDto {
|
||||
readonly name: string;
|
||||
readonly description?: string;
|
||||
readonly icon?: UriComponents | { light: UriComponents; dark: UriComponents } | ThemeIcon;
|
||||
readonly timestamp?: number;
|
||||
}
|
||||
|
||||
export interface MainThreadSCMShape extends IDisposable {
|
||||
|
||||
@@ -585,6 +585,33 @@
|
||||
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 */
|
||||
|
||||
.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 { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.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>;
|
||||
|
||||
@@ -136,6 +137,8 @@ class ArtifactGroupRenderer implements ICompressibleTreeRenderer<SCMArtifactGrou
|
||||
interface ArtifactTemplate {
|
||||
readonly icon: HTMLElement;
|
||||
readonly label: IconLabel;
|
||||
readonly timestampContainer: HTMLElement;
|
||||
readonly timestamp: HTMLElement;
|
||||
readonly actionBar: WorkbenchToolBar;
|
||||
readonly elementDisposables: DisposableStore;
|
||||
readonly templateDisposable: IDisposable;
|
||||
@@ -162,10 +165,13 @@ class ArtifactRenderer implements ICompressibleTreeRenderer<SCMArtifactTreeEleme
|
||||
const icon = append(element, $('.icon'));
|
||||
const label = new IconLabel(element, { supportIcons: false });
|
||||
|
||||
const timestampContainer = append(element, $('.timestamp-container'));
|
||||
const timestamp = append(timestampContainer, $('.timestamp'));
|
||||
|
||||
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);
|
||||
|
||||
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 {
|
||||
@@ -186,10 +192,18 @@ class ArtifactRenderer implements ICompressibleTreeRenderer<SCMArtifactTreeEleme
|
||||
? artifact.name.split('/').pop() ?? artifact.name
|
||||
: artifact.name;
|
||||
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)) {
|
||||
// Folder
|
||||
templateData.icon.className = `icon ${ThemeIcon.asClassName(Codicon.folder)}`;
|
||||
templateData.label.setLabel(basename(artifactOrFolder.uri));
|
||||
|
||||
templateData.timestamp.textContent = '';
|
||||
templateData.timestampContainer.classList.remove('duplicate');
|
||||
templateData.timestampContainer.style.display = 'none';
|
||||
}
|
||||
|
||||
// Actions
|
||||
@@ -211,10 +225,18 @@ class ArtifactRenderer implements ICompressibleTreeRenderer<SCMArtifactTreeEleme
|
||||
: '';
|
||||
|
||||
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)) {
|
||||
// Folder
|
||||
templateData.icon.className = `icon ${ThemeIcon.asClassName(Codicon.folder)}`;
|
||||
templateData.label.setLabel(artifactOrFolder.uri.fsPath.substring(1));
|
||||
|
||||
templateData.timestamp.textContent = '';
|
||||
templateData.timestampContainer.classList.remove('duplicate');
|
||||
templateData.timestampContainer.style.display = 'none';
|
||||
}
|
||||
|
||||
// Actions
|
||||
@@ -300,13 +322,29 @@ class RepositoryTreeDataSource extends Disposable implements IAsyncDataSource<IS
|
||||
if (inputOrElement.artifactGroup.supportsFolders) {
|
||||
// Resource tree for artifacts
|
||||
const artifactsTree = new ResourceTree<SCMArtifactTreeElement, SCMArtifactGroupTreeElement>(inputOrElement);
|
||||
for (const artifact of artifacts) {
|
||||
artifactsTree.add(URI.from({
|
||||
scheme: 'scm-artifact', path: artifact.name
|
||||
}), {
|
||||
for (let index = 0; index < artifacts.length; index++) {
|
||||
const artifact = artifacts[index];
|
||||
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,
|
||||
group: inputOrElement.artifactGroup,
|
||||
artifact,
|
||||
hideTimestamp,
|
||||
type: 'artifact'
|
||||
});
|
||||
}
|
||||
@@ -315,14 +353,16 @@ class RepositoryTreeDataSource extends Disposable implements IAsyncDataSource<IS
|
||||
}
|
||||
|
||||
// Flat list of artifacts
|
||||
return artifacts.map(artifact => (
|
||||
{
|
||||
return artifacts.map((artifact, index, artifacts) => ({
|
||||
repository,
|
||||
group: inputOrElement.artifactGroup,
|
||||
artifact,
|
||||
hideTimestamp: index > 0 &&
|
||||
artifact.timestamp !== undefined &&
|
||||
artifacts[index - 1].timestamp !== undefined &&
|
||||
fromNow(artifacts[index - 1].timestamp!) === fromNow(artifact.timestamp),
|
||||
type: 'artifact'
|
||||
} satisfies SCMArtifactTreeElement
|
||||
));
|
||||
} satisfies SCMArtifactTreeElement));
|
||||
} else if (isSCMArtifactNode(inputOrElement)) {
|
||||
return Iterable.map(inputOrElement.children,
|
||||
node => node.element && node.childrenCount === 0 ? node.element : node);
|
||||
|
||||
@@ -26,6 +26,7 @@ export interface ISCMArtifact {
|
||||
readonly name: string;
|
||||
readonly description?: string;
|
||||
readonly icon?: URI | { light: URI; dark: URI } | ThemeIcon;
|
||||
readonly timestamp?: number;
|
||||
}
|
||||
|
||||
export interface SCMArtifactGroupTreeElement {
|
||||
@@ -38,5 +39,6 @@ export interface SCMArtifactTreeElement {
|
||||
readonly repository: ISCMRepository;
|
||||
readonly group: ISCMArtifactGroup;
|
||||
readonly artifact: ISCMArtifact;
|
||||
readonly hideTimestamp: boolean;
|
||||
readonly type: 'artifact';
|
||||
}
|
||||
|
||||
@@ -29,5 +29,6 @@ declare module 'vscode' {
|
||||
readonly name: string;
|
||||
readonly description?: string;
|
||||
readonly icon?: IconPath;
|
||||
readonly timestamp?: number;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user