From edef3f90f15e605065a402a1266a5d6d478583cc Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Tue, 12 Jun 2018 11:46:11 -0400 Subject: [PATCH] sharing --- jslib | 2 +- .../two-factor-options.component.html | 2 +- src/app/app.module.ts | 3 + src/app/vault/add-edit.component.html | 2 +- src/app/vault/attachments.component.html | 2 +- src/app/vault/ciphers.component.ts | 6 +- src/app/vault/folder-add-edit.component.html | 2 +- src/app/vault/share.component.html | 54 +++++++++ src/app/vault/share.component.ts | 113 ++++++++++++++++++ src/app/vault/vault.component.html | 5 +- src/app/vault/vault.component.ts | 29 ++++- src/locales/en/messages.json | 12 ++ src/scss/plugins.scss | 20 ++-- 13 files changed, 229 insertions(+), 23 deletions(-) create mode 100644 src/app/vault/share.component.html create mode 100644 src/app/vault/share.component.ts diff --git a/jslib b/jslib index 4bd9a9fc11..b3f71ed8e4 160000 --- a/jslib +++ b/jslib @@ -1 +1 @@ -Subproject commit 4bd9a9fc1121cd2f9bbe3b414c6239040b4fe880 +Subproject commit b3f71ed8e406008987c29b29b0366a7c0f246765 diff --git a/src/app/accounts/two-factor-options.component.html b/src/app/accounts/two-factor-options.component.html index abbced4fac..cfa4c31710 100644 --- a/src/app/accounts/two-factor-options.component.html +++ b/src/app/accounts/two-factor-options.component.html @@ -3,7 +3,7 @@ diff --git a/src/app/vault/ciphers.component.ts b/src/app/vault/ciphers.component.ts index 406ee26dfa..22ff916ad2 100644 --- a/src/app/vault/ciphers.component.ts +++ b/src/app/vault/ciphers.component.ts @@ -23,6 +23,8 @@ import { CipherView } from 'jslib/models/view/cipherView'; }) export class CiphersComponent extends BaseCiphersComponent { @Output() onAttachmentsClicked = new EventEmitter(); + @Output() onShareClicked = new EventEmitter(); + @Output() onCollectionsClicked = new EventEmitter(); cipherType = CipherType; constructor(cipherService: CipherService, private analytics: Angulartics2, @@ -40,11 +42,11 @@ export class CiphersComponent extends BaseCiphersComponent { } share(c: CipherView) { - // + this.onShareClicked.emit(c); } collections(c: CipherView) { - // + this.onCollectionsClicked.emit(c); } delete(c: CipherView) { diff --git a/src/app/vault/folder-add-edit.component.html b/src/app/vault/folder-add-edit.component.html index 9f9f7402f8..d4aba57197 100644 --- a/src/app/vault/folder-add-edit.component.html +++ b/src/app/vault/folder-add-edit.component.html @@ -3,7 +3,7 @@ diff --git a/src/app/vault/share.component.html b/src/app/vault/share.component.html new file mode 100644 index 0000000000..48a8c5b21c --- /dev/null +++ b/src/app/vault/share.component.html @@ -0,0 +1,54 @@ + diff --git a/src/app/vault/share.component.ts b/src/app/vault/share.component.ts new file mode 100644 index 0000000000..62a777104d --- /dev/null +++ b/src/app/vault/share.component.ts @@ -0,0 +1,113 @@ +import { + Component, + EventEmitter, + Input, + OnDestroy, + 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 { Organization } from 'jslib/models/domain/organization'; +import { CipherView } from 'jslib/models/view/cipherView'; +import { CollectionView } from 'jslib/models/view/collectionView'; + +@Component({ + selector: 'app-vault-share', + templateUrl: 'share.component.html', +}) +export class ShareComponent implements OnInit, OnDestroy { + @Input() cipherId: string; + @Input() organizationId: string; + @Output() onSharedCipher = new EventEmitter(); + + formPromise: Promise; + cipher: CipherView; + collections: CollectionView[] = []; + organizations: Organization[] = []; + + private writeableCollections: CollectionView[] = []; + + constructor(private collectionService: CollectionService, private analytics: Angulartics2, + private toasterService: ToasterService, private i18nService: I18nService, + private userService: UserService, private cipherService: CipherService) { } + + async ngOnInit() { + const cipherDomain = await this.cipherService.get(this.cipherId); + this.cipher = await cipherDomain.decrypt(); + 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.unselectAll(); + } + + filterCollections() { + this.unselectAll(); + if (this.organizationId == null || this.writeableCollections.length === 0) { + this.collections = []; + } else { + this.collections = this.writeableCollections.filter((c) => c.organizationId === this.organizationId); + } + } + + async submit() { + const cipherDomain = await this.cipherService.get(this.cipherId); + const cipherView = await cipherDomain.decrypt(); + + const attachmentPromises: Array> = []; + if (cipherView.attachments != null) { + for (const attachment of cipherView.attachments) { + const promise = this.cipherService.shareAttachmentWithServer(attachment, + cipherView.id, this.organizationId); + attachmentPromises.push(promise); + } + } + + cipherView.organizationId = this.organizationId; + cipherView.collectionIds = []; + for (const collection of this.collections) { + if ((collection as any).checked) { + cipherView.collectionIds.push(collection.id); + } + } + + this.formPromise = Promise.all(attachmentPromises).then(async () => { + const encCipher = await this.cipherService.encrypt(cipherView); + await this.cipherService.shareWithServer(encCipher); + this.onSharedCipher.emit(); + this.analytics.eventTrack.next({ action: 'Shared Cipher' }); + this.toasterService.popAsync('success', null, this.i18nService.t('sharedItem')); + }); + await this.formPromise; + } + + check(c: CollectionView) { + (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; + } + } +} diff --git a/src/app/vault/vault.component.html b/src/app/vault/vault.component.html index ecd1e54341..4105ea03d4 100644 --- a/src/app/vault/vault.component.html +++ b/src/app/vault/vault.component.html @@ -44,7 +44,8 @@ - +
@@ -68,3 +69,5 @@ + + diff --git a/src/app/vault/vault.component.ts b/src/app/vault/vault.component.ts index 34ec49facf..0d2ea05871 100644 --- a/src/app/vault/vault.component.ts +++ b/src/app/vault/vault.component.ts @@ -27,6 +27,7 @@ import { OrganizationsComponent } from './organizations.component'; import { I18nService } from 'jslib/abstractions/i18n.service'; import { SyncService } from 'jslib/abstractions/sync.service'; +import { ShareComponent } from './share.component'; @Component({ selector: 'app-vault', @@ -38,7 +39,9 @@ export class VaultComponent implements OnInit { @ViewChild(OrganizationsComponent) organizationsComponent: OrganizationsComponent; @ViewChild('attachments', { read: ViewContainerRef }) attachmentsModalRef: ViewContainerRef; @ViewChild('folderAddEdit', { read: ViewContainerRef }) folderAddEditModalRef: ViewContainerRef; - @ViewChild('cipherAddEdit', { read: ViewContainerRef }) cipherAddEditRef: ViewContainerRef; + @ViewChild('cipherAddEdit', { read: ViewContainerRef }) cipherAddEditModalRef: ViewContainerRef; + @ViewChild('share', { read: ViewContainerRef }) shareModalRef: ViewContainerRef; + @ViewChild('collections', { read: ViewContainerRef }) collectionsModalRef: ViewContainerRef; cipherId: string = null; favorites: boolean = false; @@ -154,6 +157,26 @@ export class VaultComponent implements OnInit { }); } + shareCipher(cipher: CipherView) { + if (this.modal != null) { + this.modal.close(); + } + + const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent); + this.modal = this.shareModalRef.createComponent(factory).instance; + const childComponent = this.modal.show(ShareComponent, this.shareModalRef); + + childComponent.cipherId = cipher.id; + childComponent.onSharedCipher.subscribe(async () => { + this.modal.close(); + await this.ciphersComponent.refresh(); + }); + + this.modal.onClosed.subscribe(async () => { + this.modal = null; + }); + } + async addFolder() { if (this.modal != null) { this.modal.close(); @@ -212,9 +235,9 @@ export class VaultComponent implements OnInit { } const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent); - this.modal = this.cipherAddEditRef.createComponent(factory).instance; + this.modal = this.cipherAddEditModalRef.createComponent(factory).instance; const childComponent = this.modal.show( - AddEditComponent, this.cipherAddEditRef); + AddEditComponent, this.cipherAddEditModalRef); childComponent.cipherId = cipher == null ? null : cipher.id; childComponent.onSavedCipher.subscribe(async (c: CipherView) => { diff --git a/src/locales/en/messages.json b/src/locales/en/messages.json index 62dfc5276b..ea5334b77d 100644 --- a/src/locales/en/messages.json +++ b/src/locales/en/messages.json @@ -416,6 +416,9 @@ "editedItem": { "message": "Edited item" }, + "sharedItem": { + "message": "Shared item" + }, "deleteItem": { "message": "Delete Item" }, @@ -649,5 +652,14 @@ }, "continue": { "message": "Continue" + }, + "organization": { + "message": "Organization" + }, + "organizations": { + "message": "Organizations" + }, + "shareDesc": { + "message": "Choose an organization that you wish to share this item with. Sharing an item transfers ownership of that item to the organization. You will no longer be the direct owner of this item once it has been shared." } } diff --git a/src/scss/plugins.scss b/src/scss/plugins.scss index 7e599d2d6a..deed44424b 100644 --- a/src/scss/plugins.scss +++ b/src/scss/plugins.scss @@ -132,21 +132,17 @@ justify-content: flex-end; font-size: $font-size-base; - .swal-button { + button.swal-button { @extend .btn; - &:focus { - box-shadow: none; + &.swal-button--confirm { + @extend .btn-primary; + } + + &.swal-button--cancel { + @extend .btn-outline-secondary; + background-color: #ffffff; } - } - - .swal-button--confirm { - @extend .btn-primary; - } - - .swal-button--cancel { - @extend .btn-outline-secondary; - background-color: #ffffff; } } }