From f7863829e81983393563ba0dbe99f7ebf488b5db Mon Sep 17 00:00:00 2001 From: Chidozie Ononiwu <31145988+chidozieononiwu@users.noreply.github.com> Date: Wed, 11 Sep 2024 18:15:06 -0700 Subject: [PATCH] 080624.01/comment and diff navigation (#8898) * Add Previous and Next Comment and Diff Buttons * Comment and Diff Navigation * Add Navigation button to comment threads * Ensure navigation starts from current comment * Increase component budget --- src/dotnet/APIView/ClientSPA/angular.json | 2 +- .../code-panel/code-panel.component.html | 4 +- .../code-panel/code-panel.component.scss | 6 +- .../code-panel/code-panel.component.spec.ts | 4 +- .../code-panel/code-panel.component.ts | 114 ++++++++++++++++- .../review-page-options.component.html | 27 ++++ .../review-page-options.component.ts | 6 +- .../review-page/review-page.component.html | 4 +- .../review-page/review-page.component.spec.ts | 6 +- .../review-page/review-page.component.ts | 10 +- .../revisions-list.component.ts | 5 +- .../comment-thread.component.html | 13 +- .../comment-thread.component.scss | 15 ++- .../comment-thread.component.ts | 23 ++++ .../src/app/_helpers/common-helpers.ts | 14 +++ .../app/_modules/shared/shared-app.module.ts | 1 + .../app/_workers/apitree-builder.worker.ts | 6 +- .../ClientSPA/src/app/app.component.html | 1 + .../ClientSPA/src/app/app.component.spec.ts | 11 +- .../APIView/ClientSPA/src/app/app.module.ts | 4 + .../ClientSPA/src/ng-prime-overrides.scss | 117 +++++++++++++++++- 21 files changed, 364 insertions(+), 29 deletions(-) diff --git a/src/dotnet/APIView/ClientSPA/angular.json b/src/dotnet/APIView/ClientSPA/angular.json index 69860a028cf..90759d9b02a 100644 --- a/src/dotnet/APIView/ClientSPA/angular.json +++ b/src/dotnet/APIView/ClientSPA/angular.json @@ -59,7 +59,7 @@ { "type": "anyComponentStyle", "maximumWarning": "2kb", - "maximumError": "5kb" + "maximumError": "10kb" } ], "outputHashing": "all" diff --git a/src/dotnet/APIView/ClientSPA/src/app/_components/code-panel/code-panel.component.html b/src/dotnet/APIView/ClientSPA/src/app/_components/code-panel/code-panel.component.html index f58286ff870..9f2c5b7fb10 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_components/code-panel/code-panel.component.html +++ b/src/dotnet/APIView/ClientSPA/src/app/_components/code-panel/code-panel.component.html @@ -30,7 +30,9 @@ (saveCommentActionEmitter)="handleSaveCommentActionEmitter($event)" (deleteCommentActionEmitter)="handleDeleteCommentActionEmitter($event)" (commentResolutionActionEmitter)="handleCommentResolutionActionEmitter($event)" - (commentUpvoteActionEmitter)="handleCommentUpvoteActionEmitter($event)"> + (commentUpvoteActionEmitter)="handleCommentUpvoteActionEmitter($event)" + (commentThreadNavaigationEmitter)="handleCommentThreadNavaigationEmitter($event)"> + diff --git a/src/dotnet/APIView/ClientSPA/src/app/_components/code-panel/code-panel.component.scss b/src/dotnet/APIView/ClientSPA/src/app/_components/code-panel/code-panel.component.scss index 7a4e2b1c301..1fa6dfe80f0 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_components/code-panel/code-panel.component.scss +++ b/src/dotnet/APIView/ClientSPA/src/app/_components/code-panel/code-panel.component.scss @@ -60,7 +60,7 @@ } &.user-comment-thread { - padding-left: calc(var(--max-line-number-width) + 50px); + padding-left: calc(var(--max-line-number-width) + 20px); border-top: 1px solid var(--border-color); border-bottom: 1px solid var(--border-color); @@ -71,6 +71,10 @@ } } + &.user-comment-thread:hover .comment-thread-navigation { + display: block; + } + &.added { background-color: rgba(0, 255, 0, 0.25); .code-line-content::before { diff --git a/src/dotnet/APIView/ClientSPA/src/app/_components/code-panel/code-panel.component.spec.ts b/src/dotnet/APIView/ClientSPA/src/app/_components/code-panel/code-panel.component.spec.ts index ef3b601ccd5..83821d933d5 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_components/code-panel/code-panel.component.spec.ts +++ b/src/dotnet/APIView/ClientSPA/src/app/_components/code-panel/code-panel.component.spec.ts @@ -6,6 +6,7 @@ import { HttpClientTestingModule } from '@angular/common/http/testing'; import { ActivatedRoute, convertToParamMap } from '@angular/router'; import { SharedAppModule } from 'src/app/_modules/shared/shared-app.module'; import { ReviewPageModule } from 'src/app/_modules/review-page/review-page.module'; +import { MessageService } from 'primeng/api'; describe('CodePanelComponent', () => { let component: CodePanelComponent; @@ -24,7 +25,8 @@ describe('CodePanelComponent', () => { queryParamMap: convertToParamMap({ activeApiRevisionId: 'test', diffApiRevisionId: 'test' }) } } - } + }, + MessageService ], imports: [HttpClientTestingModule, SharedAppModule, diff --git a/src/dotnet/APIView/ClientSPA/src/app/_components/code-panel/code-panel.component.ts b/src/dotnet/APIView/ClientSPA/src/app/_components/code-panel/code-panel.component.ts index 0d6ee428b27..a168d02f0de 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_components/code-panel/code-panel.component.ts +++ b/src/dotnet/APIView/ClientSPA/src/app/_components/code-panel/code-panel.component.ts @@ -1,15 +1,16 @@ -import { ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; +import { ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; import { take, takeUntil } from 'rxjs/operators'; import { Datasource, IDatasource, SizeStrategy } from 'ngx-ui-scroll'; import { CommentsService } from 'src/app/_services/comments/comments.service'; import { getQueryParams } from 'src/app/_helpers/router-helpers'; import { ActivatedRoute, Router } from '@angular/router'; -import { SCROLL_TO_NODE_QUERY_PARAM } from 'src/app/_helpers/common-helpers'; +import { CodeLineRowNavigationDirection, isDiffRow, SCROLL_TO_NODE_QUERY_PARAM } from 'src/app/_helpers/common-helpers'; import { CodePanelData, CodePanelRowData, CodePanelRowDatatype } from 'src/app/_models/codePanelModels'; import { StructuredToken } from 'src/app/_models/structuredToken'; import { CommentItemModel, CommentType } from 'src/app/_models/commentItemModel'; import { UserProfile } from 'src/app/_models/userProfile'; import { Message } from 'primeng/api/message'; +import { MessageService } from 'primeng/api'; import { SignalRService } from 'src/app/_services/signal-r/signal-r.service'; import { Subject } from 'rxjs'; import { CommentThreadUpdateAction, CommentUpdatesDto } from 'src/app/_dtos/commentThreadUpdateDto'; @@ -36,6 +37,7 @@ export class CodePanelComponent implements OnChanges{ @Output() hasActiveConversationEmitter : EventEmitter = new EventEmitter(); + noDiffInContentMessage : Message[] = [{ severity: 'info', icon:'bi bi-info-circle', detail: 'There is no difference between the two API revisions.' }]; isLoading: boolean = true; codeWindowHeight: string | undefined = undefined; @@ -46,8 +48,11 @@ export class CodePanelComponent implements OnChanges{ destroy$ = new Subject(); + commentThreadNavaigationPointer: number | undefined = undefined; + diffNodeNavaigationPointer: number | undefined = undefined; + constructor(private changeDetectorRef: ChangeDetectorRef, private commentsService: CommentsService, - private signalRService: SignalRService, private route: ActivatedRoute, private router: Router) { } + private signalRService: SignalRService, private route: ActivatedRoute, private router: Router, private messageService: MessageService) { } ngOnInit() { this.codeWindowHeight = `${window.innerHeight - 80}`; @@ -559,6 +564,109 @@ export class CodePanelComponent implements OnChanges{ }); } + handleCommentThreadNavaigationEmitter(event: any) { + this.commentThreadNavaigationPointer = Number(event.commentThreadNavaigationPointer); + this.navigateToCommentThread(event.direction); + } + + navigateToCommentThread(direction: CodeLineRowNavigationDirection) { + const firstVisible = this.codePanelRowSource?.adapter?.firstVisible!.$index!; + const lastVisible = this.codePanelRowSource?.adapter?.lastVisible!.$index!; + let navigateToRow : CodePanelRowData | undefined = undefined; + if (direction == CodeLineRowNavigationDirection.next) { + const startIndex = (this.commentThreadNavaigationPointer && this.commentThreadNavaigationPointer >= firstVisible && this.commentThreadNavaigationPointer <= lastVisible) ? + this.commentThreadNavaigationPointer + 1 : firstVisible; + navigateToRow = this.findNextCommentThread(startIndex); + } + else { + const startIndex = (this.commentThreadNavaigationPointer && this.commentThreadNavaigationPointer >= firstVisible && this.commentThreadNavaigationPointer <= lastVisible) ? + this.commentThreadNavaigationPointer - 1 : lastVisible; + navigateToRow = this.findPrevCommentthread(startIndex); + } + + if (navigateToRow) { + this.scrollToNode(navigateToRow.nodeIdHashed); + } + else { + this.messageService.add({ severity: 'info', icon: 'bi bi-info-circle', detail: 'No more active comments threads to navigate to.', key: 'bl', life: 3000 }); + } + } + + navigateToDiffNode(direction: CodeLineRowNavigationDirection) { + const firstVisible = this.codePanelRowSource?.adapter?.firstVisible!.$index!; + const lastVisible = this.codePanelRowSource?.adapter?.lastVisible!.$index!; + let navigateToRow : CodePanelRowData | undefined = undefined; + if (direction == CodeLineRowNavigationDirection.next) { + const startIndex = (this.diffNodeNavaigationPointer && this.diffNodeNavaigationPointer >= firstVisible && this.diffNodeNavaigationPointer <= lastVisible) ? + this.diffNodeNavaigationPointer : firstVisible; + navigateToRow = this.findNextDiffNode(startIndex); + } + else { + const startIndex = (this.diffNodeNavaigationPointer && this.diffNodeNavaigationPointer >= firstVisible && this.diffNodeNavaigationPointer <= lastVisible) ? + this.diffNodeNavaigationPointer: lastVisible; + navigateToRow = this.findPrevDiffNode(startIndex); + } + + if (navigateToRow) { + this.scrollToNode(navigateToRow.nodeIdHashed); + } + else { + this.messageService.add({ severity: 'info', icon: 'bi bi-info-circle', detail: 'No more diffs to navigate to.', key: 'bl', life: 3000 }); + } + } + + private findNextCommentThread (index: number) : CodePanelRowData | undefined { + while (index < this.codePanelRowData.length) { + if (this.codePanelRowData[index].type === CodePanelRowDatatype.CommentThread && !this.codePanelRowData![index].isResolvedCommentThread) { + this.commentThreadNavaigationPointer = index; + return this.codePanelRowData[index]; + } + index++; + } + return undefined; + } + + private findPrevCommentthread (index: number) : CodePanelRowData | undefined { + while (index < this.codePanelRowData.length && index >= 0) { + if (this.codePanelRowData[index].type === CodePanelRowDatatype.CommentThread && !this.codePanelRowData![index].isResolvedCommentThread) { + this.commentThreadNavaigationPointer = index; + return this.codePanelRowData[index]; + } + index--; + } + return undefined; + } + + private findNextDiffNode (index: number) : CodePanelRowData | undefined { + let checkForDiffNode = (isDiffRow(this.codePanelRowData[index])) ? false : true; + while (index < this.codePanelRowData.length) { + if (!checkForDiffNode && !isDiffRow(this.codePanelRowData[index])) { + checkForDiffNode = true; + } + if (checkForDiffNode && isDiffRow(this.codePanelRowData[index])) { + this.diffNodeNavaigationPointer = index; + return this.codePanelRowData[index]; + } + index++; + } + return undefined; + } + + private findPrevDiffNode (index: number) : CodePanelRowData | undefined { + let checkForDiffNode = (isDiffRow(this.codePanelRowData[index])) ? false : true; + while (index < this.codePanelRowData.length && index >= 0) { + if (!checkForDiffNode && !isDiffRow(this.codePanelRowData[index])) { + checkForDiffNode = true; + } + if (checkForDiffNode && isDiffRow(this.codePanelRowData[index])) { + this.diffNodeNavaigationPointer = index; + return this.codePanelRowData[index]; + } + index--; + } + return undefined; + } + private updateHasActiveConversations() { let hasActiveConversation = false; for (let row of this.codePanelRowData) { diff --git a/src/dotnet/APIView/ClientSPA/src/app/_components/review-page-options/review-page-options.component.html b/src/dotnet/APIView/ClientSPA/src/app/_components/review-page-options/review-page-options.component.html index e2ca7906c2f..94c54336fef 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_components/review-page-options/review-page-options.component.html +++ b/src/dotnet/APIView/ClientSPA/src/app/_components/review-page-options/review-page-options.component.html @@ -67,6 +67,33 @@ + +
    +
  • + +
    +
    + + +
    +
    +
  • +
  • + +
    +
    + + +
    +
    +
  • +
+
+
  • diff --git a/src/dotnet/APIView/ClientSPA/src/app/_components/review-page-options/review-page-options.component.ts b/src/dotnet/APIView/ClientSPA/src/app/_components/review-page-options/review-page-options.component.ts index 9214d100b45..c40bc9d587f 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_components/review-page-options/review-page-options.component.ts +++ b/src/dotnet/APIView/ClientSPA/src/app/_components/review-page-options/review-page-options.component.ts @@ -2,7 +2,7 @@ import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChange import { ActivatedRoute, Router } from '@angular/router'; import { InputSwitchOnChangeEvent } from 'primeng/inputswitch'; import { getQueryParams } from 'src/app/_helpers/router-helpers'; -import { FULL_DIFF_STYLE, mapLanguageAliases, NODE_DIFF_STYLE, TREE_DIFF_STYLE } from 'src/app/_helpers/common-helpers'; +import { CodeLineRowNavigationDirection, FULL_DIFF_STYLE, mapLanguageAliases, NODE_DIFF_STYLE, TREE_DIFF_STYLE } from 'src/app/_helpers/common-helpers'; import { Review } from 'src/app/_models/review'; import { APIRevision } from 'src/app/_models/revision'; import { ConfigService } from 'src/app/_services/config/config.service'; @@ -42,6 +42,9 @@ export class ReviewPageOptionsComponent implements OnInit, OnChanges{ @Output() showLineNumbersEmitter : EventEmitter = new EventEmitter(); @Output() apiRevisionApprovalEmitter : EventEmitter = new EventEmitter(); @Output() reviewApprovalEmitter : EventEmitter = new EventEmitter(); + @Output() commentThreadNavaigationEmitter : EventEmitter = new EventEmitter(); + @Output() diffNavaigationEmitter : EventEmitter = new EventEmitter(); + webAppUrl : string = this.configService.webAppUrl @@ -71,6 +74,7 @@ export class ReviewPageOptionsComponent implements OnInit, OnChanges{ associatedPullRequests : PullRequestModel[] = []; pullRequestsOfAssociatedAPIRevisions : PullRequestModel[] = []; + CodeLineRowNavigationDirection = CodeLineRowNavigationDirection; //Approvers Options selectedApprovers: string[] = []; diff --git a/src/dotnet/APIView/ClientSPA/src/app/_components/review-page/review-page.component.html b/src/dotnet/APIView/ClientSPA/src/app/_components/review-page/review-page.component.html index 5cf8e2d3bcd..0db6a08ef15 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_components/review-page/review-page.component.html +++ b/src/dotnet/APIView/ClientSPA/src/app/_components/review-page/review-page.component.html @@ -69,7 +69,9 @@ (apiRevisionApprovalEmitter)="handleApiRevisionApprovalEmitter($event)" (reviewApprovalEmitter)="handleReviewApprovalEmitter($event)" (showHiddenAPIEmitter)="handleShowHiddenAPIEmitter($event)" - (disableCodeLinesLazyLoadingEmitter)="handleDisableCodeLinesLazyLoadingEmitter($event)"> + (disableCodeLinesLazyLoadingEmitter)="handleDisableCodeLinesLazyLoadingEmitter($event)" + (commentThreadNavaigationEmitter)="handleCommentThreadNavaigationEmitter($event)" + (diffNavaigationEmitter)="handleDiffNavaigationEmitter($event)"> diff --git a/src/dotnet/APIView/ClientSPA/src/app/_components/review-page/review-page.component.spec.ts b/src/dotnet/APIView/ClientSPA/src/app/_components/review-page/review-page.component.spec.ts index f474c7764b7..8962c30ca51 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_components/review-page/review-page.component.spec.ts +++ b/src/dotnet/APIView/ClientSPA/src/app/_components/review-page/review-page.component.spec.ts @@ -17,6 +17,7 @@ import { ReviewPageOptionsComponent } from '../review-page-options/review-page-o import { PageOptionsSectionComponent } from '../shared/page-options-section/page-options-section.component'; import { SharedAppModule } from 'src/app/_modules/shared/shared-app.module'; import { ReviewPageModule } from 'src/app/_modules/review-page/review-page.module'; +import { MessageService } from 'primeng/api'; describe('ReviewPageComponent', () => { let component: ReviewPageComponent; @@ -51,8 +52,9 @@ describe('ReviewPageComponent', () => { paramMap: convertToParamMap({ reviewId: 'test' }), }, queryParams: of(convertToParamMap({ activeApiRevisionId: 'test', diffApiRevisionId: 'test' })) - } - } + }, + }, + MessageService ] }); fixture = TestBed.createComponent(ReviewPageComponent); diff --git a/src/dotnet/APIView/ClientSPA/src/app/_components/review-page/review-page.component.ts b/src/dotnet/APIView/ClientSPA/src/app/_components/review-page/review-page.component.ts index 5c6c442881c..332022cffd7 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_components/review-page/review-page.component.ts +++ b/src/dotnet/APIView/ClientSPA/src/app/_components/review-page/review-page.component.ts @@ -2,7 +2,7 @@ import { ChangeDetectorRef, Component, OnInit, ViewChild } from '@angular/core'; import { ActivatedRoute, Params, Router } from '@angular/router'; import { MenuItem, TreeNode } from 'primeng/api'; import { Subject, take, takeUntil } from 'rxjs'; -import { getLanguageCssSafeName } from 'src/app/_helpers/common-helpers'; +import { CodeLineRowNavigationDirection, getLanguageCssSafeName } from 'src/app/_helpers/common-helpers'; import { getQueryParams } from 'src/app/_helpers/router-helpers'; import { Review } from 'src/app/_models/review'; import { APIRevision, ApiTreeBuilderData } from 'src/app/_models/revision'; @@ -438,6 +438,14 @@ export class ReviewPageComponent implements OnInit { }); } + handleCommentThreadNavaigationEmitter(direction: CodeLineRowNavigationDirection) { + this.codePanelComponent.navigateToCommentThread(direction); + } + + handleDiffNavaigationEmitter(direction: CodeLineRowNavigationDirection) { + this.codePanelComponent.navigateToDiffNode(direction); + } + handleHasActiveConversationEmitter(value: boolean) { this.hasActiveConversation = value; } diff --git a/src/dotnet/APIView/ClientSPA/src/app/_components/revisions-list/revisions-list.component.ts b/src/dotnet/APIView/ClientSPA/src/app/_components/revisions-list/revisions-list.component.ts index 3232b5645dd..2ad39962eba 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_components/revisions-list/revisions-list.component.ts +++ b/src/dotnet/APIView/ClientSPA/src/app/_components/revisions-list/revisions-list.component.ts @@ -1,7 +1,7 @@ import { Component, Input, OnChanges, OnInit, SimpleChanges, ViewChild } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { Router } from '@angular/router'; -import { MenuItem, SortEvent } from 'primeng/api'; +import { MenuItem, MessageService, SortEvent } from 'primeng/api'; import { FileSelectEvent, FileUpload } from 'primeng/fileupload'; import { Table, TableFilterEvent, TableLazyLoadEvent } from 'primeng/table'; import { Pagination } from 'src/app/_models/pagination'; @@ -75,7 +75,7 @@ export class RevisionsListComponent implements OnInit, OnChanges { constructor(private apiRevisionsService: RevisionsService, private userProfileService: UserProfileService, private configService: ConfigService, private fb: FormBuilder, private reviewsService: ReviewsService, - private router: Router) { } + private router: Router, private messageService: MessageService) { } ngOnInit(): void { this.createRevisionFilters(); @@ -483,6 +483,7 @@ export class RevisionsListComponent implements OnInit, OnChanges { }, error: (error: any) => { this.creatingRevision = false; + this.messageService.add({ severity: 'error', icon: 'bi bi-info-circle', detail: 'Failed to create new API Revision', key: 'bl', life: 3000 }); } }); } diff --git a/src/dotnet/APIView/ClientSPA/src/app/_components/shared/comment-thread/comment-thread.component.html b/src/dotnet/APIView/ClientSPA/src/app/_components/shared/comment-thread/comment-thread.component.html index 03e5f7b3ca7..26da61a634d 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_components/shared/comment-thread/comment-thread.component.html +++ b/src/dotnet/APIView/ClientSPA/src/app/_components/shared/comment-thread/comment-thread.component.html @@ -1,7 +1,8 @@ -
    - This thread is marked resolved by {{ threadResolvedBy }}  {{threadResolvedStateToggleText}} Resolved +
    + This thread is marked resolved by {{ threadResolvedBy }}  {{threadResolvedStateToggleText}} Resolved +
    -
    +
    @@ -72,4 +73,8 @@
    -
    \ No newline at end of file + +
    + + +
    diff --git a/src/dotnet/APIView/ClientSPA/src/app/_components/shared/comment-thread/comment-thread.component.scss b/src/dotnet/APIView/ClientSPA/src/app/_components/shared/comment-thread/comment-thread.component.scss index d0256c539c8..508363e8713 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_components/shared/comment-thread/comment-thread.component.scss +++ b/src/dotnet/APIView/ClientSPA/src/app/_components/shared/comment-thread/comment-thread.component.scss @@ -2,7 +2,15 @@ font-family: var(--font-family); .comment-thread-container { - max-width: 1000px; + width: min(100%, 1000px); + } + + .comment-thread-navigation { + display: none; + position: absolute; + top: 50%; + left: 100%; + transform: translate(0, -50%); } .user-avartar { @@ -24,6 +32,7 @@ .p-panel { border: none; + background-color: unset; p { font-size: 14px; @@ -77,4 +86,8 @@ font-weight: bolder; cursor: pointer; } + + .rendered-comment-content { + overflow-wrap: anywhere; + } } \ No newline at end of file diff --git a/src/dotnet/APIView/ClientSPA/src/app/_components/shared/comment-thread/comment-thread.component.ts b/src/dotnet/APIView/ClientSPA/src/app/_components/shared/comment-thread/comment-thread.component.ts index 3820e80cd41..6c915c12239 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_components/shared/comment-thread/comment-thread.component.ts +++ b/src/dotnet/APIView/ClientSPA/src/app/_components/shared/comment-thread/comment-thread.component.ts @@ -7,6 +7,7 @@ import { EditorComponent } from '../editor/editor.component'; import { CodePanelRowData } from 'src/app/_models/codePanelModels'; import { UserProfile } from 'src/app/_models/userProfile'; import { CommentThreadUpdateAction, CommentUpdatesDto } from 'src/app/_dtos/commentThreadUpdateDto'; +import { CodeLineRowNavigationDirection } from 'src/app/_helpers/common-helpers'; @Component({ selector: 'app-comment-thread', @@ -25,6 +26,7 @@ export class CommentThreadComponent { @Output() deleteCommentActionEmitter : EventEmitter = new EventEmitter(); @Output() commentResolutionActionEmitter : EventEmitter = new EventEmitter(); @Output() commentUpvoteActionEmitter : EventEmitter = new EventEmitter(); + @Output() commentThreadNavaigationEmitter : EventEmitter = new EventEmitter(); @ViewChildren(Menu) menus!: QueryList; @ViewChildren(EditorComponent) editor!: QueryList; @@ -43,6 +45,11 @@ export class CommentThreadComponent { spacingBasedOnResolvedState: string = 'my-2'; resolveThreadButtonText : string = 'Resolve'; + floatItemStart : string = "" + floatItemEnd : string = "" + + CodeLineRowNavigationDirection = CodeLineRowNavigationDirection; + constructor(private userProfileService: UserProfileService) { } ngOnInit(): void { @@ -126,6 +133,12 @@ export class CommentThreadComponent { this.spacingBasedOnResolvedState = (this.instanceLocation === "code-panel") ? 'my-2' : ""; this.resolveThreadButtonText = 'Resolve'; } + this.setCssPropertyBasedonInstance(); + } + + setCssPropertyBasedonInstance() { + this.floatItemStart = (this.instanceLocation === 'code-panel') ? "float-start" : ""; + this.floatItemEnd = (this.instanceLocation === 'code-panel') ? "float-end" : ""; } getCommentActionMenuContent(commentId: string) { @@ -307,6 +320,7 @@ export class CommentThreadComponent { this.threadResolvedStateToggleText = 'Show'; this.threadResolvedStateToggleIcon = 'bi-arrows-expand'; } + this.setCssPropertyBasedonInstance(); } handleThreadResolutionButtonClick(action: string) { @@ -320,4 +334,13 @@ export class CommentThreadComponent { } as CommentUpdatesDto ); } + + handleCommentThreadNavaigation(event: Event, direction: CodeLineRowNavigationDirection) { + const target = (event.target as Element).closest(".user-comment-thread")?.parentNode as Element; + const targetIndex = target.getAttribute("data-sid"); + this.commentThreadNavaigationEmitter.emit({ + commentThreadNavaigationPointer: targetIndex, + direction: direction + }); + } } diff --git a/src/dotnet/APIView/ClientSPA/src/app/_helpers/common-helpers.ts b/src/dotnet/APIView/ClientSPA/src/app/_helpers/common-helpers.ts index 86e6d5b908b..e70ebfdfe4f 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_helpers/common-helpers.ts +++ b/src/dotnet/APIView/ClientSPA/src/app/_helpers/common-helpers.ts @@ -1,3 +1,5 @@ +import { CodePanelRowData, CodePanelRowDatatype } from "../_models/codePanelModels"; + export const REVIEW_ID_ROUTE_PARAM = "reviewId"; export const ACTIVE_API_REVISION_ID_QUERY_PARAM = "activeApiRevisionId"; export const DIFF_API_REVISION_ID_QUERY_PARAM = "diffApiRevisionId"; @@ -9,6 +11,13 @@ export const NODE_DIFF_STYLE = "nodes"; export const MANUAL_ICON = "fa-solid fa-arrow-up-from-bracket"; export const PR_ICON = "fa-solid fa-code-pull-request"; export const AUTOMATIC_ICON = "fa-solid fa-robot"; +export const DIFF_ADDED = "added"; +export const DIFF_REMOVED = "removed"; + +export enum CodeLineRowNavigationDirection { + prev = 0, + next +} export function getLanguageCssSafeName(language: string): string { switch (language.toLowerCase()) { @@ -48,4 +57,9 @@ export function getTypeClass(type: string): string { break; } return result; +} + + +export function isDiffRow(row: CodePanelRowData) { + return row.type === CodePanelRowDatatype.CodeLine && (row.diffKind === DIFF_REMOVED || row.diffKind === DIFF_ADDED); } \ No newline at end of file diff --git a/src/dotnet/APIView/ClientSPA/src/app/_modules/shared/shared-app.module.ts b/src/dotnet/APIView/ClientSPA/src/app/_modules/shared/shared-app.module.ts index 2474e4426a8..d6e284d1e42 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_modules/shared/shared-app.module.ts +++ b/src/dotnet/APIView/ClientSPA/src/app/_modules/shared/shared-app.module.ts @@ -21,6 +21,7 @@ import { FileUploadModule } from 'primeng/fileupload'; import { InputTextModule } from 'primeng/inputtext'; import { MessagesModule } from 'primeng/messages'; import { BadgeModule } from 'primeng/badge'; +import { ToastModule } from 'primeng/toast'; @NgModule({ diff --git a/src/dotnet/APIView/ClientSPA/src/app/_workers/apitree-builder.worker.ts b/src/dotnet/APIView/ClientSPA/src/app/_workers/apitree-builder.worker.ts index 5e52d97a1b2..dfb3bacb318 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_workers/apitree-builder.worker.ts +++ b/src/dotnet/APIView/ClientSPA/src/app/_workers/apitree-builder.worker.ts @@ -4,7 +4,7 @@ import { ApiTreeBuilderData } from "../_models/revision"; import { CodePanelData, CodePanelNodeMetaData, CodePanelRowData, CodePanelRowDatatype } from '../_models/codePanelModels'; import { InsertCodePanelRowDataMessage, ReviewPageWorkerMessageDirective } from '../_models/insertCodePanelRowDataMessage'; import { NavigationTreeNode } from '../_models/navigationTreeModels'; -import { FULL_DIFF_STYLE, NODE_DIFF_STYLE, TREE_DIFF_STYLE } from '../_helpers/common-helpers'; +import { DIFF_ADDED, DIFF_REMOVED, FULL_DIFF_STYLE, NODE_DIFF_STYLE, TREE_DIFF_STYLE } from '../_helpers/common-helpers'; let codePanelData: CodePanelData | null = null; let codePanelRowData: CodePanelRowData[] = []; @@ -224,9 +224,9 @@ function appendToggleDocumentationClass(node: CodePanelNodeMetaData, codePanelRo } function setLineNumber(row: CodePanelRowData) { - if (row.diffKind === "removed") { + if (row.diffKind === DIFF_REMOVED) { row.lineNumber = ++lineNumber; - } else if (row.diffKind === "added") { + } else if (row.diffKind === DIFF_ADDED) { lineNumber++; diffLineNumber++; row.lineNumber = diffLineNumber; diff --git a/src/dotnet/APIView/ClientSPA/src/app/app.component.html b/src/dotnet/APIView/ClientSPA/src/app/app.component.html index 26e9a57301f..a2902cf591c 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/app.component.html +++ b/src/dotnet/APIView/ClientSPA/src/app/app.component.html @@ -1,4 +1,5 @@
    +
    diff --git a/src/dotnet/APIView/ClientSPA/src/app/app.component.spec.ts b/src/dotnet/APIView/ClientSPA/src/app/app.component.spec.ts index c5934da021d..44dcac806f2 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/app.component.spec.ts +++ b/src/dotnet/APIView/ClientSPA/src/app/app.component.spec.ts @@ -2,10 +2,19 @@ import { TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; import { AppComponent } from './app.component'; import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { ToastModule } from 'primeng/toast'; +import { MessageService } from 'primeng/api'; describe('AppComponent', () => { beforeEach(() => TestBed.configureTestingModule({ - imports: [RouterTestingModule, HttpClientTestingModule], + imports: [ + RouterTestingModule, + HttpClientTestingModule, + ToastModule + ], + providers: [ + MessageService + ], declarations: [AppComponent], })); diff --git a/src/dotnet/APIView/ClientSPA/src/app/app.module.ts b/src/dotnet/APIView/ClientSPA/src/app/app.module.ts index b90f72292d8..99e75d2b868 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/app.module.ts +++ b/src/dotnet/APIView/ClientSPA/src/app/app.module.ts @@ -8,6 +8,7 @@ import { AppComponent } from './app.component'; import { IndexPageComponent } from './_components/index-page/index-page.component'; import { ReviewsListComponent } from './_components/reviews-list/reviews-list.component'; import { TabMenuModule } from 'primeng/tabmenu'; +import { ToastModule } from 'primeng/toast'; import { ToolbarModule } from 'primeng/toolbar'; import { BadgeModule } from 'primeng/badge'; import { Observable } from 'rxjs'; @@ -15,6 +16,7 @@ import { ConfigService } from './_services/config/config.service'; import { CookieService } from 'ngx-cookie-service'; import { SharedAppModule } from './_modules/shared/shared-app.module'; import { HttpErrorInterceptorService } from './_services/http-error-interceptor/http-error-interceptor.service'; +import { MessageService } from 'primeng/api'; export function initializeApp(configService: ConfigService) { return (): Observable => { @@ -36,6 +38,7 @@ export function initializeApp(configService: ConfigService) { BrowserAnimationsModule, TabMenuModule, ToolbarModule, + ToastModule, HttpClientModule, ], providers: [ @@ -51,6 +54,7 @@ export function initializeApp(configService: ConfigService) { useClass: HttpErrorInterceptorService, multi: true }, + MessageService, CookieService ], bootstrap: [AppComponent] diff --git a/src/dotnet/APIView/ClientSPA/src/ng-prime-overrides.scss b/src/dotnet/APIView/ClientSPA/src/ng-prime-overrides.scss index bacda0d6488..6c2f9a99341 100644 --- a/src/dotnet/APIView/ClientSPA/src/ng-prime-overrides.scss +++ b/src/dotnet/APIView/ClientSPA/src/ng-prime-overrides.scss @@ -548,13 +548,118 @@ p-contextmenusub { margin: 0.1rem 0; } -.p-message.p-message-info { - background: var(--alert-info-bg); - border: solid var(--alert-info-border-color); - border-width: 1px; - color: var(--alert-info-color); - .p-message-icon { +.p-message{ + &.p-message-info { + background: var(--alert-info-bg); + border: solid var(--alert-info-border-color); + border-width: 1px; color: var(--alert-info-color); + .p-message-icon { + color: var(--alert-info-color); + } + } + + &.p-message-info { + background: var(--alert-info-bg); + border: solid var(--alert-info-border-color); + border-width: 1px; + color: var(--alert-info-color); + a { + font-weight: bold; + text-decoration: underline; + color: var(--alert-info-link-color); + } + .p-toast-message-icon { + color: var(--alert-info-color); + } + } + + &.p-message-error { + background-color: var(--alert-error-bg); + color: var(--alert-error-color); + border-top: 1px solid var(--alert-error-border-color); + border-bottom: 1px solid var(--alert-error-border-color); + a { + font-weight: bold; + text-decoration: underline; + color: var(--alert-error-link-color); + } + .p-toast-message-icon { + color: var(--alert-error-color); + } + } + &.p-message-warning { + background-color: var(--alert-warn-bg); + color: var(--alert-warn-color); + border-top: 1px solid var(--alert-warn-border-color); + border-bottom: 1px solid var(--alert-warn-border-color); + a { + font-weight: bold; + text-decoration: underline; + color: var(--alert-warn-link-color); + } + .p-toast-message-icon { + color: var(--alert-warn-color); + } + } +} + +.p-toast .p-toast-message .p-toast-message-content { + padding: 0.5rem; + border-width: 0; +} + +.p-toast .p-toast-message .p-toast-message-content .p-toast-message-icon { + font-size: 1.8rem; +} + +.p-toast .p-toast-message .p-toast-message-content .p-toast-detail { + margin: 0.2rem 0 0 0; +} + +.p-toast .p-toast-message { + &.p-toast-message-info { + background: var(--alert-info-bg); + border: solid var(--alert-info-border-color); + border-width: 1px; + color: var(--alert-info-color); + a { + font-weight: bold; + text-decoration: underline; + color: var(--alert-info-link-color); + } + .p-toast-message-icon, .p-toast-icon-close-icon { + color: var(--alert-info-color); + } + } + + &.p-toast-message-error { + background-color: var(--alert-error-bg); + color: var(--alert-error-color); + border-top: 1px solid var(--alert-error-border-color); + border-bottom: 1px solid var(--alert-error-border-color); + a { + font-weight: bold; + text-decoration: underline; + color: var(--alert-error-link-color); + } + .p-toast-message-icon, .p-toast-icon-close-icon { + color: var(--alert-error-color); + } + } + &.p-toast-message-warning { + background-color: var(--alert-warn-bg); + color: var(--alert-warn-color); + border-top: 1px solid var(--alert-warn-border-color); + border-bottom: 1px solid var(--alert-warn-border-color); + a { + font-weight: bold; + text-decoration: underline; + color: var(--alert-warn-link-color); + } + .p-toast-message-icon, .p-toast-icon-close-icon { + color: var(--alert-warn-color); + } } }