1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-13 23:03:32 +00:00

show toasts on copying, resolves #28

This commit is contained in:
Kyle Spearrin
2018-02-23 16:31:52 -05:00
parent accb4436ba
commit e24a083936
6 changed files with 53 additions and 28 deletions

View File

@@ -1,5 +1,6 @@
import * as template from './password-generator-history.component.html'; import * as template from './password-generator-history.component.html';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2'; import { Angulartics2 } from 'angulartics2';
import { import {
@@ -8,6 +9,7 @@ import {
} from '@angular/core'; } from '@angular/core';
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service'; import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { PasswordHistory } from 'jslib/models/domain/passwordHistory'; import { PasswordHistory } from 'jslib/models/domain/passwordHistory';
@@ -17,10 +19,11 @@ import { PasswordHistory } from 'jslib/models/domain/passwordHistory';
template: template, template: template,
}) })
export class PasswordGeneratorHistoryComponent implements OnInit { export class PasswordGeneratorHistoryComponent implements OnInit {
history: PasswordHistory[]; history: PasswordHistory[] = [];
constructor(private passwordGenerationService: PasswordGenerationService, private analytics: Angulartics2, constructor(private passwordGenerationService: PasswordGenerationService, private analytics: Angulartics2,
private platformUtilsService: PlatformUtilsService) { } private platformUtilsService: PlatformUtilsService, private i18nService: I18nService,
private toasterService: ToasterService) { }
async ngOnInit() { async ngOnInit() {
this.history = await this.passwordGenerationService.getHistory(); this.history = await this.passwordGenerationService.getHistory();
@@ -34,5 +37,6 @@ export class PasswordGeneratorHistoryComponent implements OnInit {
copy(password: string) { copy(password: string) {
this.analytics.eventTrack.next({ action: 'Copied Historical Password' }); this.analytics.eventTrack.next({ action: 'Copied Historical Password' });
this.platformUtilsService.copyToClipboard(password); this.platformUtilsService.copyToClipboard(password);
this.toasterService.popAsync('info', null, this.i18nService.t('valueCopied', this.i18nService.t('password')));
} }
} }

View File

@@ -12,6 +12,7 @@ import {
} from '@angular/core'; } from '@angular/core';
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service'; import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
@Component({ @Component({
@@ -28,7 +29,8 @@ export class PasswordGeneratorComponent implements OnInit {
avoidAmbiguous = false; avoidAmbiguous = false;
constructor(private passwordGenerationService: PasswordGenerationService, private analytics: Angulartics2, constructor(private passwordGenerationService: PasswordGenerationService, private analytics: Angulartics2,
private platformUtilsService: PlatformUtilsService) { } private platformUtilsService: PlatformUtilsService, private i18nService: I18nService,
private toasterService: ToasterService) { }
async ngOnInit() { async ngOnInit() {
this.options = await this.passwordGenerationService.getOptions(); this.options = await this.passwordGenerationService.getOptions();
@@ -73,6 +75,7 @@ export class PasswordGeneratorComponent implements OnInit {
copy() { copy() {
this.analytics.eventTrack.next({ action: 'Copied Generated Password' }); this.analytics.eventTrack.next({ action: 'Copied Generated Password' });
this.platformUtilsService.copyToClipboard(this.password); this.platformUtilsService.copyToClipboard(this.password);
this.toasterService.popAsync('info', null, this.i18nService.t('valueCopied', this.i18nService.t('password')));
} }
select() { select() {

View File

@@ -212,21 +212,15 @@ export class VaultComponent implements OnInit, OnDestroy {
const menu = new remote.Menu(); const menu = new remote.Menu();
menu.append(new remote.MenuItem({ menu.append(new remote.MenuItem({
label: this.i18nService.t('view'), label: this.i18nService.t('view'),
click: () => { click: () => this.functionWithChangeDetection(() => {
this.ngZone.run(async () => {
this.viewCipher(cipher); this.viewCipher(cipher);
this.changeDetectorRef.detectChanges(); }),
});
},
})); }));
menu.append(new remote.MenuItem({ menu.append(new remote.MenuItem({
label: this.i18nService.t('edit'), label: this.i18nService.t('edit'),
click: () => { click: () => this.functionWithChangeDetection(() => {
this.ngZone.run(async () => {
this.editCipher(cipher); this.editCipher(cipher);
this.changeDetectorRef.detectChanges(); }),
});
},
})); }));
switch (cipher.type) { switch (cipher.type) {
@@ -243,13 +237,13 @@ export class VaultComponent implements OnInit, OnDestroy {
if (cipher.login.username != null) { if (cipher.login.username != null) {
menu.append(new remote.MenuItem({ menu.append(new remote.MenuItem({
label: this.i18nService.t('copyUsername'), label: this.i18nService.t('copyUsername'),
click: () => this.platformUtilsService.copyToClipboard(cipher.login.username), click: () => this.copyValue(cipher.login.username, 'username'),
})); }));
} }
if (cipher.login.password != null) { if (cipher.login.password != null) {
menu.append(new remote.MenuItem({ menu.append(new remote.MenuItem({
label: this.i18nService.t('copyPassword'), label: this.i18nService.t('copyPassword'),
click: () => this.platformUtilsService.copyToClipboard(cipher.login.password), click: () => this.copyValue(cipher.login.password, 'password'),
})); }));
} }
break; break;
@@ -260,13 +254,13 @@ export class VaultComponent implements OnInit, OnDestroy {
if (cipher.card.number != null) { if (cipher.card.number != null) {
menu.append(new remote.MenuItem({ menu.append(new remote.MenuItem({
label: this.i18nService.t('copyNumber'), label: this.i18nService.t('copyNumber'),
click: () => this.platformUtilsService.copyToClipboard(cipher.card.number), click: () => this.copyValue(cipher.card.number, 'number'),
})); }));
} }
if (cipher.card.code != null) { if (cipher.card.code != null) {
menu.append(new remote.MenuItem({ menu.append(new remote.MenuItem({
label: this.i18nService.t('copySecurityCode'), label: this.i18nService.t('copySecurityCode'),
click: () => this.platformUtilsService.copyToClipboard(cipher.card.code), click: () => this.copyValue(cipher.card.code, 'securityCode'),
})); }));
} }
break; break;
@@ -488,8 +482,20 @@ export class VaultComponent implements OnInit, OnDestroy {
} }
private addCipherWithChangeDetection(type: CipherType = null) { private addCipherWithChangeDetection(type: CipherType = null) {
this.functionWithChangeDetection(() => this.addCipher(type));
}
private copyValue(value: string, labelI18nKey: string) {
this.functionWithChangeDetection(() => {
this.platformUtilsService.copyToClipboard(value);
this.toasterService.popAsync('info', null,
this.i18nService.t('valueCopied', this.i18nService.t(labelI18nKey)));
});
}
private functionWithChangeDetection(func: Function) {
this.ngZone.run(async () => { this.ngZone.run(async () => {
this.addCipher(type); func();
this.changeDetectorRef.detectChanges(); this.changeDetectorRef.detectChanges();
}); });
} }

View File

@@ -23,7 +23,7 @@
<i class="fa fa-lg fa-share-square-o"></i> <i class="fa fa-lg fa-share-square-o"></i>
</a> </a>
<a class="row-btn" href="#" appStopClick title="{{'copyValue' | i18n}}" <a class="row-btn" href="#" appStopClick title="{{'copyValue' | i18n}}"
(click)="copy(cipher.login.uri, 'URI')"> (click)="copy(cipher.login.uri, 'uri', 'URI')">
<i class="fa fa-lg fa-clipboard"></i> <i class="fa fa-lg fa-clipboard"></i>
</a> </a>
</div> </div>
@@ -35,7 +35,7 @@
</div> </div>
<div class="action-buttons"> <div class="action-buttons">
<a class="row-btn" href="#" appStopClick title="{{'copyUsername' | i18n}}" <a class="row-btn" href="#" appStopClick title="{{'copyUsername' | i18n}}"
(click)="copy(cipher.login.username, 'Username')"> (click)="copy(cipher.login.username, 'username', 'Username')">
<i class="fa fa-lg fa-clipboard"></i> <i class="fa fa-lg fa-clipboard"></i>
</a> </a>
</div> </div>
@@ -53,7 +53,7 @@
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i> [ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a> </a>
<a class="row-btn" href="#" appStopClick title="{{'copyPassword' | i18n}}" <a class="row-btn" href="#" appStopClick title="{{'copyPassword' | i18n}}"
(click)="copy(cipher.login.password, 'Password')"> (click)="copy(cipher.login.password, 'password', 'Password')">
<i class="fa fa-lg fa-clipboard"></i> <i class="fa fa-lg fa-clipboard"></i>
</a> </a>
</div> </div>
@@ -76,7 +76,7 @@
</span> </span>
<div class="action-buttons"> <div class="action-buttons">
<a class="row-btn" href="#" appStopClick title="{{'copyValue' | i18n}}" <a class="row-btn" href="#" appStopClick title="{{'copyValue' | i18n}}"
(click)="copy(totpCode, 'TOTP')"> (click)="copy(totpCode, 'verificationCodeTotp', 'TOTP')">
<i class="fa fa-lg fa-clipboard"></i> <i class="fa fa-lg fa-clipboard"></i>
</a> </a>
</div> </div>
@@ -95,7 +95,7 @@
</div> </div>
<div class="action-buttons"> <div class="action-buttons">
<a class="row-btn" href="#" appStopClick title="{{'copyNumber' | i18n}}" <a class="row-btn" href="#" appStopClick title="{{'copyNumber' | i18n}}"
(click)="copy(cipher.card.number, 'Number')"> (click)="copy(cipher.card.number, 'number', 'Number')">
<i class="fa fa-lg fa-clipboard"></i> <i class="fa fa-lg fa-clipboard"></i>
</a> </a>
</div> </div>
@@ -115,7 +115,7 @@
</div> </div>
<div class="action-buttons"> <div class="action-buttons">
<a class="row-btn" href="#" appStopClick title="{{'copySecurityCode' | i18n}}" <a class="row-btn" href="#" appStopClick title="{{'copySecurityCode' | i18n}}"
(click)="copy(cipher.card.code, 'Security Code')"> (click)="copy(cipher.card.code, 'securityCode', 'Security Code')">
<i class="fa fa-lg fa-clipboard"></i> <i class="fa fa-lg fa-clipboard"></i>
</a> </a>
</div> </div>
@@ -207,7 +207,7 @@
</a> </a>
<a class="row-btn" href="#" appStopClick title="{{'copyValue' | i18n}}" <a class="row-btn" href="#" appStopClick title="{{'copyValue' | i18n}}"
*ngIf="field.value && field.type !== fieldType.Boolean" *ngIf="field.value && field.type !== fieldType.Boolean"
(click)="copy(field.value, 'Field')"> (click)="copy(field.value, 'value', 'Field')">
<i class="fa fa-lg fa-clipboard"></i> <i class="fa fa-lg fa-clipboard"></i>
</a> </a>
</div> </div>

View File

@@ -96,13 +96,15 @@ export class ViewComponent implements OnChanges, OnDestroy {
this.platformUtilsService.launchUri(this.cipher.login.uri); this.platformUtilsService.launchUri(this.cipher.login.uri);
} }
copy(value: string, aType: string) { copy(value: string, typeI18nKey: string, aType: string) {
if (value == null) { if (value == null) {
return; return;
} }
this.analytics.eventTrack.next({ action: 'Copied ' + aType }); this.analytics.eventTrack.next({ action: 'Copied ' + aType });
this.platformUtilsService.copyToClipboard(value); this.platformUtilsService.copyToClipboard(value);
this.toasterService.popAsync('info', null,
this.i18nService.t('valueCopied', this.i18nService.t(typeI18nKey)));
} }
async downloadAttachment(attachment: AttachmentView) { async downloadAttachment(attachment: AttachmentView) {

View File

@@ -941,5 +941,15 @@
}, },
"quitBitwarden": { "quitBitwarden": {
"message": "Quit Bitwarden" "message": "Quit Bitwarden"
},
"valueCopied": {
"message": "$VALUE$ copied",
"description": "Value has been copied to the clipboard.",
"placeholders": {
"value": {
"content": "$1",
"example": "Password"
}
}
} }
} }