Skip to content

Commit

Permalink
Delete pinned entities from dropdown (#1473)
Browse files Browse the repository at this point in the history
* delete button added to html, no functionality #1379

* pinned items can now be removed from dropdown

* Delete Button icon added #1379

* Add Pool and Certificate Titles created for bookmark dropdown

* adding context menu to favorite ddl

* Favorites unpin with middle mouse click #1379

* couple of fixes

* small fixed based on comments #1379

* fix up CSS

* lint

* tests for remove logic

* lint error

* small fixes

* add other type to methods

* title to [title]
  • Loading branch information
nehal-j authored Jul 4, 2018
1 parent f74d391 commit 771472c
Show file tree
Hide file tree
Showing 10 changed files with 167 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import "./certificate-create-dialog.scss";
export class CertificateCreateDialogComponent {
public file: File;
public form: FormGroup;
public title = "Add certificate";

constructor(
private formBuilder: FormBuilder,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { List } from "immutable";
import { BehaviorSubject, Observable } from "rxjs";

import { NavigableRecord, PinnableEntity, PinnedEntityType } from "@batch-flask/core";
import { ContextMenuService } from "@batch-flask/ui";
import { DropdownModule } from "@batch-flask/ui/dropdown";
import { AccountService, PinnedEntityService } from "app/services";
import * as Fixtures from "test/fixture";
Expand Down Expand Up @@ -35,6 +36,12 @@ describe("PinnedDropDownComponent", () => {
favorites = new BehaviorSubject(List([]));
pinServiceSpy = {
favorites: favorites.asObservable(),
unPinFavorite: jasmine.createSpy("unPinFavorite").and.callFake((favorite) => {
// remove from fav list
const favArray = favorites.value.toArray();
favArray.splice(0, 1);
favorites.next(List(favArray));
}),
};

accountServiceSpy = {
Expand All @@ -52,6 +59,7 @@ describe("PinnedDropDownComponent", () => {
providers: [
{ provide: AccountService, useValue: accountServiceSpy },
{ provide: PinnedEntityService, useValue: pinServiceSpy },
{ provide: ContextMenuService, useValue: null },
],
schemas: [NO_ERRORS_SCHEMA],
});
Expand All @@ -69,6 +77,8 @@ describe("PinnedDropDownComponent", () => {
expect(component.entityType(createPin(PinnedEntityType.Job))).toBe("Batch job");
expect(component.entityType(createPin(PinnedEntityType.Pool))).toBe("Batch pool");
expect(component.entityType(createPin(PinnedEntityType.StorageContainer))).toBe("Storage container");
expect(component.entityType(createPin(PinnedEntityType.Certificate))).toBe("Batch certificate");
expect(component.entityType(createPin(PinnedEntityType.JobSchedule))).toBe("Batch job schedule");
expect(component.entityType(createPin(null))).toBe("unknown");
});

Expand All @@ -77,6 +87,8 @@ describe("PinnedDropDownComponent", () => {
expect(component.entityIcon(createPin(PinnedEntityType.Job))).toBe("fa-tasks");
expect(component.entityIcon(createPin(PinnedEntityType.Pool))).toBe("fa-database");
expect(component.entityIcon(createPin(PinnedEntityType.StorageContainer))).toBe("fa-cloud-upload");
expect(component.entityIcon(createPin(PinnedEntityType.Certificate))).toBe("fa-certificate");
expect(component.entityIcon(createPin(PinnedEntityType.JobSchedule))).toBe("fa-calendar");
expect(component.entityIcon(createPin(null))).toBe("fa-question");
});
});
Expand Down Expand Up @@ -117,14 +129,15 @@ describe("PinnedDropDownComponent", () => {
const items = fixture.debugElement.queryAll(By.css(".dropdown-item"));
expect(items.length).toBe(1);
expect(items[0].nativeElement.textContent).toContain("my-job-fred");
expect(items[0].nativeElement.textContent).toContain("Batch job");

const icon = items[0].query(By.css(".fa.fa-tasks"));
expect(icon.nativeElement).toBeDefined();
// type is now in icon title
const icon = items[0].query(By.css(".fa.fa-tasks")).nativeElement;
expect(icon).toBeDefined();
expect(icon.getAttribute("title")).toBe("Batch job");
});
});

describe("when there are more than one favorites", () => {
describe("when there are more than one favorite", () => {
beforeEach(() => {
favorites.next(List([
Fixtures.pinnable.create({
Expand Down Expand Up @@ -159,11 +172,34 @@ describe("PinnedDropDownComponent", () => {
it("pool should show name over id", () => {
const items = debugElement.queryAll(By.css(".dropdown-item"));
expect(items[0].nativeElement.textContent).toContain("my-job-matt");
expect(items[0].nativeElement.textContent).toContain("Batch job");

expect(items[1].nativeElement.textContent).toContain("my-name-is-bob");
expect(items[1].nativeElement.textContent).not.toContain("my-pool-bob");
expect(items[1].nativeElement.textContent).toContain("Batch pool");
});
});

describe("when we remove favorites", () => {
beforeEach(() => {
favorites.next(List([
Fixtures.pinnable.create({
id: "my-apple",
routerLink: ["/certificates", "my-apple"],
pinnableType: PinnedEntityType.Certificate,
url: "https://myaccount.westus.batch.com/jobs/my-apple",
}),
]));

dropDownButton.nativeElement.click();
fixture.detectChanges();
});

it("should be one favorite", () => {
expect(favorites.value.count()).toBe(1);
});

it("should remove favorite", () => {
component.removeFavorite(favorites.value.toArray()[0] as any);
expect(pinServiceSpy.unPinFavorite).toHaveBeenCalledTimes(1);
expect(favorites.value.count()).toBe(0);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { NavigationEnd, Router } from "@angular/router";
import { List } from "immutable";
import { Observable, Subscription } from "rxjs";

import { PinnableEntity, PinnedEntityType } from "@batch-flask/core";
import { DropdownComponent } from "@batch-flask/ui";
import { MouseButton, PinnableEntity, PinnedEntityType } from "@batch-flask/core";
import { ContextMenu, ContextMenuItem, ContextMenuService, DropdownComponent } from "@batch-flask/ui";
import { AccountService, PinnedEntityService } from "app/services";

import "./pinned-dropdown.scss";
Expand All @@ -26,7 +26,8 @@ export class PinnedDropDownComponent implements OnInit, OnDestroy {
constructor(
private router: Router,
private changeDetector: ChangeDetectorRef,
public pinnedEntityService: PinnedEntityService,
private contextMenuService: ContextMenuService,
private pinnedEntityService: PinnedEntityService,
private accountService: AccountService) {

this.favorites = this.pinnedEntityService.favorites;
Expand Down Expand Up @@ -59,6 +60,16 @@ export class PinnedDropDownComponent implements OnInit, OnDestroy {
this._dropdown.close();
}

public removeFavorite(favorite: PinnableEntity) {
this.pinnedEntityService.unPinFavorite(favorite);
}

public handleMiddleMouseUp(event: MouseEvent, favorite: PinnableEntity) {
if (event.button === MouseButton.middle) {
this.pinnedEntityService.unPinFavorite(favorite);
}
}

public entityType(favorite: PinnableEntity) {
switch (favorite.pinnableType) {
case PinnedEntityType.Application:
Expand Down Expand Up @@ -100,4 +111,10 @@ export class PinnedDropDownComponent implements OnInit, OnDestroy {
public trackPinnned(index, entity: PinnableEntity) {
return `${entity.pinnableType}/${entity.id}`;
}

public onContextMenu(favorite: PinnableEntity) {
this.contextMenuService.openMenu(new ContextMenu([
new ContextMenuItem("Remove favorite", () => this.pinnedEntityService.unPinFavorite(favorite)),
]));
}
}
27 changes: 14 additions & 13 deletions app/components/layout/pinned-entity-dropdown/pinned-dropdown.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,20 @@
<i class="fa fa-star"></i> {{title}}
</div>
<div bl-dropdown-content>
<bl-clickable *ngFor="let favorite of favorites | async;trackBy: trackPinnned"
class="dropdown-item"
(do)="gotoFavorite(favorite)"
<div *ngFor="let favorite of favorites | async;trackBy: trackPinnned"
class="dropdown-item favorite"
[class.selected]="favorite.url === currentUrl"
[title]="favorite.id">
<i class="fa" [ngClass]="entityIcon(favorite)"></i>
<div class="main">
<div class="alias">{{favorite.name || favorite.id}}</div>
<div class="type">{{entityType(favorite)}}</div>
</div>
<div *ngIf="favorite.url === currentUrl" class="extra">
<i class="fa fa-check"></i>
</div>
</bl-clickable>
(click)="gotoFavorite(favorite)"
(mouseup)="handleMiddleMouseUp($event, favorite)"
(contextmenu)="onContextMenu(favorite)">

<span>
<i class="entity-type fa" [ngClass]="entityIcon(favorite)"
[title]="entityType(favorite)"></i>
{{favorite.name || favorite.id}}
<i *ngIf="favorite.url === currentUrl" class="extra fa fa-check"></i>
</span>
<i class="fa fa-times" (click)="removeFavorite(favorite)"></i>
</div>
</div>
</bl-dropdown>
77 changes: 65 additions & 12 deletions app/components/layout/pinned-entity-dropdown/pinned-dropdown.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,76 @@
@import "app/styles/variables";

bl-pinned-dropdown {
$ddl-width: 280px;
[bl-dropdown-content] {
min-width: 300px;
max-width: 350px;
}

.dropdown-item.favorite {
position: relative;
min-width: 250px;
cursor: pointer;
padding: 0;
display: flex;
align-items: stretch;
justify-content: stretch;

.fa {
font-size: 1.2em;
padding-right: 5px;
&:hover {
i.fa {
color: $primary-color-dark;
}

&.fa-tasks, &.fa-database, &.fa-file-archive-o, &.fa-question, &.fa-cloud-upload, &.fa-certificate, &.fa-calendar {
color: map-get($primary, 300);
&.selected {
color: white;
}
}
}

.main {
width: calc(#{$ddl-width} - 65px);
padding-right: 4px;
&.selected {
font-weight: bold;
color: $primary-color-dark;
}

> span {
flex: 1 1 auto;
text-align: left;
min-width: 20px;
text-overflow: ellipsis;
overflow-x: hidden;
border-right: 1px solid $border-color;
padding: 5px 10px;

&:hover {
.fa {
color: white;
}
}

& i {
margin-right: 5px;

&.fa {
color: $primary-color-dark;
}
}
}

> i.fa.fa-times {
flex: 0 0 30px;
background-color: white;
color: $primary-color-dark;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;

&:hover {
color: white;
background-color: map-get($danger, 400);
}

.type {
font-size: 10px;
&:active {
background-color: map-get($danger, 600);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export class PoolCreateBasicDialogComponent extends DynamicForm<Pool, PoolCreate
public fileUri = "create.pool.batch.json";
public armNetworkOnly = true;
public certificates: Certificate[] = [];
public title = "Add pool";

private _osControl: FormControl;
private _renderingSkuSelected: boolean = false;
Expand Down
12 changes: 8 additions & 4 deletions app/services/pinned-entity.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export class PinnedEntityService {
if (this.isFavorite(entity)) {
return Observable.of(true);
}

const subject = new AsyncSubject();
const favourite: PinnableEntity = {
id: entity.id,
Expand All @@ -68,7 +69,7 @@ export class PinnedEntityService {
return subject.asObservable();
}

public unPinFavorite(entity: NavigableRecord) {
public unPinFavorite(entity: NavigableRecord | PinnableEntity) {
if (!this.isFavorite(entity)) {
return;
}
Expand All @@ -79,16 +80,19 @@ export class PinnedEntityService {
this._saveAccountFavorites();
}

public getEntityType(entity: NavigableRecord): PinnedEntityType {
public getEntityType(entity: NavigableRecord | PinnableEntity): PinnedEntityType {
for (const [type, cls] of pinnedTypeMap) {
if (entity instanceof cls) {
return type as any;
}
}
return null;

/* fallback, casting pinnable as a PinnableEntity */
const pinnable = entity as PinnableEntity;
return pinnable ? pinnable.pinnableType : null;
}

public isFavorite(entity: NavigableRecord): boolean {
public isFavorite(entity: NavigableRecord | PinnableEntity): boolean {
const id = entity.id.toLowerCase();
const favorites = this._favorites.getValue();
const entityType = this.getEntityType(entity);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
OnDestroy,
} from "@angular/core";
import { MouseButton } from "@batch-flask/core";
import { ContextMenu, ContextMenuItem, ContextMenuService } from "@batch-flask/ui/context-menu";
import { Subscription } from "rxjs";

import { SidebarManager } from "../sidebar-manager";
Expand All @@ -22,7 +23,11 @@ export class SidebarBookmarksComponent implements OnDestroy {

private _sub: Subscription;

constructor(public sidebarManager: SidebarManager, private changeDetector: ChangeDetectorRef) {
constructor(
public sidebarManager: SidebarManager,
private contextMenuService: ContextMenuService,
private changeDetector: ChangeDetectorRef) {

this._sub = sidebarManager.references.subscribe((x) => {
this.references = x;
this.changeDetector.markForCheck();
Expand Down Expand Up @@ -59,4 +64,10 @@ export class SidebarBookmarksComponent implements OnDestroy {
return reference.id;
}
}

public onContextMenu(reference: SidebarRef<any>) {
this.contextMenuService.openMenu(new ContextMenu([
new ContextMenuItem("Delete in progress form", () => reference.destroy()),
]));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
<div *ngFor="let reference of references;trackBy: trackReference"
class="dropdown-item bookmark"
(click)="selectBookmark(reference)"
(mouseup)="handleMouseUp($event, reference)">
(mouseup)="handleMouseUp($event, reference)"
(contextmenu)="onContextMenu(reference)">

<span>{{referenceTitle(reference)}}</span>
<i class="fa fa-times" (click)="destroyBookmark(reference)"></i>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ bl-sidebar-bookmarks {
color: $primary-color-dark;
}
}

> span {
flex: 1 1 auto;
text-align: left;
Expand Down Expand Up @@ -55,7 +56,6 @@ bl-sidebar-bookmarks {
}
}


[bl-dropdown-btn] {
height: $header-height;
width: $header-height;
Expand Down

0 comments on commit 771472c

Please sign in to comment.