1
0
mirror of https://github.com/bitwarden/web synced 2025-12-06 00:03:28 +00:00

[Paging] Added for Organization Users, Pages, and Collections (#539)

* Updating jslib

* Added paging for Organizational Users, Groups, and Collections

* Updated jslib fb7335b -> 2858724
This commit is contained in:
Vincent Salucci
2020-05-22 11:26:43 -05:00
committed by GitHub
parent 179884cf93
commit 7301158e54
6 changed files with 138 additions and 9 deletions

View File

@@ -16,9 +16,11 @@
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i> <i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
<span class="sr-only">{{'loading' | i18n}}</span> <span class="sr-only">{{'loading' | i18n}}</span>
</ng-container> </ng-container>
<ng-container *ngIf="!loading && (collections | search:searchText:'name':'id') as searchedCollections"> <ng-container
*ngIf="!loading && (isPaging() ? pagedCollections : collections | search:searchText:'name':'id') as searchedCollections">
<p *ngIf="!searchedCollections.length">{{'noCollectionsInList' | i18n}}</p> <p *ngIf="!searchedCollections.length">{{'noCollectionsInList' | i18n}}</p>
<table class="table table-hover table-list" *ngIf="searchedCollections.length"> <table class="table table-hover table-list" *ngIf="searchedCollections.length" infiniteScroll
[infiniteScrollDistance]="1" [infiniteScrollDisabled]="!isPaging()" (scrolled)="loadMore()">
<tbody> <tbody>
<tr *ngFor="let c of searchedCollections"> <tr *ngFor="let c of searchedCollections">
<td> <td>

View File

@@ -14,6 +14,7 @@ import { ApiService } from 'jslib/abstractions/api.service';
import { CollectionService } from 'jslib/abstractions/collection.service'; import { CollectionService } from 'jslib/abstractions/collection.service';
import { I18nService } from 'jslib/abstractions/i18n.service'; import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { SearchService } from 'jslib/abstractions/search.service';
import { UserService } from 'jslib/abstractions/user.service'; import { UserService } from 'jslib/abstractions/user.service';
import { CollectionData } from 'jslib/models/data/collectionData'; import { CollectionData } from 'jslib/models/data/collectionData';
@@ -40,15 +41,20 @@ export class CollectionsComponent implements OnInit {
loading = true; loading = true;
organizationId: string; organizationId: string;
collections: CollectionView[]; collections: CollectionView[];
pagedCollections: CollectionView[];
searchText: string; searchText: string;
protected didScroll = false;
protected pageSize = 100;
private pagedCollectionsCount = 0;
private modal: ModalComponent = null; private modal: ModalComponent = null;
constructor(private apiService: ApiService, private route: ActivatedRoute, constructor(private apiService: ApiService, private route: ActivatedRoute,
private collectionService: CollectionService, private componentFactoryResolver: ComponentFactoryResolver, private collectionService: CollectionService, private componentFactoryResolver: ComponentFactoryResolver,
private analytics: Angulartics2, private toasterService: ToasterService, private analytics: Angulartics2, private toasterService: ToasterService,
private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, private platformUtilsService: PlatformUtilsService,
private userService: UserService) { } private userService: UserService, private searchService: SearchService) { }
async ngOnInit() { async ngOnInit() {
this.route.parent.parent.params.subscribe(async (params) => { this.route.parent.parent.params.subscribe(async (params) => {
@@ -74,9 +80,27 @@ export class CollectionsComponent implements OnInit {
const collections = response.data.filter((c) => c.organizationId === this.organizationId).map((r) => const collections = response.data.filter((c) => c.organizationId === this.organizationId).map((r) =>
new Collection(new CollectionData(r as CollectionDetailsResponse))); new Collection(new CollectionData(r as CollectionDetailsResponse)));
this.collections = await this.collectionService.decryptMany(collections); this.collections = await this.collectionService.decryptMany(collections);
this.resetPaging();
this.loading = false; this.loading = false;
} }
loadMore() {
if (this.collections.length <= this.pageSize) {
return;
}
const pagedLength = this.pagedCollections.length;
let pagedSize = this.pageSize;
if (pagedLength === 0 && this.pagedCollectionsCount > this.pageSize) {
pagedSize = this.pagedCollectionsCount;
}
if (this.collections.length > pagedLength) {
this.pagedCollections =
this.pagedCollections.concat(this.collections.slice(pagedLength, pagedLength + pagedSize));
}
this.pagedCollectionsCount = this.pagedCollections.length;
this.didScroll = this.pagedCollections.length > this.pageSize;
}
edit(collection: CollectionView) { edit(collection: CollectionView) {
if (this.modal != null) { if (this.modal != null) {
this.modal.close(); this.modal.close();
@@ -147,10 +171,28 @@ export class CollectionsComponent implements OnInit {
}); });
} }
async resetPaging() {
this.pagedCollections = [];
this.loadMore();
}
isSearching() {
return this.searchService.isSearchable(this.searchText);
}
isPaging() {
const searching = this.isSearching();
if (searching && this.didScroll) {
this.resetPaging();
}
return !searching && this.collections.length > this.pageSize;
}
private removeCollection(collection: CollectionView) { private removeCollection(collection: CollectionView) {
const index = this.collections.indexOf(collection); const index = this.collections.indexOf(collection);
if (index > -1) { if (index > -1) {
this.collections.splice(index, 1); this.collections.splice(index, 1);
this.resetPaging();
} }
} }
} }

View File

@@ -16,9 +16,10 @@
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i> <i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
<span class="sr-only">{{'loading' | i18n}}</span> <span class="sr-only">{{'loading' | i18n}}</span>
</ng-container> </ng-container>
<ng-container *ngIf="!loading && (groups | search:searchText:'name':'id') as searchedGroups"> <ng-container *ngIf="!loading && (isPaging() ? pagedGroups : groups | search:searchText:'name':'id') as searchedGroups">
<p *ngIf="!searchedGroups.length">{{'noGroupsInList' | i18n}}</p> <p *ngIf="!searchedGroups.length">{{'noGroupsInList' | i18n}}</p>
<table class="table table-hover table-list" *ngIf="searchedGroups.length"> <table class="table table-hover table-list" *ngIf="searchedGroups.length" infiniteScroll
[infiniteScrollDistance]="1" [infiniteScrollDisabled]="!isPaging()" (scrolled)="loadMore()">
<tbody> <tbody>
<tr *ngFor="let g of searchedGroups"> <tr *ngFor="let g of searchedGroups">
<td> <td>

View File

@@ -16,6 +16,7 @@ import { Angulartics2 } from 'angulartics2';
import { ApiService } from 'jslib/abstractions/api.service'; import { ApiService } from 'jslib/abstractions/api.service';
import { I18nService } from 'jslib/abstractions/i18n.service'; import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { SearchService } from 'jslib/abstractions/search.service';
import { UserService } from 'jslib/abstractions/user.service'; import { UserService } from 'jslib/abstractions/user.service';
import { GroupResponse } from 'jslib/models/response/groupResponse'; import { GroupResponse } from 'jslib/models/response/groupResponse';
@@ -37,15 +38,20 @@ export class GroupsComponent implements OnInit {
loading = true; loading = true;
organizationId: string; organizationId: string;
groups: GroupResponse[]; groups: GroupResponse[];
pagedGroups: GroupResponse[];
searchText: string; searchText: string;
protected didScroll = false;
protected pageSize = 100;
private pagedGroupsCount = 0;
private modal: ModalComponent = null; private modal: ModalComponent = null;
constructor(private apiService: ApiService, private route: ActivatedRoute, constructor(private apiService: ApiService, private route: ActivatedRoute,
private i18nService: I18nService, private componentFactoryResolver: ComponentFactoryResolver, private i18nService: I18nService, private componentFactoryResolver: ComponentFactoryResolver,
private analytics: Angulartics2, private toasterService: ToasterService, private analytics: Angulartics2, private toasterService: ToasterService,
private platformUtilsService: PlatformUtilsService, private userService: UserService, private platformUtilsService: PlatformUtilsService, private userService: UserService,
private router: Router) { } private router: Router, private searchService: SearchService) { }
async ngOnInit() { async ngOnInit() {
this.route.parent.parent.params.subscribe(async (params) => { this.route.parent.parent.params.subscribe(async (params) => {
@@ -70,9 +76,26 @@ export class GroupsComponent implements OnInit {
const groups = response.data != null && response.data.length > 0 ? response.data : []; const groups = response.data != null && response.data.length > 0 ? response.data : [];
groups.sort(Utils.getSortFunction(this.i18nService, 'name')); groups.sort(Utils.getSortFunction(this.i18nService, 'name'));
this.groups = groups; this.groups = groups;
this.resetPaging();
this.loading = false; this.loading = false;
} }
loadMore() {
if (this.groups.length <= this.pageSize) {
return;
}
const pagedLength = this.pagedGroups.length;
let pagedSize = this.pageSize;
if (pagedLength === 0 && this.pagedGroupsCount > this.pageSize) {
pagedSize = this.pagedGroupsCount;
}
if (this.groups.length > pagedLength) {
this.pagedGroups = this.pagedGroups.concat(this.groups.slice(pagedLength, pagedLength + pagedSize));
}
this.pagedGroupsCount = this.pagedGroups.length;
this.didScroll = this.pagedGroups.length > this.pageSize;
}
edit(group: GroupResponse) { edit(group: GroupResponse) {
if (this.modal != null) { if (this.modal != null) {
this.modal.close(); this.modal.close();
@@ -142,10 +165,28 @@ export class GroupsComponent implements OnInit {
}); });
} }
async resetPaging() {
this.pagedGroups = [];
this.loadMore();
}
isSearching() {
return this.searchService.isSearchable(this.searchText);
}
isPaging() {
const searching = this.isSearching();
if (searching && this.didScroll) {
this.resetPaging();
}
return !searching && this.groups.length > this.pageSize;
}
private removeGroup(group: GroupResponse) { private removeGroup(group: GroupResponse) {
const index = this.groups.indexOf(group); const index = this.groups.indexOf(group);
if (index > -1) { if (index > -1) {
this.groups.splice(index, 1); this.groups.splice(index, 1);
this.resetPaging();
} }
} }
} }

View File

@@ -35,13 +35,15 @@
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i> <i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
<span class="sr-only">{{'loading' | i18n}}</span> <span class="sr-only">{{'loading' | i18n}}</span>
</ng-container> </ng-container>
<ng-container *ngIf="!loading && (users | search:searchText:'name':'email':'id') as searchedUsers"> <ng-container
*ngIf="!loading && (isPaging() ? pagedUsers : users | search:searchText:'name':'email':'id') as searchedUsers">
<p *ngIf="!searchedUsers.length">{{'noUsersInList' | i18n}}</p> <p *ngIf="!searchedUsers.length">{{'noUsersInList' | i18n}}</p>
<ng-container *ngIf="searchedUsers.length"> <ng-container *ngIf="searchedUsers.length">
<app-callout type="info" title="{{'confirmUsers' | i18n}}" icon="fa-check-circle" *ngIf="showConfirmUsers"> <app-callout type="info" title="{{'confirmUsers' | i18n}}" icon="fa-check-circle" *ngIf="showConfirmUsers">
{{'usersNeedConfirmed' | i18n}} {{'usersNeedConfirmed' | i18n}}
</app-callout> </app-callout>
<table class="table table-hover table-list"> <table class="table table-hover table-list" infiniteScroll [infiniteScrollDistance]="1"
[infiniteScrollDisabled]="!isPaging()" (scrolled)="loadMore()">
<tbody> <tbody>
<tr *ngFor="let u of searchedUsers"> <tr *ngFor="let u of searchedUsers">
<td width="30"> <td width="30">

View File

@@ -19,6 +19,7 @@ import { ApiService } from 'jslib/abstractions/api.service';
import { CryptoService } from 'jslib/abstractions/crypto.service'; import { CryptoService } from 'jslib/abstractions/crypto.service';
import { I18nService } from 'jslib/abstractions/i18n.service'; import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { SearchService } from 'jslib/abstractions/search.service';
import { StorageService } from 'jslib/abstractions/storage.service'; import { StorageService } from 'jslib/abstractions/storage.service';
import { UserService } from 'jslib/abstractions/user.service'; import { UserService } from 'jslib/abstractions/user.service';
@@ -50,6 +51,7 @@ export class PeopleComponent implements OnInit {
loading = true; loading = true;
organizationId: string; organizationId: string;
users: OrganizationUserUserDetailsResponse[]; users: OrganizationUserUserDetailsResponse[];
pagedUsers: OrganizationUserUserDetailsResponse[];
searchText: string; searchText: string;
status: OrganizationUserStatusType = null; status: OrganizationUserStatusType = null;
statusMap = new Map<OrganizationUserStatusType, OrganizationUserUserDetailsResponse[]>(); statusMap = new Map<OrganizationUserStatusType, OrganizationUserUserDetailsResponse[]>();
@@ -59,6 +61,10 @@ export class PeopleComponent implements OnInit {
accessEvents = false; accessEvents = false;
accessGroups = false; accessGroups = false;
protected didScroll = false;
protected pageSize = 100;
private pagedUsersCount = 0;
private modal: ModalComponent = null; private modal: ModalComponent = null;
private allUsers: OrganizationUserUserDetailsResponse[]; private allUsers: OrganizationUserUserDetailsResponse[];
@@ -67,7 +73,7 @@ export class PeopleComponent implements OnInit {
private platformUtilsService: PlatformUtilsService, private analytics: Angulartics2, private platformUtilsService: PlatformUtilsService, private analytics: Angulartics2,
private toasterService: ToasterService, private cryptoService: CryptoService, private toasterService: ToasterService, private cryptoService: CryptoService,
private userService: UserService, private router: Router, private userService: UserService, private router: Router,
private storageService: StorageService) { } private storageService: StorageService, private searchService: SearchService) { }
async ngOnInit() { async ngOnInit() {
this.route.parent.parent.params.subscribe(async (params) => { this.route.parent.parent.params.subscribe(async (params) => {
@@ -119,6 +125,23 @@ export class PeopleComponent implements OnInit {
} else { } else {
this.users = this.allUsers; this.users = this.allUsers;
} }
this.resetPaging();
}
loadMore() {
if (this.users.length <= this.pageSize) {
return;
}
const pagedLength = this.pagedUsers.length;
let pagedSize = this.pageSize;
if (pagedLength === 0 && this.pagedUsersCount > this.pageSize) {
pagedSize = this.pagedUsersCount;
}
if (this.users.length > pagedLength) {
this.pagedUsers = this.pagedUsers.concat(this.users.slice(pagedLength, pagedLength + pagedSize));
}
this.pagedUsersCount = this.pagedUsers.length;
this.didScroll = this.pagedUsers.length > this.pageSize;
} }
get allCount() { get allCount() {
@@ -294,6 +317,23 @@ export class PeopleComponent implements OnInit {
}); });
} }
async resetPaging() {
this.pagedUsers = [];
this.loadMore();
}
isSearching() {
return this.searchService.isSearchable(this.searchText);
}
isPaging() {
const searching = this.isSearching();
if (searching && this.didScroll) {
this.resetPaging();
}
return !searching && this.users.length > this.pageSize;
}
private async doConfirmation(user: OrganizationUserUserDetailsResponse) { private async doConfirmation(user: OrganizationUserUserDetailsResponse) {
const orgKey = await this.cryptoService.getOrgKey(this.organizationId); const orgKey = await this.cryptoService.getOrgKey(this.organizationId);
const publicKeyResponse = await this.apiService.getUserPublicKey(user.userId); const publicKeyResponse = await this.apiService.getUserPublicKey(user.userId);
@@ -313,6 +353,7 @@ export class PeopleComponent implements OnInit {
let index = this.users.indexOf(user); let index = this.users.indexOf(user);
if (index > -1) { if (index > -1) {
this.users.splice(index, 1); this.users.splice(index, 1);
this.resetPaging();
} }
if (this.statusMap.has(OrganizationUserStatusType.Accepted)) { if (this.statusMap.has(OrganizationUserStatusType.Accepted)) {
index = this.statusMap.get(OrganizationUserStatusType.Accepted).indexOf(user); index = this.statusMap.get(OrganizationUserStatusType.Accepted).indexOf(user);