[Send] Groupings component (#1605)
* Initial commit of groupings component
* Update jslib ee164be
* Updated send-list item icons
* Requested changes
* Removed obsolete safari hacks
This commit is contained in:
parent
1868b99d17
commit
0d6e212463
2
jslib
2
jslib
|
@ -1 +1 @@
|
|||
Subproject commit 11249e34441ea747f53fcb0b6e38f690366b46b5
|
||||
Subproject commit ee164bebc65aa56e41a122eb4ece8971eb23119b
|
|
@ -1474,5 +1474,70 @@
|
|||
"send": {
|
||||
"message": "Send",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
},
|
||||
"searchSends": {
|
||||
"message": "Search Sends",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
},
|
||||
"addSend": {
|
||||
"message": "Add Send",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
},
|
||||
"sendTypeText": {
|
||||
"message": "Text"
|
||||
},
|
||||
"sendTypeFile": {
|
||||
"message": "File"
|
||||
},
|
||||
"allSends": {
|
||||
"message": "All Sends",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
},
|
||||
"maxAccessCountReached": {
|
||||
"message": "Max access count reached"
|
||||
},
|
||||
"expired": {
|
||||
"message": "Expired"
|
||||
},
|
||||
"pendingDeletion": {
|
||||
"message": "Pending deletion"
|
||||
},
|
||||
"passwordProtected": {
|
||||
"message": "Password protected"
|
||||
},
|
||||
"copySendLink": {
|
||||
"message": "Copy Send Link",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
},
|
||||
"removePassword": {
|
||||
"message": "Remove Password"
|
||||
},
|
||||
"delete": {
|
||||
"message": "Delete"
|
||||
},
|
||||
"removedPassword": {
|
||||
"message": "Removed Password"
|
||||
},
|
||||
"deletedSend": {
|
||||
"message": "Deleted Send",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
},
|
||||
"sendLink": {
|
||||
"message": "Send Link",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
},
|
||||
"disabled": {
|
||||
"message": "Disabled"
|
||||
},
|
||||
"removePasswordConfirmation": {
|
||||
"message": "Are you sure you want to remove the password?"
|
||||
},
|
||||
"deleteSend": {
|
||||
"message": "Delete Send",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
},
|
||||
"deleteSendConfirmation": {
|
||||
"message": "Are you sure you want to delete this Send?",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ import { PasswordHistoryComponent } from './vault/password-history.component';
|
|||
import { ShareComponent } from './vault/share.component';
|
||||
import { ViewComponent } from './vault/view.component';
|
||||
|
||||
import { SendComponent } from './send/send.component';
|
||||
import { SendGroupingsComponent } from './send/send-groupings.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
|
@ -273,7 +273,7 @@ const routes: Routes = [
|
|||
},
|
||||
{
|
||||
path: 'send',
|
||||
component: SendComponent,
|
||||
component: SendGroupingsComponent,
|
||||
canActivate: [AuthGuardService],
|
||||
data: { state: 'tabs_send' },
|
||||
},
|
||||
|
|
|
@ -52,7 +52,7 @@ import { PasswordHistoryComponent } from './vault/password-history.component';
|
|||
import { ShareComponent } from './vault/share.component';
|
||||
import { ViewComponent } from './vault/view.component';
|
||||
|
||||
import { SendComponent } from './send/send.component';
|
||||
import { SendGroupingsComponent } from './send/send-groupings.component';
|
||||
|
||||
import { A11yTitleDirective } from 'jslib/angular/directives/a11y-title.directive';
|
||||
import { ApiActionDirective } from 'jslib/angular/directives/api-action.directive';
|
||||
|
@ -73,6 +73,7 @@ import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe';
|
|||
import { ActionButtonsComponent } from './components/action-buttons.component';
|
||||
import { CiphersListComponent } from './components/ciphers-list.component';
|
||||
import { PopOutComponent } from './components/pop-out.component';
|
||||
import { SendListComponent } from './components/send-list.component';
|
||||
|
||||
import { CalloutComponent } from 'jslib/angular/components/callout.component';
|
||||
import { IconComponent } from 'jslib/angular/components/icon.component';
|
||||
|
@ -211,7 +212,8 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
|||
RegisterComponent,
|
||||
SearchCiphersPipe,
|
||||
SelectCopyDirective,
|
||||
SendComponent,
|
||||
SendGroupingsComponent,
|
||||
SendListComponent,
|
||||
SettingsComponent,
|
||||
ShareComponent,
|
||||
StopClickDirective,
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
<a *ngFor="let s of sends" (click)="selectSend(s)" href="#" appStopClick title="{{title}} - {{s.name}}"
|
||||
class="box-content-row box-content-row-flex">
|
||||
<div class="row-main">
|
||||
<div class="app-vault-icon">
|
||||
<div class="icon" aria-hidden="true">
|
||||
<i class="fa fa-fw fa-lg fa-file-text-o" *ngIf="s.type === sendType.Text"></i>
|
||||
<i class="fa fa-fw fa-lg fa-file-o" *ngIf="s.type === sendType.File"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row-main-content">
|
||||
<span class="text">
|
||||
{{s.name}}
|
||||
<ng-container *ngIf="s.disabled">
|
||||
<i class="fa fa-ban text-muted" title="{{'disabled' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'disabled' | i18n}}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="s.password">
|
||||
<i class="fa fa-key text-muted" title="{{'passwordProtected' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'passwordProtected' | i18n}}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="s.maxAccessCountReached">
|
||||
<i class="fa fa-warning text-muted" title="{{'maxAccessCountReached' | i18n}}"
|
||||
aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'maxAccessCountReached' | i18n}}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="s.expired">
|
||||
<i class="fa fa-clock-o text-muted" title="{{'expired' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'expired' | i18n}}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="s.pendingDelete">
|
||||
<i class="fa fa-trash text-muted" title="{{'pendingDeletion' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'pendingDeletion' | i18n}}</span>
|
||||
</ng-container>
|
||||
</span>
|
||||
<span class="detail">{{s.deletionDate | date:'medium'}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="action-buttons">
|
||||
<span class="row-btn" appStopClick appStopProp appA11yTitle="{{'copySendLink' | i18n}}"
|
||||
(click)="copySendLink(s)">
|
||||
<i class="fa fa-lg fa-copy" aria-hidden="true"></i>
|
||||
</span>
|
||||
<span class="row-btn" appStopClick appStopProp appA11yTitle="{{'removePassword' | i18n}}"
|
||||
(click)="removePassword(s)" *ngIf="s.password">
|
||||
<i class="fa fa-lg fa-undo" aria-hidden="true"></i>
|
||||
</span>
|
||||
<span class="row-btn" appStopClick appStopProp appA11yTitle="{{'delete' | i18n}}" (click)="delete(s)">
|
||||
<i class="fa fa-lg fa-trash-o" aria-hidden="true"></i>
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
|
@ -0,0 +1,41 @@
|
|||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
Output,
|
||||
} from '@angular/core';
|
||||
|
||||
import { SendView } from 'jslib/models/view/sendView';
|
||||
|
||||
import { SendType } from 'jslib/enums/sendType';
|
||||
|
||||
@Component({
|
||||
selector: 'app-send-list',
|
||||
templateUrl: 'send-list.component.html',
|
||||
})
|
||||
export class SendListComponent {
|
||||
@Input() sends: SendView[];
|
||||
@Input() title: string;
|
||||
@Output() onSelected = new EventEmitter<SendView>();
|
||||
@Output() onCopySendLink = new EventEmitter<SendView>();
|
||||
@Output() onRemovePassword = new EventEmitter<SendView>();
|
||||
@Output() onDeleteSend = new EventEmitter<SendView>();
|
||||
|
||||
sendType = SendType;
|
||||
|
||||
selectSend(s: SendView) {
|
||||
this.onSelected.emit(s);
|
||||
}
|
||||
|
||||
copySendLink(s: SendView) {
|
||||
this.onCopySendLink.emit(s);
|
||||
}
|
||||
|
||||
removePassword(s: SendView) {
|
||||
this.onRemovePassword.emit(s);
|
||||
}
|
||||
|
||||
delete(s: SendView) {
|
||||
this.onDeleteSend.emit(s);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
<header>
|
||||
<div class="left" *ngIf="showLeftHeader">
|
||||
<app-pop-out></app-pop-out>
|
||||
</div>
|
||||
<div class="search">
|
||||
<input type="search" placeholder="{{'searchSends' | i18n}}" id="search" [(ngModel)]="searchText"
|
||||
(input)="search(200)" autocomplete="off" appAutofocus>
|
||||
<i class="fa fa-search"></i>
|
||||
</div>
|
||||
<div class="right">
|
||||
<button appBlurClick (click)="addSend()" appA11yTitle="{{'addSend' | i18n}}">
|
||||
<i class="fa fa-plus fa-lg fa-fw" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<content>
|
||||
<div class="no-items" *ngIf="(!sends || !sends.length) && !showSearching()">
|
||||
<i class="fa fa-spinner fa-spin fa-3x" *ngIf="!loaded"></i>
|
||||
<ng-container *ngIf="loaded">
|
||||
<i class="fa fa-frown-o fa-4x"></i>
|
||||
<p>{{'noItemsInList' | i18n}}</p>
|
||||
<button (click)="addSend()" class="btn block primary link">{{'addSend' | i18n}}</button>
|
||||
</ng-container>
|
||||
</div>
|
||||
<ng-container *ngIf="sends && sends.length && !showSearching()">
|
||||
<div class="box list">
|
||||
<div class="box-header">
|
||||
{{'types' | i18n}}
|
||||
</div>
|
||||
<div class="box-content single-line">
|
||||
<a href="#" class="box-content-row" appStopClick appBlurClick (click)="selectType(sendType.Text)">
|
||||
<div class="row-main">
|
||||
<div class="icon"><i class="fa fa-fw fa-lg fa-file-text-o"></i></div>
|
||||
<span class="text">{{'sendTypeText' | i18n}}</span>
|
||||
</div>
|
||||
<span class="row-sub-label">{{typeCounts.get(sendType.Text) || 0}}</span>
|
||||
<span><i class="fa fa-chevron-right fa-lg row-sub-icon"></i></span>
|
||||
</a>
|
||||
<a href="#" class="box-content-row" appStopClick appBlurClick (click)="selectType(sendType.File)">
|
||||
<div class="row-main">
|
||||
<div class="icon"><i class="fa fa-fw fa-lg fa-file-o"></i></div>
|
||||
<span class="text">{{'sendTypeFile' | i18n}}</span>
|
||||
</div>
|
||||
<span class="row-sub-label">{{typeCounts.get(sendType.File) || 0}}</span>
|
||||
<span><i class="fa fa-chevron-right fa-lg row-sub-icon"></i></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box list">
|
||||
<div class="box-header">
|
||||
{{'allSends' | i18n}}
|
||||
<div class="flex-right">{{sends.length}}</div>
|
||||
</div>
|
||||
<div class="box-content">
|
||||
<app-send-list [sends]="sends" title="{{'editItem' | i18n}}" (onSelected)="selectSend($event)"
|
||||
(onCopySendLink)="copy($event)" (onRemovePassword)="removePassword($event)"
|
||||
(onDeleteSend)="delete($event)"></app-send-list>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="showSearching()">
|
||||
<div class="no-items" *ngIf="!filteredSends || !filteredSends.length">
|
||||
<p>{{'noItemsInList' | i18n}}</p>
|
||||
</div>
|
||||
<div class="box list full-list" *ngIf="filteredSends && filteredSends.length > 0">
|
||||
<div class="box-content">
|
||||
<app-send-list [sends]="filteredSends" title="{{'editItem' | i18n}}" (onSelected)="selectSend($event)"
|
||||
(onCopySendLink)="copy($event)" (onRemovePassword)="removePassword($event)"
|
||||
(onDeleteSend)="delete($event)">
|
||||
</app-send-list>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</content>
|
|
@ -0,0 +1,157 @@
|
|||
import {
|
||||
Component,
|
||||
NgZone,
|
||||
} from '@angular/core';
|
||||
|
||||
import {
|
||||
ActivatedRoute,
|
||||
Router,
|
||||
} from '@angular/router';
|
||||
|
||||
import { SendView } from 'jslib/models/view/sendView';
|
||||
|
||||
import { SendComponent as BaseSendComponent } from 'jslib/angular/components/send/send.component';
|
||||
|
||||
import { EnvironmentService } from 'jslib/abstractions/environment.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
import { PolicyService } from 'jslib/abstractions/policy.service';
|
||||
import { SearchService } from 'jslib/abstractions/search.service';
|
||||
import { SendService } from 'jslib/abstractions/send.service';
|
||||
import { StateService } from 'jslib/abstractions/state.service';
|
||||
import { SyncService } from 'jslib/abstractions/sync.service';
|
||||
import { UserService } from 'jslib/abstractions/user.service';
|
||||
|
||||
import { BroadcasterService } from 'jslib/angular/services/broadcaster.service';
|
||||
|
||||
import { PopupUtilsService } from '../services/popup-utils.service';
|
||||
|
||||
import { SendType } from 'jslib/enums/sendType';
|
||||
|
||||
const ComponentId = 'SendComponent';
|
||||
const ScopeStateId = ComponentId + 'Scope';
|
||||
|
||||
@Component({
|
||||
selector: 'app-send-groupings',
|
||||
templateUrl: 'send-groupings.component.html',
|
||||
})
|
||||
export class SendGroupingsComponent extends BaseSendComponent {
|
||||
// Header
|
||||
showLeftHeader = true;
|
||||
// Send Type Calculations
|
||||
typeCounts = new Map<SendType, number>();
|
||||
// State Handling
|
||||
state: any;
|
||||
scopeState: any;
|
||||
private loadedTimeout: number;
|
||||
|
||||
constructor(sendService: SendService, i18nService: I18nService,
|
||||
platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService,
|
||||
broadcasterService: BroadcasterService, ngZone: NgZone, policyService: PolicyService,
|
||||
userService: UserService, searchService: SearchService,
|
||||
private popupUtils: PopupUtilsService, private stateService: StateService,
|
||||
private route: ActivatedRoute, private router: Router, private syncService: SyncService) {
|
||||
super(sendService, i18nService, platformUtilsService, environmentService, broadcasterService, ngZone,
|
||||
searchService, policyService, userService);
|
||||
super.onSuccessfulLoad = async () => {
|
||||
this.calculateTypeCounts();
|
||||
};
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
// Determine Header details
|
||||
this.showLeftHeader = !(this.popupUtils.inSidebar(window) && this.platformUtilsService.isFirefox());
|
||||
// Let super class finish
|
||||
await super.ngOnInit();
|
||||
// Handle State Restore if necessary
|
||||
const restoredScopeState = await this.restoreState();
|
||||
this.state = (await this.stateService.get<any>(ComponentId)) || {};
|
||||
if (this.state.searchText != null) {
|
||||
this.searchText = this.state.searchText;
|
||||
}
|
||||
|
||||
if (!this.syncService.syncInProgress) {
|
||||
this.load();
|
||||
} else {
|
||||
this.loadedTimeout = window.setTimeout(() => {
|
||||
if (!this.loaded) {
|
||||
this.load();
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
if (!this.syncService.syncInProgress || restoredScopeState) {
|
||||
window.setTimeout(() => this.popupUtils.setContentScrollY(window, this.state.scrollY), 0);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
// Remove timeout
|
||||
if (this.loadedTimeout != null) {
|
||||
window.clearTimeout(this.loadedTimeout);
|
||||
}
|
||||
// Save state
|
||||
this.saveState();
|
||||
// Allow super to finish
|
||||
super.ngOnDestroy();
|
||||
}
|
||||
|
||||
async selectType(type: SendType) {
|
||||
// TODO this.router.navigate(['/send-type-list'], { queryParams: { type: type } });
|
||||
}
|
||||
|
||||
async selectSend(s: SendView) {
|
||||
// TODO -> Route to edit send
|
||||
}
|
||||
|
||||
async addSend() {
|
||||
// TODO -> Route to create send
|
||||
}
|
||||
|
||||
showSearching() {
|
||||
return this.hasSearched || (!this.searchPending && this.searchService.isSearchable(this.searchText));
|
||||
}
|
||||
|
||||
private calculateTypeCounts() {
|
||||
// Create type counts
|
||||
const typeCounts = new Map<SendType, number>();
|
||||
this.sends.forEach((s) => {
|
||||
if (typeCounts.has(s.type)) {
|
||||
typeCounts.set(s.type, typeCounts.get(s.type) + 1);
|
||||
} else {
|
||||
typeCounts.set(s.type, 1);
|
||||
}
|
||||
});
|
||||
this.typeCounts = typeCounts;
|
||||
}
|
||||
|
||||
private async saveState() {
|
||||
this.state = {
|
||||
scrollY: this.popupUtils.getContentScrollY(window),
|
||||
searchText: this.searchText,
|
||||
};
|
||||
await this.stateService.save(ComponentId, this.state);
|
||||
|
||||
this.scopeState = {
|
||||
sends: this.sends,
|
||||
typeCounts: this.typeCounts,
|
||||
};
|
||||
await this.stateService.save(ScopeStateId, this.scopeState);
|
||||
}
|
||||
|
||||
private async restoreState(): Promise<boolean> {
|
||||
this.scopeState = await this.stateService.get<any>(ScopeStateId);
|
||||
if (this.scopeState == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.scopeState.sends != null) {
|
||||
this.sends = this.scopeState.sends;
|
||||
}
|
||||
if (this.scopeState.typeCounts != null) {
|
||||
this.typeCounts = this.scopeState.typeCounts;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
<header>
|
||||
</header>
|
||||
<content>
|
||||
<div class="no-items">
|
||||
<i class="fa fa-smile-o fa-4x"></i>
|
||||
<p>Coming soon...</p>
|
||||
</div>
|
||||
</content>
|
|
@ -1,35 +0,0 @@
|
|||
import {
|
||||
Component,
|
||||
NgZone,
|
||||
} from '@angular/core';
|
||||
|
||||
import { SendView } from 'jslib/models/view/sendView';
|
||||
|
||||
import { SendComponent as BaseSendComponent } from 'jslib/angular/components/send/send.component';
|
||||
|
||||
import { EnvironmentService } from 'jslib/abstractions/environment.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
import { SendService } from 'jslib/abstractions/send.service';
|
||||
|
||||
import { BroadcasterService } from 'jslib/angular/services/broadcaster.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-send',
|
||||
templateUrl: 'send.component.html',
|
||||
})
|
||||
export class SendComponent extends BaseSendComponent {
|
||||
constructor(sendService: SendService, i18nService: I18nService,
|
||||
platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService,
|
||||
broadcasterService: BroadcasterService, ngZone: NgZone) {
|
||||
super(sendService, i18nService, platformUtilsService, environmentService, broadcasterService, ngZone);
|
||||
}
|
||||
|
||||
addSend() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
editSend(send: SendView) {
|
||||
// TODO
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue