mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-23 11:56:00 +01:00
stub out bulk share
This commit is contained in:
parent
3edf761549
commit
e18f76d2b0
2
jslib
2
jslib
@ -1 +1 @@
|
||||
Subproject commit 7a5a4e654a76b7a7e546d33b1e2ad4e3d6c9adc3
|
||||
Subproject commit cfad521ea8ef205abe774907d8f7a243b3adaf10
|
@ -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,
|
||||
|
54
src/app/vault/bulk-share.component.html
Normal file
54
src/app/vault/bulk-share.component.html
Normal 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">×</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>
|
88
src/app/vault/bulk-share.component.ts
Normal file
88
src/app/vault/bulk-share.component.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user