From 87dbe8997dfa8dc78c310a8adf9c1df4ac1edc47 Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Thu, 19 Oct 2023 22:33:41 +0200 Subject: [PATCH] [PM-4209] Enable importing on browser (#6503) * Split up import/export into separate modules * Fix routing and apply PR feedback * Renamed OrganizationExport exports to OrganizationVaultExport * Make import dialogs standalone and move them to libs/importer * Make import.component re-usable - Move functionality which was previously present on the org-import.component into import.component - Move import.component into libs/importer Make import.component standalone Create import-web.component to represent Web UI Fix module imports and routing Remove unused org-import-files * Enable importing on browser Create import-dialog Add routing and routing animations Settings import items no longer navigates to help page but opens import page Extend messages.json to include all the necessary messages from shared components * Fix back navigation * Renamed filenames according to export rename * Make ImportWebComponent standalone, simplify routing * Pass organizationId as Input to ImportComponent * use formLoading and formDisabled outputs * add loading and disabled state to import-browser * override popup header styles * Emit an event when the import succeeds Remove Angular router from base-component as other clients might not have routing (i.e. desktop) Move logic that happened on web successful import into the import-web.component * Enable importing on browser Create import-dialog Add routing and routing animations Settings import items no longer navigates to help page but opens import page Extend messages.json to include all the necessary messages from shared components * Fix back navigation * add loading and disabled state to import-browser * override popup header styles * Add missing message for importBlockedByPolicy callout * Implement onSuccessfulImport to navigate back to settings * fix table themes on desktop & browser * fix fileSelector button styles * update selectors to use tools prefix; remove unused selectors * rename selector * Wall off UI components in libs/importer Create barrel-file for libs/importer/components Remove components and dialog exports from libs/importer/index.ts Extend libs/shared/tsconfig.libs.json to include @bitwarden/importer/ui -> libs/importer/components Extend apps/web/tsconfig.ts to include @bitwarden/importer/ui Update all usages * Rename @bitwarden/importer to @bitwarden/importer/core Create more barrel files in libs/importer/* Update imports within libs/importer Extend tsconfig files Update imports in web, desktop, browser and cli * Lazy-load the ImportWebComponent via both routes * Fix import path for ImportComponent * Navigate to import opens in popout when navigated from the popup Make import call async and await router navigate - If the user has the popup open and selects import, it will navigate to the import page and popout into a new window. This is necessary as any focus-loss (i.e Choose file) would close the popup. - If the user is using the for example the sidebar or an already popped out window, just navigate to import page * Use SharedModule as import in import-web.component * File selector should be displayed as secondary * Update description of "importData" in messages.json * Add missing messages for file-password-prompt * Add missing messages for import-error-dialog * Add missing message for import-success-dialog * Use bitSubmit to override submit preventDefault (#6607) Co-authored-by: Daniel James Smith * Add missing importWarning --------- Co-authored-by: Daniel James Smith Co-authored-by: William Martin --- apps/browser/src/_locales/en/messages.json | 111 ++++++++++++++++++ .../src/popup/app-routing.animations.ts | 3 + apps/browser/src/popup/app-routing.module.ts | 7 ++ apps/browser/src/popup/scss/base.scss | 2 +- .../src/popup/settings/settings.component.ts | 7 +- .../import/import-browser.component.html | 24 ++++ .../import/import-browser.component.ts | 31 +++++ 7 files changed, 182 insertions(+), 3 deletions(-) create mode 100644 apps/browser/src/tools/popup/settings/import/import-browser.component.html create mode 100644 apps/browser/src/tools/popup/settings/import/import-browser.component.ts diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 9a68f8bb9fb..24e1bc3ce30 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -1656,6 +1656,9 @@ "personalOwnershipPolicyInEffect": { "message": "An organization policy is affecting your ownership options." }, + "personalOwnershipPolicyInEffectImports": { + "message": "An organization policy has blocked importing items into your individual vault." + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2449,6 +2452,114 @@ "message": "Turn off master password re-prompt to edit this field", "description": "Message appearing below the autofill on load message when master password reprompt is set for a vault item." }, + "importData": { + "message": "Import data", + "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" + }, + "importError": { + "message": "Import error" + }, + "importErrorDesc": { + "message": "There was a problem with the data you tried to import. Please resolve the errors listed below in your source file and try again." + }, + "resolveTheErrorsBelowAndTryAgain": { + "message": "Resolve the errors below and try again." + }, + "description": { + "message": "Description" + }, + "importSuccess": { + "message": "Data successfully imported" + }, + "importSuccessNumberOfItems": { + "message": "A total of $AMOUNT$ items were imported.", + "placeholders": { + "amount": { + "content": "$1", + "example": "2" + } + } + }, + "total": { + "message": "Total" + }, + "importWarning": { + "message": "You are importing data to $ORGANIZATION$. Your data may be shared with members of this organization. Do you want to proceed?", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "importFormatError": { + "message": "Data is not formatted correctly. Please check your import file and try again." + }, + "importNothingError": { + "message": "Nothing was imported." + }, + "importEncKeyError": { + "message": "Error decrypting the exported file. Your encryption key does not match the encryption key used export the data." + }, + "importDestination": { + "message": "Import destination" + }, + "learnAboutImportOptions": { + "message": "Learn about your import options" + }, + "selectImportFolder": { + "message": "Select a folder" + }, + "selectImportCollection": { + "message": "Select a collection" + }, + "importTargetHint": { + "message": "Select this option if you want the imported file contents moved to a $DESTINATION$", + "description": "Located as a hint under the import target. Will be appended by either folder or collection, depending if the user is importing into an individual or an organizational vault.", + "placeholders": { + "destination": { + "content": "$1", + "example": "folder or collection" + } + } + }, + "importUnassignedItemsError": { + "message": "File contains unassigned items." + }, + "selectFormat": { + "message": "Select the format of the import file" + }, + "selectImportFile": { + "message": "Select the import file" + }, + "chooseFile": { + "message": "Choose File" + }, + "noFileChosen": { + "message": "No file chosen" + }, + "orCopyPasteFileContents": { + "message": "or copy/paste the import file contents" + }, + "instructionsFor": { + "message": "$NAME$ Instructions", + "description": "The title for the import tool instructions.", + "placeholders": { + "name": { + "content": "$1", + "example": "LastPass (csv)" + } + } + }, + "confirmVaultImport": { + "message": "Confirm vault import" + }, + "confirmVaultImportDesc": { + "message": "This file is password-protected. Please enter the file password to import data." + }, + "confirmFilePassword": { + "message": "Confirm file password" + }, "passkeyNotCopied": { "message": "Passkey will not be copied" }, diff --git a/apps/browser/src/popup/app-routing.animations.ts b/apps/browser/src/popup/app-routing.animations.ts index 2304944acb0..42baf65c270 100644 --- a/apps/browser/src/popup/app-routing.animations.ts +++ b/apps/browser/src/popup/app-routing.animations.ts @@ -174,6 +174,9 @@ export const routerTransition = trigger("routerTransition", [ transition("clone-cipher => attachments, clone-cipher => collections", inSlideLeft), transition("attachments => clone-cipher, collections => clone-cipher", outSlideRight), + transition("tabs => import", inSlideLeft), + transition("import => tabs", outSlideRight), + transition("tabs => export", inSlideLeft), transition("export => tabs", outSlideRight), diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index 10159a715f0..df7b9ffb1cf 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -32,6 +32,7 @@ import { SendAddEditComponent } from "../tools/popup/send/send-add-edit.componen import { SendGroupingsComponent } from "../tools/popup/send/send-groupings.component"; import { SendTypeComponent } from "../tools/popup/send/send-type.component"; import { ExportComponent } from "../tools/popup/settings/export.component"; +import { ImportBrowserComponent } from "../tools/popup/settings/import/import-browser.component"; import { Fido2Component } from "../vault/popup/components/fido2/fido2.component"; import { AddEditComponent } from "../vault/popup/components/vault/add-edit.component"; import { AttachmentsComponent } from "../vault/popup/components/vault/attachments.component"; @@ -222,6 +223,12 @@ const routes: Routes = [ canActivate: [AuthGuard], data: { state: "generator-history" }, }, + { + path: "import", + component: ImportBrowserComponent, + canActivate: [AuthGuard], + data: { state: "import" }, + }, { path: "export", component: ExportComponent, diff --git a/apps/browser/src/popup/scss/base.scss b/apps/browser/src/popup/scss/base.scss index 3b401d356f5..6cd99abb0d5 100644 --- a/apps/browser/src/popup/scss/base.scss +++ b/apps/browser/src/popup/scss/base.scss @@ -175,7 +175,7 @@ cdk-virtual-scroll-viewport::-webkit-scrollbar-thumb, } } -header { +header:not(bit-callout header) { min-height: 44px; max-height: 44px; display: flex; diff --git a/apps/browser/src/popup/settings/settings.component.ts b/apps/browser/src/popup/settings/settings.component.ts index 274ed871225..252a2097156 100644 --- a/apps/browser/src/popup/settings/settings.component.ts +++ b/apps/browser/src/popup/settings/settings.component.ts @@ -473,8 +473,11 @@ export class SettingsComponent implements OnInit { BrowserApi.createNewTab(url); } - import() { - BrowserApi.createNewTab("https://bitwarden.com/help/import-data/"); + async import() { + await this.router.navigate(["/import"]); + if (await BrowserApi.isPopupOpen()) { + this.popupUtilsService.popOut(window); + } } export() { diff --git a/apps/browser/src/tools/popup/settings/import/import-browser.component.html b/apps/browser/src/tools/popup/settings/import/import-browser.component.html new file mode 100644 index 00000000000..b305e6c395f --- /dev/null +++ b/apps/browser/src/tools/popup/settings/import/import-browser.component.html @@ -0,0 +1,24 @@ +
+
+ +
+

+ {{ "importData" | i18n }} +

+
+ +
+
+
+ +
diff --git a/apps/browser/src/tools/popup/settings/import/import-browser.component.ts b/apps/browser/src/tools/popup/settings/import/import-browser.component.ts new file mode 100644 index 00000000000..3fea3aad04b --- /dev/null +++ b/apps/browser/src/tools/popup/settings/import/import-browser.component.ts @@ -0,0 +1,31 @@ +import { CommonModule } from "@angular/common"; +import { Component } from "@angular/core"; +import { Router, RouterLink } from "@angular/router"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AsyncActionsModule, ButtonModule, DialogModule } from "@bitwarden/components"; +import { ImportComponent } from "@bitwarden/importer/ui"; + +@Component({ + templateUrl: "import-browser.component.html", + standalone: true, + imports: [ + CommonModule, + RouterLink, + JslibModule, + DialogModule, + AsyncActionsModule, + ButtonModule, + ImportComponent, + ], +}) +export class ImportBrowserComponent { + protected disabled = false; + protected loading = false; + + constructor(private router: Router) {} + + protected async onSuccessfulImport(organizationId: string): Promise { + this.router.navigate(["/tabs/settings"]); + } +}