context menu options for cipher listing

This commit is contained in:
Kyle Spearrin 2018-02-16 13:59:46 -05:00
parent 6c0148bb96
commit 34b3890647
8 changed files with 146 additions and 12 deletions

View File

@ -9,7 +9,8 @@
<ng-container *ngIf="(ciphers | searchCiphers: searchText) as searchedCiphers">
<div class="list" *ngIf="searchedCiphers.length > 0">
<a *ngFor="let c of searchedCiphers" appStopClick (click)="cipherClicked(c)"
href="#" title="{{'viewItem' | i18n}}" [ngClass]="{'active': c.id === activeCipherId}">
(contextmenu)="cipherRightClicked(c)" href="#" title="{{'viewItem' | i18n}}"
[ngClass]="{'active': c.id === activeCipherId}">
<app-vault-icon [cipher]="c"></app-vault-icon>
<span class="text">
{{c.name}}
@ -32,7 +33,8 @@
</ng-container>
</div>
<div class="footer">
<button appBlurClick (click)="addCipher()" class="block primary" title="{{'addItem' | i18n}}">
<button appBlurClick (click)="addCipher()" (contextmenu)="addCipherOptions()"
class="block primary" title="{{'addItem' | i18n}}">
<i class="fa fa-plus fa-lg"></i>
</button>
</div>

View File

@ -18,7 +18,9 @@ import { CipherView } from 'jslib/models/view/cipherView';
export class CiphersComponent {
@Input() activeCipherId: string = null;
@Output() onCipherClicked = new EventEmitter<CipherView>();
@Output() onCipherRightClicked = new EventEmitter<CipherView>();
@Output() onAddCipher = new EventEmitter();
@Output() onAddCipherOptions = new EventEmitter();
loaded: boolean = false;
ciphers: CipherView[] = [];
@ -51,7 +53,15 @@ export class CiphersComponent {
this.onCipherClicked.emit(cipher);
}
cipherRightClicked(cipher: CipherView) {
this.onCipherRightClicked.emit(cipher);
}
addCipher() {
this.onAddCipher.emit();
}
addCipherOptions() {
this.onAddCipherOptions.emit();
}
}

View File

@ -11,7 +11,9 @@
<app-vault-ciphers id="items"
[activeCipherId]="cipherId"
(onCipherClicked)="viewCipher($event)"
(onAddCipher)="addCipher($event)">
(onCipherRightClicked)="viewCipherMenu($event)"
(onAddCipher)="addCipher($event)"
(onAddCipherOptions)="addCipherOptions($event)">
</app-vault-ciphers>
<app-vault-view id="details"
*ngIf="cipherId && action === 'view'"

View File

@ -1,5 +1,7 @@
import * as template from './vault.component.html';
import { remote } from 'electron';
import { Location } from '@angular/common';
import {
ChangeDetectorRef,
@ -38,6 +40,7 @@ import { FolderView } from 'jslib/models/view/folderView';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { SyncService } from 'jslib/abstractions/sync.service';
const SyncInterval = 6 * 60 * 60 * 1000; // 6 hours
@ -69,8 +72,8 @@ export class VaultComponent implements OnInit, OnDestroy {
private componentFactoryResolver: ComponentFactoryResolver, private i18nService: I18nService,
private broadcasterService: BroadcasterService, private changeDetectorRef: ChangeDetectorRef,
private ngZone: NgZone, private syncService: SyncService, private analytics: Angulartics2,
private toasterService: ToasterService, private messagingService: MessagingService) {
}
private toasterService: ToasterService, private messagingService: MessagingService,
private platformUtilsService: PlatformUtilsService) { }
async ngOnInit() {
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
@ -205,6 +208,74 @@ export class VaultComponent implements OnInit, OnDestroy {
this.go();
}
viewCipherMenu(cipher: CipherView) {
const menu = new remote.Menu();
menu.append(new remote.MenuItem({
label: this.i18nService.t('view'),
click: () => {
this.ngZone.run(async () => {
this.viewCipher(cipher);
this.changeDetectorRef.detectChanges();
});
},
}));
menu.append(new remote.MenuItem({
label: this.i18nService.t('edit'),
click: () => {
this.ngZone.run(async () => {
this.editCipher(cipher);
this.changeDetectorRef.detectChanges();
});
},
}));
switch (cipher.type) {
case CipherType.Login:
if (cipher.login.canLaunch || cipher.login.username != null || cipher.login.password != null) {
menu.append(new remote.MenuItem({ type: 'separator' }));
}
if (cipher.login.canLaunch) {
menu.append(new remote.MenuItem({
label: this.i18nService.t('launch'),
click: () => this.platformUtilsService.launchUri(cipher.login.uri),
}));
}
if (cipher.login.username != null) {
menu.append(new remote.MenuItem({
label: this.i18nService.t('copyUsername'),
click: () => this.platformUtilsService.copyToClipboard(cipher.login.username),
}));
}
if (cipher.login.password != null) {
menu.append(new remote.MenuItem({
label: this.i18nService.t('copyPassword'),
click: () => this.platformUtilsService.copyToClipboard(cipher.login.password),
}));
}
break;
case CipherType.Card:
if (cipher.card.number != null || cipher.card.code != null) {
menu.append(new remote.MenuItem({ type: 'separator' }));
}
if (cipher.card.number != null) {
menu.append(new remote.MenuItem({
label: this.i18nService.t('copyNumber'),
click: () => this.platformUtilsService.copyToClipboard(cipher.card.number),
}));
}
if (cipher.card.code != null) {
menu.append(new remote.MenuItem({
label: this.i18nService.t('copySecurityCode'),
click: () => this.platformUtilsService.copyToClipboard(cipher.card.code),
}));
}
break;
default:
break;
}
menu.popup(remote.getCurrentWindow());
}
editCipher(cipher: CipherView) {
if (this.action === 'edit' && this.cipherId === cipher.id) {
return;
@ -226,6 +297,27 @@ export class VaultComponent implements OnInit, OnDestroy {
this.go();
}
addCipherOptions() {
const menu = new remote.Menu();
menu.append(new remote.MenuItem({
label: this.i18nService.t('typeLogin'),
click: () => this.addCipherWithChangeDetection(CipherType.Login),
}));
menu.append(new remote.MenuItem({
label: this.i18nService.t('typeCard'),
click: () => this.addCipherWithChangeDetection(CipherType.Card),
}));
menu.append(new remote.MenuItem({
label: this.i18nService.t('typeIdentity'),
click: () => this.addCipherWithChangeDetection(CipherType.Identity),
}));
menu.append(new remote.MenuItem({
label: this.i18nService.t('typeSecureNote'),
click: () => this.addCipherWithChangeDetection(CipherType.SecureNote),
}));
menu.popup(remote.getCurrentWindow());
}
async savedCipher(cipher: CipherView) {
this.cipherId = cipher.id;
this.action = 'view';
@ -394,4 +486,11 @@ export class VaultComponent implements OnInit, OnDestroy {
const url = this.router.createUrlTree(['vault'], { queryParams: queryParams }).toString();
this.location.go(url);
}
private addCipherWithChangeDetection(type: CipherType = null) {
this.ngZone.run(async () => {
this.addCipher(type);
this.changeDetectorRef.detectChanges();
});
}
}

View File

@ -34,7 +34,7 @@
{{cipher.login.username}}
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick title="{{'copyValue' | i18n}}"
<a class="row-btn" href="#" appStopClick title="{{'copyUsername' | i18n}}"
(click)="copy(cipher.login.username, 'Username')">
<i class="fa fa-lg fa-clipboard"></i>
</a>
@ -52,7 +52,7 @@
<i class="fa fa-lg"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
<a class="row-btn" href="#" appStopClick title="{{'copyValue' | i18n}}"
<a class="row-btn" href="#" appStopClick title="{{'copyPassword' | i18n}}"
(click)="copy(cipher.login.password, 'Password')">
<i class="fa fa-lg fa-clipboard"></i>
</a>
@ -94,7 +94,7 @@
{{cipher.card.number}}
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick title="{{'copyValue' | i18n}}"
<a class="row-btn" href="#" appStopClick title="{{'copyNumber' | i18n}}"
(click)="copy(cipher.card.number, 'Number')">
<i class="fa fa-lg fa-clipboard"></i>
</a>
@ -114,7 +114,7 @@
{{cipher.card.code}}
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick title="{{'copyValue' | i18n}}"
<a class="row-btn" href="#" appStopClick title="{{'copySecurityCode' | i18n}}"
(click)="copy(cipher.card.code, 'Security Code')">
<i class="fa fa-lg fa-clipboard"></i>
</a>

View File

@ -78,7 +78,8 @@
"message": "Launch"
},
"copyValue": {
"message": "Copy Value"
"message": "Copy Value",
"description": "Copy value to clipboard"
},
"toggleVisibility": {
"message": "Toggle Visibility"
@ -802,5 +803,16 @@
},
"unknown": {
"message": "Unknown"
},
"copyUsername": {
"message": "Copy Username"
},
"copyNumber": {
"message": "Copy Number",
"description": "Copy credit card number"
},
"copySecurityCode": {
"message": "Copy Security Code",
"description": "Copy credit card security code (CVV)"
}
}

View File

@ -8,8 +8,8 @@ import { autoUpdater } from 'electron-updater';
import { Main } from '../main';
import {
isDev,
isAppImage,
isDev,
} from '../scripts/utils';
const UpdaterCheckInitalDelay = 5 * 1000; // 5 seconds

View File

@ -1,4 +1,8 @@
import { remote, shell } from 'electron';
import {
clipboard,
remote,
shell,
} from 'electron';
import { isDev } from '../scripts/utils';
@ -149,4 +153,9 @@ export class DesktopPlatformUtilsService implements PlatformUtilsService {
isDev(): boolean {
return isDev();
}
copyToClipboard(text: string, options?: any): void {
const type = options ? options.type : null;
clipboard.writeText(text, type);
}
}