mirror of
https://github.com/bitwarden/browser
synced 2025-12-19 17:53:39 +00:00
[PM-294489 Extract send table to libs/tools (#17841)
* Extract send table to libs/tools so it can be re-used in desktop * Remove *ngIf * Add story * Move story under sends * Remove duplicate class * Move the dates to prevent any changes in storybook * Revert changes to web component to speed up merge
This commit is contained in:
@@ -6,3 +6,4 @@ export { SendItemsService } from "./services/send-items.service";
|
|||||||
export { SendSearchComponent } from "./send-search/send-search.component";
|
export { SendSearchComponent } from "./send-search/send-search.component";
|
||||||
export { SendListFiltersComponent } from "./send-list-filters/send-list-filters.component";
|
export { SendListFiltersComponent } from "./send-list-filters/send-list-filters.component";
|
||||||
export { SendListFiltersService } from "./services/send-list-filters.service";
|
export { SendListFiltersService } from "./services/send-list-filters.service";
|
||||||
|
export { SendTableComponent } from "./send-table/send-table.component";
|
||||||
|
|||||||
102
libs/tools/send/send-ui/src/send-table/send-table.component.html
Normal file
102
libs/tools/send/send-ui/src/send-table/send-table.component.html
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
<bit-table [dataSource]="dataSource()">
|
||||||
|
<ng-container header>
|
||||||
|
<tr>
|
||||||
|
<th bitCell bitSortable="name" default>{{ "name" | i18n }}</th>
|
||||||
|
<th bitCell bitSortable="deletionDate">{{ "deletionDate" | i18n }}</th>
|
||||||
|
<th bitCell>{{ "options" | i18n }}</th>
|
||||||
|
</tr>
|
||||||
|
</ng-container>
|
||||||
|
<ng-template body let-rows$>
|
||||||
|
<tr bitRow *ngFor="let s of rows$ | async">
|
||||||
|
<td bitCell (click)="onEditSend(s)" class="tw-cursor-pointer">
|
||||||
|
<div class="tw-flex tw-gap-2 tw-items-center">
|
||||||
|
<span aria-hidden="true">
|
||||||
|
@if (s.type == sendType.File) {
|
||||||
|
<i class="bwi bwi-fw bwi-lg bwi-file"></i>
|
||||||
|
}
|
||||||
|
@if (s.type == sendType.Text) {
|
||||||
|
<i class="bwi bwi-fw bwi-lg bwi-file-text"></i>
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
<button type="button" bitLink>
|
||||||
|
{{ s.name }}
|
||||||
|
</button>
|
||||||
|
@if (s.disabled) {
|
||||||
|
<i
|
||||||
|
class="bwi bwi-exclamation-triangle"
|
||||||
|
appStopProp
|
||||||
|
title="{{ 'disabled' | i18n }}"
|
||||||
|
aria-hidden="true"
|
||||||
|
></i>
|
||||||
|
<span class="tw-sr-only">{{ "disabled" | i18n }}</span>
|
||||||
|
}
|
||||||
|
@if (s.password) {
|
||||||
|
<i
|
||||||
|
class="bwi bwi-key"
|
||||||
|
appStopProp
|
||||||
|
title="{{ 'password' | i18n }}"
|
||||||
|
aria-hidden="true"
|
||||||
|
></i>
|
||||||
|
<span class="tw-sr-only">{{ "password" | i18n }}</span>
|
||||||
|
}
|
||||||
|
@if (s.maxAccessCountReached) {
|
||||||
|
<i
|
||||||
|
class="bwi bwi-exclamation-triangle"
|
||||||
|
appStopProp
|
||||||
|
title="{{ 'maxAccessCountReached' | i18n }}"
|
||||||
|
aria-hidden="true"
|
||||||
|
></i>
|
||||||
|
<span class="tw-sr-only">{{ "maxAccessCountReached" | i18n }}</span>
|
||||||
|
}
|
||||||
|
@if (s.expired) {
|
||||||
|
<i
|
||||||
|
class="bwi bwi-clock"
|
||||||
|
appStopProp
|
||||||
|
title="{{ 'expired' | i18n }}"
|
||||||
|
aria-hidden="true"
|
||||||
|
></i>
|
||||||
|
<span class="tw-sr-only">{{ "expired" | i18n }}</span>
|
||||||
|
}
|
||||||
|
@if (s.pendingDelete) {
|
||||||
|
<i
|
||||||
|
class="bwi bwi-trash"
|
||||||
|
appStopProp
|
||||||
|
title="{{ 'pendingDeletion' | i18n }}"
|
||||||
|
aria-hidden="true"
|
||||||
|
></i>
|
||||||
|
<span class="tw-sr-only">{{ "pendingDeletion" | i18n }}</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td bitCell (click)="onEditSend(s)" class="tw-text-muted tw-cursor-pointer">
|
||||||
|
<small bitTypography="body2" appStopProp>{{ s.deletionDate | date: "medium" }}</small>
|
||||||
|
</td>
|
||||||
|
<td bitCell class="tw-w-0 tw-text-right">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
[bitMenuTriggerFor]="sendOptions"
|
||||||
|
bitIconButton="bwi-ellipsis-v"
|
||||||
|
label="{{ 'options' | i18n }}"
|
||||||
|
></button>
|
||||||
|
<bit-menu #sendOptions>
|
||||||
|
<button type="button" bitMenuItem (click)="onCopy(s)">
|
||||||
|
<i class="bwi bwi-fw bwi-clone" aria-hidden="true"></i>
|
||||||
|
{{ "copySendLink" | i18n }}
|
||||||
|
</button>
|
||||||
|
@if (s.password && !disableSend()) {
|
||||||
|
<button type="button" bitMenuItem (click)="onRemovePassword(s)">
|
||||||
|
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
|
||||||
|
{{ "removePassword" | i18n }}
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
<button type="button" bitMenuItem (click)="onDelete(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>
|
||||||
|
</ng-template>
|
||||||
|
</bit-table>
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
|
||||||
|
|
||||||
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
|
||||||
|
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
|
||||||
|
import { TableDataSource, I18nMockService } from "@bitwarden/components";
|
||||||
|
|
||||||
|
import { SendTableComponent } from "./send-table.component";
|
||||||
|
|
||||||
|
function createMockSend(id: number, overrides: Partial<SendView> = {}): SendView {
|
||||||
|
const send = new SendView();
|
||||||
|
|
||||||
|
send.id = `send-${id}`;
|
||||||
|
send.name = "My Send";
|
||||||
|
send.type = SendType.Text;
|
||||||
|
send.deletionDate = new Date("2030-01-01T12:00:00Z");
|
||||||
|
send.password = null as any;
|
||||||
|
|
||||||
|
Object.assign(send, overrides);
|
||||||
|
|
||||||
|
return send;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataSource = new TableDataSource<SendView>();
|
||||||
|
dataSource.data = [
|
||||||
|
createMockSend(0, {
|
||||||
|
name: "Project Documentation",
|
||||||
|
type: SendType.Text,
|
||||||
|
}),
|
||||||
|
createMockSend(1, {
|
||||||
|
name: "Meeting Notes",
|
||||||
|
type: SendType.File,
|
||||||
|
}),
|
||||||
|
createMockSend(2, {
|
||||||
|
name: "Password Protected Send",
|
||||||
|
type: SendType.Text,
|
||||||
|
password: "123",
|
||||||
|
}),
|
||||||
|
createMockSend(3, {
|
||||||
|
name: "Disabled Send",
|
||||||
|
type: SendType.Text,
|
||||||
|
disabled: true,
|
||||||
|
}),
|
||||||
|
createMockSend(4, {
|
||||||
|
name: "Expired Send",
|
||||||
|
type: SendType.File,
|
||||||
|
expirationDate: new Date("2025-12-01T00:00:00Z"),
|
||||||
|
}),
|
||||||
|
createMockSend(5, {
|
||||||
|
name: "Max Access Reached",
|
||||||
|
type: SendType.Text,
|
||||||
|
maxAccessCount: 5,
|
||||||
|
accessCount: 5,
|
||||||
|
password: "123",
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "Tools/Sends/Send Table",
|
||||||
|
component: SendTableComponent,
|
||||||
|
decorators: [
|
||||||
|
moduleMetadata({
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: I18nService,
|
||||||
|
useFactory: () => {
|
||||||
|
return new I18nMockService({
|
||||||
|
name: "Name",
|
||||||
|
deletionDate: "Deletion Date",
|
||||||
|
options: "Options",
|
||||||
|
disabled: "Disabled",
|
||||||
|
password: "Password",
|
||||||
|
maxAccessCountReached: "Max access count reached",
|
||||||
|
expired: "Expired",
|
||||||
|
pendingDeletion: "Pending deletion",
|
||||||
|
copySendLink: "Copy Send link",
|
||||||
|
removePassword: "Remove password",
|
||||||
|
delete: "Delete",
|
||||||
|
loading: "Loading",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
args: {
|
||||||
|
dataSource,
|
||||||
|
disableSend: false,
|
||||||
|
},
|
||||||
|
argTypes: {
|
||||||
|
editSend: { action: "editSend" },
|
||||||
|
copySend: { action: "copySend" },
|
||||||
|
removePassword: { action: "removePassword" },
|
||||||
|
deleteSend: { action: "deleteSend" },
|
||||||
|
},
|
||||||
|
} as Meta<SendTableComponent>;
|
||||||
|
|
||||||
|
type Story = StoryObj<SendTableComponent>;
|
||||||
|
|
||||||
|
export const Default: Story = {};
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
import { CommonModule } from "@angular/common";
|
||||||
|
import { ChangeDetectionStrategy, Component, input, output } from "@angular/core";
|
||||||
|
|
||||||
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
|
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
|
||||||
|
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
|
||||||
|
import {
|
||||||
|
BadgeModule,
|
||||||
|
ButtonModule,
|
||||||
|
IconButtonModule,
|
||||||
|
LinkModule,
|
||||||
|
MenuModule,
|
||||||
|
TableDataSource,
|
||||||
|
TableModule,
|
||||||
|
TypographyModule,
|
||||||
|
} from "@bitwarden/components";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A table component for displaying Send items with sorting, status indicators, and action menus. Handles the presentation of sends in a tabular format with options
|
||||||
|
* for editing, copying links, removing passwords, and deleting.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: "tools-send-table",
|
||||||
|
templateUrl: "./send-table.component.html",
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
JslibModule,
|
||||||
|
TableModule,
|
||||||
|
ButtonModule,
|
||||||
|
LinkModule,
|
||||||
|
IconButtonModule,
|
||||||
|
MenuModule,
|
||||||
|
BadgeModule,
|
||||||
|
TypographyModule,
|
||||||
|
],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class SendTableComponent {
|
||||||
|
protected readonly sendType = SendType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The data source containing the Send items to display in the table.
|
||||||
|
*/
|
||||||
|
readonly dataSource = input<TableDataSource<SendView>>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether Send functionality is disabled by policy.
|
||||||
|
* When true, the "Remove Password" option is hidden from the action menu.
|
||||||
|
*/
|
||||||
|
readonly disableSend = input(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emitted when a user clicks on a Send item to edit it.
|
||||||
|
* The clicked SendView is passed as the event payload.
|
||||||
|
*/
|
||||||
|
readonly editSend = output<SendView>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emitted when a user clicks the "Copy Send Link" action.
|
||||||
|
* The SendView is passed as the event payload for generating and copying the link.
|
||||||
|
*/
|
||||||
|
readonly copySend = output<SendView>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emitted when a user clicks the "Remove Password" action.
|
||||||
|
* The SendView is passed as the event payload for password removal.
|
||||||
|
* This action is only available if the Send has a password and Send is not disabled.
|
||||||
|
*/
|
||||||
|
readonly removePassword = output<SendView>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emitted when a user clicks the "Delete" action.
|
||||||
|
* The SendView is passed as the event payload for deletion.
|
||||||
|
*/
|
||||||
|
readonly deleteSend = output<SendView>();
|
||||||
|
|
||||||
|
protected onEditSend(send: SendView): void {
|
||||||
|
this.editSend.emit(send);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onCopy(send: SendView): void {
|
||||||
|
this.copySend.emit(send);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onRemovePassword(send: SendView): void {
|
||||||
|
this.removePassword.emit(send);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onDelete(send: SendView): void {
|
||||||
|
this.deleteSend.emit(send);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user