share and collection management

This commit is contained in:
Kyle Spearrin 2018-10-23 15:50:21 -04:00
parent 2244519596
commit 7b0063902f
10 changed files with 204 additions and 1 deletions

2
jslib

@ -1 +1 @@
Subproject commit 8e377050e9bfddae46fa0a167771b670593eca16
Subproject commit 2f510a798853ef3adfed7e8285c9d3f54eba493c

View File

@ -43,12 +43,14 @@ import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe';
import { AddEditComponent } from './vault/add-edit.component';
import { AttachmentsComponent } from './vault/attachments.component';
import { CiphersComponent } from './vault/ciphers.component';
import { CollectionsComponent } from './vault/collections.component';
import { ExportComponent } from './vault/export.component';
import { FolderAddEditComponent } from './vault/folder-add-edit.component';
import { GroupingsComponent } from './vault/groupings.component';
import { PasswordGeneratorHistoryComponent } from './vault/password-generator-history.component';
import { PasswordGeneratorComponent } from './vault/password-generator.component';
import { PasswordHistoryComponent } from './vault/password-history.component';
import { ShareComponent } from './vault/share.component';
import { VaultComponent } from './vault/vault.component';
import { ViewComponent } from './vault/view.component';
@ -138,6 +140,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
BlurClickDirective,
BoxRowDirective,
CiphersComponent,
CollectionsComponent,
EnvironmentComponent,
ExportComponent,
FallbackSrcDirective,
@ -156,6 +159,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
RegisterComponent,
SearchCiphersPipe,
SettingsComponent,
ShareComponent,
StopClickDirective,
StopPropDirective,
TrueFalseValueDirective,
@ -166,6 +170,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
],
entryComponents: [
AttachmentsComponent,
CollectionsComponent,
EnvironmentComponent,
ExportComponent,
FolderAddEditComponent,
@ -175,6 +180,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
PasswordHistoryComponent,
PremiumComponent,
SettingsComponent,
ShareComponent,
TwoFactorOptionsComponent,
],
providers: [],

View File

@ -240,6 +240,11 @@
<div class="row-main">{{'attachments' | i18n}}</div>
<i class="fa fa-chevron-right row-sub-icon"></i>
</a>
<a class="box-content-row box-content-row-flex text-default" href="#" appStopClick appBlurClick
(click)="editCollections()" *ngIf="editMode && cipher.organizationId">
<div class="row-main">{{'collections' | i18n}}</div>
<i class="fa fa-chevron-right row-sub-icon"></i>
</a>
</div>
</div>
<div class="box">
@ -309,6 +314,10 @@
{{'cancel' | i18n}}
</button>
<div class="right">
<button appBlurClick type="button" (click)="share()" title="{{'shareItem' | i18n}}"
*ngIf="editMode && cipher && !cipher.organizationId">
<i class="fa fa-share-alt fa-lg fa-fw"></i>
</button>
<button #deleteBtn appBlurClick type="button" (click)="delete()" class="danger"
title="{{'delete' | i18n}}" *ngIf="editMode" [disabled]="deleteBtn.loading"
[appApiAction]="deletePromise">

View File

@ -0,0 +1,31 @@
<div class="modal fade">
<div class="modal-dialog">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="modal-body">
<div class="box">
<div class="box-header">
{{'collections' | i18n}}
</div>
<div class="box-content" *ngIf="!collections || !collections.length">
{{'noCollectionsInList' | i18n}}
</div>
<div class="box-content" *ngIf="collections && collections.length">
<div class="box-content-row box-content-row-checkbox"
*ngFor="let c of collections; let i = index" appBoxRow>
<label for="collection_{{i}}">{{c.name}}</label>
<input id="collection_{{i}}" type="checkbox" [(ngModel)]="c.checked"
name="Collection[{{i}}].Checked">
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button appBlurClick type="submit" class="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">{{'cancel' | i18n}}</button>
</div>
</form>
</div>
</div>

View File

@ -0,0 +1,19 @@
import { Component } from '@angular/core';
import { CipherService } from 'jslib/abstractions/cipher.service';
import { CollectionService } from 'jslib/abstractions/collection.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { CollectionsComponent as BaseCollectionsComponent } from 'jslib/angular/components/collections.component';
@Component({
selector: 'app-vault-collections',
templateUrl: 'collections.component.html',
})
export class CollectionsComponent extends BaseCollectionsComponent {
constructor(cipherService: CipherService, i18nService: I18nService,
collectionService: CollectionService, platformUtilsService: PlatformUtilsService) {
super(collectionService, platformUtilsService, i18nService, cipherService);
}
}

View File

@ -0,0 +1,52 @@
<div class="modal fade">
<div class="modal-dialog">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="modal-body">
<div class="box">
<div class="box-header">
{{'share' | i18n}}
</div>
<div class="box-content" *ngIf="!organizations || !organizations.length">
{{'noOrganizationsList' | i18n}}
</div>
<div class="box-content" *ngIf="organizations && organizations.length">
<div class="box-content-row" appBoxRow>
<label for="organization">{{'organization' | i18n}}</label>
<select id="organization" name="OrganizationId" [(ngModel)]="organizationId"
(change)="filterCollections()">
<option *ngFor="let o of organizations" [ngValue]="o.id">{{o.name}}</option>
</select>
</div>
</div>
<div class="box-footer">
{{'shareDesc' | i18n}}
</div>
</div>
<div class="box" *ngIf="organizations && organizations.length">
<div class="box-header">
{{'collections' | i18n}}
</div>
<div class="box-content" *ngIf="!collections || !collections.length">
{{'noCollectionsInList' | i18n}}
</div>
<div class="box-content" *ngIf="collections && collections.length">
<div class="box-content-row box-content-row-checkbox"
*ngFor="let c of collections; let i = index" appBoxRow>
<label for="collection_{{i}}">{{c.name}}</label>
<input id="collection_{{i}}" type="checkbox" [(ngModel)]="c.checked"
name="Collection[{{i}}].Checked">
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button appBlurClick type="submit" class="primary" title="{{'save' | i18n}}"
[disabled]="form.loading || !canSave" *ngIf="organizations && organizations.length">
<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">{{'cancel' | i18n}}</button>
</div>
</form>
</div>
</div>

View File

@ -0,0 +1,21 @@
import { Component } from '@angular/core';
import { CipherService } from 'jslib/abstractions/cipher.service';
import { CollectionService } from 'jslib/abstractions/collection.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { UserService } from 'jslib/abstractions/user.service';
import { ShareComponent as BaseShareComponent } from 'jslib/angular/components/share.component';
@Component({
selector: 'app-vault-share',
templateUrl: 'share.component.html',
})
export class ShareComponent extends BaseShareComponent {
constructor(cipherService: CipherService, i18nService: I18nService,
collectionService: CollectionService, userService: UserService,
platformUtilsService: PlatformUtilsService) {
super(collectionService, platformUtilsService, i18nService, userService, cipherService);
}
}

View File

@ -30,6 +30,8 @@
(onDeletedCipher)="deletedCipher($event)"
(onEditAttachments)="editCipherAttachments($event)"
(onCancelled)="cancelledAddEdit($event)"
(onShareCipher)="shareCipher($event)"
(onEditCollections)="cipherCollections($event)"
(onGeneratePassword)="openPasswordGenerator(true)">
</app-vault-add-edit>
<div id="logo" *ngIf="action !== 'add' && action !== 'edit' && action !== 'view'">
@ -41,6 +43,8 @@
</div>
<ng-template #passwordGenerator></ng-template>
<ng-template #attachments></ng-template>
<ng-template #collections></ng-template>
<ng-template #share></ng-template>
<ng-template #folderAddEdit></ng-template>
<ng-template #passwordHistory></ng-template>
<ng-template #exportVault></ng-template>

View File

@ -26,11 +26,13 @@ import { ModalComponent } from 'jslib/angular/components/modal.component';
import { AddEditComponent } from './add-edit.component';
import { AttachmentsComponent } from './attachments.component';
import { CiphersComponent } from './ciphers.component';
import { CollectionsComponent } from './collections.component';
import { ExportComponent } from './export.component';
import { FolderAddEditComponent } from './folder-add-edit.component';
import { GroupingsComponent } from './groupings.component';
import { PasswordGeneratorComponent } from './password-generator.component';
import { PasswordHistoryComponent } from './password-history.component';
import { ShareComponent } from './share.component';
import { CipherType } from 'jslib/enums/cipherType';
@ -58,6 +60,8 @@ export class VaultComponent implements OnInit, OnDestroy {
@ViewChild('folderAddEdit', { read: ViewContainerRef }) folderAddEditModalRef: ViewContainerRef;
@ViewChild('passwordHistory', { read: ViewContainerRef }) passwordHistoryModalRef: ViewContainerRef;
@ViewChild('exportVault', { read: ViewContainerRef }) exportVaultModalRef: ViewContainerRef;
@ViewChild('share', { read: ViewContainerRef }) shareModalRef: ViewContainerRef;
@ViewChild('collections', { read: ViewContainerRef }) collectionsModalRef: ViewContainerRef;
action: string;
cipherId: string = null;
@ -353,6 +357,45 @@ export class VaultComponent implements OnInit, OnDestroy {
});
}
shareCipher(cipher: CipherView) {
if (this.modal != null) {
this.modal.close();
}
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
this.modal = this.shareModalRef.createComponent(factory).instance;
const childComponent = this.modal.show<ShareComponent>(ShareComponent, this.shareModalRef);
childComponent.cipherId = cipher.id;
childComponent.onSharedCipher.subscribe(async () => {
this.modal.close();
this.viewCipher(cipher);
await this.ciphersComponent.refresh();
});
this.modal.onClosed.subscribe(async () => {
this.modal = null;
});
}
cipherCollections(cipher: CipherView) {
if (this.modal != null) {
this.modal.close();
}
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
this.modal = this.collectionsModalRef.createComponent(factory).instance;
const childComponent = this.modal.show<CollectionsComponent>(CollectionsComponent, this.collectionsModalRef);
childComponent.cipherId = cipher.id;
childComponent.onSavedCollections.subscribe(() => {
this.modal.close();
this.viewCipher(cipher);
});
this.modal.onClosed.subscribe(async () => {
this.modal = null;
});
}
viewCipherPasswordHistory(cipher: CipherView) {
if (this.modal != null) {
this.modal.close();

View File

@ -38,6 +38,18 @@
"shared": {
"message": "Shared"
},
"share": {
"message": "Share"
},
"shareItem": {
"message": "Share Item"
},
"sharedItem": {
"message": "Shared Item"
},
"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."
},
"attachments": {
"message": "Attachments"
},
@ -1100,5 +1112,11 @@
},
"exportMasterPassword": {
"message": "Enter your master password to export your vault data."
},
"noOrganizationsList": {
"message": "You do not belong to any organizations. Organizations allow you to securely share items with other users."
},
"noCollectionsInList": {
"message": "There are no collections to list."
}
}