cipher attachments modal

This commit is contained in:
Kyle Spearrin 2018-06-08 12:04:03 -04:00
parent 3db86e2a6b
commit d256a872fc
7 changed files with 152 additions and 85 deletions

View File

@ -1,49 +1,45 @@
<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">
{{'attachments' | i18n}}
<small>{{cipher.name}}</small>
</h2>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="box" *ngIf="cipher && cipher.hasAttachments">
<div class="box-header">
{{'attachments' | i18n}}
</div>
<div class="box-content no-hover">
<div class="box-content-row box-content-row-flex" *ngFor="let a of cipher.attachments">
<div class="row-main">
<table class="table table-hover table-list" *ngIf="cipher && cipher.hasAttachments">
<tbody>
<tr *ngFor="let a of cipher.attachments">
<td>
{{a.fileName}}
</div>
<small class="row-sub-label">{{a.sizeName}}</small>
<div class="action-buttons no-pad">
<button class="row-btn btn" type="button" appStopClick appBlurClick
title="{{'delete' | i18n}}" (click)="delete(a)" #deleteBtn
[appApiAction]="deletePromises[a.id]" [disabled]="deleteBtn.loading">
<br>
<small>{{a.sizeName}}</small>
</td>
<td class="table-list-options">
<button class="btn btn-outline-danger" type="button" appStopClick appBlurClick title="{{'delete' | i18n}}" (click)="delete(a)"
#deleteBtn [appApiAction]="deletePromises[a.id]" [disabled]="deleteBtn.loading">
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading"></i>
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!deleteBtn.loading"></i>
</button>
</div>
</div>
</div>
</div>
<div class="box">
<div class="box-header">
{{'newAttachment' | i18n}}
</div>
<div class="box-content no-hover">
<div class="box-content-row">
<label for="file">{{'file' | i18n}}</label>
<input type="file" id="file" name="file" required>
</div>
</div>
<div class="box-footer">
{{'maxFileSize' | i18n}}
</div>
</div>
</td>
</tr>
</tbody>
</table>
<h3>{{'newAttachment' | i18n}}</h3>
<label for="file" class="sr-only">{{'file' | i18n}}</label>
<input type="file" id="file" class="form-control-file" name="file" required>
<small class="form-text text-muted">{{'maxFileSize' | i18n}}</small>
</div>
<div class="modal-footer">
<button appBlurClick type="submit" class="primary" title="{{'save' | i18n}}" [disabled]="form.loading">
<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" data-dismiss="modal">{{'close' | i18n}}</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal" title="{{'close' | i18n}}">{{'close' | i18n}}</button>
</div>
</form>
</div>

View File

@ -1,5 +1,5 @@
<ng-container *ngIf="(ciphers | searchCiphers: searchText) as searchedCiphers">
<table class="table table-hover" *ngIf="searchedCiphers.length > 0">
<table class="table table-hover table-list table-ciphers" *ngIf="searchedCiphers.length > 0">
<tbody>
<tr *ngFor="let c of searchedCiphers">
<td (click)="checkCipher(c)">
@ -15,30 +15,30 @@
<br>
<small appStopProp>{{c.subTitle}}</small>
</td>
<td>
<td class="table-list-options">
<div class="dropdown" appListDropdown>
<button class="btn btn-outline-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true"
aria-expanded="false">
<i class="fa fa-cog"></i>
<i class="fa fa-cog fa-lg"></i>
</button>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton">
<a class="dropdown-item" href="#" appStopClick *ngIf="c.type === cipherType.Login">
<a class="dropdown-item" href="#" appStopClick *ngIf="c.type === cipherType.Login" (click)="copy(c.login.password, 'password', 'password')">
<i class="fa fa-fw fa-clipboard"></i>
{{'copyPassword' | i18n}}
</a>
<a class="dropdown-item" href="#" appStopClick>
<a class="dropdown-item" href="#" appStopClick (click)="attachments(c)">
<i class="fa fa-fw fa-paperclip"></i>
{{'attachments' | i18n}}
</a>
<a class="dropdown-item" href="#" appStopClick *ngIf="!c.organizationId">
<a class="dropdown-item" href="#" appStopClick *ngIf="!c.organizationId" (click)="share(c)">
<i class="fa fa-fw fa-share-alt"></i>
{{'share' | i18n}}
</a>
<a class="dropdown-item" href="#" appStopClick *ngIf="c.organizationId">
<a class="dropdown-item" href="#" appStopClick *ngIf="c.organizationId" (click)="collections(c)">
<i class="fa fa-fw fa-cubes"></i>
{{'collections' | i18n}}
</a>
<a class="dropdown-item text-danger" href="#" appStopClick>
<a class="dropdown-item text-danger" href="#" appStopClick (click)="delete(c)">
<i class="fa fa-fw fa-trash-o"></i>
{{'delete' | i18n}}
</a>

View File

@ -1,6 +1,15 @@
import { Component } from '@angular/core';
import {
Component,
EventEmitter,
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';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { CiphersComponent as BaseCiphersComponent } from 'jslib/angular/components/ciphers.component';
@ -13,13 +22,43 @@ import { CipherView } from 'jslib/models/view/cipherView';
templateUrl: 'ciphers.component.html',
})
export class CiphersComponent extends BaseCiphersComponent {
@Output() onAttachmentsClicked = new EventEmitter<CipherView>();
cipherType = CipherType;
constructor(cipherService: CipherService) {
constructor(cipherService: CipherService, private analytics: Angulartics2,
private toasterService: ToasterService, private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService) {
super(cipherService);
}
checkCipher(c: CipherView) {
(c as any).checked = !(c as any).checked;
}
attachments(c: CipherView) {
this.onAttachmentsClicked.emit(c);
}
share(c: CipherView) {
//
}
collections(c: CipherView) {
//
}
delete(c: CipherView) {
//
}
copy(value: string, typeI18nKey: string, aType: string) {
if (value == null) {
return;
}
this.analytics.eventTrack.next({ action: 'Copied ' + aType.toLowerCase() + ' from listing.' });
this.platformUtilsService.copyToClipboard(value, { doc: window.document });
this.toasterService.popAsync('info', null,
this.i18nService.t('valueCopied', this.i18nService.t(typeI18nKey)));
}
}

View File

@ -73,7 +73,7 @@
</button>
</div>
</div>
<app-vault-ciphers (onCipherClicked)="editCipher($event)">
<app-vault-ciphers (onCipherClicked)="editCipher($event)" (onAttachmentsClicked)="editCipherAttachments($event)">
</app-vault-ciphers>
</div>
<div class="col-3">

View File

@ -205,10 +205,10 @@ export class VaultComponent implements OnInit {
AddEditComponent, this.cipherAddEditRef);
childComponent.cipherId = cipher == null ? null : cipher.id;
childComponent.onSavedCipher.subscribe(async (cipher: CipherView) => {
childComponent.onSavedCipher.subscribe(async (c: CipherView) => {
this.modal.close();
});
childComponent.onDeletedCipher.subscribe(async (cipher: CipherView) => {
childComponent.onDeletedCipher.subscribe(async (c: CipherView) => {
this.modal.close();
});

View File

@ -385,5 +385,29 @@
},
"launch": {
"message": "Launch"
},
"newAttachment": {
"message": "Add New Attachment"
},
"deletedAttachment": {
"message": "Deleted attachment"
},
"deleteAttachmentConfirmation": {
"message": "Are you sure you want to delete this attachment?"
},
"attachmentSaved": {
"message": "The attachment has been saved."
},
"file": {
"message": "File"
},
"selectFile": {
"message": "Select a file."
},
"maxFileSize": {
"message": "Maximum file size is 100 MB."
},
"updateKey": {
"message": "You cannot use this feature until you update your encryption key."
}
}

View File

@ -48,6 +48,7 @@ $input-bg: #fafafa;
$input-focus-bg: #ffffff;
$input-disabled-bg: #e0e0e0;
$table-accent-bg: rgba(#000000, .02);
$table-hover-bg: rgba(#000000, .03);
@import "../../node_modules/bootstrap/scss/bootstrap";
@ -143,32 +144,57 @@ form label {
}
}
.table.table-list {
tr:first-child {
td {
border: none;
}
}
tr:hover {
td.table-list-options > .dropdown button, td.table-list-options > button {
visibility: visible;
}
}
td {
vertical-align: middle;
line-height: 1;
small {
color: $text-muted;
}
}
td.table-list-options {
width: 76px;
max-width: 76px;
text-align: right;
.btn {
line-height: 1;
}
.dropdown-menu {
line-height: $line-height-base;
}
> .dropdown:not(.show) button {
visibility: hidden;
}
> button {
visibility: hidden;
}
}
}
app-vault-icon .fa {
color: $text-muted;
}
app-vault {
.table {
tr:first-child {
td {
border: none;
}
}
tr:hover {
td:last-child .dropdown button {
visibility: visible;
}
}
td {
vertical-align: middle;
small {
color: $text-muted;
}
}
.table-ciphers {
td:first-child {
width: 35px;
max-width: 35px;
@ -184,24 +210,6 @@ app-vault {
@extend .img-fluid;
}
}
td:nth-child(3) {
line-height: 1;
}
td:last-child {
width: 72px;
max-width: 72px;
text-align: right;
.btn {
line-height: 1;
}
.dropdown:not(.show) button {
visibility: hidden;
}
}
}
}