@@ -46,6 +46,7 @@ import { IAsyncDataTreeViewState, ITreeCompressionDelegate } from '../../../../b
4646import { Codicon } from '../../../../base/common/codicons.js' ;
4747import { IStorageService , StorageScope , StorageTarget } from '../../../../platform/storage/common/storage.js' ;
4848import { IActionViewItemProvider } from '../../../../base/browser/ui/actionbar/actionbar.js' ;
49+ import { fromNow } from '../../../../base/common/date.js' ;
4950
5051type TreeElement = ISCMRepository | SCMArtifactGroupTreeElement | SCMArtifactTreeElement | IResourceNode < SCMArtifactTreeElement , SCMArtifactGroupTreeElement > ;
5152
@@ -136,6 +137,8 @@ class ArtifactGroupRenderer implements ICompressibleTreeRenderer<SCMArtifactGrou
136137interface ArtifactTemplate {
137138 readonly icon : HTMLElement ;
138139 readonly label : IconLabel ;
140+ readonly timestampContainer : HTMLElement ;
141+ readonly timestamp : HTMLElement ;
139142 readonly actionBar : WorkbenchToolBar ;
140143 readonly elementDisposables : DisposableStore ;
141144 readonly templateDisposable : IDisposable ;
@@ -162,10 +165,13 @@ class ArtifactRenderer implements ICompressibleTreeRenderer<SCMArtifactTreeEleme
162165 const icon = append ( element , $ ( '.icon' ) ) ;
163166 const label = new IconLabel ( element , { supportIcons : false } ) ;
164167
168+ const timestampContainer = append ( element , $ ( '.timestamp-container' ) ) ;
169+ const timestamp = append ( timestampContainer , $ ( '.timestamp' ) ) ;
170+
165171 const actionsContainer = append ( element , $ ( '.actions' ) ) ;
166172 const actionBar = new WorkbenchToolBar ( actionsContainer , { actionViewItemProvider : this . actionViewItemProvider } , this . _menuService , this . _contextKeyService , this . _contextMenuService , this . _keybindingService , this . _commandService , this . _telemetryService ) ;
167173
168- return { icon, label, actionBar, elementDisposables : new DisposableStore ( ) , templateDisposable : combinedDisposable ( label , actionBar ) } ;
174+ return { icon, label, timestampContainer , timestamp , actionBar, elementDisposables : new DisposableStore ( ) , templateDisposable : combinedDisposable ( label , actionBar ) } ;
169175 }
170176
171177 renderElement ( nodeOrElement : ITreeNode < SCMArtifactTreeElement | IResourceNode < SCMArtifactTreeElement , SCMArtifactGroupTreeElement > , FuzzyScore > , index : number , templateData : ArtifactTemplate ) : void {
@@ -186,10 +192,18 @@ class ArtifactRenderer implements ICompressibleTreeRenderer<SCMArtifactTreeEleme
186192 ? artifact . name . split ( '/' ) . pop ( ) ?? artifact . name
187193 : artifact . name ;
188194 templateData . label . setLabel ( artifactLabel , artifact . description ) ;
195+
196+ templateData . timestamp . textContent = artifact . timestamp ? fromNow ( artifact . timestamp ) : '' ;
197+ templateData . timestampContainer . classList . toggle ( 'duplicate' , artifactOrFolder . hideTimestamp ) ;
198+ templateData . timestampContainer . style . display = '' ;
189199 } else if ( isSCMArtifactNode ( artifactOrFolder ) ) {
190200 // Folder
191201 templateData . icon . className = `icon ${ ThemeIcon . asClassName ( Codicon . folder ) } ` ;
192202 templateData . label . setLabel ( basename ( artifactOrFolder . uri ) ) ;
203+
204+ templateData . timestamp . textContent = '' ;
205+ templateData . timestampContainer . classList . remove ( 'duplicate' ) ;
206+ templateData . timestampContainer . style . display = 'none' ;
193207 }
194208
195209 // Actions
@@ -211,10 +225,18 @@ class ArtifactRenderer implements ICompressibleTreeRenderer<SCMArtifactTreeEleme
211225 : '' ;
212226
213227 templateData . label . setLabel ( artifact . name , artifact . description ) ;
228+
229+ templateData . timestamp . textContent = artifact . timestamp ? fromNow ( artifact . timestamp ) : '' ;
230+ templateData . timestampContainer . classList . toggle ( 'duplicate' , artifactOrFolder . hideTimestamp ) ;
231+ templateData . timestampContainer . style . display = '' ;
214232 } else if ( isSCMArtifactNode ( artifactOrFolder ) ) {
215233 // Folder
216234 templateData . icon . className = `icon ${ ThemeIcon . asClassName ( Codicon . folder ) } ` ;
217235 templateData . label . setLabel ( artifactOrFolder . uri . fsPath . substring ( 1 ) ) ;
236+
237+ templateData . timestamp . textContent = '' ;
238+ templateData . timestampContainer . classList . remove ( 'duplicate' ) ;
239+ templateData . timestampContainer . style . display = 'none' ;
218240 }
219241
220242 // Actions
@@ -300,13 +322,29 @@ class RepositoryTreeDataSource extends Disposable implements IAsyncDataSource<IS
300322 if ( inputOrElement . artifactGroup . supportsFolders ) {
301323 // Resource tree for artifacts
302324 const artifactsTree = new ResourceTree < SCMArtifactTreeElement , SCMArtifactGroupTreeElement > ( inputOrElement ) ;
303- for ( const artifact of artifacts ) {
304- artifactsTree . add ( URI . from ( {
305- scheme : 'scm-artifact' , path : artifact . name
306- } ) , {
325+ for ( let index = 0 ; index < artifacts . length ; index ++ ) {
326+ const artifact = artifacts [ index ] ;
327+ const artifactUri = URI . from ( { scheme : 'scm-artifact' , path : artifact . name } ) ;
328+ const artifactBasename = artifact . id . lastIndexOf ( '/' ) > 0
329+ ? artifact . id . substring ( 0 , artifact . id . lastIndexOf ( '/' ) )
330+ : artifact . id ;
331+
332+ const prevArtifact = index > 0 ? artifacts [ index - 1 ] : undefined ;
333+ const prevArtifactBasename = prevArtifact && prevArtifact . id . lastIndexOf ( '/' ) > 0
334+ ? prevArtifact . id . substring ( 0 , prevArtifact . id . lastIndexOf ( '/' ) )
335+ : prevArtifact ?. id ;
336+
337+ const hideTimestamp = index > 0 &&
338+ artifact . timestamp !== undefined &&
339+ prevArtifact ?. timestamp !== undefined &&
340+ artifactBasename === prevArtifactBasename &&
341+ fromNow ( prevArtifact . timestamp ) === fromNow ( artifact . timestamp ) ;
342+
343+ artifactsTree . add ( artifactUri , {
307344 repository,
308345 group : inputOrElement . artifactGroup ,
309346 artifact,
347+ hideTimestamp,
310348 type : 'artifact'
311349 } ) ;
312350 }
@@ -315,14 +353,16 @@ class RepositoryTreeDataSource extends Disposable implements IAsyncDataSource<IS
315353 }
316354
317355 // Flat list of artifacts
318- return artifacts . map ( artifact => (
319- {
320- repository,
321- group : inputOrElement . artifactGroup ,
322- artifact,
323- type : 'artifact'
324- } satisfies SCMArtifactTreeElement
325- ) ) ;
356+ return artifacts . map ( ( artifact , index , artifacts ) => ( {
357+ repository,
358+ group : inputOrElement . artifactGroup ,
359+ artifact,
360+ hideTimestamp : index > 0 &&
361+ artifact . timestamp !== undefined &&
362+ artifacts [ index - 1 ] . timestamp !== undefined &&
363+ fromNow ( artifacts [ index - 1 ] . timestamp ! ) === fromNow ( artifact . timestamp ) ,
364+ type : 'artifact'
365+ } satisfies SCMArtifactTreeElement ) ) ;
326366 } else if ( isSCMArtifactNode ( inputOrElement ) ) {
327367 return Iterable . map ( inputOrElement . children ,
328368 node => node . element && node . childrenCount === 0 ? node . element : node ) ;
0 commit comments