cipher listings in vault work

This commit is contained in:
Kyle Spearrin 2018-04-05 17:27:31 -04:00
parent c35d60025c
commit dd49b34024
8 changed files with 215 additions and 85 deletions

View File

@ -40,6 +40,7 @@ import { I18nPipe } from 'jslib/angular/pipes/i18n.pipe';
import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe';
import { ActionButtonsComponent } from './components/action-buttons.component';
import { CiphersListComponent } from './components/ciphers-list.component';
import { PopOutComponent } from './components/pop-out.component';
import { IconComponent } from 'jslib/angular/components/icon.component';
@ -66,6 +67,7 @@ import { IconComponent } from 'jslib/angular/components/icon.component';
BlurClickDirective,
BoxRowDirective,
CiphersComponent,
CiphersListComponent,
CurrentTabComponent,
EnvironmentComponent,
FallbackSrcDirective,

View File

@ -0,0 +1,13 @@
<a *ngFor="let c of ciphers" (click)="selectCipher(c)" href="#" appStopClick title="{{title}}"
class="box-content-row box-content-row-flex">
<div class="row-main">
<app-vault-icon [cipher]="c"></app-vault-icon>
<span class="text">
{{c.name}}
<i class="fa fa-share-alt text-muted" *ngIf="c.organizationId" title="{{'shared' | i18n}}"></i>
<i class="fa fa-paperclip text-muted" *ngIf="c.hasAttachments" title="{{'attachments' | i18n}}"></i>
</span>
<span class="detail">{{c.subTitle}}</span>
</div>
<app-action-buttons [cipher]="c" class="action-buttons"></app-action-buttons>
</a>

View File

@ -0,0 +1,40 @@
import * as template from './ciphers-list.component.html';
import {
Component,
EventEmitter,
Input,
Output,
} from '@angular/core';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
import { BrowserApi } from '../../browser/browserApi';
import { CipherType } from 'jslib/enums/cipherType';
import { CipherView } from 'jslib/models/view/cipherView';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { PopupUtilsService } from '../services/popup-utils.service';
@Component({
selector: 'app-ciphers-list',
template: template,
})
export class CiphersListComponent {
@Output() onSelected = new EventEmitter<CipherView>();
@Input() ciphers: CipherView[];
@Input() showView: boolean = false;
@Input() title: string;
cipherType = CipherType;
constructor() { }
selectCipher(c: CipherView) {
this.onSelected.emit(c);
}
}

View File

@ -16,26 +16,12 @@
</header>
<content>
<ng-container *ngIf="(ciphers | searchCiphers: searchText) as searchedCiphers">
<div class="box list" *ngIf="searchedCiphers.length > 0">
<div class="box list only-list" *ngIf="searchedCiphers.length > 0">
<div class="box-header">
Some name here
</div>
<div class="box-content">
<a *ngFor="let c of searchedCiphers" appStopClick (click)="selectCipher(c)"
href="#" title="{{'viewItem' | i18n}}" class="box-content-row box-content-row-flex">
<div class="row-main">
<app-vault-icon [cipher]="c"></app-vault-icon>
<span class="text">
{{c.name}}
<i class="fa fa-share-alt text-muted" *ngIf="c.organizationId"
title="{{'shared' | i18n}}"></i>
<i class="fa fa-paperclip text-muted" *ngIf="c.hasAttachments"
title="{{'attachments' | i18n}}"></i>
</span>
<span class="detail">{{c.subTitle}}</span>
</div>
<app-action-buttons [cipher]="c" class="action-buttons"></app-action-buttons>
</a>
<app-ciphers-list [ciphers]="searchedCiphers" title="{{'viewItem' | i18n}}"></app-ciphers-list>
</div>
</div>
<div class="no-items" *ngIf="!searchedCiphers.length">

View File

@ -6,68 +6,107 @@
<span class="title">{{'myVault' | i18n}}</span>
</div>
<div class="right">
<button appBlurClick (click)="addCipher()" title="{{'addItem' | i18n}}">
<i class="fa fa-plus fa-lg"></i>
</button>
</div>
</header>
<content>
<div class="box">
<div class="box-header">
{{'favorites' | i18n}}
</div>
<div class="box-content">
<a *ngFor="let c of favoriteCiphers" href="#" class="box-content-row" appStopClick appBlurClick>
{{c.name}}
</a>
</div>
</div>
<div class="box">
<div class="box-header">
{{'types' | i18n}}
</div>
<div class="box-content">
<a href="#" class="box-content-row" appStopClick appBlurClick (click)="selectType(cipherType.Login)">
{{'typeLogin' | i18n}}
<span>{{typeCounts.get(cipherType.Login) || 0}}</span>
</a>
<a href="#" class="box-content-row" appStopClick appBlurClick (click)="selectType(cipherType.Card)">
{{'typeCard' | i18n}}
<span>{{typeCounts.get(cipherType.Card) || 0}}</span>
</a>
<a href="#" class="box-content-row" appStopClick appBlurClick (click)="selectType(cipherType.Identity)">
{{'typeIdentity' | i18n}}
<span>{{typeCounts.get(cipherType.Identity) || 0}}</span>
</a>
<a href="#" class="box-content-row" appStopClick appBlurClick (click)="selectType(cipherType.SecureNote)">
{{'typeSecureNote' | i18n}}
<span>{{typeCounts.get(cipherType.SecureNote) || 0}}</span>
</a>
</div>
</div>
<p *ngIf="!loaded" class="text-muted">{{'loading' | i18n}}</p>
<ng-container *ngIf="loaded">
<div class="box">
<div class="box list" *ngIf="favoriteCiphers">
<div class="box-header">
{{'folders' | i18n}}
{{'favorites' | i18n}}
<span class="flex-right">{{favoriteCiphers.length}}</span>
</div>
<div class="box-content">
<a *ngFor="let f of folders" href="#" class="box-content-row" appStopClick appBlurClick
(click)="selectFolder(f)">
{{f.name}}
<span>{{folderCounts.get(f.id) || 0}}</span>
<app-ciphers-list [ciphers]="favoriteCiphers" title="{{'viewItem' | i18n}}"></app-ciphers-list>
</div>
</div>
<div class="box list">
<div class="box-header">
{{'types' | i18n}}
<span class="flex-right">4</span>
</div>
<div class="box-content single-line">
<a href="#" class="box-content-row" appStopClick appBlurClick (click)="selectType(cipherType.Login)">
<div class="icon"><i class="fa fa-fw fa-lg fa-globe"></i></div>
{{'typeLogin' | i18n}}
<div class="flex-right">
<span class="row-sub-label">{{typeCounts.get(cipherType.Login) || 0}}</span>
<i class="fa fa-chevron-right fa-lg"></i>
</div>
</a>
<a href="#" class="box-content-row" appStopClick appBlurClick (click)="selectType(cipherType.Card)">
<div class="icon"><i class="fa fa-fw fa-lg fa-credit-card"></i></div>
{{'typeCard' | i18n}}
<div class="flex-right">
<span class="row-sub-label">{{typeCounts.get(cipherType.Card) || 0}}</span>
<i class="fa fa-chevron-right fa-lg"></i>
</div>
</a>
<a href="#" class="box-content-row" appStopClick appBlurClick (click)="selectType(cipherType.Identity)">
<div class="icon"><i class="fa fa-fw fa-lg fa-id-card-o"></i></div>
{{'typeIdentity' | i18n}}
<div class="flex-right">
<span class="row-sub-label">{{typeCounts.get(cipherType.Identity) || 0}}</span>
<i class="fa fa-chevron-right fa-lg"></i>
</div>
</a>
<a href="#" class="box-content-row" appStopClick appBlurClick (click)="selectType(cipherType.SecureNote)">
<div class="icon"><i class="fa fa-fw fa-lg fa-sticky-note-o"></i></div>
{{'typeSecureNote' | i18n}}
<div class="flex-right">
<span class="row-sub-label">{{typeCounts.get(cipherType.SecureNote) || 0}}</span>
<i class="fa fa-chevron-right fa-lg"></i>
</div>
</a>
</div>
</div>
<div class="box">
<div class="box list">
<div class="box-header">
{{'folders' | i18n}}
<span class="flex-right">{{folderCount}}</span>
</div>
<div class="box-content single-line">
<a *ngFor="let f of folders" href="#" class="box-content-row" appStopClick appBlurClick
(click)="selectFolder(f)">
<div class="icon">
<i class="fa fa-fw fa-lg" [ngClass]="{'fa-folder-open': f.id, 'fa-folder-open-o': !f.id}"></i>
</div>
{{f.name}}
<div class="flex-right">
<span class="row-sub-label">{{folderCounts.get(f.id) || 0}}</span>
<i class="fa fa-chevron-right fa-lg"></i>
</div>
</a>
</div>
</div>
<div class="box list">
<div class="box-header">
{{'collections' | i18n}}
<span class="flex-right">{{collections.length}}</span>
</div>
<div class="box-content">
<div class="box-content single-line">
<a *ngFor="let c of collections" href="#" class="box-content-row" appStopClick appBlurClick
(click)="selectCollection(c)">
<div class="icon"><i class="fa fa-fw fa-lg fa-cube"></i></div>
{{c.name}}
<span>{{collectionCounts.get(c.id) || 0}}</span>
<div class="flex-right">
<span class="row-sub-label">{{collectionCounts.get(c.id) || 0}}</span>
<i class="fa fa-chevron-right fa-lg"></i>
</div>
</a>
</div>
</div>
<div class="box list" *ngIf="showNoFolderCiphers">
<div class="box-header">
{{'noneFolder' | i18n}}
<div class="flex-right">{{noFolderCiphers.length}}</div>
</div>
<div class="box-content">
<app-ciphers-list [ciphers]="noFolderCiphers" title="{{'viewItem' | i18n}}"></app-ciphers-list>
</div>
</div>
</ng-container>
</content>

View File

@ -25,9 +25,12 @@ import { GroupingsComponent as BaseGroupingsComponent } from 'jslib/angular/comp
export class GroupingsComponent extends BaseGroupingsComponent implements OnInit {
ciphers: CipherView[];
favoriteCiphers: CipherView[];
noFolderCiphers: CipherView[];
folderCount: number;
folderCounts = new Map<string, number>();
collectionCounts = new Map<string, number>();
typeCounts = new Map<CipherType, number>();
showNoFolderCiphers = false;
constructor(collectionService: CollectionService, folderService: FolderService,
private cipherService: CipherService, private router: Router) {
@ -35,9 +38,20 @@ export class GroupingsComponent extends BaseGroupingsComponent implements OnInit
}
async ngOnInit() {
super.load();
await super.load();
super.loaded = false;
await this.loadCiphers();
this.showNoFolderCiphers = this.noFolderCiphers != null && this.noFolderCiphers.length < 100 &&
this.collections.length === 0;
if (this.showNoFolderCiphers) {
// Remove "No Folder" from folder listing
this.folders.pop();
this.folderCount = this.folders.length;
} else {
this.folderCount = this.folders.length - 1;
}
super.loaded = true;
}
@ -51,6 +65,13 @@ export class GroupingsComponent extends BaseGroupingsComponent implements OnInit
this.favoriteCiphers.push(c);
}
if (c.folderId == null) {
if (this.noFolderCiphers == null) {
this.noFolderCiphers = [];
}
this.noFolderCiphers.push(c);
}
if (this.typeCounts.has(c.type)) {
this.typeCounts.set(c.type, this.typeCounts.get(c.type) + 1);
} else {

View File

@ -12,6 +12,7 @@
margin: 0 10px 5px 10px;
color: $gray-light;
text-transform: uppercase;
display: flex;
}
.box-content {
@ -227,7 +228,7 @@
width: calc(100% - 25px);
}
.row-sub-icon {
.row-sub-icon, .row-sub-label + i.fa {
color: $list-icon-color;
}
@ -236,6 +237,23 @@
color: $gray-light;
white-space: nowrap;
}
.icon {
display: flex;
justify-content: center;
align-items: center;
float: left;
height: 36px;
width: 34px;
margin-left: -5px;
color: $text-muted;
img {
border-radius: $border-radius;
max-height: 20px;
max-width: 20px;
}
}
}
&.condensed .box-content-row, .box-content-row.condensed {
@ -248,6 +266,17 @@
background-color: initial;
}
}
&.single-line {
.box-content-row {
display: flex;
.icon {
height: initial;
text-align: center;
}
}
}
}
.box-footer {
@ -257,8 +286,6 @@
}
&.list {
margin-bottom: 0;
.box-content {
.box-content-row {
padding: 3px 10px;
@ -274,36 +301,34 @@
padding-left: 5px;
}
.action-buttons {
.row-btn {
padding-left: 5px;
padding-right: 5px;
}
}
.text, .detail {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: block;
color: $text-color;
}
}
.detail {
font-size: $font-size-small;
color: $gray-light;
}
.icon {
display: flex;
justify-content: center;
align-items: center;
float: left;
height: 36px;
width: 34px;
margin-left: -5px;
color: $text-muted;
img {
border-radius: $border-radius;
max-height: 20px;
max-width: 20px;
}
&.single-line {
.box-content-row {
padding-top: 10px;
padding-bottom: 10px;
}
}
}
}
&.only-list {
margin-bottom: 0;
.box-content {
border-bottom: none;
}
}
}

View File

@ -28,6 +28,10 @@ small {
text-align: center;
}
.flex-right {
margin-left: auto;
}
.no-margin {
margin: 0 !important;
}