From 4853fb3e29e84a7a2525838cd5fa66b47ee2cb88 Mon Sep 17 00:00:00 2001 From: Vincent Salucci <26154748+vincentsalucci@users.noreply.github.com> Date: Tue, 23 Feb 2021 15:37:55 -0600 Subject: [PATCH] [Send] Add/Edit functionality (#1622) * Update jslib (0951424 -> 1968dbf) * [Send] Browser integration initial commit * Update jslib (1968dbf -> 8a3b551) * Cleaned up integration * added radio button style support // updated warning UI/UX * Update jslib (8a3b551 ->42348e2) --- jslib | 2 +- src/_locales/en/messages.json | 109 ++++++++- src/popup/app-routing.animations.ts | 6 + src/popup/app-routing.module.ts | 13 ++ src/popup/app.component.ts | 2 +- src/popup/app.module.ts | 4 + src/popup/scss/box.scss | 66 +++++- src/popup/scss/misc.scss | 8 + src/popup/send/send-add-edit.component.html | 246 ++++++++++++++++++++ src/popup/send/send-add-edit.component.ts | 124 ++++++++++ src/popup/send/send-groupings.component.ts | 7 +- src/popup/send/send-type.component.ts | 7 +- 12 files changed, 582 insertions(+), 12 deletions(-) create mode 100644 src/popup/send/send-add-edit.component.html create mode 100644 src/popup/send/send-add-edit.component.ts diff --git a/jslib b/jslib index b0ae1bfa4c..42348e2fdc 160000 --- a/jslib +++ b/jslib @@ -1 +1 @@ -Subproject commit b0ae1bfa4cb3bc2642e1ecb14c6c1f0eceb06cb6 +Subproject commit 42348e2fdc6206157d68d8a9f496eaa70520ab01 diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index 790c635f4b..07f5ea794b 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -1506,7 +1506,7 @@ "message": "Password protected" }, "copySendLink": { - "message": "Copy Send Link", + "message": "Copy Send link", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "removePassword": { @@ -1523,7 +1523,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendLink": { - "message": "Send Link", + "message": "Send link", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "disabled": { @@ -1539,5 +1539,110 @@ "deleteSendConfirmation": { "message": "Are you sure you want to delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "editSend": { + "message": "Edit Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeHeader": { + "message": "What type of Send is this?", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendNameDesc": { + "message": "A friendly name to describe this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendFileDesc": { + "message": "The file you want to send." + }, + "deletionDate": { + "message": "Deletion Date" + }, + "deletionDateDesc": { + "message": "The Send will be permanently deleted on the specified date and time.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "expirationDate": { + "message": "Expiration Date" + }, + "expirationDateDesc": { + "message": "If set, access to this Send will expire on the specified date and time.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "oneDay": { + "message": "1 day" + }, + "days": { + "message": "$DAYS$ days", + "placeholders": { + "days": { + "content": "$1", + "example": "2" + } + } + }, + "custom": { + "message": "Custom" + }, + "maximumAccessCount": { + "message": "Maximum Access Count" + }, + "maximumAccessCountDesc": { + "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendPasswordDesc": { + "message": "Optionally require a password for users to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendNotesDesc": { + "message": "Private notes about this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendDisableDesc": { + "message": "Disable this Send so that no one can access it.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendShareDesc": { + "message": "Copy this Send's link to clipboard upon save.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTextDesc": { + "message": "The text you want to send." + }, + "sendHideText": { + "message": "Hide this Send's text by default.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "currentAccessCount": { + "message": "Current Access Count" + }, + "createSend": { + "message": "Create New Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "newPassword": { + "message": "New Password" + }, + "sendDisabledWarning": { + "message": "Due to an enterprise policy, you are only able to delete an existing Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "createdSend": { + "message": "Created Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "editedSend": { + "message": "Edited Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendFirefoxFileWarning": { + "message": "In order to choose a file using Firefox, open the extension in the sidebar or pop out to a new window by clicking this banner." + }, + "sendSafariFileWarning": { + "message": "In order to choose a file using Safari, pop out to a new window by clicking this banner." + }, + "sendFileCalloutHeader": { + "message": "Before you start" } } diff --git a/src/popup/app-routing.animations.ts b/src/popup/app-routing.animations.ts index a05df5f4fc..e2362bec02 100644 --- a/src/popup/app-routing.animations.ts +++ b/src/popup/app-routing.animations.ts @@ -190,4 +190,10 @@ export const routerTransition = trigger('routerTransition', [ transition('tabs => send-type', inSlideLeft), transition('send-type => tabs', outSlideRight), + + transition('tabs => add-send, send-type => add-send', inSlideUp), + transition('add-send => tabs, add-send => send-type', outSlideDown), + + transition('tabs => edit-send, send-type => edit-send', inSlideUp), + transition('edit-send => tabs, edit-send => send-type', outSlideDown), ]); diff --git a/src/popup/app-routing.module.ts b/src/popup/app-routing.module.ts index 2e87c0f6d3..96fcd02f08 100644 --- a/src/popup/app-routing.module.ts +++ b/src/popup/app-routing.module.ts @@ -46,6 +46,7 @@ import { PasswordHistoryComponent } from './vault/password-history.component'; import { ShareComponent } from './vault/share.component'; import { ViewComponent } from './vault/view.component'; +import { SendAddEditComponent } from './send/send-add-edit.component'; import { SendGroupingsComponent } from './send/send-groupings.component'; import { SendTypeComponent } from './send/send-type.component'; @@ -243,6 +244,18 @@ const routes: Routes = [ canActivate: [AuthGuardService], data: { state: 'send-type' }, }, + { + path: 'add-send', + component: SendAddEditComponent, + canActivate: [AuthGuardService], + data: { state: 'add-send' }, + }, + { + path: 'edit-send', + component: SendAddEditComponent, + canActivate: [AuthGuardService], + data: { state: 'edit-send' }, + }, { path: 'tabs', component: TabsComponent, diff --git a/src/popup/app.component.ts b/src/popup/app.component.ts index c5e9a20b17..cbf6891fb5 100644 --- a/src/popup/app.component.ts +++ b/src/popup/app.component.ts @@ -145,6 +145,7 @@ export class AppComponent implements OnInit { if (url.startsWith('/tabs/') && (window as any).previousPopupUrl != null && (window as any).previousPopupUrl.startsWith('/tabs/')) { this.stateService.remove('GroupingsComponent'); + this.stateService.remove('GroupingsComponentScope'); this.stateService.remove('CiphersComponent'); this.stateService.remove('SendGroupingsComponent'); this.stateService.remove('SendGroupingsComponentScope'); @@ -152,7 +153,6 @@ export class AppComponent implements OnInit { } if (url.startsWith('/tabs/')) { this.stateService.remove('addEditCipherInfo'); - // TODO Remove any Send add/edit state information (?) } (window as any).previousPopupUrl = url; diff --git a/src/popup/app.module.ts b/src/popup/app.module.ts index f3e931e561..7cf73315ca 100644 --- a/src/popup/app.module.ts +++ b/src/popup/app.module.ts @@ -52,6 +52,7 @@ import { PasswordHistoryComponent } from './vault/password-history.component'; import { ShareComponent } from './vault/share.component'; import { ViewComponent } from './vault/view.component'; +import { SendAddEditComponent } from './send/send-add-edit.component'; import { SendGroupingsComponent } from './send/send-groupings.component'; import { SendTypeComponent } from './send/send-type.component'; @@ -81,6 +82,7 @@ import { IconComponent } from 'jslib/angular/components/icon.component'; import { CurrencyPipe, + DatePipe, registerLocaleData, } from '@angular/common'; import localeBe from '@angular/common/locales/be'; @@ -213,6 +215,7 @@ registerLocaleData(localeZhTw, 'zh-TW'); RegisterComponent, SearchCiphersPipe, SelectCopyDirective, + SendAddEditComponent, SendGroupingsComponent, SendListComponent, SendTypeComponent, @@ -232,6 +235,7 @@ registerLocaleData(localeZhTw, 'zh-TW'); entryComponents: [], providers: [ CurrencyPipe, + DatePipe, ], bootstrap: [AppComponent], }) diff --git a/src/popup/scss/box.scss b/src/popup/scss/box.scss index 91f8db1d5a..11341c69f3 100644 --- a/src/popup/scss/box.scss +++ b/src/popup/scss/box.scss @@ -19,6 +19,32 @@ } } + .box-header-expandable { + margin: 0 10px 5px 10px; + text-transform: uppercase; + display: flex; + + @include themify($themes) { + color: themed('headingColor'); + } + + &:hover, &:focus, &.active { + @include themify($themes) { + background-color: themed('boxBackgroundHoverColor'); + } + } + + .icon { + display: flex; + align-items: center; + margin-left: 5px; + + @include themify($themes) { + color: themed('headingColor'); + } + } + } + .box-content { border-top: 1px solid #000000; border-bottom: 1px solid #000000; @@ -202,6 +228,21 @@ } } + .flex-label { + font-size: $font-size-small; + display: flex; + flex-grow: 1; + margin-bottom: 5px; + + @include themify($themes) { + color: themed('mutedColor'); + } + + > a { + flex-grow: 0; + } + } + .text, .detail { display: block; @@ -329,7 +370,7 @@ } } - input:not([type="checkbox"]), textarea { + input:not([type="checkbox"]):not([type="radio"]), textarea { border: none; width: 100%; background-color: transparent !important; @@ -546,4 +587,27 @@ background-color: $brand-primary; } } + + .radio-group { + display: flex; + justify-content: flex-start; + align-items: center; + margin-bottom: 5px; + + input { + flex-grow: 0; + } + + label { + margin: 0 0 0 5px; + flex-grow: 1; + font-size: $font-size-base; + display: block; + width: 100%; + + @include themify($themes) { + color: themed('textColor'); + } + } + } } diff --git a/src/popup/scss/misc.scss b/src/popup/scss/misc.scss index 95f8bd2dde..5d2d666624 100644 --- a/src/popup/scss/misc.scss +++ b/src/popup/scss/misc.scss @@ -302,6 +302,14 @@ app-vault-icon { } } } + + &.clickable { + &:hover, &:focus, &.active { + @include themify($themes) { + background-color: themed('boxBackgroundHoverColor'); + } + } + } } input[type="password"]::-ms-reveal { diff --git a/src/popup/send/send-add-edit.component.html b/src/popup/send/send-add-edit.component.html new file mode 100644 index 0000000000..8a39e599b1 --- /dev/null +++ b/src/popup/send/send-add-edit.component.html @@ -0,0 +1,246 @@ +
+
+
+ +
+
+ {{title}} +
+
+ +
+
+ + + +
{{'sendFirefoxFileWarning' | i18n}}
+
{{'sendSafariFileWarning' | i18n}}
+
+ +
+
+
+ + +
+
+ +
+ +
+
+
+ +
+ + +
+
+
+
+ +
+
+
+ +
{{send.file.fileName}} ({{send.file.sizeName}})
+
+
+ + +
+
+ +
+ +
+
+
+ + +
+
+ +
+
+ + +
+
+
+ +
+
+ {{'share' | i18n}} +
+
+ +
+ + +
+
+
+ +
+
+ {{'options' | i18n}} + + +
+
+ + +
+
+
+ + +
+
+ +
+
+ + +
+
+ +
+ +
+
+
+ + +
+
+ +
+
+
+ + + {{'clear' | i18n}} + +
+ +
+
+ +
+ +
+
+
+ + +
+
+ +
+ +
+
+
+ + +
+
+
+ +
+
+
+
+ + + +
+
+ + + +
+
+
+ +
+ +
+
+
+ + +
+
+ +
+ +
+
+
+ + +
+
+
+
+ + +
+
diff --git a/src/popup/send/send-add-edit.component.ts b/src/popup/send/send-add-edit.component.ts new file mode 100644 index 0000000000..4823f8027f --- /dev/null +++ b/src/popup/send/send-add-edit.component.ts @@ -0,0 +1,124 @@ +import { + DatePipe, + Location, +} from '@angular/common'; + +import { Component } from '@angular/core'; + +import { + ActivatedRoute, + Router, +} from '@angular/router'; + +import { EnvironmentService } from 'jslib/abstractions/environment.service'; +import { I18nService } from 'jslib/abstractions/i18n.service'; +import { MessagingService } from 'jslib/abstractions/messaging.service'; +import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; +import { PolicyService } from 'jslib/abstractions/policy.service'; +import { SendService } from 'jslib/abstractions/send.service'; +import { UserService } from 'jslib/abstractions/user.service'; + +import { PopupUtilsService } from '../services/popup-utils.service'; + +import { AddEditComponent as BaseAddEditComponent } from 'jslib/angular/components/send/add-edit.component'; + +@Component({ + selector: 'app-send-add-edit', + templateUrl: 'send-add-edit.component.html', +}) +export class SendAddEditComponent extends BaseAddEditComponent { + // Options header + showOptions = false; + // File visibility + isFirefox = false; + isSafari = false; + inPopout = false; + inSidebar = false; + + constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService, + userService: UserService, messagingService: MessagingService, policyService: PolicyService, + environmentService: EnvironmentService, datePipe: DatePipe, sendService: SendService, + private route: ActivatedRoute, private router: Router, private location: Location, + private popupUtilsService: PopupUtilsService) { + super(i18nService, platformUtilsService, environmentService, datePipe, sendService, userService, + messagingService, policyService); + } + + get showFileSelector(): boolean { + return !this.editMode && (!this.isFirefox && !this.isSafari) || + (this.isFirefox && (this.inSidebar || this.inPopout)) || + (this.isSafari && this.inPopout); + } + + get showFilePopoutMessage(): boolean { + return !this.editMode && (this.showFirefoxFileWarning || this.showSafariFileWarning); + } + + get showFirefoxFileWarning(): boolean { + return this.isFirefox && !(this.inSidebar || this.inPopout); + } + + get showSafariFileWarning(): boolean { + return this.isSafari && !this.inPopout; + } + + popOutWindow() { + this.popupUtilsService.popOut(window); + } + + async ngOnInit() { + // File visilibity + this.isFirefox = this.platformUtilsService.isFirefox(); + this.isSafari = this.platformUtilsService.isSafari(); + this.inPopout = this.popupUtilsService.inPopout(window); + this.inSidebar = this.popupUtilsService.inSidebar(window); + + const queryParamsSub = this.route.queryParams.subscribe(async (params) => { + if (params.sendId) { + this.sendId = params.sendId; + } + if (params.type) { + const type = parseInt(params.type, null); + this.type = type; + } + await this.load(); + + if (queryParamsSub != null) { + queryParamsSub.unsubscribe(); + } + }); + + window.setTimeout(() => { + if (!this.editMode) { + document.getElementById('name').focus(); + } + }, 200); + } + + async submit(): Promise { + if (await super.submit()) { + this.cancel(); + return true; + } + + return false; + } + + async delete(): Promise { + if (await super.delete()) { + this.cancel(); + return true; + } + + return false; + } + + cancel() { + // If true, the window was pop'd out on the add-send page. location.back will not work + if ((window as any).previousPopupUrl.startsWith('/add-send')) { + this.router.navigate(['tabs/send']); + } else { + this.location.back(); + } + } +} diff --git a/src/popup/send/send-groupings.component.ts b/src/popup/send/send-groupings.component.ts index e03fe3aa61..446e26af18 100644 --- a/src/popup/send/send-groupings.component.ts +++ b/src/popup/send/send-groupings.component.ts @@ -5,7 +5,6 @@ import { } from '@angular/core'; import { - ActivatedRoute, Router, } from '@angular/router'; @@ -50,7 +49,7 @@ export class SendGroupingsComponent extends BaseSendComponent { platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService, ngZone: NgZone, policyService: PolicyService, userService: UserService, searchService: SearchService, private popupUtils: PopupUtilsService, private stateService: StateService, - private route: ActivatedRoute, private router: Router, private syncService: SyncService, + private router: Router, private syncService: SyncService, private changeDetectorRef: ChangeDetectorRef, private broadcasterService: BroadcasterService) { super(sendService, i18nService, platformUtilsService, environmentService, ngZone, searchService, policyService, userService); @@ -122,11 +121,11 @@ export class SendGroupingsComponent extends BaseSendComponent { } async selectSend(s: SendView) { - // TODO -> Route to edit send + this.router.navigate(['/edit-send'], { queryParams: { sendId: s.id } }); } async addSend() { - // TODO -> Route to create send + this.router.navigate(['/add-send']); } showSearching() { diff --git a/src/popup/send/send-type.component.ts b/src/popup/send/send-type.component.ts index 17488529fe..e8215b7515 100644 --- a/src/popup/send/send-type.component.ts +++ b/src/popup/send/send-type.component.ts @@ -6,6 +6,7 @@ import { import { ActivatedRoute, + Router, } from '@angular/router'; import { Location } from '@angular/common'; @@ -47,7 +48,7 @@ export class SendTypeComponent extends BaseSendComponent { policyService: PolicyService, userService: UserService, searchService: SearchService, private popupUtils: PopupUtilsService, private stateService: StateService, private route: ActivatedRoute, private location: Location, private changeDetectorRef: ChangeDetectorRef, - private broadcasterService: BroadcasterService) { + private broadcasterService: BroadcasterService, private router: Router) { super(sendService, i18nService, platformUtilsService, environmentService, ngZone, searchService, policyService, userService); super.onSuccessfulLoad = async () => { @@ -127,11 +128,11 @@ export class SendTypeComponent extends BaseSendComponent { } async selectSend(s: SendView) { - // TODO -> Route to edit send + this.router.navigate(['/edit-send'], { queryParams: { sendId: s.id } }); } async addSend() { - // TODO -> Route to create send + this.router.navigate(['/add-send'], { queryParams: { type: this.type } }); } back() {