Skip to content

Commit

Permalink
Add scroll to bottom and top buttons
Browse files Browse the repository at this point in the history
* Add scroll to bottom button
* Add scroll to top when descending order
  • Loading branch information
minottic authored Feb 5, 2024
1 parent a25404b commit d97214f
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 30 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<div [ngSwitch]="viewOption" class="logbook-container" (resized)="onResized($event)">
<div [ngSwitch]="viewOption" class="logbook-container" (resized)="onResized()">
<!-- <div #searchBar>
<button (click)="toggleSearch()" >
Search <mat-icon>search</mat-icon>
Expand All @@ -23,10 +23,16 @@
</div>
</div>

<button mat-mini-fab class="float" (click)="scrollOnClickTo('end')" [@scrollButton] *ngIf="!isAt('end') && !isDescending">
<mat-icon>keyboard_double_arrow_down</mat-icon>
</button>
<button mat-mini-fab class="float" (click)="scrollOnClickTo('start')" [@scrollButton] *ngIf="!isAt('start') && isDescending">
<mat-icon>keyboard_double_arrow_up</mat-icon>
</button>
<div *ngIf="!mobile && !isReadOnly" [ngClass]="isLightMode == true ? 'editor-container' : 'editor-container-no-border'">
<div #editor class="content-editor"
[ngStyle]="{'padding-left':dashboardView === true ? '0px' : '78px', 'width': dashboardView === true ? '97%' : '92%'}"
(resized)="onResized($event)">
(resized)="onResized()">
<div class='accessSettings'>
<span [ngStyle]="{'flex':'1 1 auto'}"></span>
<span matTooltip="ownerGroup">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,3 +188,11 @@
font-size: initial;
vertical-align: middle;
}

.float {
position: absolute;
margin: -25px;
left: 50%;
transform: translate(-50%, -50%);
opacity: 0.4;
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { ChangeStreamNotification } from '@shared/changestreamnotification.model
import { LogbookDataService } from '@shared/remote-data.service';
import { LogbookScrollService } from '@shared/logbook-scroll.service';
import { AppConfigService } from 'src/app/app-config.service';
import { Renderer2 } from '@angular/core';

class ChangeStreamServiceMock {

Expand Down Expand Up @@ -192,7 +193,6 @@ describe('LogbookItemComponent', () => {
}
}];


const activatedRouteMock = {
parent: { url: of(queryParams) },
snapshot: { queryParams: { id: '1234' } }
Expand All @@ -201,8 +201,11 @@ describe('LogbookItemComponent', () => {

viewsSpy = jasmine.createSpyObj("ViewsService", ["getLogbookViews"]);

logbookItemDataServiceSpy = jasmine.createSpyObj("LogbookItemDataService", ["getDataBuffer", "uploadParagraph"]);
scrollServiceSpy = jasmine.createSpyObj("LogbookScrollService", ["initialize", "updateViewportEstimate", "remove", "appendToEOF", "relax", "isBOF", "isEOF", "prependToBOF"]);
logbookItemDataServiceSpy = jasmine.createSpyObj("LogbookItemDataService", ["uploadParagraph", "getCount"]);
scrollServiceSpy = jasmine.createSpyObj("LogbookScrollService", [
"initialize", "updateViewportEstimate", "remove",
"appendToEOF", "relax", "prependToBOF", "goToSnippetIndex",
]);
scrollServiceSpy.appendToEOF.and.returnValue(of({}));


Expand Down Expand Up @@ -239,6 +242,7 @@ describe('LogbookItemComponent', () => {
component.config = defaultConfig[1].config;
component.configIndex = 1;
component.logbookCount = 10;
component['renderer'] = {setStyle: () => true} as unknown as Renderer2;

fixture.detectChanges();
// views = TestBed.get(ViewsService);
Expand Down Expand Up @@ -508,7 +512,7 @@ describe('LogbookItemComponent', () => {
})

it('Should remove from index', () => {
component.config = { view: { order: ["defaultOrder DESC"] }, general: {}, filter: {} };
component.isDescending = true;
expect(component["_indexOrder"](2)).toEqual(8);
})

Expand Down Expand Up @@ -595,6 +599,80 @@ describe('LogbookItemComponent', () => {
component["updateSnippetValues"](content, snippet);
expect(snippet.dashboardName).toEqual(content.dashboardName);
expect(snippet.parentId).toEqual(snippet.parentId);
});

[
{input: [0, 0], output: 0},
{input: [1, 0], output: 1},
{input: [0, 1], output: 1},
{input: [1, 1], output: 1},
].forEach((t, i) => {
it(`should test onResized ${i}`, () => {
component._editorHeightRef = 0;
component._snippetHeightRef = 0;
component.snippetContainerRef = {nativeElement: {
parentElement: {parentElement: {parentElement: {parentElement: {offsetHeight: t.input[0]}}}}
}};
component.editorRef = {nativeElement: {offsetHeight: t.input[1]}};
const updateViewHeightsSpy = spyOn(component, 'updateViewHeights').and.callFake(() => true);
component.onResized();
expect(updateViewHeightsSpy).toHaveBeenCalledTimes(t.output);
})
})

it('should test stillToScrollToEnd equals old formula', () => {
const autoScrollFraction = 0.4;
const element = {scrollHeight: 10, clientHeight: 5, scrollTop: 4} as Element;
const oldFormula = element.scrollHeight - element.scrollTop - element.clientHeight - autoScrollFraction * element.clientHeight;
expect(component['stillToScrollToEnd'](element, autoScrollFraction, 0)).toEqual(oldFormula);
});

it('should test stillToScrollToEnd >0', () => {
const element = {scrollHeight: 10, clientHeight: 3, scrollTop: 6} as Element;
expect(component['stillToScrollToEnd'](element, 0, 0)).toEqual(1);
});

it('should test stillToScrollToEnd <0', () => {
const element = {scrollHeight: 10, clientHeight: 10, scrollTop: 4} as Element;
expect(component['stillToScrollToEnd'](element, 0.1, 1)).toEqual(-6);
});

['start', 'end'].forEach(t => {
it(`should test scrollTo ${t}`, async () => {
spyOn(component["logbookItemDataService"], "getCount").and.resolveTo({count: 10});
const goToSnippetIndexSpy = spyOn(component["logbookScrollService"], "goToSnippetIndex");
await component['scrollTo'](t as 'start' | 'end');
expect(goToSnippetIndexSpy).toHaveBeenCalled();
expect(goToSnippetIndexSpy.calls.mostRecent().args[0]).toEqual(t === 'end'? 9: 0);
});
});

['start', 'end'].forEach(t => {
it(`should test isAt ${t}`, () => {
const stillToScrollToEndSpy = spyOn<any>(component, "stillToScrollToEnd");
component.isAt(t as 'start' | 'end');
expect(stillToScrollToEndSpy).toHaveBeenCalledTimes(t === 'end'? 1: 0);
});
});

[
{position: 'end', scrollPosition: 'notEmpty'},
{position: 'end', scrollPosition: undefined},
{position: 'start', scrollPosition: 'notEmpty'},
{position: 'start', scrollPosition: undefined},
].forEach((t, i) => {
it(`should test scrollOnClickTo ${t.position}:${i}`, () => {
spyOnProperty(component["logbookScrollService"], 'isEOF', 'get').and.returnValue(t.scrollPosition);
spyOnProperty(component["logbookScrollService"], 'isBOF', 'get').and.returnValue(t.scrollPosition);
const scrollWindowToSpy = spyOn<any>(component, "scrollWindowTo").and.callFake(() => true);
const scrollToSpy = spyOn<any>(component, "scrollTo").and.callFake(() => true);
component.scrollOnClickTo(t.position as 'end' | 'start');
if (t.scrollPosition)
expect(scrollWindowToSpy).toHaveBeenCalledOnceWith(t.position);
else
expect(scrollToSpy).toHaveBeenCalledOnceWith(t.position);
});
});


});
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import { ActivatedRoute } from '@angular/router';
import { ViewsService } from '@shared/views.service';
import * as ClassicEditor from '@shared/ckeditor/ckeditor5/build/ckeditor';
import { CKEditorComponent } from '@shared/ckeditor/ckeditor5/build/ckeditor';
import { ResizedEvent } from 'angular-resize-event';
import { MediaObserver } from '@angular/flex-layout';
import { CK5ImageUploadAdapter } from '@shared/ckeditor/ck5-image-upload-adapter';
import { extractNotificationMessage } from '@shared/add-content/add-content.component';
Expand Down Expand Up @@ -81,6 +80,12 @@ import { LinkType } from 'src/app/core/model/paragraphs';
transition('start => end', animate('200ms ease-in')),
transition('end => start', animate('200ms ease-in'))
]),
trigger('scrollButton', [
transition(':enter', [
style({opacity: 0}),
animate('1ms 0.2s ease-out', style({opacity: 0.4}))
])
]),
]
})
export class LogbookItemComponent implements OnInit {
Expand Down Expand Up @@ -132,12 +137,14 @@ export class LogbookItemComponent implements OnInit {
isReadOnly = false;

_snippetHeightRef = 0;
_editorHeightRef = 0;
showSearch = false
showSearchExpanded = false;

forceScrollToEnd = false;

logbookCount = 0;
isDescending: boolean;

@ViewChildren(SnippetComponent) childSnippets: QueryList<SnippetComponent>;

Expand Down Expand Up @@ -195,10 +202,10 @@ export class LogbookItemComponent implements OnInit {
if (change[0].mqAlias === 'sm' || change[0].mqAlias === 'xs') {
this.mobile = true;
console.log("mobile");
this.updateViewHeights({});
this.updateViewHeights();
} else {
this.mobile = false;
this.updateViewHeights({});
this.updateViewHeights();
}
}));
}
Expand Down Expand Up @@ -242,6 +249,7 @@ export class LogbookItemComponent implements OnInit {
// console.log(config[this.configIndex].config);
if (config != null) {
this.config = config[this.configIndex].config;
this.isDescending = this.config?.view?.order[0]?.split(" ")?.[1] === 'DESC';
this.targetId = this.config.filter.targetId;
this.isReadOnly = this.config.general.readonly;
await this.logbookScrollService.initialize(this.config);
Expand Down Expand Up @@ -370,7 +378,7 @@ export class LogbookItemComponent implements OnInit {
// pos = this.insertIntoSortedArray(notification.content);
// console.log(pos);
console.log(notification.content);
let _descending = this.config.view.order[0].split(" ")[1] == 'DESC' ? true : false;
let _descending = this.isDescending;
let _bof = _descending ? (notification.content.defaultOrder > this.childSnippets.toArray()[0].snippet.defaultOrder) : (notification.content.defaultOrder < this.childSnippets.toArray()[0].snippet.defaultOrder)
let _eof = _descending ? (notification.content.defaultOrder < this.childSnippets.toArray()[this.childSnippets.toArray().length - 1].snippet.defaultOrder) : (notification.content.defaultOrder > this.childSnippets.toArray()[this.childSnippets.toArray().length - 1].snippet.defaultOrder);
if ((this.logbookScrollService.isBOF) && (_bof)) {
Expand All @@ -386,15 +394,15 @@ export class LogbookItemComponent implements OnInit {
console.log("appending to EOF")
console.log(notification.content);
await this.logbookScrollService.appendToEOF(notification.content);
let autoScrollEnabled = ((this.snippetContainerRef.nativeElement.scrollHeight - this.snippetContainerRef.nativeElement.scrollTop - this.snippetContainerRef.nativeElement.clientHeight < this.autoScrollFraction * this.snippetContainerRef.nativeElement.clientHeight)) ? true : false;
let autoScrollEnabled = this.isAt('end', this.autoScrollFraction, 0);
console.log("autoscroll: ", autoScrollEnabled)
if (autoScrollEnabled || this.forceScrollToEnd) {
console.log("scheduling scrolling to EOF");
this.logbookScrollService.scrollToEnd = true;
// await this.logbookScrollService.isLoaded$;
setTimeout(() => {
console.log("scrolling to EOF");
this.snippetContainerRef.nativeElement.scrollTop = this.snippetContainerRef.nativeElement.scrollHeight;
this.scrollWindowTo('end');
}, 50);
}
} else {
Expand All @@ -404,17 +412,7 @@ export class LogbookItemComponent implements OnInit {
}
if (this.forceScrollToEnd) {
console.log("scrolling to new item")
this.forceScrollToEnd = false;
let count = await this.logbookItemDataService.getCount(this.config);
await this.logbookScrollService.goToSnippetIndex(count.count - 1, () => {
this.logbookScrollService.datasource.adapter.relax(() => {
setTimeout(() => {
this.snippetContainerRef.nativeElement.scrollTop = this.snippetContainerRef.nativeElement.scrollHeight;
}, 50);
});

}
);
await this.scrollTo('end');
} else {
console.log(this.childSnippets.toArray());
await this.logbookScrollService.appendToEOF(notification.content);
Expand Down Expand Up @@ -442,6 +440,25 @@ export class LogbookItemComponent implements OnInit {
}
}

private async scrollTo(position: 'end' | 'start') {
let positionIndex = 0;
if (position === 'end') {
this.forceScrollToEnd = false;
positionIndex = (await this.logbookItemDataService.getCount(this.config)).count - 1;
}
await this.logbookScrollService.goToSnippetIndex(positionIndex, () => {
this.logbookScrollService.datasource.adapter.relax(() => {
setTimeout(() => {
this.scrollWindowTo(position);
}, 50);
});
});
}

private scrollWindowTo(position: 'end' | 'start') {
this.snippetContainerRef.nativeElement.scrollTop = position === 'end'? this.snippetContainerRef.nativeElement.scrollHeight: 0;
}

private updateSnippetValues(content: Basesnippets, snippet: Basesnippets) {
for (const key in content) {
snippet[key] = content[key];
Expand Down Expand Up @@ -672,11 +689,13 @@ export class LogbookItemComponent implements OnInit {
this.submitContent(notification);
}

onResized(event: ResizedEvent) {
let _heightRef = this.snippetContainerRef.nativeElement.parentElement.parentElement.parentElement.parentElement.offsetHeight;
if (this._snippetHeightRef != _heightRef) {
onResized() {
const _heightRef = this.snippetContainerRef.nativeElement.parentElement.parentElement.parentElement.parentElement.offsetHeight;
const _editorHeight = this.editorRef?.nativeElement?.offsetHeight;
if (this._snippetHeightRef != _heightRef || this._editorHeightRef != _editorHeight) {
this._snippetHeightRef = _heightRef;
this.updateViewHeights(event)
this._editorHeightRef = _editorHeight;
this.updateViewHeights();
}

// console.log(this.snippetContainerRef.nativeElement.parentElement.parentElement.parentElement.parentElement.offsetHeight);
Expand All @@ -686,7 +705,7 @@ export class LogbookItemComponent implements OnInit {
// }, 10);
}

updateViewHeights(event) {
updateViewHeights() {
if (this.editorRef) {
let offset = this.mobile ? 210 - 28 : (this.dashboardView ? 130 - 28 : 195 - 25);
let snippetHeight: number;
Expand All @@ -704,7 +723,7 @@ export class LogbookItemComponent implements OnInit {
ngAfterViewInit(): void {
//Called after ngAfterContentInit when the component's view has been initialized. Applies to components only.
//Add 'implements AfterViewInit' to the class.
this.updateViewHeights({});
this.updateViewHeights();
this.cdr.detectChanges();
}

Expand Down Expand Up @@ -751,6 +770,24 @@ export class LogbookItemComponent implements OnInit {
})
}

isAt(position: 'end' | 'start', scrollPortion = 0, offset = 1) {
const element = this.snippetContainerRef?.nativeElement;
if (element)
return position === 'end'? this.stillToScrollToEnd(element, scrollPortion, offset) <= 0: element.scrollTop <= offset;
}

private stillToScrollToEnd(element: Element, scrollPortion: number, offset: number) {
return element.scrollHeight - element.clientHeight * (1 + scrollPortion) - element.scrollTop - offset;
}

scrollOnClickTo(position: 'end' | 'start') {
const scrollServicePosition = position === 'end'? 'isEOF' : 'isBOF';
if (this.logbookScrollService[scrollServicePosition])
this.scrollWindowTo(position);
else
this.scrollTo(position);
}

addContent(isMessage = false) {
this.forceScrollToEnd = true;
console.log(this.editorContentRef.editorInstance.prel_filestorage);
Expand Down Expand Up @@ -801,7 +838,7 @@ export class LogbookItemComponent implements OnInit {
}

private _indexOrder(i: number) {
if (this.config.view.order[0].split(" ")[1] == 'DESC') {
if (this.isDescending) {
return this.logbookCount - i
}
return i + 1
Expand Down

0 comments on commit d97214f

Please sign in to comment.