mirror of
https://github.com/bitwarden/browser.git
synced 2024-12-21 16:18:28 +01:00
[PM-13907] [PM-13849] Browser Refresh - Improve launch login UX (#11680)
* [PM-13907] Move canLaunch logic to CipherView * [PM-13907] Add external link icon to vault list items * [PM-13907] Remove launch option from more options dropdown * [PM-13849] Add double click to launch support
This commit is contained in:
parent
b486fcc689
commit
a9d9130f01
@ -578,6 +578,15 @@
|
|||||||
"launchWebsite": {
|
"launchWebsite": {
|
||||||
"message": "Launch website"
|
"message": "Launch website"
|
||||||
},
|
},
|
||||||
|
"launchWebsiteName": {
|
||||||
|
"message": "Launch website $ITEMNAME$",
|
||||||
|
"placeholders": {
|
||||||
|
"itemname": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "Secret item"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"website": {
|
"website": {
|
||||||
"message": "Website"
|
"message": "Website"
|
||||||
},
|
},
|
||||||
|
@ -17,9 +17,6 @@
|
|||||||
{{ "fillAndSave" | i18n }}
|
{{ "fillAndSave" | i18n }}
|
||||||
</button>
|
</button>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<button type="button" bitMenuItem *ngIf="this.canLaunch" (click)="launchCipher()">
|
|
||||||
{{ "launchWebsite" | i18n }}
|
|
||||||
</button>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<button type="button" bitMenuItem (click)="toggleFavorite()">
|
<button type="button" bitMenuItem (click)="toggleFavorite()">
|
||||||
{{ favoriteText | i18n }}
|
{{ favoriteText | i18n }}
|
||||||
|
@ -19,8 +19,6 @@ import {
|
|||||||
} from "@bitwarden/components";
|
} from "@bitwarden/components";
|
||||||
import { PasswordRepromptService } from "@bitwarden/vault";
|
import { PasswordRepromptService } from "@bitwarden/vault";
|
||||||
|
|
||||||
import { BrowserApi } from "../../../../../platform/browser/browser-api";
|
|
||||||
import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils";
|
|
||||||
import { VaultPopupAutofillService } from "../../../services/vault-popup-autofill.service";
|
import { VaultPopupAutofillService } from "../../../services/vault-popup-autofill.service";
|
||||||
import { AddEditQueryParams } from "../add-edit/add-edit-v2.component";
|
import { AddEditQueryParams } from "../add-edit/add-edit-v2.component";
|
||||||
|
|
||||||
@ -91,30 +89,6 @@ export class ItemMoreOptionsComponent implements OnInit {
|
|||||||
await this.vaultPopupAutofillService.doAutofillAndSave(this.cipher, false);
|
await this.vaultPopupAutofillService.doAutofillAndSave(this.cipher, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines if the login cipher can be launched in a new browser tab.
|
|
||||||
*/
|
|
||||||
get canLaunch() {
|
|
||||||
return this.cipher.type === CipherType.Login && this.cipher.login.canLaunch;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Launches the login cipher in a new browser tab.
|
|
||||||
*/
|
|
||||||
async launchCipher() {
|
|
||||||
if (!this.canLaunch) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.cipherService.updateLastLaunchedDate(this.cipher.id);
|
|
||||||
|
|
||||||
await BrowserApi.createNewTab(this.cipher.login.launchUri);
|
|
||||||
|
|
||||||
if (BrowserPopupUtils.inPopup(window)) {
|
|
||||||
BrowserApi.closePopup(window);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggles the favorite status of the cipher and updates it on the server.
|
* Toggles the favorite status of the cipher and updates it on the server.
|
||||||
*/
|
*/
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
bit-item-content
|
bit-item-content
|
||||||
type="button"
|
type="button"
|
||||||
(click)="onViewCipher(cipher)"
|
(click)="onViewCipher(cipher)"
|
||||||
|
(dblclick)="launchCipher(cipher)"
|
||||||
[appA11yTitle]="'viewItemTitle' | i18n: cipher.name"
|
[appA11yTitle]="'viewItemTitle' | i18n: cipher.name"
|
||||||
class="{{ ItemHeightClass }}"
|
class="{{ ItemHeightClass }}"
|
||||||
>
|
>
|
||||||
@ -60,6 +61,16 @@
|
|||||||
{{ "fill" | i18n }}
|
{{ "fill" | i18n }}
|
||||||
</button>
|
</button>
|
||||||
</bit-item-action>
|
</bit-item-action>
|
||||||
|
<bit-item-action *ngIf="!showAutofillButton && cipher.canLaunch">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
bitIconButton="bwi-external-link"
|
||||||
|
size="small"
|
||||||
|
(click)="launchCipher(cipher)"
|
||||||
|
[attr.aria-label]="'launchWebsiteName' | i18n: cipher.name"
|
||||||
|
[title]="'launchWebsiteName' | i18n: cipher.name"
|
||||||
|
></button>
|
||||||
|
</bit-item-action>
|
||||||
<app-item-copy-actions [cipher]="cipher"></app-item-copy-actions>
|
<app-item-copy-actions [cipher]="cipher"></app-item-copy-actions>
|
||||||
<app-item-more-options
|
<app-item-more-options
|
||||||
[cipher]="cipher"
|
[cipher]="cipher"
|
||||||
|
@ -5,6 +5,8 @@ import { Router, RouterLink } from "@angular/router";
|
|||||||
|
|
||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
import {
|
import {
|
||||||
BadgeModule,
|
BadgeModule,
|
||||||
BitItemHeight,
|
BitItemHeight,
|
||||||
@ -18,6 +20,8 @@ import {
|
|||||||
} from "@bitwarden/components";
|
} from "@bitwarden/components";
|
||||||
import { OrgIconDirective, PasswordRepromptService } from "@bitwarden/vault";
|
import { OrgIconDirective, PasswordRepromptService } from "@bitwarden/vault";
|
||||||
|
|
||||||
|
import { BrowserApi } from "../../../../../platform/browser/browser-api";
|
||||||
|
import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils";
|
||||||
import { VaultPopupAutofillService } from "../../../services/vault-popup-autofill.service";
|
import { VaultPopupAutofillService } from "../../../services/vault-popup-autofill.service";
|
||||||
import { PopupCipherView } from "../../../views/popup-cipher.view";
|
import { PopupCipherView } from "../../../views/popup-cipher.view";
|
||||||
import { ItemCopyActionsComponent } from "../item-copy-action/item-copy-actions.component";
|
import { ItemCopyActionsComponent } from "../item-copy-action/item-copy-actions.component";
|
||||||
@ -48,6 +52,12 @@ export class VaultListItemsContainerComponent {
|
|||||||
protected ItemHeightClass = BitItemHeightClass;
|
protected ItemHeightClass = BitItemHeightClass;
|
||||||
protected ItemHeight = BitItemHeight;
|
protected ItemHeight = BitItemHeight;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timeout used to add a small delay when selecting a cipher to allow for double click to launch
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private viewCipherTimeout: number | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The list of ciphers to display.
|
* The list of ciphers to display.
|
||||||
*/
|
*/
|
||||||
@ -108,21 +118,60 @@ export class VaultListItemsContainerComponent {
|
|||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private vaultPopupAutofillService: VaultPopupAutofillService,
|
private vaultPopupAutofillService: VaultPopupAutofillService,
|
||||||
private passwordRepromptService: PasswordRepromptService,
|
private passwordRepromptService: PasswordRepromptService,
|
||||||
|
private cipherService: CipherService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Launches the login cipher in a new browser tab.
|
||||||
|
*/
|
||||||
|
async launchCipher(cipher: CipherView) {
|
||||||
|
if (!cipher.canLaunch) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is a view action pending, clear it
|
||||||
|
if (this.viewCipherTimeout != null) {
|
||||||
|
window.clearTimeout(this.viewCipherTimeout);
|
||||||
|
this.viewCipherTimeout = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.cipherService.updateLastLaunchedDate(cipher.id);
|
||||||
|
|
||||||
|
await BrowserApi.createNewTab(cipher.login.launchUri);
|
||||||
|
|
||||||
|
if (BrowserPopupUtils.inPopup(window)) {
|
||||||
|
BrowserApi.closePopup(window);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async doAutofill(cipher: PopupCipherView) {
|
async doAutofill(cipher: PopupCipherView) {
|
||||||
await this.vaultPopupAutofillService.doAutofill(cipher);
|
await this.vaultPopupAutofillService.doAutofill(cipher);
|
||||||
}
|
}
|
||||||
|
|
||||||
async onViewCipher(cipher: PopupCipherView) {
|
async onViewCipher(cipher: PopupCipherView) {
|
||||||
const repromptPassed = await this.passwordRepromptService.passwordRepromptCheck(cipher);
|
// We already have a view action in progress, don't start another
|
||||||
if (!repromptPassed) {
|
if (this.viewCipherTimeout != null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.router.navigate(["/view-cipher"], {
|
// Wrap in a timeout to allow for double click to launch
|
||||||
queryParams: { cipherId: cipher.id, type: cipher.type },
|
this.viewCipherTimeout = window.setTimeout(
|
||||||
});
|
async () => {
|
||||||
|
try {
|
||||||
|
const repromptPassed = await this.passwordRepromptService.passwordRepromptCheck(cipher);
|
||||||
|
if (!repromptPassed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.router.navigate(["/view-cipher"], {
|
||||||
|
queryParams: { cipherId: cipher.id, type: cipher.type },
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
// Ensure the timeout is always cleared
|
||||||
|
this.viewCipherTimeout = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cipher.canLaunch ? 200 : 0,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,8 @@ import { View } from "../../../models/view/view";
|
|||||||
import { InitializerMetadata } from "../../../platform/interfaces/initializer-metadata.interface";
|
import { InitializerMetadata } from "../../../platform/interfaces/initializer-metadata.interface";
|
||||||
import { InitializerKey } from "../../../platform/services/cryptography/initializer-key";
|
import { InitializerKey } from "../../../platform/services/cryptography/initializer-key";
|
||||||
import { DeepJsonify } from "../../../types/deep-jsonify";
|
import { DeepJsonify } from "../../../types/deep-jsonify";
|
||||||
import { LinkedIdType } from "../../enums";
|
import { CipherType, LinkedIdType } from "../../enums";
|
||||||
import { CipherRepromptType } from "../../enums/cipher-reprompt-type";
|
import { CipherRepromptType } from "../../enums/cipher-reprompt-type";
|
||||||
import { CipherType } from "../../enums/cipher-type";
|
|
||||||
import { LocalData } from "../data/local.data";
|
import { LocalData } from "../data/local.data";
|
||||||
import { Cipher } from "../domain/cipher";
|
import { Cipher } from "../domain/cipher";
|
||||||
|
|
||||||
@ -132,6 +131,13 @@ export class CipherView implements View, InitializerMetadata {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the cipher can be launched in a new browser tab.
|
||||||
|
*/
|
||||||
|
get canLaunch(): boolean {
|
||||||
|
return this.type === CipherType.Login && this.login.canLaunch;
|
||||||
|
}
|
||||||
|
|
||||||
linkedFieldValue(id: LinkedIdType) {
|
linkedFieldValue(id: LinkedIdType) {
|
||||||
const linkedFieldOption = this.linkedFieldOptions?.get(id);
|
const linkedFieldOption = this.linkedFieldOptions?.get(id);
|
||||||
if (linkedFieldOption == null) {
|
if (linkedFieldOption == null) {
|
||||||
|
Loading…
Reference in New Issue
Block a user