1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-09-27 04:03:00 +02:00

stub out bulk share

This commit is contained in:
Kyle Spearrin 2018-06-13 00:03:48 -04:00
parent 3edf761549
commit e18f76d2b0
9 changed files with 203 additions and 29 deletions

2
jslib

@ -1 +1 @@
Subproject commit 7a5a4e654a76b7a7e546d33b1e2ad4e3d6c9adc3
Subproject commit cfad521ea8ef205abe774907d8f7a243b3adaf10

View File

@ -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,

View File

@ -0,0 +1,54 @@
<div class="modal fade">
<div class="modal-dialog">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="modal-header">
<h2 class="modal-title">
{{'shareSelected' | i18n}}
</h2>
<button type="button" class="close" data-dismiss="modal" attr.aria-label="{{'close' | i18n}}">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>{{'shareManyDesc' | i18n}}</p>
<p>{{'shareSelectedItemsDesc' | i18n: this.ciphers.length : shareableCiphers.length : nonShareableCount}}</p>
<div class="form-group">
<label for="organization">{{'organization' | i18n}}</label>
<select id="organization" name="OrganizationId" [(ngModel)]="organizationId" class="form-control" (change)="filterCollections()">
<option *ngFor="let o of organizations" [ngValue]="o.id">{{o.name}}</option>
</select>
</div>
<div class="d-flex">
<h3>{{'collections' | i18n}}</h3>
<small class="ml-auto d-flex">
<button type="button" appBlurClick (click)="selectAll(true)" class="btn btn-link btn-sm py-0">
{{'selectAll' | i18n}}
</button>
<button type="button" appBlurClick (click)="selectAll(false)" class="btn btn-link btn-sm py-0">
{{'unselectAll' | i18n}}
</button>
</small>
</div>
<table class="table table-hover table-list mb-0" *ngIf="collections">
<tbody>
<tr *ngFor="let c of collections; let i = index" (click)="check(c)">
<td class="table-list-checkbox">
<input type="checkbox" [(ngModel)]="c.checked" name="Collection[{{i}}].Checked">
</td>
<td>
<span appStopProp>{{c.name}}</span>
</td>
</tr>
</tbody>
</table>
</div>
<div class="modal-footer">
<button appBlurClick type="submit" class="btn btn-primary" title="{{'save' | i18n}}" [disabled]="form.loading">
<i class="fa fa-save fa-lg fa-fw" [hidden]="form.loading"></i>
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!form.loading"></i>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal" title="{{'cancel' | i18n}}">{{'cancel' | i18n}}</button>
</div>
</form>
</div>
</div>

View File

@ -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<any>;
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;
}
}
}

View File

@ -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) {

View File

@ -21,10 +21,10 @@
<div class="d-flex">
<h3>{{'collections' | i18n}}</h3>
<small class="ml-auto d-flex">
<button type="button" appBlurClick (click)="selectAll()" class="btn btn-link btn-sm py-0">
<button type="button" appBlurClick (click)="selectAll(true)" class="btn btn-link btn-sm py-0">
{{'selectAll' | i18n}}
</button>
<button type="button" appBlurClick (click)="unselectAll()" class="btn btn-link btn-sm py-0">
<button type="button" appBlurClick (click)="selectAll(false)" class="btn btn-link btn-sm py-0">
{{'unselectAll' | i18n}}
</button>
</small>

View File

@ -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;
}
}
}

View File

@ -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>(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>(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>(BulkMoveComponent, this.bulkMoveModalRef);
childComponent.cipherIds = this.ciphersComponent.getSelected();
childComponent.cipherIds = this.ciphersComponent.getSelectedIds();
childComponent.onMoved.subscribe(async () => {
this.modal.close();
await this.ciphersComponent.refresh();

View File

@ -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"
}
}
}
}