1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-11-23 11:56:00 +01:00

bulk actions stubbed out. bulk delete implemented

This commit is contained in:
Kyle Spearrin 2018-06-12 17:11:24 -04:00
parent 3666ee5a87
commit 314ab61349
7 changed files with 146 additions and 8 deletions

View File

@ -35,6 +35,7 @@ import { ToolsComponent } from './tools/tools.component';
import { AddEditComponent } from './vault/add-edit.component'; import { AddEditComponent } from './vault/add-edit.component';
import { AttachmentsComponent } from './vault/attachments.component'; import { AttachmentsComponent } from './vault/attachments.component';
import { BulkDeleteComponent } from './vault/bulk-delete.component';
import { CiphersComponent } from './vault/ciphers.component'; import { CiphersComponent } from './vault/ciphers.component';
import { CollectionsComponent } from './vault/collections.component'; import { CollectionsComponent } from './vault/collections.component';
import { FolderAddEditComponent } from './vault/folder-add-edit.component'; import { FolderAddEditComponent } from './vault/folder-add-edit.component';
@ -81,6 +82,7 @@ import { Folder } from 'jslib/models/domain';
AutofocusDirective, AutofocusDirective,
BlurClickDirective, BlurClickDirective,
BoxRowDirective, BoxRowDirective,
BulkDeleteComponent,
CiphersComponent, CiphersComponent,
CollectionsComponent, CollectionsComponent,
ExportComponent, ExportComponent,
@ -115,6 +117,7 @@ import { Folder } from 'jslib/models/domain';
entryComponents: [ entryComponents: [
AddEditComponent, AddEditComponent,
AttachmentsComponent, AttachmentsComponent,
BulkDeleteComponent,
CollectionsComponent, CollectionsComponent,
FolderAddEditComponent, FolderAddEditComponent,
ModalComponent, ModalComponent,

View File

@ -0,0 +1,25 @@
<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">
{{'deleteSelected' | 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">
{{'deleteSelectedItemsDesc' | i18n: cipherIds.length}}
</div>
<div class="modal-footer">
<button appBlurClick type="submit" class="btn btn-danger" title="{{'delete' | i18n}}" [disabled]="form.loading">
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="form.loading"></i>
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!form.loading"></i>
{{'delete' | i18n}}
</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,34 @@
import {
Component,
EventEmitter,
Input,
Output,
} from '@angular/core';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
import { CipherService } from 'jslib/abstractions/cipher.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
@Component({
selector: 'app-vault-bulk-delete',
templateUrl: 'bulk-delete.component.html',
})
export class BulkDeleteComponent {
@Input() cipherIds: string[] = [];
@Output() onDeleted = new EventEmitter();
formPromise: Promise<any>;
constructor(private analytics: Angulartics2, private cipherService: CipherService,
private toasterService: ToasterService, private i18nService: I18nService) { }
async submit() {
this.formPromise = this.cipherService.deleteManyWithServer(this.cipherIds);
await this.formPromise;
this.onDeleted.emit();
this.analytics.eventTrack.next({ action: 'Bulk Deleted Items' });
this.toasterService.popAsync('success', null, this.i18nService.t('deletedItems'));
}
}

View File

@ -17,6 +17,8 @@ import { CipherType } from 'jslib/enums/cipherType';
import { CipherView } from 'jslib/models/view/cipherView'; import { CipherView } from 'jslib/models/view/cipherView';
const MaxCheckedCount = 500;
@Component({ @Component({
selector: 'app-vault-ciphers', selector: 'app-vault-ciphers',
templateUrl: 'ciphers.component.html', templateUrl: 'ciphers.component.html',
@ -25,6 +27,7 @@ export class CiphersComponent extends BaseCiphersComponent {
@Output() onAttachmentsClicked = new EventEmitter<CipherView>(); @Output() onAttachmentsClicked = new EventEmitter<CipherView>();
@Output() onShareClicked = new EventEmitter<CipherView>(); @Output() onShareClicked = new EventEmitter<CipherView>();
@Output() onCollectionsClicked = new EventEmitter<CipherView>(); @Output() onCollectionsClicked = new EventEmitter<CipherView>();
cipherType = CipherType; cipherType = CipherType;
constructor(cipherService: CipherService, private analytics: Angulartics2, constructor(cipherService: CipherService, private analytics: Angulartics2,
@ -37,6 +40,23 @@ export class CiphersComponent extends BaseCiphersComponent {
(c as any).checked = !(c as any).checked; (c as any).checked = !(c as any).checked;
} }
selectAll(select: boolean) {
if (select) {
this.selectAll(false);
}
const selectCount = select && this.ciphers.length > MaxCheckedCount ? MaxCheckedCount : this.ciphers.length;
for (let i = 0; i < selectCount; i++) {
(this.ciphers[i] as any).checked = select;
}
}
getSelected(): string[] {
if (this.ciphers == null) {
return [];
}
return this.ciphers.filter((c) => !!(c as any).checked).map((c) => c.id);
}
attachments(c: CipherView) { attachments(c: CipherView) {
this.onAttachmentsClicked.emit(c); this.onAttachmentsClicked.emit(c);
} }

View File

@ -11,35 +11,35 @@
<h1>{{'myVault' | i18n}}</h1> <h1>{{'myVault' | i18n}}</h1>
<div class="ml-auto d-flex"> <div class="ml-auto d-flex">
<div class="dropdown mr-2" appListDropdown> <div class="dropdown mr-2" appListDropdown>
<button class="btn btn-sm btn-outline-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" <button class="btn btn-sm btn-outline-secondary dropdown-toggle" type="button" id="bulkActionsButton" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false"> aria-haspopup="true" aria-expanded="false">
<i class="fa fa-cog"></i> <i class="fa fa-cog"></i>
</button> </button>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton"> <div class="dropdown-menu dropdown-menu-right" aria-labelledby="bulkActionsButton">
<a class="dropdown-item" href="#"> <a class="dropdown-item" href="#" appStopClick (click)="bulkMove()">
<i class="fa fa-fw fa-share"></i> <i class="fa fa-fw fa-share"></i>
{{'moveSelected' | i18n}} {{'moveSelected' | i18n}}
</a> </a>
<a class="dropdown-item" href="#"> <a class="dropdown-item" href="#" appStopClick (click)="bulkShare()">
<i class="fa fa-fw fa-share-alt"></i> <i class="fa fa-fw fa-share-alt"></i>
{{'shareSelected' | i18n}} {{'shareSelected' | i18n}}
</a> </a>
<a class="dropdown-item text-danger" href="#"> <a class="dropdown-item text-danger" href="#" (click)="bulkDelete()">
<i class="fa fa-fw fa-trash-o"></i> <i class="fa fa-fw fa-trash-o"></i>
{{'deleteSelected' | i18n}} {{'deleteSelected' | i18n}}
</a> </a>
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
<a class="dropdown-item" href="#"> <a class="dropdown-item" href="#" appStopClick (click)="selectAll(true)">
<i class="fa fa-fw fa-check-square-o"></i> <i class="fa fa-fw fa-check-square-o"></i>
{{'selectAll' | i18n}} {{'selectAll' | i18n}}
</a> </a>
<a class="dropdown-item" href="#"> <a class="dropdown-item" href="#" appStopClick (click)="selectAll(false)">
<i class="fa fa-fw fa-minus-square-o"></i> <i class="fa fa-fw fa-minus-square-o"></i>
{{'unselectAll' | i18n}} {{'unselectAll' | i18n}}
</a> </a>
</div> </div>
</div> </div>
<button type="button" class="btn btn-primary btn-sm" (click)="addCipher()"> <button type="button" class="btn btn-primary btn-sm" (click)="addCipher()" appBlurClick>
<i class="fa fa-plus fa-fw"></i>{{'addItem' | i18n}} <i class="fa fa-plus fa-fw"></i>{{'addItem' | i18n}}
</button> </button>
</div> </div>
@ -71,3 +71,6 @@
<ng-template #cipherAddEdit></ng-template> <ng-template #cipherAddEdit></ng-template>
<ng-template #share></ng-template> <ng-template #share></ng-template>
<ng-template #collections></ng-template> <ng-template #collections></ng-template>
<ng-template #bulkDeleteTemplate></ng-template>
<ng-template #bulkMoveTemplate></ng-template>
<ng-template #bulkShareTemplate></ng-template>

View File

@ -20,6 +20,7 @@ import { ModalComponent } from '../modal.component';
import { AddEditComponent } from './add-edit.component'; import { AddEditComponent } from './add-edit.component';
import { AttachmentsComponent } from './attachments.component'; import { AttachmentsComponent } from './attachments.component';
import { BulkDeleteComponent } from './bulk-delete.component';
import { CiphersComponent } from './ciphers.component'; import { CiphersComponent } from './ciphers.component';
import { CollectionsComponent } from './collections.component'; import { CollectionsComponent } from './collections.component';
import { FolderAddEditComponent } from './folder-add-edit.component'; import { FolderAddEditComponent } from './folder-add-edit.component';
@ -43,6 +44,7 @@ export class VaultComponent implements OnInit {
@ViewChild('cipherAddEdit', { read: ViewContainerRef }) cipherAddEditModalRef: ViewContainerRef; @ViewChild('cipherAddEdit', { read: ViewContainerRef }) cipherAddEditModalRef: ViewContainerRef;
@ViewChild('share', { read: ViewContainerRef }) shareModalRef: ViewContainerRef; @ViewChild('share', { read: ViewContainerRef }) shareModalRef: ViewContainerRef;
@ViewChild('collections', { read: ViewContainerRef }) collectionsModalRef: ViewContainerRef; @ViewChild('collections', { read: ViewContainerRef }) collectionsModalRef: ViewContainerRef;
@ViewChild('bulkDeleteTemplate', { read: ViewContainerRef }) bulkDeleteModalRef: ViewContainerRef;
cipherId: string = null; cipherId: string = null;
favorites: boolean = false; favorites: boolean = false;
@ -277,6 +279,39 @@ export class VaultComponent implements OnInit {
return childComponent; return childComponent;
} }
bulkDelete() {
if (this.modal != null) {
this.modal.close();
}
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
this.modal = this.bulkDeleteModalRef.createComponent(factory).instance;
const childComponent = this.modal.show<BulkDeleteComponent>(
BulkDeleteComponent, this.bulkDeleteModalRef);
childComponent.cipherIds = this.ciphersComponent.getSelected();
childComponent.onDeleted.subscribe(async () => {
this.modal.close();
await this.ciphersComponent.refresh();
});
this.modal.onClosed.subscribe(() => {
this.modal = null;
});
}
bulkShare() {
//
}
bulkMove() {
//
}
selectAll(select: boolean) {
this.ciphersComponent.selectAll(select);
}
private clearFilters() { private clearFilters() {
this.folderId = null; this.folderId = null;
this.collectionId = null; this.collectionId = null;

View File

@ -419,6 +419,9 @@
"sharedItem": { "sharedItem": {
"message": "Shared item" "message": "Shared item"
}, },
"sharedItems": {
"message": "Shared items"
},
"deleteItem": { "deleteItem": {
"message": "Delete Item" "message": "Delete Item"
}, },
@ -434,6 +437,12 @@
"deletedItem": { "deletedItem": {
"message": "Deleted item" "message": "Deleted item"
}, },
"deletedItems": {
"message": "Deleted items"
},
"movedItems": {
"message": "Moved items"
},
"overwritePasswordConfirmation": { "overwritePasswordConfirmation": {
"message": "Are you sure you want to overwrite the current password?" "message": "Are you sure you want to overwrite the current password?"
}, },
@ -664,5 +673,14 @@
}, },
"collectionsDesc": { "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." "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."
},
"deleteSelectedItemsDesc": {
"message": "You have selected $COUNT$ item(s) to delete. Are you sure you want to delete all of these items?",
"placeholders": {
"count": {
"content": "$1",
"example": "150"
}
}
} }
} }