From e18f76d2b0c7a5faaf901e01a949cc3ee0c99fdd Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Wed, 13 Jun 2018 00:03:48 -0400 Subject: [PATCH] stub out bulk share --- jslib | 2 +- src/app/app.module.ts | 3 + src/app/vault/bulk-share.component.html | 54 +++++++++++++++ src/app/vault/bulk-share.component.ts | 88 +++++++++++++++++++++++++ src/app/vault/ciphers.component.ts | 8 ++- src/app/vault/share.component.html | 4 +- src/app/vault/share.component.ts | 29 +++----- src/app/vault/vault.component.ts | 24 ++++++- src/locales/en/messages.json | 20 ++++++ 9 files changed, 203 insertions(+), 29 deletions(-) create mode 100644 src/app/vault/bulk-share.component.html create mode 100644 src/app/vault/bulk-share.component.ts diff --git a/jslib b/jslib index 7a5a4e654a7..cfad521ea8e 160000 --- a/jslib +++ b/jslib @@ -1 +1 @@ -Subproject commit 7a5a4e654a76b7a7e546d33b1e2ad4e3d6c9adc3 +Subproject commit cfad521ea8ef205abe774907d8f7a243b3adaf10 diff --git a/src/app/app.module.ts b/src/app/app.module.ts index f535650c37c..ec927de8682 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -37,6 +37,7 @@ import { AddEditComponent } from './vault/add-edit.component'; import { AttachmentsComponent } from './vault/attachments.component'; import { BulkDeleteComponent } from './vault/bulk-delete.component'; import { BulkMoveComponent } from './vault/bulk-move.component'; +import { BulkShareComponent } from './vault/bulk-share.component'; import { CiphersComponent } from './vault/ciphers.component'; import { CollectionsComponent } from './vault/collections.component'; import { FolderAddEditComponent } from './vault/folder-add-edit.component'; @@ -85,6 +86,7 @@ import { Folder } from 'jslib/models/domain'; BoxRowDirective, BulkDeleteComponent, BulkMoveComponent, + BulkShareComponent, CiphersComponent, CollectionsComponent, ExportComponent, @@ -121,6 +123,7 @@ import { Folder } from 'jslib/models/domain'; AttachmentsComponent, BulkDeleteComponent, BulkMoveComponent, + BulkShareComponent, CollectionsComponent, FolderAddEditComponent, ModalComponent, diff --git a/src/app/vault/bulk-share.component.html b/src/app/vault/bulk-share.component.html new file mode 100644 index 00000000000..6e9a4ca2047 --- /dev/null +++ b/src/app/vault/bulk-share.component.html @@ -0,0 +1,54 @@ + diff --git a/src/app/vault/bulk-share.component.ts b/src/app/vault/bulk-share.component.ts new file mode 100644 index 00000000000..09ff609cbe2 --- /dev/null +++ b/src/app/vault/bulk-share.component.ts @@ -0,0 +1,88 @@ +import { + Component, + EventEmitter, + Input, + OnInit, + Output, +} from '@angular/core'; + +import { ToasterService } from 'angular2-toaster'; +import { Angulartics2 } from 'angulartics2'; + +import { CipherService } from 'jslib/abstractions/cipher.service'; +import { CollectionService } from 'jslib/abstractions/collection.service'; +import { I18nService } from 'jslib/abstractions/i18n.service'; +import { UserService } from 'jslib/abstractions/user.service'; + +import { CipherView } from 'jslib/models/view'; +import { CollectionView } from 'jslib/models/view/collectionView'; + +import { Organization } from 'jslib/models/domain/organization'; + +@Component({ + selector: 'app-vault-bulk-share', + templateUrl: 'bulk-share.component.html', +}) +export class BulkShareComponent implements OnInit { + @Input() ciphers: CipherView[] = []; + @Input() organizationId: string; + @Output() onShared = new EventEmitter(); + + nonShareableCount = 0; + collections: CollectionView[] = []; + organizations: Organization[] = []; + formPromise: Promise; + + private writeableCollections: CollectionView[] = []; + private shareableCiphers: CipherView[] = []; + + constructor(private analytics: Angulartics2, private cipherService: CipherService, + private toasterService: ToasterService, private i18nService: I18nService, + private collectionService: CollectionService, private userService: UserService) { } + + async ngOnInit() { + this.shareableCiphers = this.ciphers.filter((c) => !c.hasAttachments && c.organizationId == null); + this.nonShareableCount = this.ciphers.length - this.shareableCiphers.length; + const allCollections = await this.collectionService.getAllDecrypted(); + this.writeableCollections = allCollections.filter((c) => !c.readOnly); + this.organizations = await this.userService.getAllOrganizations(); + if (this.organizationId == null && this.organizations.length > 0) { + this.organizationId = this.organizations[0].id; + } + this.filterCollections(); + } + + ngOnDestroy() { + this.selectAll(false); + } + + filterCollections() { + this.selectAll(false); + if (this.organizationId == null || this.writeableCollections.length === 0) { + this.collections = []; + } else { + this.collections = this.writeableCollections.filter((c) => c.organizationId === this.organizationId); + } + } + + async submit() { + const checkedCollectionIds = this.collections.filter((c) => (c as any).checked).map((c) => c.id); + this.formPromise = this.cipherService.shareManyWithServer(this.shareableCiphers, this.organizationId, + checkedCollectionIds); + await this.formPromise; + this.onShared.emit(); + this.analytics.eventTrack.next({ action: 'Bulk Shared Items' }); + this.toasterService.popAsync('success', null, this.i18nService.t('sharedItems')); + } + + check(c: CollectionView) { + (c as any).checked = !(c as any).checked; + } + + selectAll(select: false) { + const collections = select ? this.collections : this.writeableCollections; + for (const c of collections) { + (c as any).checked = select; + } + } +} diff --git a/src/app/vault/ciphers.component.ts b/src/app/vault/ciphers.component.ts index 1bc374de93e..acc237fe5b7 100644 --- a/src/app/vault/ciphers.component.ts +++ b/src/app/vault/ciphers.component.ts @@ -50,11 +50,15 @@ export class CiphersComponent extends BaseCiphersComponent { } } - getSelected(): string[] { + getSelected(): CipherView[] { if (this.ciphers == null) { return []; } - return this.ciphers.filter((c) => !!(c as any).checked).map((c) => c.id); + return this.ciphers.filter((c) => !!(c as any).checked); + } + + getSelectedIds(): string[] { + return this.getSelected().map((c) => c.id); } attachments(c: CipherView) { diff --git a/src/app/vault/share.component.html b/src/app/vault/share.component.html index 48a8c5b21c4..0a6c74ba1c4 100644 --- a/src/app/vault/share.component.html +++ b/src/app/vault/share.component.html @@ -21,10 +21,10 @@

{{'collections' | i18n}}

- - diff --git a/src/app/vault/share.component.ts b/src/app/vault/share.component.ts index 62a777104dc..5fcb459c15a 100644 --- a/src/app/vault/share.component.ts +++ b/src/app/vault/share.component.ts @@ -52,11 +52,11 @@ export class ShareComponent implements OnInit, OnDestroy { } ngOnDestroy() { - this.unselectAll(); + this.selectAll(false); } filterCollections() { - this.unselectAll(); + this.selectAll(false); if (this.organizationId == null || this.writeableCollections.length === 0) { this.collections = []; } else { @@ -77,17 +77,9 @@ export class ShareComponent implements OnInit, OnDestroy { } } - cipherView.organizationId = this.organizationId; - cipherView.collectionIds = []; - for (const collection of this.collections) { - if ((collection as any).checked) { - cipherView.collectionIds.push(collection.id); - } - } - + const checkedCollectionIds = this.collections.filter((c) => (c as any).checked).map((c) => c.id); this.formPromise = Promise.all(attachmentPromises).then(async () => { - const encCipher = await this.cipherService.encrypt(cipherView); - await this.cipherService.shareWithServer(encCipher); + await this.cipherService.shareWithServer(cipherView, this.organizationId, checkedCollectionIds); this.onSharedCipher.emit(); this.analytics.eventTrack.next({ action: 'Shared Cipher' }); this.toasterService.popAsync('success', null, this.i18nService.t('sharedItem')); @@ -99,15 +91,10 @@ export class ShareComponent implements OnInit, OnDestroy { (c as any).checked = !(c as any).checked; } - selectAll() { - for (const c of this.collections) { - (c as any).checked = true; - } - } - - unselectAll() { - for (const c of this.writeableCollections) { - (c as any).checked = false; + selectAll(select: false) { + const collections = select ? this.collections : this.writeableCollections; + for (const c of collections) { + (c as any).checked = select; } } } diff --git a/src/app/vault/vault.component.ts b/src/app/vault/vault.component.ts index 5a893f6457e..1d4365ae4e8 100644 --- a/src/app/vault/vault.component.ts +++ b/src/app/vault/vault.component.ts @@ -31,6 +31,7 @@ import { ShareComponent } from './share.component'; import { I18nService } from 'jslib/abstractions/i18n.service'; import { SyncService } from 'jslib/abstractions/sync.service'; import { BulkMoveComponent } from './bulk-move.component'; +import { BulkShareComponent } from './bulk-share.component'; @Component({ selector: 'app-vault', @@ -47,6 +48,7 @@ export class VaultComponent implements OnInit { @ViewChild('collections', { read: ViewContainerRef }) collectionsModalRef: ViewContainerRef; @ViewChild('bulkDeleteTemplate', { read: ViewContainerRef }) bulkDeleteModalRef: ViewContainerRef; @ViewChild('bulkMoveTemplate', { read: ViewContainerRef }) bulkMoveModalRef: ViewContainerRef; + @ViewChild('bulkShareTemplate', { read: ViewContainerRef }) bulkShareModalRef: ViewContainerRef; cipherId: string = null; favorites: boolean = false; @@ -290,7 +292,7 @@ export class VaultComponent implements OnInit { this.modal = this.bulkDeleteModalRef.createComponent(factory).instance; const childComponent = this.modal.show(BulkDeleteComponent, this.bulkDeleteModalRef); - childComponent.cipherIds = this.ciphersComponent.getSelected(); + childComponent.cipherIds = this.ciphersComponent.getSelectedIds(); childComponent.onDeleted.subscribe(async () => { this.modal.close(); await this.ciphersComponent.refresh(); @@ -302,7 +304,23 @@ export class VaultComponent implements OnInit { } bulkShare() { - // + if (this.modal != null) { + this.modal.close(); + } + + const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent); + this.modal = this.bulkShareModalRef.createComponent(factory).instance; + const childComponent = this.modal.show(BulkShareComponent, this.bulkShareModalRef); + + childComponent.ciphers = this.ciphersComponent.getSelected(); + childComponent.onShared.subscribe(async () => { + this.modal.close(); + await this.ciphersComponent.refresh(); + }); + + this.modal.onClosed.subscribe(async () => { + this.modal = null; + }); } bulkMove() { @@ -314,7 +332,7 @@ export class VaultComponent implements OnInit { this.modal = this.bulkMoveModalRef.createComponent(factory).instance; const childComponent = this.modal.show(BulkMoveComponent, this.bulkMoveModalRef); - childComponent.cipherIds = this.ciphersComponent.getSelected(); + childComponent.cipherIds = this.ciphersComponent.getSelectedIds(); childComponent.onMoved.subscribe(async () => { this.modal.close(); await this.ciphersComponent.refresh(); diff --git a/src/locales/en/messages.json b/src/locales/en/messages.json index 42d928849d4..9a8671b2a77 100644 --- a/src/locales/en/messages.json +++ b/src/locales/en/messages.json @@ -671,6 +671,9 @@ "shareDesc": { "message": "Choose an organization that you wish to share this item with. Sharing transfers ownership of the item to the organization. You will no longer be the direct owner of this item once it has been shared." }, + "shareManyDesc": { + "message": "Choose an organization that you wish to share these items with. Sharing transfers ownership of the items to the organization. You will no longer be the direct owner of these items once they have been shared." + }, "collectionsDesc": { "message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item." }, @@ -691,5 +694,22 @@ "example": "150" } } + }, + "shareSelectedItemsDesc": { + "message": "You have selected $COUNT$ item(s). $SHAREABLE_COUNT$ items are sharable, $NONSHAREABLE_COUNT$ are not. Items with attachments must be shared individually.", + "placeholders": { + "count": { + "content": "$1", + "example": "10" + }, + "shareable_count": { + "content": "$2", + "example": "8" + }, + "nonshareable_count": { + "content": "$3", + "example": "2" + } + } } }