1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 15:53:27 +00:00

[EC-16] Implement new Groups Tab (#3563)

* [EC-16] Cleanup RxJS linting problems

* [EC-16] Update Group tab to use table component and show collections.

* [EC-16] Extract interface from GroupResponse and use it in the view

* [EC-16] Remove heading underline

* [EC-16] Cleanup i18n

* [EC-16] More i18n cleanup

* [EC-16] Fix bulk group request type name

* [EC-16] Rename group details type

* [EC-86] Clear collectionMap before populating it with new collections

* [EC-86] Update initialization/loading logic to make better use of the Observable pattern

* [EC-86] Make table cells use a pointer cursor

* [EC-86] Use bitIconButton for row menu triggers

* [EC-86] Refactor GroupDetailsRow interface to wrap GroupDetailsResponse.

 Remove response model interfaces.

 Cleanup GroupsComponent.

* [EC-86] Add bit-badge-list component and tweak BadgeModule to support both the component and directive.

Update mockI18nService to support templated strings.

* [EC-86] Cleanup badge color and bitIconButton classes

* [EC-86] Cleanup more styles

* [EC-86] Add GroupApiService

Add a new GroupApiService to replace Group Api calls in the ApiService.

* [EC-86] Revisions for badge-list implementation.

- Remove `| null` for maxItems according to ADR-0014
- Remove custom setter for items
- Use ngOnChanges to update filteredItems
- Fix sr-only tailwind class and show screen reader comma after last item if truncated.

* [EC-86] Refactor badge-list module/component

- Move the badge list component to its own module.
- Extract badge list stories from badge stories.
- Cleanup bade stories and module after refactor.

* [EC-86] Refactor/rename GroupApiService

- Re-name GroupApiService to GroupService
 as there is no need for a separate Api service (no sync or local data for admin services)
- Add GroupView for use in the GroupService instead of raw API models
- Update views to use GroupView instead of raw GroupResponse models

* [EC-86] Refactor group API request models

- Move organizationGroupBulkRequest to group requests folder
- Fix relative imports in GroupService

* [EC-86] Fix linting errors

* Fix tab item text color

Tab item text color broke after a merge from master and needs a fix to account for bootstrap styles in Web.

* [EC-86] Rename new files using kebab-case

* [EC-86] Fix group view file name

* [EC-86] Fix group request/response file names

* [EC-86] Cleanup badge stories per review suggestions

* [EC-86] Use inline-flex for badge list container

* [EC-86] Move GroupService and Views to Web org module

- Move GroupService and GroupServiceAbstraction to Organization Module
- Add GroupService provider to Organization Module
- Move collection-add-edit.component, user-groups.component, group-add-edit.component, and groups.component into Organization Module as they now depend on GroupService
- Remove moved components from Loose Component module

* [EC-86] Fix Group table search

Adds the id and name properties to GroupDetailsRow to support using the searchPipe (which cannot access nested values such as details.name for filtering).

* [EC-86] Fix badge story controls

* [EC-87] Edit Group Dialog (#3651)

* [EC-87] Update the edit dialog to use content tabs

* [EC-87] WIP FormListSelection abstract controller

* [EC-87] WIP FormListSelection for members and collections

* [EC-87] More WIP on FormListSelection

* [EC-87] WIP Working FormSelectionList with initial value support

* [EC-87] WIP SelectionList without FormControls and with i18n support for sorting

* [EC-87] Final sorted SelectionList with FormArray support

* [EC-87] Extract and document FormSelectionList

* [EC-87] Functional edit group modal

* [EC-87] Remove button icon padding for bitButton directives

* [EC-87] Use new disablePadding attribute for Dialog component

* [EC-87] Some more cleanup and finetuning

* [EC-87] Move enum declaration to top

* [EC-87] Remove inline style from access selector

* [EC-87] Move Group components into Organization Module

* [EC-87] Add MultiSelectModule to Shared Web module

* [EC-87] Integrate AccessSelector component in GroupAddEdit modal

- Remove duplicate permission / selection readonly helpers from GroupAddEdit component
- Use access item views/values for collection and member lists
- Replace access selector HTMl with the AccessSelector component

* [EC-87] Update Group collections column to open Collection tab

* [EC-87] Remove old FormSelectionList file

* [EC-87] Fix missed file import changes after merge

* [EC-87] Remove GroupAddEditComponent modal service registration

Groups component is now using the DialogService which does not require explicit registration for lazy loaded components.

* [EC-87] Use injected DIALOG_DATA for GroupAddEdit component

- Add types for the GroupAddEdit dialog params, result, and tab indices
- Add strongly typed helper method to open GroupAddEdit dialogs
- Remove @Input()/@Output() properties. Replaced with the injected DIALOG_DATA params instead
- Use dialogRef.close() and result type instead of event emitters

* [EC-87] Rename collection tab type to collections

* [EC-87] Refactor postGroup() and putGroup() from ApiService

- Move postGroup() and putGroup() methods to GroupService
- Remove postGroup() and putGroup() from ApiService
- Move GroupResponse and GroupRequest into Web (from lib/common)

* [EC-87] Remove required attribute

* [EC-87] Use PascalCase for template Enums

* [EC-87] Use group modal tab enum in template

* [EC-87] Convert dialog result to promise

* [EC-87] Refactor dialog positionStrategy

- Add .top() to position strategy to allow clicking the backdrop to close the dialog
- Move the positionStrategy option into the openGroupAddEditDialog helper

* [EC-87] Remove [preserveContent] from tab group

* [EC-87] Use new CL async actions

- Update handlers to be arrow-functions
- Remove old form and delete promises
- Use [bitSubmit] directive on form
- Use bitFormButton directive and [bitAction] for submit and delete buttons
- Remove delete/spinner bwi icons as they are handled by the new async directives

* [EC-87] Introduce CollectionAccessSelectionView

Use a new view to replace the SelectionReadonlyResponse/Request classes.

* [EC-87] Use new access selection view in GroupView

- Change the collections type
- Add members list to make the view more complete
- Update the static fromResponse helper to properly map the GroupDetailsResponse to the new access selection view
- Update access selector helpers to use new access selection view instead of response/request models

* [EC-87] Update GroupService to have a single save() method that accepts a GroupView

- Add save() method that checks for existing group id to determine which API method to use
- Make post/put group methods private

* [EC-87] Utilize the new save() method in the group modal

* [EC-87] Use observables for fetching data

- Introduce 3 observables for collections, members, and group details
- Combine and subscribe to those observables in ngOnInit
- Add destroy$ subject
- Inject changeDetectorRef to handle quirk of patching the AccessSelector value before available items are set
This commit is contained in:
Shane Melton
2022-11-21 08:40:27 -08:00
committed by GitHub
parent 1dd9fbab72
commit 21a9f84956
37 changed files with 1129 additions and 511 deletions

View File

@@ -0,0 +1,9 @@
<div class="tw-inline-flex tw-gap-2">
<span *ngFor="let item of filteredItems; let last = last" bitBadge [badgeType]="badgeType">
{{ item }}
<span class="tw-sr-only" *ngIf="!last || isFiltered">, </span>
</span>
<span *ngIf="isFiltered" bitBadge [badgeType]="badgeType">
{{ "plusNMore" | i18n: (items.length - filteredItems.length).toString() }}
</span>
</div>

View File

@@ -0,0 +1,35 @@
import { Component, Input, OnChanges } from "@angular/core";
import { BadgeTypes } from "../badge";
@Component({
selector: "bit-badge-list",
templateUrl: "badge-list.component.html",
})
export class BadgeListComponent implements OnChanges {
private _maxItems: number;
protected filteredItems: string[] = [];
protected isFiltered = false;
@Input() badgeType: BadgeTypes = "primary";
@Input() items: string[] = [];
@Input()
get maxItems(): number | undefined {
return this._maxItems;
}
set maxItems(value: number | undefined) {
this._maxItems = value == undefined ? undefined : Math.max(1, value);
}
ngOnChanges() {
if (this.maxItems == undefined) {
this.filteredItems = this.items;
} else {
this.filteredItems = this.items.slice(0, this.maxItems);
}
this.isFiltered = this.items.length > this.filteredItems.length;
}
}

View File

@@ -0,0 +1,13 @@
import { NgModule } from "@angular/core";
import { BadgeModule } from "../badge";
import { SharedModule } from "../shared";
import { BadgeListComponent } from "./badge-list.component";
@NgModule({
imports: [SharedModule, BadgeModule],
exports: [BadgeListComponent],
declarations: [BadgeListComponent],
})
export class BadgeListModule {}

View File

@@ -0,0 +1,53 @@
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { BadgeModule } from "../badge";
import { SharedModule } from "../shared";
import { I18nMockService } from "../utils/i18n-mock.service";
import { BadgeListComponent } from "./badge-list.component";
export default {
title: "Component Library/Badge/List",
component: BadgeListComponent,
decorators: [
moduleMetadata({
imports: [SharedModule, BadgeModule],
declarations: [BadgeListComponent],
providers: [
{
provide: I18nService,
useFactory: () => {
return new I18nMockService({
plusNMore: (n) => `+ ${n} more`,
});
},
},
],
}),
],
args: {
badgeType: "primary",
},
parameters: {
design: {
type: "figma",
url: "https://www.figma.com/file/f32LSg3jaegICkMu7rPARm/Tailwind-Component-Library-Update?node-id=1881%3A16956",
},
},
} as Meta;
const ListTemplate: Story<BadgeListComponent> = (args: BadgeListComponent) => ({
props: args,
template: `
<bit-badge-list [badgeType]="badgeType" [maxItems]="maxItems" [items]="items"></bit-badge-list>
`,
});
export const Default = ListTemplate.bind({});
Default.args = {
badgeType: "info",
maxItems: 3,
items: ["Badge 1", "Badge 2", "Badge 3", "Badge 4", "Badge 5"],
};

View File

@@ -0,0 +1 @@
export * from "./badge-list.module";

View File

@@ -1,6 +1,6 @@
import { Directive, ElementRef, HostBinding, Input } from "@angular/core";
type BadgeTypes = "primary" | "secondary" | "success" | "danger" | "warning" | "info";
export type BadgeTypes = "primary" | "secondary" | "success" | "danger" | "warning" | "info";
const styles: Record<BadgeTypes, string[]> = {
primary: ["tw-bg-primary-500"],

View File

@@ -1,10 +1,17 @@
import { Meta, Story } from "@storybook/angular";
import { CommonModule } from "@angular/common";
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { BadgeDirective } from "./badge.directive";
export default {
title: "Component Library/Badge",
component: BadgeDirective,
decorators: [
moduleMetadata({
imports: [CommonModule],
declarations: [BadgeDirective],
}),
],
args: {
badgeType: "primary",
},

View File

@@ -1,2 +1,2 @@
export * from "./badge.directive";
export { BadgeDirective, BadgeTypes } from "./badge.directive";
export * from "./badge.module";

View File

@@ -18,7 +18,7 @@ import { SimpleDialogComponent } from "./simple-dialog/simple-dialog.component";
DialogComponent,
SimpleDialogComponent,
],
exports: [CdkDialogModule, DialogComponent, SimpleDialogComponent],
exports: [CdkDialogModule, DialogComponent, SimpleDialogComponent, DialogCloseDirective],
providers: [DialogService],
})
export class DialogModule {}

View File

@@ -1,6 +1,7 @@
export * from "./async-actions";
export * from "./avatar";
export * from "./badge";
export * from "./badge-list";
export * from "./banner";
export * from "./button";
export * from "./callout";

View File

@@ -9,10 +9,14 @@ export class I18nMockService implements I18nService {
collator: Intl.Collator;
localeNames: Map<string, string>;
constructor(private lookupTable: Record<string, string>) {}
constructor(private lookupTable: Record<string, string | ((...args: string[]) => string)>) {}
t(id: string, p1?: string, p2?: string, p3?: string) {
return this.lookupTable[id];
const value = this.lookupTable[id];
if (typeof value == "string") {
return value;
}
return value(p1, p2, p3);
}
translate(id: string, p1?: string, p2?: string, p3?: string) {