From 34b3890647f187b6a94035a572ee8a4e0d3ad575 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Fri, 16 Feb 2018 13:59:46 -0500 Subject: [PATCH] context menu options for cipher listing --- src/app/vault/ciphers.component.html | 6 +- src/app/vault/ciphers.component.ts | 10 ++ src/app/vault/vault.component.html | 4 +- src/app/vault/vault.component.ts | 103 ++++++++++++++++++- src/app/vault/view.component.html | 8 +- src/locales/en/messages.json | 14 ++- src/main/updater.main.ts | 2 +- src/services/desktopPlatformUtils.service.ts | 11 +- 8 files changed, 146 insertions(+), 12 deletions(-) diff --git a/src/app/vault/ciphers.component.html b/src/app/vault/ciphers.component.html index 088da5865e..2122b60912 100644 --- a/src/app/vault/ciphers.component.html +++ b/src/app/vault/ciphers.component.html @@ -9,7 +9,8 @@ diff --git a/src/app/vault/ciphers.component.ts b/src/app/vault/ciphers.component.ts index 3bf2494713..851783ca69 100644 --- a/src/app/vault/ciphers.component.ts +++ b/src/app/vault/ciphers.component.ts @@ -18,7 +18,9 @@ import { CipherView } from 'jslib/models/view/cipherView'; export class CiphersComponent { @Input() activeCipherId: string = null; @Output() onCipherClicked = new EventEmitter(); + @Output() onCipherRightClicked = new EventEmitter(); @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(); + } } diff --git a/src/app/vault/vault.component.html b/src/app/vault/vault.component.html index b9d270ada5..5b33b0d6bc 100644 --- a/src/app/vault/vault.component.html +++ b/src/app/vault/vault.component.html @@ -11,7 +11,9 @@ + (onCipherRightClicked)="viewCipherMenu($event)" + (onAddCipher)="addCipher($event)" + (onAddCipherOptions)="addCipherOptions($event)"> { @@ -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(); + }); + } } diff --git a/src/app/vault/view.component.html b/src/app/vault/view.component.html index 8fff134b2f..d58923953d 100644 --- a/src/app/vault/view.component.html +++ b/src/app/vault/view.component.html @@ -34,7 +34,7 @@ {{cipher.login.username}}
- @@ -52,7 +52,7 @@ - @@ -94,7 +94,7 @@ {{cipher.card.number}}
- @@ -114,7 +114,7 @@ {{cipher.card.code}}
- diff --git a/src/locales/en/messages.json b/src/locales/en/messages.json index 400a5bb737..fcfe9f554c 100644 --- a/src/locales/en/messages.json +++ b/src/locales/en/messages.json @@ -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)" } } diff --git a/src/main/updater.main.ts b/src/main/updater.main.ts index 2b5a780a05..8f0ed59c6d 100644 --- a/src/main/updater.main.ts +++ b/src/main/updater.main.ts @@ -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 diff --git a/src/services/desktopPlatformUtils.service.ts b/src/services/desktopPlatformUtils.service.ts index 461f961174..ba9d55be0e 100644 --- a/src/services/desktopPlatformUtils.service.ts +++ b/src/services/desktopPlatformUtils.service.ts @@ -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); + } }