diff --git a/jslib b/jslib index 11249e3444..ee164bebc6 160000 --- a/jslib +++ b/jslib @@ -1 +1 @@ -Subproject commit 11249e34441ea747f53fcb0b6e38f690366b46b5 +Subproject commit ee164bebc65aa56e41a122eb4ece8971eb23119b diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index 0f700a0eae..790c635f4b 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -1474,5 +1474,70 @@ "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "searchSends": { + "message": "Search Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "addSend": { + "message": "Add Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeText": { + "message": "Text" + }, + "sendTypeFile": { + "message": "File" + }, + "allSends": { + "message": "All Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "maxAccessCountReached": { + "message": "Max access count reached" + }, + "expired": { + "message": "Expired" + }, + "pendingDeletion": { + "message": "Pending deletion" + }, + "passwordProtected": { + "message": "Password protected" + }, + "copySendLink": { + "message": "Copy Send Link", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "removePassword": { + "message": "Remove Password" + }, + "delete": { + "message": "Delete" + }, + "removedPassword": { + "message": "Removed Password" + }, + "deletedSend": { + "message": "Deleted Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendLink": { + "message": "Send Link", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "disabled": { + "message": "Disabled" + }, + "removePasswordConfirmation": { + "message": "Are you sure you want to remove the password?" + }, + "deleteSend": { + "message": "Delete Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "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." } } diff --git a/src/popup/app-routing.module.ts b/src/popup/app-routing.module.ts index 16a4598fdd..f05bb157e6 100644 --- a/src/popup/app-routing.module.ts +++ b/src/popup/app-routing.module.ts @@ -46,7 +46,7 @@ import { PasswordHistoryComponent } from './vault/password-history.component'; import { ShareComponent } from './vault/share.component'; import { ViewComponent } from './vault/view.component'; -import { SendComponent } from './send/send.component'; +import { SendGroupingsComponent } from './send/send-groupings.component'; const routes: Routes = [ { @@ -273,7 +273,7 @@ const routes: Routes = [ }, { path: 'send', - component: SendComponent, + component: SendGroupingsComponent, canActivate: [AuthGuardService], data: { state: 'tabs_send' }, }, diff --git a/src/popup/app.module.ts b/src/popup/app.module.ts index 13ea36c4f1..7628e2479c 100644 --- a/src/popup/app.module.ts +++ b/src/popup/app.module.ts @@ -52,7 +52,7 @@ import { PasswordHistoryComponent } from './vault/password-history.component'; import { ShareComponent } from './vault/share.component'; import { ViewComponent } from './vault/view.component'; -import { SendComponent } from './send/send.component'; +import { SendGroupingsComponent } from './send/send-groupings.component'; import { A11yTitleDirective } from 'jslib/angular/directives/a11y-title.directive'; import { ApiActionDirective } from 'jslib/angular/directives/api-action.directive'; @@ -73,6 +73,7 @@ import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe'; import { ActionButtonsComponent } from './components/action-buttons.component'; import { CiphersListComponent } from './components/ciphers-list.component'; import { PopOutComponent } from './components/pop-out.component'; +import { SendListComponent } from './components/send-list.component'; import { CalloutComponent } from 'jslib/angular/components/callout.component'; import { IconComponent } from 'jslib/angular/components/icon.component'; @@ -211,7 +212,8 @@ registerLocaleData(localeZhTw, 'zh-TW'); RegisterComponent, SearchCiphersPipe, SelectCopyDirective, - SendComponent, + SendGroupingsComponent, + SendListComponent, SettingsComponent, ShareComponent, StopClickDirective, diff --git a/src/popup/components/send-list.component.html b/src/popup/components/send-list.component.html new file mode 100644 index 0000000000..3c0454ab4d --- /dev/null +++ b/src/popup/components/send-list.component.html @@ -0,0 +1,51 @@ + +
+
+ +
+
+ + {{s.name}} + + + {{'disabled' | i18n}} + + + + {{'passwordProtected' | i18n}} + + + + {{'maxAccessCountReached' | i18n}} + + + + {{'expired' | i18n}} + + + + {{'pendingDeletion' | i18n}} + + + {{s.deletionDate | date:'medium'}} +
+
+
+ + + + + + + + + +
+
diff --git a/src/popup/components/send-list.component.ts b/src/popup/components/send-list.component.ts new file mode 100644 index 0000000000..ddb396acb0 --- /dev/null +++ b/src/popup/components/send-list.component.ts @@ -0,0 +1,41 @@ +import { + Component, + EventEmitter, + Input, + Output, +} from '@angular/core'; + +import { SendView } from 'jslib/models/view/sendView'; + +import { SendType } from 'jslib/enums/sendType'; + +@Component({ + selector: 'app-send-list', + templateUrl: 'send-list.component.html', +}) +export class SendListComponent { + @Input() sends: SendView[]; + @Input() title: string; + @Output() onSelected = new EventEmitter(); + @Output() onCopySendLink = new EventEmitter(); + @Output() onRemovePassword = new EventEmitter(); + @Output() onDeleteSend = new EventEmitter(); + + sendType = SendType; + + selectSend(s: SendView) { + this.onSelected.emit(s); + } + + copySendLink(s: SendView) { + this.onCopySendLink.emit(s); + } + + removePassword(s: SendView) { + this.onRemovePassword.emit(s); + } + + delete(s: SendView) { + this.onDeleteSend.emit(s); + } +} diff --git a/src/popup/send/send-groupings.component.html b/src/popup/send/send-groupings.component.html new file mode 100644 index 0000000000..9e2dfc609b --- /dev/null +++ b/src/popup/send/send-groupings.component.html @@ -0,0 +1,74 @@ +
+
+ +
+ +
+ +
+
+ +
+ + + +

{{'noItemsInList' | i18n}}

+ +
+
+ + +
+
+ {{'allSends' | i18n}} +
{{sends.length}}
+
+
+ +
+
+
+ +
+

{{'noItemsInList' | i18n}}

+
+
+
+ + +
+
+
+
diff --git a/src/popup/send/send-groupings.component.ts b/src/popup/send/send-groupings.component.ts new file mode 100644 index 0000000000..bc405f55b0 --- /dev/null +++ b/src/popup/send/send-groupings.component.ts @@ -0,0 +1,157 @@ +import { + Component, + NgZone, +} from '@angular/core'; + +import { + ActivatedRoute, + Router, +} from '@angular/router'; + +import { SendView } from 'jslib/models/view/sendView'; + +import { SendComponent as BaseSendComponent } from 'jslib/angular/components/send/send.component'; + +import { EnvironmentService } from 'jslib/abstractions/environment.service'; +import { I18nService } from 'jslib/abstractions/i18n.service'; +import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; +import { PolicyService } from 'jslib/abstractions/policy.service'; +import { SearchService } from 'jslib/abstractions/search.service'; +import { SendService } from 'jslib/abstractions/send.service'; +import { StateService } from 'jslib/abstractions/state.service'; +import { SyncService } from 'jslib/abstractions/sync.service'; +import { UserService } from 'jslib/abstractions/user.service'; + +import { BroadcasterService } from 'jslib/angular/services/broadcaster.service'; + +import { PopupUtilsService } from '../services/popup-utils.service'; + +import { SendType } from 'jslib/enums/sendType'; + +const ComponentId = 'SendComponent'; +const ScopeStateId = ComponentId + 'Scope'; + +@Component({ + selector: 'app-send-groupings', + templateUrl: 'send-groupings.component.html', +}) +export class SendGroupingsComponent extends BaseSendComponent { + // Header + showLeftHeader = true; + // Send Type Calculations + typeCounts = new Map(); + // State Handling + state: any; + scopeState: any; + private loadedTimeout: number; + + constructor(sendService: SendService, i18nService: I18nService, + platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService, + broadcasterService: BroadcasterService, ngZone: NgZone, policyService: PolicyService, + userService: UserService, searchService: SearchService, + private popupUtils: PopupUtilsService, private stateService: StateService, + private route: ActivatedRoute, private router: Router, private syncService: SyncService) { + super(sendService, i18nService, platformUtilsService, environmentService, broadcasterService, ngZone, + searchService, policyService, userService); + super.onSuccessfulLoad = async () => { + this.calculateTypeCounts(); + }; + } + + async ngOnInit() { + // Determine Header details + this.showLeftHeader = !(this.popupUtils.inSidebar(window) && this.platformUtilsService.isFirefox()); + // Let super class finish + await super.ngOnInit(); + // Handle State Restore if necessary + const restoredScopeState = await this.restoreState(); + this.state = (await this.stateService.get(ComponentId)) || {}; + if (this.state.searchText != null) { + this.searchText = this.state.searchText; + } + + if (!this.syncService.syncInProgress) { + this.load(); + } else { + this.loadedTimeout = window.setTimeout(() => { + if (!this.loaded) { + this.load(); + } + }, 5000); + } + + if (!this.syncService.syncInProgress || restoredScopeState) { + window.setTimeout(() => this.popupUtils.setContentScrollY(window, this.state.scrollY), 0); + } + } + + ngOnDestroy() { + // Remove timeout + if (this.loadedTimeout != null) { + window.clearTimeout(this.loadedTimeout); + } + // Save state + this.saveState(); + // Allow super to finish + super.ngOnDestroy(); + } + + async selectType(type: SendType) { + // TODO this.router.navigate(['/send-type-list'], { queryParams: { type: type } }); + } + + async selectSend(s: SendView) { + // TODO -> Route to edit send + } + + async addSend() { + // TODO -> Route to create send + } + + showSearching() { + return this.hasSearched || (!this.searchPending && this.searchService.isSearchable(this.searchText)); + } + + private calculateTypeCounts() { + // Create type counts + const typeCounts = new Map(); + this.sends.forEach((s) => { + if (typeCounts.has(s.type)) { + typeCounts.set(s.type, typeCounts.get(s.type) + 1); + } else { + typeCounts.set(s.type, 1); + } + }); + this.typeCounts = typeCounts; + } + + private async saveState() { + this.state = { + scrollY: this.popupUtils.getContentScrollY(window), + searchText: this.searchText, + }; + await this.stateService.save(ComponentId, this.state); + + this.scopeState = { + sends: this.sends, + typeCounts: this.typeCounts, + }; + await this.stateService.save(ScopeStateId, this.scopeState); + } + + private async restoreState(): Promise { + this.scopeState = await this.stateService.get(ScopeStateId); + if (this.scopeState == null) { + return false; + } + + if (this.scopeState.sends != null) { + this.sends = this.scopeState.sends; + } + if (this.scopeState.typeCounts != null) { + this.typeCounts = this.scopeState.typeCounts; + } + + return true; + } +} diff --git a/src/popup/send/send.component.html b/src/popup/send/send.component.html deleted file mode 100644 index 7c646071e9..0000000000 --- a/src/popup/send/send.component.html +++ /dev/null @@ -1,8 +0,0 @@ -
-
- -
- -

Coming soon...

-
-
diff --git a/src/popup/send/send.component.ts b/src/popup/send/send.component.ts deleted file mode 100644 index 540e3989d1..0000000000 --- a/src/popup/send/send.component.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { - Component, - NgZone, -} from '@angular/core'; - -import { SendView } from 'jslib/models/view/sendView'; - -import { SendComponent as BaseSendComponent } from 'jslib/angular/components/send/send.component'; - -import { EnvironmentService } from 'jslib/abstractions/environment.service'; -import { I18nService } from 'jslib/abstractions/i18n.service'; -import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; -import { SendService } from 'jslib/abstractions/send.service'; - -import { BroadcasterService } from 'jslib/angular/services/broadcaster.service'; - -@Component({ - selector: 'app-send', - templateUrl: 'send.component.html', -}) -export class SendComponent extends BaseSendComponent { - constructor(sendService: SendService, i18nService: I18nService, - platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService, - broadcasterService: BroadcasterService, ngZone: NgZone) { - super(sendService, i18nService, platformUtilsService, environmentService, broadcasterService, ngZone); - } - - addSend() { - // TODO - } - - editSend(send: SendView) { - // TODO - } -}