diff --git a/jslib b/jslib index ee164bebc6..0951424de7 160000 --- a/jslib +++ b/jslib @@ -1 +1 @@ -Subproject commit ee164bebc65aa56e41a122eb4ece8971eb23119b +Subproject commit 0951424de77fbb61a38616d13d6c67f74ee19775 diff --git a/src/popup/app-routing.animations.ts b/src/popup/app-routing.animations.ts index 0c44460d62..a05df5f4fc 100644 --- a/src/popup/app-routing.animations.ts +++ b/src/popup/app-routing.animations.ts @@ -187,4 +187,7 @@ export const routerTransition = trigger('routerTransition', [ transition('premium => tabs', outSlideRight), transition('tabs => lock', inSlideDown), + + transition('tabs => send-type', inSlideLeft), + transition('send-type => tabs', outSlideRight), ]); diff --git a/src/popup/app-routing.module.ts b/src/popup/app-routing.module.ts index f05bb157e6..2e87c0f6d3 100644 --- a/src/popup/app-routing.module.ts +++ b/src/popup/app-routing.module.ts @@ -47,6 +47,7 @@ import { ShareComponent } from './vault/share.component'; import { ViewComponent } from './vault/view.component'; import { SendGroupingsComponent } from './send/send-groupings.component'; +import { SendTypeComponent } from './send/send-type.component'; const routes: Routes = [ { @@ -236,6 +237,12 @@ const routes: Routes = [ canActivate: [AuthGuardService], data: { state: 'clone-cipher' }, }, + { + path: 'send-type', + component: SendTypeComponent, + canActivate: [AuthGuardService], + data: { state: 'send-type' }, + }, { path: 'tabs', component: TabsComponent, diff --git a/src/popup/app.component.ts b/src/popup/app.component.ts index 33fea7c59f..c5e9a20b17 100644 --- a/src/popup/app.component.ts +++ b/src/popup/app.component.ts @@ -146,9 +146,13 @@ export class AppComponent implements OnInit { (window as any).previousPopupUrl.startsWith('/tabs/')) { this.stateService.remove('GroupingsComponent'); this.stateService.remove('CiphersComponent'); + this.stateService.remove('SendGroupingsComponent'); + this.stateService.remove('SendGroupingsComponentScope'); + this.stateService.remove('SendTypeComponent'); } 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 7628e2479c..f3e931e561 100644 --- a/src/popup/app.module.ts +++ b/src/popup/app.module.ts @@ -53,6 +53,7 @@ import { ShareComponent } from './vault/share.component'; import { ViewComponent } from './vault/view.component'; import { SendGroupingsComponent } from './send/send-groupings.component'; +import { SendTypeComponent } from './send/send-type.component'; import { A11yTitleDirective } from 'jslib/angular/directives/a11y-title.directive'; import { ApiActionDirective } from 'jslib/angular/directives/api-action.directive'; @@ -214,6 +215,7 @@ registerLocaleData(localeZhTw, 'zh-TW'); SelectCopyDirective, SendGroupingsComponent, SendListComponent, + SendTypeComponent, SettingsComponent, ShareComponent, StopClickDirective, diff --git a/src/popup/components/send-list.component.html b/src/popup/components/send-list.component.html index 3c0454ab4d..7f2105296c 100644 --- a/src/popup/components/send-list.component.html +++ b/src/popup/components/send-list.component.html @@ -11,7 +11,7 @@ {{s.name}} - + {{'disabled' | i18n}} @@ -19,8 +19,7 @@ {{'passwordProtected' | i18n}} - + {{'maxAccessCountReached' | i18n}} diff --git a/src/popup/send/send-groupings.component.ts b/src/popup/send/send-groupings.component.ts index bc405f55b0..e03fe3aa61 100644 --- a/src/popup/send/send-groupings.component.ts +++ b/src/popup/send/send-groupings.component.ts @@ -1,4 +1,5 @@ import { + ChangeDetectorRef, Component, NgZone, } from '@angular/core'; @@ -46,21 +47,24 @@ export class SendGroupingsComponent extends BaseSendComponent { private loadedTimeout: number; constructor(sendService: SendService, i18nService: I18nService, - platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService, - broadcasterService: BroadcasterService, ngZone: NgZone, policyService: PolicyService, - userService: UserService, searchService: SearchService, + 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) { - super(sendService, i18nService, platformUtilsService, environmentService, broadcasterService, ngZone, - searchService, policyService, userService); + private route: ActivatedRoute, private router: Router, private syncService: SyncService, + private changeDetectorRef: ChangeDetectorRef, private broadcasterService: BroadcasterService) { + super(sendService, i18nService, platformUtilsService, environmentService, ngZone, searchService, + policyService, userService); super.onSuccessfulLoad = async () => { this.calculateTypeCounts(); + this.selectAll(); }; } async ngOnInit() { // Determine Header details this.showLeftHeader = !(this.popupUtils.inSidebar(window) && this.platformUtilsService.isFirefox()); + // Clear state of Send Type Component + this.stateService.remove('SendTypeComponent'); // Let super class finish await super.ngOnInit(); // Handle State Restore if necessary @@ -83,6 +87,23 @@ export class SendGroupingsComponent extends BaseSendComponent { if (!this.syncService.syncInProgress || restoredScopeState) { window.setTimeout(() => this.popupUtils.setContentScrollY(window, this.state.scrollY), 0); } + + // Load all sends if sync completed in background + this.broadcasterService.subscribe(ComponentId, (message: any) => { + this.ngZone.run(async () => { + switch (message.command) { + case 'syncCompleted': + window.setTimeout(() => { + this.load(); + }, 500); + break; + default: + break; + } + + this.changeDetectorRef.detectChanges(); + }); + }); } ngOnDestroy() { @@ -92,12 +113,12 @@ export class SendGroupingsComponent extends BaseSendComponent { } // Save state this.saveState(); - // Allow super to finish - super.ngOnDestroy(); + // Unsubscribe + this.broadcasterService.unsubscribe(ComponentId); } async selectType(type: SendType) { - // TODO this.router.navigate(['/send-type-list'], { queryParams: { type: type } }); + this.router.navigate(['/send-type'], { queryParams: { type: type } }); } async selectSend(s: SendView) { @@ -115,7 +136,7 @@ export class SendGroupingsComponent extends BaseSendComponent { private calculateTypeCounts() { // Create type counts const typeCounts = new Map(); - this.sends.forEach((s) => { + this.sends.forEach(s => { if (typeCounts.has(s.type)) { typeCounts.set(s.type, typeCounts.get(s.type) + 1); } else { diff --git a/src/popup/send/send-type.component.html b/src/popup/send/send-type.component.html new file mode 100644 index 0000000000..24c9f881cd --- /dev/null +++ b/src/popup/send/send-type.component.html @@ -0,0 +1,41 @@ +
+
+ +
+ +
+ +
+
+ +
+ + +

{{'noItemsInList' | i18n}}

+ +
+
+
+
+ {{groupingTitle}} + {{filteredSends.length}} +
+
+ + +
+
+
diff --git a/src/popup/send/send-type.component.ts b/src/popup/send/send-type.component.ts new file mode 100644 index 0000000000..17488529fe --- /dev/null +++ b/src/popup/send/send-type.component.ts @@ -0,0 +1,149 @@ +import { + ChangeDetectorRef, + Component, + NgZone, +} from '@angular/core'; + +import { + ActivatedRoute, +} from '@angular/router'; + +import { Location } from '@angular/common'; + +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 { 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 = 'SendTypeComponent'; + +@Component({ + selector: 'app-send-type', + templateUrl: 'send-type.component.html', +}) +export class SendTypeComponent extends BaseSendComponent { + groupingTitle: string; + // State Handling + state: any; + private refreshTimeout: number; + private applySavedState = true; + + constructor(sendService: SendService, i18nService: I18nService, + platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService, ngZone: NgZone, + policyService: PolicyService, userService: UserService, searchService: SearchService, + private popupUtils: PopupUtilsService, private stateService: StateService, + private route: ActivatedRoute, private location: Location, private changeDetectorRef: ChangeDetectorRef, + private broadcasterService: BroadcasterService) { + super(sendService, i18nService, platformUtilsService, environmentService, ngZone, searchService, + policyService, userService); + super.onSuccessfulLoad = async () => { + this.selectType(this.type); + }; + this.applySavedState = (window as any).previousPopupUrl != null && + !(window as any).previousPopupUrl.startsWith('/send-type'); + } + + async ngOnInit() { + // Let super class finish + await super.ngOnInit(); + const queryParamsSub = this.route.queryParams.subscribe(async params => { + if (this.applySavedState) { + this.state = (await this.stateService.get(ComponentId)) || {}; + if (this.state.searchText != null) { + this.searchText = this.state.searchText; + } + } + + if (params.type != null) { + this.type = parseInt(params.type, null); + switch (this.type) { + case SendType.Text: + this.groupingTitle = this.i18nService.t('sendTypeText'); + break; + case SendType.File: + this.groupingTitle = this.i18nService.t('sendTypeFile'); + break; + default: + break; + } + await this.load(s => s.type === this.type); + } + + // Restore state and remove reference + if (this.applySavedState && this.state != null) { + window.setTimeout(() => this.popupUtils.setContentScrollY(window, this.state.scrollY), 0); + } + this.stateService.remove(ComponentId); + + // Unsubscribe + if (queryParamsSub != null) { + queryParamsSub.unsubscribe(); + } + }); + + // Refresh Send list if sync completed in background + this.broadcasterService.subscribe(ComponentId, (message: any) => { + this.ngZone.run(async () => { + switch (message.command) { + case 'syncCompleted': + if (message.successfully) { + this.refreshTimeout = window.setTimeout(() => { + this.refresh(); + }, 500); + } + break; + default: + break; + } + + this.changeDetectorRef.detectChanges(); + }); + }); + } + + ngOnDestroy() { + // Remove timeout + if (this.refreshTimeout != null) { + window.clearTimeout(this.refreshTimeout); + } + // Save state + this.saveState(); + // Unsubscribe + this.broadcasterService.unsubscribe(ComponentId); + } + + async selectSend(s: SendView) { + // TODO -> Route to edit send + } + + async addSend() { + // TODO -> Route to create send + } + + back() { + (window as any).routeDirection = 'b'; + this.location.back(); + } + + private async saveState() { + this.state = { + scrollY: this.popupUtils.getContentScrollY(window), + searchText: this.searchText, + }; + await this.stateService.save(ComponentId, this.state); + } +}