Skip to content
This repository was archived by the owner on Mar 8, 2020. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/composer-playground/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ import { SampleBusinessNetworkService } from './services/samplebusinessnetwork.s
import { AboutService } from './services/about.service';
import { AlertService } from './services/alert.service';
import { EditorService } from './services/editor.service';
import { ScrollToElementDirective } from './directives/scroll';

let actionBasedIcons = require.context('../assets/svg/action-based', false, /.*\.svg$/);
actionBasedIcons.keys().forEach(actionBasedIcons);
Expand Down Expand Up @@ -145,6 +146,7 @@ type StoreType = {
RegistryComponent,
ReplaceComponent,
ResourceComponent,
ScrollToElementDirective,
SuccessComponent,
SwitchIdentityComponent,
TestComponent,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './scroll-to-element.directive';
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/* tslint:disable:no-unused-variable */
/* tslint:disable:no-unused-expression */
/* tslint:disable:no-var-requires */
/* tslint:disable:max-classes-per-file */
import { ComponentFixture, TestBed, async, fakeAsync, tick, inject } from '@angular/core/testing';
import { Component, Renderer, QueryList, ElementRef } from '@angular/core';
import { By } from '@angular/platform-browser';

import * as sinon from 'sinon';
import * as chai from 'chai';
import { ScrollToElementDirective } from './scroll-to-element.directive';

let should = chai.should();

@Component({
selector: 'editorFileList',
template: `
<div scroll-to-element [elementId]="listItem">
<ul>
<li #editorFileList id="editorFileList0"><div>Item0</div></li>
<li #editorFileList id="editorFileList1"><div>Item1</div></li>
<li #editorFileList id="editorFileList2"><div>Item2</div></li>
</ul>
</div>`
})

class TestComponent {
listItem: string = 'editorFileList1';
}

describe('ScrollToElementDirective', () => {
let component: TestComponent;
let fixture: ComponentFixture<TestComponent>;

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [TestComponent, ScrollToElementDirective],
providers: [Renderer]
})
.compileComponents();
}));

it('should create the directive', async(fakeAsync(() => {
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
fixture.detectChanges();

tick();

component.should.be.ok;
})));

describe('#retreiveSelectedItem', () => {

it('should return an array of nativeElement.id\'s that match the selected', () => {
let directiveEl = fixture.debugElement.query(By.directive(ScrollToElementDirective));
let directiveInstance = directiveEl.injector.get(ScrollToElementDirective);

let matchItem: ElementRef[] = directiveInstance.retreiveSelectedItem();
matchItem[0].nativeElement.id.should.equal('editorFileList1');
});

it('should return an empty array if no matches for nativeElement.id\'s', () => {
let directiveEl = fixture.debugElement.query(By.directive(ScrollToElementDirective));
let directiveInstance = directiveEl.injector.get(ScrollToElementDirective);

directiveInstance.items = new QueryList<ElementRef>();

let emptyList = new QueryList<ElementRef>();
let matchItem: ElementRef[] = directiveInstance.retreiveSelectedItem();
matchItem.should.be.empty;
});
});

describe('#performScrollAction', () => {

it('should call stepVerticalScoll', async(fakeAsync(() => {
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
let directiveEl = fixture.debugElement.query(By.directive(ScrollToElementDirective));
let directiveInstance = directiveEl.injector.get(ScrollToElementDirective);
let stepSpy = sinon.spy(directiveInstance, 'stepVerticalScoll');

fixture.detectChanges();
directiveInstance.performScrollAction();

// Big fake step to ensure all setTimeout actinos completed
tick(1000);

// Check initial call based on test information was correct
stepSpy.getCall(0).args[0].should.equal(0.08, 0);
})));

it('should not action if no items matched for selected element', () => {
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
let directiveEl = fixture.debugElement.query(By.directive(ScrollToElementDirective));
let directiveInstance = directiveEl.injector.get(ScrollToElementDirective);
let stepSpy = sinon.spy(directiveInstance, 'stepVerticalScoll');

directiveInstance.items = new QueryList<ElementRef>();

directiveInstance.performScrollAction();

// Check no call
stepSpy.should.not.have.been.called;
});
});

describe('#isOvershoot', () => {

it('should detect positive overshoot', () => {
let directiveEl = fixture.debugElement.query(By.directive(ScrollToElementDirective));
let directiveInstance = directiveEl.injector.get(ScrollToElementDirective);

directiveInstance.isOvershoot(1, 10, 1).should.be.true;
});

it('should detect negative overshoot', () => {
let directiveEl = fixture.debugElement.query(By.directive(ScrollToElementDirective));
let directiveInstance = directiveEl.injector.get(ScrollToElementDirective);

directiveInstance.isOvershoot(-1, -10, -1).should.be.true;
});

});

describe('inputs', () => {

it('should do nothing if data not initialised', async(fakeAsync(() => {
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
let directiveEl = fixture.debugElement.query(By.directive(ScrollToElementDirective));
let directiveInstance = directiveEl.injector.get(ScrollToElementDirective);

component.listItem = 'editorFileList2';
let scrollMock = sinon.stub(directiveInstance, 'performScrollAction');

fixture.detectChanges();
tick();

scrollMock.should.not.have.been.called;
})));

it('should call performScrollAction and data initialised', async(fakeAsync(() => {
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
let directiveEl = fixture.debugElement.query(By.directive(ScrollToElementDirective));
let directiveInstance = directiveEl.injector.get(ScrollToElementDirective);

// initialise data
directiveInstance.ngAfterContentInit();

// set input
component.listItem = 'editorFileList2';
let scrollMock = sinon.stub(directiveInstance, 'performScrollAction');

fixture.detectChanges();
tick();

scrollMock.should.have.been.called;
})));

it('should update when selecteditem changes and data initialised', async(fakeAsync(() => {
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
let directiveEl = fixture.debugElement.query(By.directive(ScrollToElementDirective));
let directiveInstance = directiveEl.injector.get(ScrollToElementDirective);

// initialise data
directiveInstance.ngAfterContentInit();

component.listItem = 'editorFileList2';
let scrollMock = sinon.stub(directiveInstance, 'performScrollAction');

fixture.detectChanges();
tick();

scrollMock.should.have.been.called;
})));
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import {
Directive,
ElementRef,
Input,
Renderer,
ContentChildren,
AfterContentInit,
QueryList
} from '@angular/core';

@Directive({
selector: '[scroll-to-element]',
})

export class ScrollToElementDirective implements AfterContentInit {

@Input()
set elementId(elementId) {
this._thing = elementId;
if (this._thing && this.initialised) {
this.performScrollAction();
}
}

@ContentChildren('editorFileList') items: QueryList<ElementRef>;

private _thing = null;
private initialised = false;

constructor(private el: ElementRef, private renderer: Renderer) {
}

ngAfterContentInit() {
this.initialised = true;
}

performScrollAction() {
let element = this.el;
let selectedItem = this.retreiveSelectedItem();
if (selectedItem && selectedItem.length > 0) {
let parentOffset = element.nativeElement.offsetTop;
let selectOffset = selectedItem[0].nativeElement.offsetTop;

let endScrollTop = selectOffset - parentOffset - 10;
let startScrollTop = element.nativeElement.scrollTop;
let scrollDiff = startScrollTop - endScrollTop;

let steps = 100; let timer = 0; let slow = 4;
let step = scrollDiff / steps;
let stepTarget = startScrollTop - step;
while (steps > 0) {
this.stepVerticalScoll(stepTarget, slow * timer);
timer++; // slow on approach to target
steps--; // while condition
stepTarget -= step;
// Prevent overshoot
if ( this.isOvershoot(scrollDiff, endScrollTop, stepTarget) ) {
steps = 0;
}
// Final adjust
if (steps === 0) {
this.stepVerticalScoll(endScrollTop, slow * timer);
}
}
}
}

isOvershoot(scrollDiff, endScrollTop, stepTarget) {
if (scrollDiff < 0) {
return stepTarget > endScrollTop;
} else {
return stepTarget < endScrollTop;
}
}

retreiveSelectedItem() {
return this.items.filter( (item) => { return item.nativeElement.id === this._thing; });
}

stepVerticalScoll(yLocation, duration) {
setTimeout(() => {
this.renderer.setElementProperty(this.el.nativeElement, 'scrollTop', yLocation);
}, duration);
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<section class="side-bar">
<div class="files">
<span>Files</span>
<perfect-scrollbar class="side-bar-nav">
<perfect-scrollbar class="side-bar-nav" scroll-to-element [elementId]="listItem">
<ul>
<li *ngFor="let file of files" [class.active]="file.id === currentFile.id" (click)="setCurrentFile(file)">
<li #editorFileList id="editorFileList{{fileIndex}}" *ngFor="let file of files; let fileIndex = index" [class.active]="file.id === currentFile.id" (click)="setCurrentFile(file)">
<div class="flex-container">
<div class="flex">
<h3 [class.error]="file.invalid" *ngIf="file.package">Package Details</h3>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ app-editor {

.side-bar-nav {
padding: 0;
max-height: 25rem;
max-height: 60vh;
overflow-y: scroll;
.error {
color: $first-warning;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { SampleBusinessNetworkService } from '../services/samplebusinessnetwork.service';
import { AlertService } from '../services/alert.service';
import { ModelFile, Script, AclManager, AclFile } from 'composer-common';
import { ScrollToElementDirective } from '../directives/scroll/scroll-to-element.directive';

import * as sinon from 'sinon';
import * as chai from 'chai';
Expand Down Expand Up @@ -90,7 +91,7 @@ describe('EditorComponent', () => {

TestBed.configureTestingModule({
imports: [FormsModule],
declarations: [EditorComponent, MockEditorFileDirective, MockPerfectScrollBarDirective ],
declarations: [EditorComponent, MockEditorFileDirective, MockPerfectScrollBarDirective, ScrollToElementDirective ],
providers: [
{provide: SampleBusinessNetworkService, useValue: mockBusinessNetworkService},
{provide: AdminService, useValue: mockAdminService},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ export class EditorComponent implements OnInit, OnDestroy {
private inputFileNameArray: string[] = null ; // This is the input 'FileName' before the currentFile is updated
private fileNameError: string = null;

private listItem; // Used in html passage for auto scroll action

constructor(private adminService: AdminService,
private clientService: ClientService,
private initializationService: InitializationService,
Expand Down Expand Up @@ -133,6 +135,7 @@ export class EditorComponent implements OnInit, OnDestroy {
}

setCurrentFile(file) {
this.listItem = 'editorFileList' + this.findFileIndex(true, file.id);
let always = (this.currentFile === null || file.readme || file.acl);
let conditional = (always || this.currentFile.id !== file.id || this.currentFile.displayID !== file.displayID);
if ( always || conditional ) {
Expand Down