mirror of
https://github.com/bitwarden/browser
synced 2025-12-22 11:13:46 +00:00
[PM-328] Move Send to Tools (#5104)
* Move send in libs/common * Move send in libs/angular * Move send in browser * Move send in cli * Move send in desktop * Move send in web
This commit is contained in:
committed by
GitHub
parent
e645688f8a
commit
e238ea20a9
152
apps/web/src/app/tools/send/access.component.html
Normal file
152
apps/web/src/app/tools/send/access.component.html
Normal file
@@ -0,0 +1,152 @@
|
||||
<form #form (ngSubmit)="load()" [appApiAction]="formPromise" class="container" ngNativeValidate>
|
||||
<div class="row justify-content-center mt-5">
|
||||
<div class="col-12">
|
||||
<h1 class="lead text-center mb-4">Bitwarden Send</h1>
|
||||
</div>
|
||||
<div class="col-12 text-center" *ngIf="creatorIdentifier != null">
|
||||
<p>{{ "sendCreatorIdentifier" | i18n : creatorIdentifier }}</p>
|
||||
</div>
|
||||
<div class="col-8" *ngIf="hideEmail">
|
||||
<app-callout type="warning" title="{{ 'warning' | i18n }}">
|
||||
{{ "viewSendHiddenEmailWarning" | i18n }}
|
||||
<a href="https://bitwarden.com/help/receive-send/" target="_blank">{{
|
||||
"learnMore" | i18n
|
||||
}}</a
|
||||
>.
|
||||
</app-callout>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-5">
|
||||
<div class="card d-block">
|
||||
<div class="card-body" *ngIf="loading" class="text-center">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin bwi-2x text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</div>
|
||||
<div class="card-body" *ngIf="!loading && passwordRequired">
|
||||
<p>{{ "sendProtectedPassword" | i18n }}</p>
|
||||
<p>{{ "sendProtectedPasswordDontKnow" | i18n }}</p>
|
||||
<div class="form-group">
|
||||
<label for="password">{{ "password" | i18n }}</label>
|
||||
<input
|
||||
id="password"
|
||||
type="password"
|
||||
name="Password"
|
||||
class="text-monospace form-control"
|
||||
[(ngModel)]="password"
|
||||
required
|
||||
appInputVerbatim
|
||||
appAutofocus
|
||||
/>
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary btn-block btn-submit"
|
||||
[disabled]="form.loading"
|
||||
>
|
||||
<span>
|
||||
<i class="bwi bwi-sign-in" aria-hidden="true"></i> {{ "continue" | i18n }}
|
||||
</span>
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body" *ngIf="!loading && unavailable">
|
||||
{{ "sendAccessUnavailable" | i18n }}
|
||||
</div>
|
||||
<div class="card-body" *ngIf="!loading && error">
|
||||
{{ "unexpectedError" | i18n }}
|
||||
</div>
|
||||
<div class="card-body" *ngIf="!loading && !passwordRequired && send">
|
||||
<p class="text-center">
|
||||
<b>{{ send.name }}</b>
|
||||
</p>
|
||||
<hr />
|
||||
<!-- Text -->
|
||||
<ng-container *ngIf="send.type === sendType.Text">
|
||||
<app-callout *ngIf="send.text.hidden" type="tip">{{
|
||||
"sendHiddenByDefault" | i18n
|
||||
}}</app-callout>
|
||||
<div class="form-group">
|
||||
<textarea
|
||||
id="text"
|
||||
rows="8"
|
||||
name="Text"
|
||||
[ngModel]="sendText"
|
||||
class="form-control"
|
||||
readonly
|
||||
></textarea>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-block btn-link"
|
||||
type="button"
|
||||
(click)="toggleText()"
|
||||
*ngIf="send.text.hidden"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-lg"
|
||||
aria-hidden="true"
|
||||
[ngClass]="{ 'bwi-eye': !showText, 'bwi-eye-slash': showText }"
|
||||
></i>
|
||||
{{ "toggleVisibility" | i18n }}
|
||||
</button>
|
||||
<button class="btn btn-block btn-link" type="button" (click)="copyText()">
|
||||
<i class="bwi bwi-copy" aria-hidden="true"></i> {{ "copyValue" | i18n }}
|
||||
</button>
|
||||
</ng-container>
|
||||
<!-- File -->
|
||||
<ng-container *ngIf="send.type === sendType.File">
|
||||
<p>{{ send.file.fileName }}</p>
|
||||
<button
|
||||
class="btn btn-primary btn-block"
|
||||
type="button"
|
||||
(click)="download()"
|
||||
*ngIf="!downloading"
|
||||
>
|
||||
<i class="bwi bwi-download" aria-hidden="true"></i>
|
||||
{{ "downloadFile" | i18n }} ({{ send.file.sizeName }})
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-primary btn-block"
|
||||
type="button"
|
||||
*ngIf="downloading"
|
||||
disabled="true"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</button>
|
||||
</ng-container>
|
||||
<p *ngIf="expirationDate" class="text-center text-muted">
|
||||
Expires: {{ expirationDate | date : "medium" }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 text-center mt-5 text-muted">
|
||||
<p class="mb-0">
|
||||
{{ "sendAccessTaglineProductDesc" | i18n }}<br />
|
||||
{{ "sendAccessTaglineLearnMore" | i18n }}
|
||||
<a href="https://www.bitwarden.com/products/send?source=web-vault" target="_blank"
|
||||
>Bitwarden Send</a
|
||||
>
|
||||
{{ "sendAccessTaglineOr" | i18n }}
|
||||
<a href="https://vault.bitwarden.com/#/register" target="_blank">{{
|
||||
"sendAccessTaglineSignUp" | i18n
|
||||
}}</a>
|
||||
{{ "sendAccessTaglineTryToday" | i18n }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
190
apps/web/src/app/tools/send/access.component.ts
Normal file
190
apps/web/src/app/tools/send/access.component.ts
Normal file
@@ -0,0 +1,190 @@
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service";
|
||||
import { FileDownloadService } from "@bitwarden/common/abstractions/fileDownload/fileDownload.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { SEND_KDF_ITERATIONS } from "@bitwarden/common/enums/kdfType";
|
||||
import { Utils } from "@bitwarden/common/misc/utils";
|
||||
import { EncArrayBuffer } from "@bitwarden/common/models/domain/enc-array-buffer";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key";
|
||||
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
|
||||
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
|
||||
import { SendAccess } from "@bitwarden/common/tools/send/models/domain/send-access";
|
||||
import { SendAccessRequest } from "@bitwarden/common/tools/send/models/request/send-access.request";
|
||||
import { SendAccessResponse } from "@bitwarden/common/tools/send/models/response/send-access.response";
|
||||
import { SendAccessView } from "@bitwarden/common/tools/send/models/view/send-access.view";
|
||||
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
|
||||
|
||||
@Component({
|
||||
selector: "app-send-access",
|
||||
templateUrl: "access.component.html",
|
||||
})
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
export class AccessComponent implements OnInit {
|
||||
send: SendAccessView;
|
||||
sendType = SendType;
|
||||
downloading = false;
|
||||
loading = true;
|
||||
passwordRequired = false;
|
||||
formPromise: Promise<SendAccessResponse>;
|
||||
password: string;
|
||||
showText = false;
|
||||
unavailable = false;
|
||||
error = false;
|
||||
hideEmail = false;
|
||||
|
||||
private id: string;
|
||||
private key: string;
|
||||
private decKey: SymmetricCryptoKey;
|
||||
private accessRequest: SendAccessRequest;
|
||||
|
||||
constructor(
|
||||
private i18nService: I18nService,
|
||||
private cryptoFunctionService: CryptoFunctionService,
|
||||
private apiService: ApiService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private route: ActivatedRoute,
|
||||
private cryptoService: CryptoService,
|
||||
private fileDownloadService: FileDownloadService,
|
||||
private sendApiService: SendApiService
|
||||
) {}
|
||||
|
||||
get sendText() {
|
||||
if (this.send == null || this.send.text == null) {
|
||||
return null;
|
||||
}
|
||||
return this.showText ? this.send.text.text : this.send.text.maskedText;
|
||||
}
|
||||
|
||||
get expirationDate() {
|
||||
if (this.send == null || this.send.expirationDate == null) {
|
||||
return null;
|
||||
}
|
||||
return this.send.expirationDate;
|
||||
}
|
||||
|
||||
get creatorIdentifier() {
|
||||
if (this.send == null || this.send.creatorIdentifier == null) {
|
||||
return null;
|
||||
}
|
||||
return this.send.creatorIdentifier;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
|
||||
this.route.params.subscribe(async (params) => {
|
||||
this.id = params.sendId;
|
||||
this.key = params.key;
|
||||
if (this.key == null || this.id == null) {
|
||||
return;
|
||||
}
|
||||
await this.load();
|
||||
});
|
||||
}
|
||||
|
||||
async download() {
|
||||
if (this.send == null || this.decKey == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.downloading) {
|
||||
return;
|
||||
}
|
||||
|
||||
const downloadData = await this.sendApiService.getSendFileDownloadData(
|
||||
this.send,
|
||||
this.accessRequest
|
||||
);
|
||||
|
||||
if (Utils.isNullOrWhitespace(downloadData.url)) {
|
||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("missingSendFile"));
|
||||
return;
|
||||
}
|
||||
|
||||
this.downloading = true;
|
||||
const response = await fetch(new Request(downloadData.url, { cache: "no-store" }));
|
||||
if (response.status !== 200) {
|
||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred"));
|
||||
this.downloading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const encBuf = await EncArrayBuffer.fromResponse(response);
|
||||
const decBuf = await this.cryptoService.decryptFromBytes(encBuf, this.decKey);
|
||||
this.fileDownloadService.download({
|
||||
fileName: this.send.file.fileName,
|
||||
blobData: decBuf,
|
||||
downloadMethod: "save",
|
||||
});
|
||||
} catch (e) {
|
||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred"));
|
||||
}
|
||||
|
||||
this.downloading = false;
|
||||
}
|
||||
|
||||
copyText() {
|
||||
this.platformUtilsService.copyToClipboard(this.send.text.text);
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("valueCopied", this.i18nService.t("sendTypeText"))
|
||||
);
|
||||
}
|
||||
|
||||
toggleText() {
|
||||
this.showText = !this.showText;
|
||||
}
|
||||
|
||||
async load() {
|
||||
this.unavailable = false;
|
||||
this.error = false;
|
||||
this.hideEmail = false;
|
||||
const keyArray = Utils.fromUrlB64ToArray(this.key);
|
||||
this.accessRequest = new SendAccessRequest();
|
||||
if (this.password != null) {
|
||||
const passwordHash = await this.cryptoFunctionService.pbkdf2(
|
||||
this.password,
|
||||
keyArray,
|
||||
"sha256",
|
||||
SEND_KDF_ITERATIONS
|
||||
);
|
||||
this.accessRequest.password = Utils.fromBufferToB64(passwordHash);
|
||||
}
|
||||
try {
|
||||
let sendResponse: SendAccessResponse = null;
|
||||
if (this.loading) {
|
||||
sendResponse = await this.sendApiService.postSendAccess(this.id, this.accessRequest);
|
||||
} else {
|
||||
this.formPromise = this.sendApiService.postSendAccess(this.id, this.accessRequest);
|
||||
sendResponse = await this.formPromise;
|
||||
}
|
||||
this.passwordRequired = false;
|
||||
const sendAccess = new SendAccess(sendResponse);
|
||||
this.decKey = await this.cryptoService.makeSendKey(keyArray);
|
||||
this.send = await sendAccess.decrypt(this.decKey);
|
||||
this.showText = this.send.text != null ? !this.send.text.hidden : true;
|
||||
} catch (e) {
|
||||
if (e instanceof ErrorResponse) {
|
||||
if (e.statusCode === 401) {
|
||||
this.passwordRequired = true;
|
||||
} else if (e.statusCode === 404) {
|
||||
this.unavailable = true;
|
||||
} else {
|
||||
this.error = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.loading = false;
|
||||
this.hideEmail =
|
||||
this.creatorIdentifier == null &&
|
||||
!this.passwordRequired &&
|
||||
!this.loading &&
|
||||
!this.unavailable;
|
||||
}
|
||||
}
|
||||
305
apps/web/src/app/tools/send/add-edit.component.html
Normal file
305
apps/web/src/app/tools/send/add-edit.component.html
Normal file
@@ -0,0 +1,305 @@
|
||||
<!-- Please remove this disable statement when editing this file! -->
|
||||
<!-- eslint-disable @angular-eslint/template/button-has-type -->
|
||||
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="sendAddEditTitle">
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
|
||||
<form
|
||||
class="modal-content"
|
||||
#form
|
||||
(ngSubmit)="submit()"
|
||||
[appApiAction]="formPromise"
|
||||
ngNativeValidate
|
||||
autocomplete="off"
|
||||
>
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title" id="sendAddEditTitle">{{ title }}</h1>
|
||||
<button
|
||||
type="button"
|
||||
class="close"
|
||||
data-dismiss="modal"
|
||||
appA11yTitle="{{ 'close' | i18n }}"
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body" *ngIf="send">
|
||||
<app-callout *ngIf="disableSend">
|
||||
<span>{{ "sendDisabledWarning" | i18n }}</span>
|
||||
</app-callout>
|
||||
<app-callout *ngIf="!disableSend && disableHideEmail">
|
||||
<span>{{ "sendOptionsPolicyInEffect" | i18n }}</span>
|
||||
<ul class="mb-0">
|
||||
<li>{{ "sendDisableHideEmailInEffect" | i18n }}</li>
|
||||
</ul>
|
||||
</app-callout>
|
||||
<div class="row">
|
||||
<div class="col-6 form-group">
|
||||
<label for="name">{{ "name" | i18n }}</label>
|
||||
<input
|
||||
id="name"
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="Name"
|
||||
[(ngModel)]="send.name"
|
||||
required
|
||||
[readOnly]="disableSend"
|
||||
/>
|
||||
<small class="form-text text-muted">{{ "sendNameDesc" | i18n }}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" *ngIf="!editMode">
|
||||
<div class="col-6 form-group">
|
||||
<label>{{ "whatTypeOfSend" | i18n }}</label>
|
||||
<div class="form-check" *ngFor="let o of typeOptions">
|
||||
<input
|
||||
class="form-check-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 class="form-check-label" for="type_{{ o.value }}">
|
||||
{{ o.name }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Text -->
|
||||
<ng-container *ngIf="send.type === sendType.Text">
|
||||
<div class="form-group">
|
||||
<label for="text">{{ "sendTypeText" | i18n }}</label>
|
||||
<textarea
|
||||
id="text"
|
||||
name="Text.Text"
|
||||
rows="6"
|
||||
[(ngModel)]="send.text.text"
|
||||
class="form-control"
|
||||
[readOnly]="disableSend"
|
||||
></textarea>
|
||||
<small class="form-text text-muted">{{ "sendTextDesc" | i18n }}</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
[(ngModel)]="send.text.hidden"
|
||||
id="text-hidden"
|
||||
name="Text.Hidden"
|
||||
[disabled]="disableSend"
|
||||
/>
|
||||
<label class="form-check-label" for="text-hidden">{{
|
||||
"textHiddenByDefault" | i18n
|
||||
}}</label>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<!-- File -->
|
||||
<ng-container *ngIf="send.type === sendType.File">
|
||||
<div class="form-group">
|
||||
<div *ngIf="editMode">
|
||||
<strong class="d-block">{{ "file" | i18n }}</strong>
|
||||
{{ send.file.fileName }} ({{ send.file.sizeName }})
|
||||
</div>
|
||||
<div *ngIf="!editMode">
|
||||
<label for="file">{{ "file" | i18n }}</label>
|
||||
<input
|
||||
type="file"
|
||||
id="file"
|
||||
class="form-control-file"
|
||||
name="file"
|
||||
required
|
||||
[disabled]="disableSend"
|
||||
/>
|
||||
<small class="form-text text-muted"
|
||||
>{{ "sendFileDesc" | i18n }} {{ "maxFileSize" | i18n }}</small
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<h3 class="mt-5">{{ "share" | i18n }}</h3>
|
||||
<div class="form-group" *ngIf="link">
|
||||
<label for="link">{{ "sendLinkLabel" | i18n }}</label>
|
||||
<input type="text" readonly id="link" name="Link" [ngModel]="link" class="form-control" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
[(ngModel)]="copyLink"
|
||||
id="copy-link"
|
||||
name="CopyLink"
|
||||
/>
|
||||
<label class="form-check-label" for="copy-link">{{
|
||||
"copySendLinkOnSave" | i18n
|
||||
}}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id="options-header"
|
||||
class="section-header d-flex flex-row align-items-center mt-5"
|
||||
(click)="toggleOptions()"
|
||||
>
|
||||
<h3 class="mb-0 mr-2">
|
||||
<button appStopClick class="header-expandable">
|
||||
<i
|
||||
class="bwi"
|
||||
aria-hidden="true"
|
||||
[ngClass]="{ 'bwi-angle-right': !showOptions, 'bwi-angle-down': showOptions }"
|
||||
></i>
|
||||
{{ "options" | i18n }}
|
||||
</button>
|
||||
</h3>
|
||||
</div>
|
||||
<div id="options" [hidden]="!showOptions">
|
||||
<app-send-efflux-dates
|
||||
[initialDeletionDate]="send.deletionDate"
|
||||
[initialExpirationDate]="send.expirationDate"
|
||||
[editMode]="editMode"
|
||||
[disabled]="disableSend"
|
||||
(datesChanged)="setDates($event)"
|
||||
>
|
||||
</app-send-efflux-dates>
|
||||
<div class="row">
|
||||
<div class="col-6 form-group">
|
||||
<label for="maxAccessCount">{{ "maxAccessCount" | i18n }}</label>
|
||||
<input
|
||||
id="maxAccessCount"
|
||||
class="form-control"
|
||||
type="number"
|
||||
name="MaxAccessCount"
|
||||
[(ngModel)]="send.maxAccessCount"
|
||||
min="1"
|
||||
[readOnly]="disableSend"
|
||||
/>
|
||||
<div class="form-text text-muted small">{{ "maxAccessCountDesc" | i18n }}</div>
|
||||
</div>
|
||||
<div class="col-6 form-group" *ngIf="editMode">
|
||||
<label for="accessCount">{{ "currentAccessCount" | i18n }}</label>
|
||||
<input
|
||||
id="accessCount"
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="AccessCount"
|
||||
readonly
|
||||
[(ngModel)]="send.accessCount"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-6 form-group">
|
||||
<label for="password" *ngIf="!hasPassword">{{ "password" | i18n }}</label>
|
||||
<label for="password" *ngIf="hasPassword">{{ "newPassword" | i18n }}</label>
|
||||
<div class="input-group">
|
||||
<input
|
||||
id="password"
|
||||
class="form-control text-monospace"
|
||||
type="{{ showPassword ? 'text' : 'password' }}"
|
||||
name="Password"
|
||||
[(ngModel)]="password"
|
||||
[readOnly]="disableSend"
|
||||
/>
|
||||
<div class="input-group-append">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
|
||||
(click)="togglePasswordVisible()"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-lg"
|
||||
aria-hidden="true"
|
||||
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-text text-muted small">{{ "sendPasswordDesc" | i18n }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="notes">{{ "notes" | i18n }}</label>
|
||||
<textarea
|
||||
id="notes"
|
||||
name="Notes"
|
||||
rows="6"
|
||||
[(ngModel)]="send.notes"
|
||||
class="form-control"
|
||||
[readOnly]="disableSend"
|
||||
></textarea>
|
||||
<div class="form-text text-muted small">{{ "sendNotesDesc" | i18n }}</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
[(ngModel)]="send.hideEmail"
|
||||
id="hideEmail"
|
||||
name="HideEmail"
|
||||
[disabled]="(disableHideEmail && !send.hideEmail) || disableSend"
|
||||
/>
|
||||
<label class="form-check-label" for="hideEmail">
|
||||
{{ "hideEmail" | i18n }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
[(ngModel)]="send.disabled"
|
||||
id="disabled"
|
||||
name="Disabled"
|
||||
[disabled]="disableSend"
|
||||
/>
|
||||
<label class="form-check-label" for="disabled">{{ "disableThisSend" | i18n }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary btn-submit manual"
|
||||
[ngClass]="{ loading: form.loading }"
|
||||
[disabled]="form.loading || disableSend"
|
||||
>
|
||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "save" | i18n }}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
<div class="ml-auto" *ngIf="send">
|
||||
<button
|
||||
#deleteBtn
|
||||
type="button"
|
||||
(click)="delete()"
|
||||
class="btn btn-outline-danger"
|
||||
appA11yTitle="{{ 'delete' | i18n }}"
|
||||
*ngIf="editMode"
|
||||
[disabled]="$any(deleteBtn).loading"
|
||||
[appApiAction]="deletePromise"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-trash bwi-lg bwi-fw"
|
||||
[hidden]="$any(deleteBtn).loading"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin bwi-lg bwi-fw"
|
||||
[hidden]="!$any(deleteBtn).loading"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
55
apps/web/src/app/tools/send/add-edit.component.ts
Normal file
55
apps/web/src/app/tools/send/add-edit.component.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { DatePipe } from "@angular/common";
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { AddEditComponent as BaseAddEditComponent } from "@bitwarden/angular/tools/send/add-edit.component";
|
||||
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
|
||||
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
|
||||
|
||||
@Component({
|
||||
selector: "app-send-add-edit",
|
||||
templateUrl: "add-edit.component.html",
|
||||
})
|
||||
export class AddEditComponent extends BaseAddEditComponent {
|
||||
override componentName = "app-send-add-edit";
|
||||
|
||||
constructor(
|
||||
i18nService: I18nService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
environmentService: EnvironmentService,
|
||||
datePipe: DatePipe,
|
||||
sendService: SendService,
|
||||
stateService: StateService,
|
||||
messagingService: MessagingService,
|
||||
policyService: PolicyService,
|
||||
logService: LogService,
|
||||
sendApiService: SendApiService
|
||||
) {
|
||||
super(
|
||||
i18nService,
|
||||
platformUtilsService,
|
||||
environmentService,
|
||||
datePipe,
|
||||
sendService,
|
||||
messagingService,
|
||||
policyService,
|
||||
logService,
|
||||
stateService,
|
||||
sendApiService
|
||||
);
|
||||
}
|
||||
|
||||
async copyLinkToClipboard(link: string): Promise<void | boolean> {
|
||||
// Copy function on web depends on the modal being open or not. Since this event occurs during a transition
|
||||
// of the modal closing we need to add a small delay to make sure state of the DOM is consistent.
|
||||
return new Promise((resolve) => {
|
||||
window.setTimeout(() => resolve(super.copyLinkToClipboard(link)), 500);
|
||||
});
|
||||
}
|
||||
}
|
||||
188
apps/web/src/app/tools/send/efflux-dates.component.html
Normal file
188
apps/web/src/app/tools/send/efflux-dates.component.html
Normal file
@@ -0,0 +1,188 @@
|
||||
<div class="row" [formGroup]="datesForm">
|
||||
<div class="col-6 form-group">
|
||||
<label for="deletionDate">{{ "deletionDate" | i18n }}</label>
|
||||
<ng-template #deletionDateCustom>
|
||||
<ng-container [ngSwitch]="browserPath">
|
||||
<ng-container *ngSwitchCase="'firefox'">
|
||||
<div class="d-flex justify-content-around">
|
||||
<input
|
||||
id="deletionDateCustomFallback"
|
||||
class="form-control mt-1"
|
||||
type="date"
|
||||
name="DeletionDateFallback"
|
||||
formControlName="fallbackDeletionDate"
|
||||
required
|
||||
placeholder="MM/DD/YYYY"
|
||||
data-date-format="mm/dd/yyyy"
|
||||
/>
|
||||
<input
|
||||
id="deletionTimeCustomFallback"
|
||||
class="form-control mt-1 ml-1"
|
||||
type="time"
|
||||
name="DeletionTimeDate"
|
||||
formControlName="fallbackDeletionTime"
|
||||
required
|
||||
placeholder="HH:MM AM/PM"
|
||||
/>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="'safari'">
|
||||
<div class="d-flex justify-content-around">
|
||||
<input
|
||||
id="deletionDateCustomFallback"
|
||||
class="form-control mt-1"
|
||||
type="date"
|
||||
name="DeletionDateFallback"
|
||||
formControlName="fallbackDeletionDate"
|
||||
required
|
||||
placeholder="MM/DD/YYYY"
|
||||
data-date-format="mm/dd/yyyy"
|
||||
/>
|
||||
<select
|
||||
id="deletionTimeCustomFallback"
|
||||
class="form-control mt-1 ml-1"
|
||||
[required]="!editMode"
|
||||
formControlName="fallbackDeletionTime"
|
||||
name="SafariDeletionTime"
|
||||
>
|
||||
<option
|
||||
*ngFor="let o of safariDeletionTimePresetOptions"
|
||||
[ngValue]="o.twentyFourHour"
|
||||
>
|
||||
{{ o.twelveHour }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchDefault>
|
||||
<input
|
||||
id="deletionDateCustom"
|
||||
class="form-control mt-1"
|
||||
type="datetime-local"
|
||||
name="DeletionDate"
|
||||
formControlName="defaultDeletionDateTime"
|
||||
required
|
||||
placeholder="MM/DD/YYYY HH:MM AM/PM"
|
||||
[readOnly]="disabled"
|
||||
/>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
<div *ngIf="!editMode">
|
||||
<select
|
||||
id="deletionDate"
|
||||
name="SelectedDeletionDatePreset"
|
||||
formControlName="selectedDeletionDatePreset"
|
||||
class="form-control"
|
||||
required
|
||||
>
|
||||
<option *ngFor="let o of deletionDatePresets" [ngValue]="o.value">{{ o.name }}</option>
|
||||
</select>
|
||||
<ng-container *ngIf="selectedDeletionDatePreset.value === 0">
|
||||
<ng-container *ngTemplateOutlet="deletionDateCustom"> </ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div *ngIf="editMode">
|
||||
<ng-container *ngTemplateOutlet="deletionDateCustom"> </ng-container>
|
||||
</div>
|
||||
<div class="form-text text-muted small">{{ "deletionDateDesc" | i18n }}</div>
|
||||
</div>
|
||||
<div class="col-6 form-group">
|
||||
<div class="d-flex">
|
||||
<label for="expirationDate">{{ "expirationDate" | i18n }}</label>
|
||||
<a
|
||||
href="#"
|
||||
appStopClick
|
||||
(click)="clearExpiration()"
|
||||
class="ml-auto"
|
||||
*ngIf="editMode && !disabled"
|
||||
>
|
||||
{{ "clear" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
<ng-template #expirationDateCustom>
|
||||
<ng-container [ngSwitch]="browserPath">
|
||||
<div *ngSwitchCase="'firefox'" class="d-flex justify-content-around">
|
||||
<input
|
||||
id="expirationDateCustomFallback"
|
||||
class="form-control mt-1"
|
||||
type="date"
|
||||
name="ExpirationDateFallback"
|
||||
formControlName="fallbackExpirationDate"
|
||||
[required]="!editMode"
|
||||
placeholder="MM/DD/YYYY"
|
||||
[readOnly]="disabled"
|
||||
data-date-format="mm/dd/yyyy"
|
||||
/>
|
||||
<input
|
||||
id="expirationTimeCustomFallback"
|
||||
class="form-control mt-1 ml-1"
|
||||
type="time"
|
||||
name="ExpirationTimeFallback"
|
||||
formControlName="fallbackExpirationTime"
|
||||
[required]="!editMode"
|
||||
placeholder="HH:MM AM/PM"
|
||||
[readOnly]="disabled"
|
||||
/>
|
||||
</div>
|
||||
<!-- non-default cases are not showing up -->
|
||||
<div *ngSwitchCase="'safari'" class="d-flex justify-content-around">
|
||||
<input
|
||||
id="expirationDateCustomFallback"
|
||||
class="form-control mt-1"
|
||||
type="date"
|
||||
name="ExpirationDateFallback"
|
||||
formControlName="fallbackExpirationDate"
|
||||
[required]="!editMode"
|
||||
placeholder="MM/DD/YYYY"
|
||||
[readOnly]="disabled"
|
||||
data-date-format="mm/dd/yyyy"
|
||||
/>
|
||||
<select
|
||||
id="expirationTimeCustomFallback"
|
||||
class="form-control mt-1 ml-1"
|
||||
[required]="!editMode"
|
||||
formControlName="fallbackExpirationTime"
|
||||
name="SafariExpirationTime"
|
||||
>
|
||||
<option
|
||||
*ngFor="let o of safariExpirationTimePresetOptions"
|
||||
[ngValue]="o.twentyFourHour"
|
||||
>
|
||||
{{ o.twelveHour }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<ng-container *ngSwitchDefault>
|
||||
<input
|
||||
id="expirationDateCustom"
|
||||
class="form-control mt-1"
|
||||
type="datetime-local"
|
||||
name="ExpirationDate"
|
||||
formControlName="defaultExpirationDateTime"
|
||||
placeholder="MM/DD/YYYY HH:MM AM/PM"
|
||||
[readOnly]="disabled"
|
||||
/>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
<div *ngIf="!editMode">
|
||||
<select
|
||||
id="expirationDate"
|
||||
name="SelectedExpirationDatePreset"
|
||||
formControlName="selectedExpirationDatePreset"
|
||||
class="form-control"
|
||||
required
|
||||
>
|
||||
<option *ngFor="let o of expirationDatePresets" [ngValue]="o.value">{{ o.name }}</option>
|
||||
</select>
|
||||
<ng-container *ngIf="selectedExpirationDatePreset.value === 0">
|
||||
<ng-container *ngTemplateOutlet="expirationDateCustom"> </ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div *ngIf="editMode">
|
||||
<ng-container *ngTemplateOutlet="expirationDateCustom"> </ng-container>
|
||||
</div>
|
||||
<div class="form-text text-muted small">{{ "expirationDateDesc" | i18n }}</div>
|
||||
</div>
|
||||
</div>
|
||||
22
apps/web/src/app/tools/send/efflux-dates.component.ts
Normal file
22
apps/web/src/app/tools/send/efflux-dates.component.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { DatePipe } from "@angular/common";
|
||||
import { Component } from "@angular/core";
|
||||
import { ControlContainer, NgForm } from "@angular/forms";
|
||||
|
||||
import { EffluxDatesComponent as BaseEffluxDatesComponent } from "@bitwarden/angular/tools/send/efflux-dates.component";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-send-efflux-dates",
|
||||
templateUrl: "efflux-dates.component.html",
|
||||
viewProviders: [{ provide: ControlContainer, useExisting: NgForm }],
|
||||
})
|
||||
export class EffluxDatesComponent extends BaseEffluxDatesComponent {
|
||||
constructor(
|
||||
protected i18nService: I18nService,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
protected datePipe: DatePipe
|
||||
) {
|
||||
super(i18nService, platformUtilsService, datePipe);
|
||||
}
|
||||
}
|
||||
194
apps/web/src/app/tools/send/send.component.html
Normal file
194
apps/web/src/app/tools/send/send.component.html
Normal file
@@ -0,0 +1,194 @@
|
||||
<!-- Please remove this disable statement when editing this file! -->
|
||||
<!-- eslint-disable @angular-eslint/template/button-has-type -->
|
||||
<div class="container page-content">
|
||||
<app-callout type="warning" title="{{ 'sendDisabled' | i18n }}" *ngIf="disableSend">
|
||||
<span>{{ "sendDisabledWarning" | i18n }}</span>
|
||||
</app-callout>
|
||||
<div class="row">
|
||||
<div class="col-3 groupings">
|
||||
<div class="card vault-filters">
|
||||
<div class="card-header d-flex">
|
||||
{{ "filters" | i18n }}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<input
|
||||
type="search"
|
||||
placeholder="{{ searchPlaceholder || ('searchSends' | i18n) }}"
|
||||
id="search"
|
||||
class="form-control"
|
||||
[(ngModel)]="searchText"
|
||||
(input)="searchTextChanged()"
|
||||
autocomplete="off"
|
||||
appAutofocus
|
||||
/>
|
||||
<div class="filter">
|
||||
<ul class="filter-options">
|
||||
<li class="filter-option" [ngClass]="{ active: selectedAll }">
|
||||
<span class="filter-buttons">
|
||||
<button class="filter-button" appStopClick (click)="selectAll()">
|
||||
<i class="bwi bwi-fw bwi-filter"></i>{{ "allSends" | i18n }}
|
||||
</button>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="filter">
|
||||
<div class="filter-heading">
|
||||
<h3>{{ "types" | i18n }}</h3>
|
||||
</div>
|
||||
<ul class="filter-options">
|
||||
<li class="filter-option" [ngClass]="{ active: selectedType === sendType.Text }">
|
||||
<span class="filter-buttons">
|
||||
<button class="filter-button" appStopClick (click)="selectType(sendType.Text)">
|
||||
<i class="bwi bwi-fw bwi-file-text"></i>{{ "sendTypeText" | i18n }}
|
||||
</button>
|
||||
</span>
|
||||
</li>
|
||||
<li class="filter-option" [ngClass]="{ active: selectedType === sendType.File }">
|
||||
<span class="filter-buttons">
|
||||
<button class="filter-button" appStopClick (click)="selectType(sendType.File)">
|
||||
<i class="bwi bwi-fw bwi-file"></i>{{ "sendTypeFile" | i18n }}
|
||||
</button>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<div class="page-header d-flex">
|
||||
<h1>
|
||||
{{ "send" | i18n }}
|
||||
<small #actionSpinner [appApiAction]="actionPromise">
|
||||
<ng-container *ngIf="$any(actionSpinner).loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
</small>
|
||||
</h1>
|
||||
<div class="ml-auto d-flex">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-primary btn-sm"
|
||||
(click)="addSend()"
|
||||
[disabled]="disableSend"
|
||||
>
|
||||
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>{{ "createSend" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!--Listing Table-->
|
||||
<table class="table table-hover table-list" *ngIf="filteredSends && filteredSends.length">
|
||||
<tbody>
|
||||
<tr *ngFor="let s of filteredSends">
|
||||
<td class="table-list-icon">
|
||||
<div class="icon" aria-hidden="true">
|
||||
<i class="bwi bwi-fw bwi-lg bwi-file" *ngIf="s.type == sendType.File"></i>
|
||||
<i class="bwi bwi-fw bwi-lg bwi-file-text" *ngIf="s.type == sendType.Text"></i>
|
||||
</div>
|
||||
</td>
|
||||
<td class="reduced-lh wrap">
|
||||
<a href="#" appStopClick appStopProp (click)="editSend(s)">{{ s.name }}</a>
|
||||
<ng-container *ngIf="s.disabled">
|
||||
<i
|
||||
class="bwi bwi-exclamation-triangle"
|
||||
appStopProp
|
||||
title="{{ 'disabled' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "disabled" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="s.password">
|
||||
<i
|
||||
class="bwi bwi-key"
|
||||
appStopProp
|
||||
title="{{ 'password' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "password" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="s.maxAccessCountReached">
|
||||
<i
|
||||
class="bwi bwi-ban"
|
||||
appStopProp
|
||||
title="{{ 'maxAccessCountReached' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "maxAccessCountReached" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="s.expired">
|
||||
<i
|
||||
class="bwi bwi-clock"
|
||||
appStopProp
|
||||
title="{{ 'expired' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "expired" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="s.pendingDelete">
|
||||
<i
|
||||
class="bwi bwi-trash"
|
||||
appStopProp
|
||||
title="{{ 'pendingDeletion' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "pendingDeletion" | i18n }}</span>
|
||||
</ng-container>
|
||||
<br />
|
||||
<small appStopProp>{{ s.deletionDate | date : "medium" }}</small>
|
||||
</td>
|
||||
<td class="table-list-options">
|
||||
<button
|
||||
[bitMenuTriggerFor]="sendOptions"
|
||||
class="tw-border-none tw-bg-transparent tw-text-main"
|
||||
type="button"
|
||||
appA11yTitle="{{ 'options' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-ellipsis-v bwi-lg" aria-hidden="true"></i>
|
||||
</button>
|
||||
<bit-menu #sendOptions>
|
||||
<button bitMenuItem (click)="copy(s)">
|
||||
<i class="bwi bwi-fw bwi-clone" aria-hidden="true"></i>
|
||||
{{ "copySendLink" | i18n }}
|
||||
</button>
|
||||
<button bitMenuItem (click)="removePassword(s)" *ngIf="s.password && !disableSend">
|
||||
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
|
||||
{{ "removePassword" | i18n }}
|
||||
</button>
|
||||
<button bitMenuItem (click)="delete(s)">
|
||||
<span class="tw-text-danger">
|
||||
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>
|
||||
{{ "delete" | i18n }}
|
||||
</span>
|
||||
</button>
|
||||
</bit-menu>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="no-items" *ngIf="filteredSends && !filteredSends.length">
|
||||
<ng-container *ngIf="!loaded">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="loaded">
|
||||
<bit-icon [icon]="noItemIcon" aria-hidden="true"></bit-icon>
|
||||
<p>{{ "noSendsInList" | i18n }}</p>
|
||||
<button (click)="addSend()" class="btn btn-outline-primary" [disabled]="disableSend">
|
||||
<i class="bwi bwi-plus bwi-fw"></i>{{ "createSend" | i18n }}
|
||||
</button>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ng-template #sendAddEdit></ng-template>
|
||||
108
apps/web/src/app/tools/send/send.component.ts
Normal file
108
apps/web/src/app/tools/send/send.component.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import { Component, NgZone, ViewChild, ViewContainerRef } from "@angular/core";
|
||||
|
||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||
import { SendComponent as BaseSendComponent } from "@bitwarden/angular/tools/send/send.component";
|
||||
import { BroadcasterService } from "@bitwarden/common/abstractions/broadcaster.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
|
||||
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
|
||||
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
|
||||
import { Icons } from "@bitwarden/components";
|
||||
|
||||
import { AddEditComponent } from "./add-edit.component";
|
||||
|
||||
const BroadcasterSubscriptionId = "SendComponent";
|
||||
|
||||
@Component({
|
||||
selector: "app-send",
|
||||
templateUrl: "send.component.html",
|
||||
})
|
||||
export class SendComponent extends BaseSendComponent {
|
||||
@ViewChild("sendAddEdit", { read: ViewContainerRef, static: true })
|
||||
sendAddEditModalRef: ViewContainerRef;
|
||||
noItemIcon = Icons.Search;
|
||||
|
||||
constructor(
|
||||
sendService: SendService,
|
||||
i18nService: I18nService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
environmentService: EnvironmentService,
|
||||
ngZone: NgZone,
|
||||
searchService: SearchService,
|
||||
policyService: PolicyService,
|
||||
private modalService: ModalService,
|
||||
private broadcasterService: BroadcasterService,
|
||||
logService: LogService,
|
||||
sendApiService: SendApiService
|
||||
) {
|
||||
super(
|
||||
sendService,
|
||||
i18nService,
|
||||
platformUtilsService,
|
||||
environmentService,
|
||||
ngZone,
|
||||
searchService,
|
||||
policyService,
|
||||
logService,
|
||||
sendApiService
|
||||
);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
await super.ngOnInit();
|
||||
await this.load();
|
||||
|
||||
// Broadcaster subscription - load if sync completes in the background
|
||||
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
|
||||
this.ngZone.run(async () => {
|
||||
switch (message.command) {
|
||||
case "syncCompleted":
|
||||
if (message.successfully) {
|
||||
await this.load();
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
||||
}
|
||||
|
||||
async addSend() {
|
||||
if (this.disableSend) {
|
||||
return;
|
||||
}
|
||||
|
||||
const component = await this.editSend(null);
|
||||
component.type = this.type;
|
||||
}
|
||||
|
||||
async editSend(send: SendView) {
|
||||
const [modal, childComponent] = await this.modalService.openViewRef(
|
||||
AddEditComponent,
|
||||
this.sendAddEditModalRef,
|
||||
(comp) => {
|
||||
comp.sendId = send == null ? null : send.id;
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
|
||||
comp.onSavedSend.subscribe(async () => {
|
||||
modal.close();
|
||||
await this.load();
|
||||
});
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
|
||||
comp.onDeletedSend.subscribe(async () => {
|
||||
modal.close();
|
||||
await this.load();
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
return childComponent;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user