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:
parent
3666ee5a87
commit
314ab61349
@ -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,
|
||||||
|
25
src/app/vault/bulk-delete.component.html
Normal file
25
src/app/vault/bulk-delete.component.html
Normal 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">×</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>
|
34
src/app/vault/bulk-delete.component.ts
Normal file
34
src/app/vault/bulk-delete.component.ts
Normal 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'));
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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;
|
||||||
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user