1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-23 03:33:54 +00:00

[Send] Add/Edit functionality (#1622)

* Update jslib (0951424 -> 1968dbf)

* [Send] Browser integration initial commit

* Update jslib (1968dbf -> 8a3b551)

* Cleaned up integration

* added radio button style support // updated warning UI/UX

* Update jslib (8a3b551 ->42348e2)
This commit is contained in:
Vincent Salucci
2021-02-23 15:37:55 -06:00
committed by GitHub
parent 2ac9f92267
commit 4853fb3e29
12 changed files with 582 additions and 12 deletions

View File

@@ -0,0 +1,246 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<header>
<div class="left">
<button type="button" appBlurClick (click)="cancel()">{{'cancel' | i18n}}</button>
</div>
<div class="center">
<span class="title">{{title}}</span>
</div>
<div class="right">
<button type="submit" appBlurClick [disabled]="form.loading">
<span [hidden]="form.loading">{{'save' | i18n}}</span>
<i class="fa fa-spinner fa-lg fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
</div>
</header>
<content *ngIf="send">
<!-- File Warning -->
<app-callout type="warning" icon="fa fa-external-link fa-rotate-270 fa-fw" [clickable]="true"
title="{{'sendFileCalloutHeader' | i18n}}" *ngIf="showFilePopoutMessage && send.type === sendType.File"
(click)="popOutWindow()">
<div *ngIf="showFirefoxFileWarning">{{'sendFirefoxFileWarning' | i18n}}</div>
<div *ngIf="showSafariFileWarning">{{'sendSafariFileWarning' | i18n}}</div>
</app-callout>
<!-- Name -->
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="name">{{'name' | i18n}}</label>
<input id="name" type="text" name="Name" [(ngModel)]="send.name">
</div>
</div>
<div class="box-footer">
{{'sendNameDesc' | i18n}}
</div>
</div>
<!-- Type Options -->
<div class="box" *ngIf="!editMode">
<div class="box-content no-hover">
<div class="box-content-row">
<label for="sendTypeOptions">{{'sendTypeHeader' | i18n}}</label>
<div class="radio-group text-default" appBoxRow name="SendTypeOptions"
*ngFor="let o of typeOptions">
<input type="radio" [(ngModel)]="send.type" name="Type_{{o.value}}" id="type_{{o.value}}"
[value]="o.value" (change)="typeChanged()" [checked]="send.type === o.value">
<label for="type_{{o.value}}">
{{o.name}}
</label>
</div>
</div>
</div>
</div>
<!-- File -->
<div class="box" *ngIf="send.type === sendType.File && (editMode || showFileSelector)">
<div class="box-content no-hover">
<div class="box-content-row" *ngIf="editMode">
<label for="file">{{'file' | i18n}}</label>
<div class="row-main">{{send.file.fileName}} ({{send.file.sizeName}})</div>
</div>
<div class="box-content-row" *ngIf="showFileSelector">
<label for="file">{{'file' | i18n}}</label>
<input type="file" id="file" name="file" required>
</div>
</div>
<div class="box-footer" *ngIf="showFileSelector">
{{'sendFileDesc' | i18n}} {{'maxFileSize' | i18n}}
</div>
</div>
<!-- Text -->
<div class="box" *ngIf="send.type === sendType.Text">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="text">{{'sendTypeText' | i18n}}</label>
<textarea id="text" name="Text" rows="6" [(ngModel)]="send.text.text"></textarea>
</div>
</div>
<div class="box-footer">
{{'sendTextDesc' | i18n}}
</div>
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="hideText">{{'sendHideText' | i18n}}</label>
<input id="hideText" type="checkbox" name="HideText" [(ngModel)]="send.text.hidden">
</div>
</div>
</div>
<!-- Share -->
<div class="box">
<div class="box-header">
{{'share' | i18n}}
</div>
<div class="box-content">
<!-- Copy Link on Save -->
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="copyOnSave">{{'sendShareDesc' | i18n}}</label>
<input id="copyOnSave" type="checkbox" name="CopyOnSave" [(ngModel)]="copyLink">
</div>
</div>
</div>
<!-- Options -->
<div class="box">
<div class="box-header-expandable" (click)="showOptions = !showOptions">
{{'options' | i18n}}
<i *ngIf="!showOptions" class="fa fa-chevron-down fa-sm icon"></i>
<i *ngIf="showOptions" class="fa fa-chevron-up fa-sm icon"></i>
</div>
</div>
<ng-container *ngIf="showOptions">
<!-- Deletion Date -->
<div class="box">
<div class="box-content">
<div class="box-content-row" *ngIf="!editMode">
<label for="deletionDate">{{'deletionDate' | i18n}}</label>
<select id="deletionDate" name="DeletionDateSelect" [(ngModel)]="deletionDateSelect" required>
<option *ngFor="let o of deletionDateOptions" [ngValue]="o.value">{{o.name}}
</option>
</select>
</div>
<div class="box-content-row" *ngIf="deletionDateSelect === 0 && !editMode">
<input id="deletionDateCustom" type="datetime-local" name="DeletionDate"
[(ngModel)]="deletionDate" required placeholder="MM/DD/YYYY HH:MM AM/PM">
</div>
<div class="box-content-row" *ngIf="editMode" appBoxRow>
<label for="editDeletionDate">{{'deletionDate' | i18n}}</label>
<input id="editDeletionDate" type="datetime-local" name="EditDeletionDate"
[(ngModel)]="deletionDate" required placeholder="MM/DD/YYYY HH:MM AM/PM">
</div>
</div>
<div class="box-footer">
{{'deletionDateDesc' | i18n}}
</div>
</div>
<!-- Expiration Date -->
<div class="box">
<div class="box-content">
<div class="box-content-row" *ngIf="!editMode">
<label for="expirationDate">{{'expirationDate' | i18n}}</label>
<select id="expirationDate" name="ExpirationDateSelect" [(ngModel)]="expirationDateSelect"
required>
<option *ngFor="let o of expirationDateOptions" [ngValue]="o.value">{{o.name}}
</option>
</select>
</div>
<div class="box-content-row" *ngIf="expirationDateSelect === 0 && !editMode">
<input id="expirationDateCustom" type="datetime-local" name="ExpirationDate"
[(ngModel)]="expirationDate" required placeholder="MM/DD/YYYY HH:MM AM/PM">
</div>
<div class="box-content-row" *ngIf="editMode" appBoxRow>
<div class="flex-label">
<label for="editExpirationDate">{{'expirationDate' | i18n}}</label>
<a href="#" appStopClick (click)="clearExpiration()">
{{'clear' | i18n}}
</a>
</div>
<input id="editExpirationDate" type="datetime-local" name="EditExpirationDate"
[(ngModel)]="expirationDate" placeholder="MM/DD/YYYY HH:MM AM/PM">
</div>
</div>
<div class="box-footer">
{{'expirationDateDesc' | i18n}}
</div>
</div>
<!-- Maximum Access Count -->
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="maximumAccessCount">{{'maximumAccessCount' | i18n}}</label>
<input id="maximumAccessCount" min="1" type="number" name="MaximumAccessCount"
[(ngModel)]="send.maxAccessCount">
</div>
</div>
<div class="box-footer">
{{'maximumAccessCountDesc' | i18n}}
</div>
</div>
<!-- Current Access Count -->
<div class="box" *ngIf="editMode">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="currentAccessCount">{{'currentAccessCount' | i18n}}</label>
<input id="currentAccessCount" readonly type="number" name="CurrentAccessCount"
[(ngModel)]="send.accessCount">
</div>
</div>
</div>
<!-- Password -->
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="password" *ngIf="hasPassword">{{'newPassword' | i18n}}</label>
<label for="password" *ngIf="!hasPassword">{{'password' | i18n}}</label>
<input id="password" type="{{showPassword ? 'text' : 'password'}}" name="Password"
class="monospaced" [(ngModel)]="password" appInputVerbatim>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePasswordVisible()">
<i class="fa fa-lg" [ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"
aria-hidden="true"></i>
</a>
</div>
</div>
</div>
<div class="box-footer">
{{'sendPasswordDesc' | i18n}}
</div>
</div>
<!-- Notes -->
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="notes">{{'notes' | i18n}}</label>
<textarea id="notes" name="Notes" rows="6" [(ngModel)]="send.notes"></textarea>
</div>
</div>
<div class="box-footer">
{{'sendNotesDesc' | i18n}}
</div>
</div>
<!-- Disable Send -->
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="disableSend">{{'sendDisableDesc' | i18n}}</label>
<input id="disableSend" type="checkbox" name="DisableSend" [(ngModel)]="send.disabled">
</div>
</div>
</div>
</ng-container>
<!-- Delete -->
<div class="box list" *ngIf="editMode">
<div class="box-content single-line">
<a class="box-content-row" href="#" appStopClick appBlurClick (click)="delete()"
[appApiAction]="deletePromise" #deleteBtn>
<div class="row-main text-danger">
<div class="icon text-danger" aria-hidden="true">
<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>
</div>
<span>{{'deleteSend' | i18n}}</span>
</div>
</a>
</div>
</div>
</content>
</form>

View File

@@ -0,0 +1,124 @@
import {
DatePipe,
Location,
} from '@angular/common';
import { Component } from '@angular/core';
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { EnvironmentService } from 'jslib/abstractions/environment.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { PolicyService } from 'jslib/abstractions/policy.service';
import { SendService } from 'jslib/abstractions/send.service';
import { UserService } from 'jslib/abstractions/user.service';
import { PopupUtilsService } from '../services/popup-utils.service';
import { AddEditComponent as BaseAddEditComponent } from 'jslib/angular/components/send/add-edit.component';
@Component({
selector: 'app-send-add-edit',
templateUrl: 'send-add-edit.component.html',
})
export class SendAddEditComponent extends BaseAddEditComponent {
// Options header
showOptions = false;
// File visibility
isFirefox = false;
isSafari = false;
inPopout = false;
inSidebar = false;
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService,
userService: UserService, messagingService: MessagingService, policyService: PolicyService,
environmentService: EnvironmentService, datePipe: DatePipe, sendService: SendService,
private route: ActivatedRoute, private router: Router, private location: Location,
private popupUtilsService: PopupUtilsService) {
super(i18nService, platformUtilsService, environmentService, datePipe, sendService, userService,
messagingService, policyService);
}
get showFileSelector(): boolean {
return !this.editMode && (!this.isFirefox && !this.isSafari) ||
(this.isFirefox && (this.inSidebar || this.inPopout)) ||
(this.isSafari && this.inPopout);
}
get showFilePopoutMessage(): boolean {
return !this.editMode && (this.showFirefoxFileWarning || this.showSafariFileWarning);
}
get showFirefoxFileWarning(): boolean {
return this.isFirefox && !(this.inSidebar || this.inPopout);
}
get showSafariFileWarning(): boolean {
return this.isSafari && !this.inPopout;
}
popOutWindow() {
this.popupUtilsService.popOut(window);
}
async ngOnInit() {
// File visilibity
this.isFirefox = this.platformUtilsService.isFirefox();
this.isSafari = this.platformUtilsService.isSafari();
this.inPopout = this.popupUtilsService.inPopout(window);
this.inSidebar = this.popupUtilsService.inSidebar(window);
const queryParamsSub = this.route.queryParams.subscribe(async (params) => {
if (params.sendId) {
this.sendId = params.sendId;
}
if (params.type) {
const type = parseInt(params.type, null);
this.type = type;
}
await this.load();
if (queryParamsSub != null) {
queryParamsSub.unsubscribe();
}
});
window.setTimeout(() => {
if (!this.editMode) {
document.getElementById('name').focus();
}
}, 200);
}
async submit(): Promise<boolean> {
if (await super.submit()) {
this.cancel();
return true;
}
return false;
}
async delete(): Promise<boolean> {
if (await super.delete()) {
this.cancel();
return true;
}
return false;
}
cancel() {
// If true, the window was pop'd out on the add-send page. location.back will not work
if ((window as any).previousPopupUrl.startsWith('/add-send')) {
this.router.navigate(['tabs/send']);
} else {
this.location.back();
}
}
}

View File

@@ -5,7 +5,6 @@ import {
} from '@angular/core';
import {
ActivatedRoute,
Router,
} from '@angular/router';
@@ -50,7 +49,7 @@ export class SendGroupingsComponent extends BaseSendComponent {
platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService, ngZone: NgZone,
policyService: PolicyService, userService: UserService, searchService: SearchService,
private popupUtils: PopupUtilsService, private stateService: StateService,
private route: ActivatedRoute, private router: Router, private syncService: SyncService,
private router: Router, private syncService: SyncService,
private changeDetectorRef: ChangeDetectorRef, private broadcasterService: BroadcasterService) {
super(sendService, i18nService, platformUtilsService, environmentService, ngZone, searchService,
policyService, userService);
@@ -122,11 +121,11 @@ export class SendGroupingsComponent extends BaseSendComponent {
}
async selectSend(s: SendView) {
// TODO -> Route to edit send
this.router.navigate(['/edit-send'], { queryParams: { sendId: s.id } });
}
async addSend() {
// TODO -> Route to create send
this.router.navigate(['/add-send']);
}
showSearching() {

View File

@@ -6,6 +6,7 @@ import {
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { Location } from '@angular/common';
@@ -47,7 +48,7 @@ export class SendTypeComponent extends BaseSendComponent {
policyService: PolicyService, userService: UserService, searchService: SearchService,
private popupUtils: PopupUtilsService, private stateService: StateService,
private route: ActivatedRoute, private location: Location, private changeDetectorRef: ChangeDetectorRef,
private broadcasterService: BroadcasterService) {
private broadcasterService: BroadcasterService, private router: Router) {
super(sendService, i18nService, platformUtilsService, environmentService, ngZone, searchService,
policyService, userService);
super.onSuccessfulLoad = async () => {
@@ -127,11 +128,11 @@ export class SendTypeComponent extends BaseSendComponent {
}
async selectSend(s: SendView) {
// TODO -> Route to edit send
this.router.navigate(['/edit-send'], { queryParams: { sendId: s.id } });
}
async addSend() {
// TODO -> Route to create send
this.router.navigate(['/add-send'], { queryParams: { type: this.type } });
}
back() {