Merge remote-tracking branch 'origin/main' into feature/new-models-20231107
# Conflicts: # src/mocks/data/user.data.ts
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
import { UmbEntityData } from './entity.data.js';
|
||||
import { umbUserGroupData } from './user-group.data.js';
|
||||
import { arrayFilter, stringFilter, queryFilter } from './utils.js';
|
||||
import { UmbId } from '@umbraco-cms/backoffice/id';
|
||||
import { UmbLoggedInUser } from '@umbraco-cms/backoffice/auth';
|
||||
import {
|
||||
CreateUserRequestModel,
|
||||
CreateUserResponseModel,
|
||||
InviteUserRequestModel,
|
||||
PagedUserResponseModel,
|
||||
UpdateUserGroupsOnUserRequestModel,
|
||||
UserItemResponseModel,
|
||||
UserResponseModel,
|
||||
@@ -19,6 +21,10 @@ const createUserItem = (item: UserResponseModel): UserItemResponseModel => {
|
||||
};
|
||||
};
|
||||
|
||||
const userGroupFilter = (filterOptions: any, item: UserResponseModel) => arrayFilter(filterOptions.userGroupIds, item.userGroupIds);
|
||||
const userStateFilter = (filterOptions: any, item: UserResponseModel) => stringFilter(filterOptions.userStates, item.state);
|
||||
const userQueryFilter = (filterOptions: any, item: UserResponseModel) => queryFilter(filterOptions.filter, item.name);
|
||||
|
||||
// Temp mocked database
|
||||
class UmbUserData extends UmbEntityData<UserResponseModel> {
|
||||
constructor(data: UserResponseModel[]) {
|
||||
@@ -139,6 +145,11 @@ class UmbUserData extends UmbEntityData<UserResponseModel> {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Invites a user
|
||||
* @param {InviteUserRequestModel} data
|
||||
* @memberof UmbUserData
|
||||
*/
|
||||
invite(data: InviteUserRequestModel): void {
|
||||
const invitedUser = {
|
||||
status: UserStateModel.INVITED,
|
||||
@@ -147,6 +158,27 @@ class UmbUserData extends UmbEntityData<UserResponseModel> {
|
||||
|
||||
this.createUser(invitedUser);
|
||||
}
|
||||
|
||||
filter (options: any): PagedUserResponseModel {
|
||||
const { items: allItems } = this.getAll();
|
||||
|
||||
const filterOptions = {
|
||||
skip: options.skip || 0,
|
||||
take: options.take || 25,
|
||||
orderBy: options.orderBy || 'name',
|
||||
orderDirection: options.orderDirection || 'asc',
|
||||
userGroupIds: options.userGroupIds,
|
||||
userStates: options.userStates,
|
||||
filter: options.filter,
|
||||
};
|
||||
|
||||
const filteredItems = allItems.filter((item) => userGroupFilter(filterOptions, item) && userStateFilter(filterOptions, item) && userQueryFilter(filterOptions, item));
|
||||
const totalItems = filteredItems.length;
|
||||
|
||||
const paginatedItems = filteredItems.slice(filterOptions.skip, filterOptions.skip + filterOptions.take);
|
||||
|
||||
return { total: totalItems, items: paginatedItems };
|
||||
};
|
||||
}
|
||||
|
||||
export const data: Array<UserResponseModel & { type: string }> = [
|
||||
|
||||
@@ -87,3 +87,29 @@ export const createFileItemResponseModelBaseModel = (item: any): FileItemRespons
|
||||
name: item.name,
|
||||
icon: item.icon,
|
||||
});
|
||||
|
||||
export const arrayFilter = (filterBy: Array<string>, value?: Array<string>): boolean => {
|
||||
// if a filter is not set, return all items
|
||||
if (!filterBy) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return filterBy.some((filterValue: string) => value?.includes(filterValue));
|
||||
}
|
||||
|
||||
export const stringFilter = (filterBy: Array<string>, value?: string): boolean => {
|
||||
// if a filter is not set, return all items
|
||||
if (!filterBy || !value) {
|
||||
return true;
|
||||
}
|
||||
return filterBy.includes(value);
|
||||
};
|
||||
|
||||
export const queryFilter = (filterBy: string, value?: string) => {
|
||||
if (!filterBy || !value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const query = filterBy.toLowerCase();
|
||||
return value.toLowerCase().includes(query);
|
||||
};
|
||||
|
||||
@@ -4,23 +4,16 @@ import { slug } from './slug.js';
|
||||
import { umbracoPath } from '@umbraco-cms/backoffice/utils';
|
||||
|
||||
export const handlers = [
|
||||
rest.post(umbracoPath(`${slug}`), async (req, res, ctx) => {
|
||||
const data = await req.json();
|
||||
if (!data) return;
|
||||
|
||||
const response = umbUsersData.createUser(data);
|
||||
|
||||
return res(ctx.status(200), ctx.json(response));
|
||||
}),
|
||||
|
||||
rest.get(umbracoPath(`${slug}`), (req, res, ctx) => {
|
||||
const response = umbUsersData.getAll();
|
||||
return res(ctx.status(200), ctx.json(response));
|
||||
}),
|
||||
|
||||
rest.get(umbracoPath(`${slug}/filter`), (req, res, ctx) => {
|
||||
//TODO: Implementer filter
|
||||
const response = umbUsersData.getAll();
|
||||
rest.post(umbracoPath(`${slug}`), async (req, res, ctx) => {
|
||||
const data = await req.json();
|
||||
if (!data) return;
|
||||
|
||||
const response = umbUsersData.createUser(data);
|
||||
|
||||
return res(ctx.status(200), ctx.json(response));
|
||||
}),
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
const { rest } = window.MockServiceWorker;
|
||||
import { umbUsersData } from '../../data/user.data.js';
|
||||
import { slug } from './slug.js';
|
||||
import { umbracoPath } from '@umbraco-cms/backoffice/utils';
|
||||
|
||||
export const handlers = [
|
||||
rest.get(umbracoPath(`${slug}/filter`), (req, res, ctx) => {
|
||||
|
||||
const skip = Number(req.url.searchParams.get('skip'));
|
||||
const take = Number(req.url.searchParams.get('take'));
|
||||
const orderBy = req.url.searchParams.get('orderBy');
|
||||
const orderDirection = req.url.searchParams.get('orderDirection');
|
||||
const userGroupIds = req.url.searchParams.getAll('userGroupIds');
|
||||
const userStates = req.url.searchParams.getAll('userStates');
|
||||
const filter = req.url.searchParams.get('filter');
|
||||
|
||||
const options = {
|
||||
skip: skip || undefined,
|
||||
take: take || undefined,
|
||||
orderBy: orderBy || undefined,
|
||||
orderDirection: orderDirection || undefined,
|
||||
userGroupIds: userGroupIds.length > 0 ? userGroupIds : undefined,
|
||||
userStates: userStates.length > 0 ? userStates : undefined,
|
||||
filter: filter || undefined,
|
||||
};
|
||||
|
||||
const response = umbUsersData.filter(options);
|
||||
return res(ctx.status(200), ctx.json(response));
|
||||
}),
|
||||
];
|
||||
@@ -7,6 +7,7 @@ import { handlers as disableHandlers } from './disable.handlers.js';
|
||||
import { handlers as changePasswordHandlers } from './change-password.handlers.js';
|
||||
import { handlers as unlockHandlers } from './unlock.handlers.js';
|
||||
import { handlers as inviteHandlers } from './invite.handlers.js';
|
||||
import { handlers as filterHandlers } from './filter.handlers.js';
|
||||
|
||||
export const handlers = [
|
||||
...itemHandlers,
|
||||
@@ -16,6 +17,7 @@ export const handlers = [
|
||||
...setUserGroupsHandlers,
|
||||
...changePasswordHandlers,
|
||||
...unlockHandlers,
|
||||
...detailHandlers,
|
||||
...filterHandlers,
|
||||
...inviteHandlers,
|
||||
...detailHandlers,
|
||||
];
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { UmbCollectionConfiguration } from './types.js';
|
||||
import { UmbCollectionRepository } from '@umbraco-cms/backoffice/repository';
|
||||
import { UmbBaseController, type UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
|
||||
@@ -10,7 +11,8 @@ import { createExtensionApi } from '@umbraco-cms/backoffice/extension-api';
|
||||
import { ManifestCollectionView, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import type { UmbCollectionFilterModel } from '@umbraco-cms/backoffice/collection';
|
||||
import { map } from '@umbraco-cms/backoffice/external/rxjs';
|
||||
import { UmbSelectionManager } from '@umbraco-cms/backoffice/utils';
|
||||
import { UmbSelectionManager, UmbPaginationManager } from '@umbraco-cms/backoffice/utils';
|
||||
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
|
||||
|
||||
export class UmbCollectionContext<ItemType, FilterModelType extends UmbCollectionFilterModel> extends UmbBaseController {
|
||||
protected entityType: string;
|
||||
@@ -19,8 +21,8 @@ export class UmbCollectionContext<ItemType, FilterModelType extends UmbCollectio
|
||||
#items = new UmbArrayState<ItemType>([]);
|
||||
public readonly items = this.#items.asObservable();
|
||||
|
||||
#total = new UmbNumberState(0);
|
||||
public readonly total = this.#total.asObservable();
|
||||
#totalItems = new UmbNumberState(0);
|
||||
public readonly totalItems = this.#totalItems.asObservable();
|
||||
|
||||
#selectionManager = new UmbSelectionManager();
|
||||
public readonly selection = this.#selectionManager.selection;
|
||||
@@ -37,39 +39,25 @@ export class UmbCollectionContext<ItemType, FilterModelType extends UmbCollectio
|
||||
repository?: UmbCollectionRepository;
|
||||
collectionRootPathname: string;
|
||||
|
||||
constructor(host: UmbControllerHostElement, entityType: string, repositoryAlias: string) {
|
||||
public readonly pagination = new UmbPaginationManager();
|
||||
|
||||
constructor(host: UmbControllerHostElement, entityType: string, repositoryAlias: string, config: UmbCollectionConfiguration = { pageSize: 50 }) {
|
||||
super(host);
|
||||
this.entityType = entityType;
|
||||
|
||||
this.#selectionManager.setMultiple(true);
|
||||
// listen for page changes on the pagination manager
|
||||
this.pagination.addEventListener(UmbChangeEvent.TYPE, this.#onPageChange);
|
||||
|
||||
const currentUrl = new URL(window.location.href);
|
||||
this.collectionRootPathname = currentUrl.pathname.substring(0, currentUrl.pathname.lastIndexOf('/'));
|
||||
|
||||
this.init = Promise.all([
|
||||
this.observe(
|
||||
umbExtensionsRegistry.getByTypeAndAlias('repository', repositoryAlias),
|
||||
async (repositoryManifest) => {
|
||||
if (repositoryManifest) {
|
||||
const result = await createExtensionApi(repositoryManifest, [this._host]);
|
||||
this.repository = result as UmbCollectionRepository;
|
||||
this.requestCollection();
|
||||
}
|
||||
},
|
||||
'umbCollectionRepositoryObserver'
|
||||
).asPromise(),
|
||||
|
||||
this.observe(umbExtensionsRegistry.extensionsOfType('collectionView').pipe(
|
||||
map((extensions) => {
|
||||
return extensions.filter((extension) => extension.conditions.entityType === this.getEntityType());
|
||||
}),
|
||||
),
|
||||
(views) => {
|
||||
this.#views.next(views);
|
||||
this.#setCurrentView();
|
||||
}, 'umbCollectionViewsObserver').asPromise(),
|
||||
this.#observeRepository(repositoryAlias).asPromise(),
|
||||
this.#observeViews().asPromise(),
|
||||
]);
|
||||
|
||||
this.#configure(config);
|
||||
|
||||
this.provideContext(UMB_COLLECTION_CONTEXT, this);
|
||||
}
|
||||
|
||||
@@ -148,8 +136,9 @@ export class UmbCollectionContext<ItemType, FilterModelType extends UmbCollectio
|
||||
const { data } = await this.repository.requestCollection(filter);
|
||||
|
||||
if (data) {
|
||||
this.#total.next(data.total);
|
||||
this.#items.next(data.items);
|
||||
this.#totalItems.next(data.total);
|
||||
this.pagination.setTotalItems(data.total);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,6 +171,44 @@ export class UmbCollectionContext<ItemType, FilterModelType extends UmbCollectio
|
||||
return this.#currentView.getValue();
|
||||
}
|
||||
|
||||
#configure(configuration: UmbCollectionConfiguration) {
|
||||
this.#selectionManager.setMultiple(true);
|
||||
this.pagination.setPageSize(configuration.pageSize);
|
||||
this.#filter.next({ ...this.#filter.getValue(), skip: 0, take: configuration.pageSize });
|
||||
}
|
||||
|
||||
#observeRepository(repositoryAlias: string) {
|
||||
return this.observe(
|
||||
umbExtensionsRegistry.getByTypeAndAlias('repository', repositoryAlias),
|
||||
async (repositoryManifest) => {
|
||||
if (repositoryManifest) {
|
||||
const result = await createExtensionApi(repositoryManifest, [this._host]);
|
||||
this.repository = result as UmbCollectionRepository;
|
||||
this.requestCollection();
|
||||
}
|
||||
},
|
||||
'umbCollectionRepositoryObserver'
|
||||
)
|
||||
}
|
||||
|
||||
#observeViews() {
|
||||
return this.observe(umbExtensionsRegistry.extensionsOfType('collectionView').pipe(
|
||||
map((extensions) => {
|
||||
return extensions.filter((extension) => extension.conditions.entityType === this.getEntityType());
|
||||
}),
|
||||
),
|
||||
(views) => {
|
||||
this.#views.next(views);
|
||||
this.#setCurrentView();
|
||||
}, 'umbCollectionViewsObserver');
|
||||
}
|
||||
|
||||
#onPageChange = (event: UmbChangeEvent) => {
|
||||
const target = event.target as UmbPaginationManager;
|
||||
const skipFilter = { skip: target.getSkip() } as Partial<FilterModelType>;
|
||||
this.setFilter(skipFilter);
|
||||
}
|
||||
|
||||
#setCurrentView() {
|
||||
const currentUrl = new URL(window.location.href);
|
||||
const lastPathSegment = currentUrl.pathname.split('/').pop();
|
||||
|
||||
@@ -61,6 +61,7 @@ export class UmbCollectionElement extends UmbLitElement {
|
||||
<umb-body-layout header-transparent>
|
||||
${this.renderToolbar()}
|
||||
<umb-router-slot id="router-slot" .routes="${this._routes}"></umb-router-slot>
|
||||
${this.renderPagination()}
|
||||
${this.renderSelectionActions()}
|
||||
</umb-body-layout>
|
||||
`;
|
||||
@@ -70,6 +71,10 @@ export class UmbCollectionElement extends UmbLitElement {
|
||||
return html`<umb-collection-toolbar slot="header"></umb-collection-toolbar>`;
|
||||
}
|
||||
|
||||
protected renderPagination () {
|
||||
return html`<umb-collection-pagination></umb-collection-pagination>`;
|
||||
}
|
||||
|
||||
protected renderSelectionActions() {
|
||||
return html`<umb-collection-selection-actions slot="footer-info"></umb-collection-selection-actions>`;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
|
||||
@customElement('umb-collection-selection-actions')
|
||||
export class UmbCollectionSelectionActionsElement extends UmbLitElement {
|
||||
@state()
|
||||
private _nodesLength = 0;
|
||||
private _totalItems = 0;
|
||||
|
||||
@state()
|
||||
private _selectionLength = 0;
|
||||
@@ -40,13 +40,12 @@ export class UmbCollectionSelectionActionsElement extends UmbLitElement {
|
||||
private _observeCollectionContext() {
|
||||
if (!this._collectionContext) return;
|
||||
|
||||
// TODO: Make sure it only updates on length change.
|
||||
this.observe(
|
||||
this._collectionContext.items,
|
||||
(mediaItems) => {
|
||||
this._nodesLength = mediaItems.length;
|
||||
this._collectionContext.totalItems,
|
||||
(value) => {
|
||||
this._totalItems = value;
|
||||
},
|
||||
'umbItemsLengthObserver',
|
||||
'umbTotalItemsObserver',
|
||||
);
|
||||
|
||||
this.observe(
|
||||
@@ -61,7 +60,7 @@ export class UmbCollectionSelectionActionsElement extends UmbLitElement {
|
||||
}
|
||||
|
||||
private _renderSelectionCount() {
|
||||
return html`<div>${this._selectionLength} of ${this._nodesLength} selected</div>`;
|
||||
return html`<div>${this._selectionLength} of ${this._totalItems} selected</div>`;
|
||||
}
|
||||
|
||||
#onActionExecuted(event: UmbActionExecutedEvent) {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import './pagination/collection-pagination.element.js';
|
||||
import './collection-selection-actions.element.js';
|
||||
import './collection-toolbar.element.js';
|
||||
import './collection-view-bundle.element.js';
|
||||
|
||||
export * from './pagination/collection-pagination.element.js';
|
||||
export * from './collection-selection-actions.element.js';
|
||||
export * from './collection-toolbar.element.js';
|
||||
export * from './collection-view-bundle.element.js';
|
||||
export * from './collection-view-bundle.element.js';
|
||||
@@ -0,0 +1,66 @@
|
||||
import { UUIPaginationEvent } from '@umbraco-cms/backoffice/external/uui';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { css, html, customElement, nothing, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UMB_COLLECTION_CONTEXT, UmbCollectionContext } from '@umbraco-cms/backoffice/collection';
|
||||
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
|
||||
|
||||
@customElement('umb-collection-pagination')
|
||||
export class UmbCollectionPaginationElement extends UmbLitElement {
|
||||
|
||||
@state()
|
||||
_totalPages = 0;
|
||||
|
||||
@state()
|
||||
_currentPage = 1;
|
||||
|
||||
private _collectionContext?: UmbCollectionContext<any, any>;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.consumeContext(UMB_COLLECTION_CONTEXT, (instance) => {
|
||||
this._collectionContext = instance;
|
||||
this.#observeCurrentPage();
|
||||
this.#observerTotalPages();
|
||||
});
|
||||
}
|
||||
|
||||
#observeCurrentPage () {
|
||||
this.observe(this._collectionContext!.pagination.currentPage, (currentPage) => {
|
||||
this._currentPage = currentPage;
|
||||
}, 'umbCurrentPageObserver');
|
||||
}
|
||||
|
||||
#observerTotalPages () {
|
||||
this.observe(this._collectionContext!.pagination.totalPages, (totalPages) => {
|
||||
this._totalPages = totalPages;
|
||||
}, 'umbTotalPagesObserver');
|
||||
}
|
||||
|
||||
#onChange (event: UUIPaginationEvent) {
|
||||
this._collectionContext?.pagination.setCurrentPageNumber(event.target.current);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this._totalPages <= 1) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`<uui-pagination .current=${this._currentPage} .total=${this._totalPages} @change=${this.#onChange}></uui-pagination>`;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
UmbTextStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
margin-top: var(--uui-size-layout-1);
|
||||
}
|
||||
`
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-collection-pagination': UmbCollectionPaginationElement;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface UmbCollectionConfiguration {
|
||||
pageSize: number;
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
export class UmbChangeEvent extends Event {
|
||||
public static readonly TYPE = 'change';
|
||||
|
||||
public constructor() {
|
||||
// mimics the native change event
|
||||
super('change', { bubbles: true, composed: false, cancelable: false });
|
||||
super(UmbChangeEvent.TYPE, { bubbles: true, composed: false, cancelable: false });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { UmbPagedData } from '../tree-repository.interface.js';
|
||||
import type { DataSourceResponse } from '../index.js';
|
||||
import type { UmbPagedData } from './types.js';
|
||||
|
||||
export interface UmbCollectionDataSource<ItemType = any, PagedItemType = UmbPagedData<ItemType>> {
|
||||
getCollection(): Promise<DataSourceResponse<PagedItemType>>;
|
||||
filterCollection(filter: any): Promise<DataSourceResponse<PagedItemType>>;
|
||||
export interface UmbCollectionDataSource<ItemType = any, FilterType = unknown> {
|
||||
getCollection(filter: FilterType): Promise<DataSourceResponse<UmbPagedData<ItemType>>>;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { expect } from '@open-wc/testing';
|
||||
import { type UmbPagedData } from '../tree-repository.interface.js';
|
||||
import { type UmbPagedData } from './types.js';
|
||||
import { type DataSourceResponse } from './data-source-response.interface.js';
|
||||
import { extendDataSourcePagedResponseData } from './extend-data-source-paged-response-data.function.js';
|
||||
|
||||
|
||||
@@ -8,3 +8,4 @@ export * from './folder-data-source.interface.js';
|
||||
export * from './item-data-source.interface.js';
|
||||
export * from './move-data-source.interface.js';
|
||||
export * from './tree-data-source.interface.js';
|
||||
export * from './types.js';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { UmbPagedData } from '../tree-repository.interface.js';
|
||||
import type { UmbPagedData } from './types.js';
|
||||
import type { DataSourceResponse } from './data-source-response.interface.js';
|
||||
|
||||
export interface UmbTreeDataSource<ItemType = any, PagedItemType = UmbPagedData<ItemType>> {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface UmbPagedData<T> {
|
||||
total: number;
|
||||
items: Array<T>;
|
||||
}
|
||||
@@ -1,12 +1,8 @@
|
||||
import type { Observable } from '@umbraco-cms/backoffice/external/rxjs';
|
||||
import { type UmbPagedData } from './data-source/types.js';
|
||||
import { type Observable } from '@umbraco-cms/backoffice/external/rxjs';
|
||||
import type { UmbTreeRootEntityModel, UmbTreeRootModel } from '@umbraco-cms/backoffice/tree';
|
||||
import { ProblemDetails, EntityTreeItemResponseModel } from '@umbraco-cms/backoffice/backend-api';
|
||||
|
||||
export interface UmbPagedData<T> {
|
||||
total: number;
|
||||
items: Array<T>;
|
||||
}
|
||||
|
||||
export interface UmbTreeRepository<
|
||||
TreeItemType extends EntityTreeItemResponseModel,
|
||||
TreeRootType extends UmbTreeRootModel = UmbTreeRootEntityModel
|
||||
|
||||
@@ -27,7 +27,7 @@ export class UmbUserGroupCollectionRepository implements UmbCollectionRepository
|
||||
async requestCollection(filter: UmbUserGroupCollectionFilterModel = { skip: 0, take: 100 }) {
|
||||
await this.#init;
|
||||
|
||||
const { data, error } = await this.#collectionSource.filterCollection(filter);
|
||||
const { data, error } = await this.#collectionSource.getCollection(filter);
|
||||
|
||||
if (data) {
|
||||
this.#detailStore?.appendItems(data.items);
|
||||
|
||||
@@ -22,11 +22,7 @@ export class UmbUserGroupCollectionServerDataSource implements UmbCollectionData
|
||||
this.#host = host;
|
||||
}
|
||||
|
||||
getCollection() {
|
||||
return tryExecuteAndNotify(this.#host, UserGroupResource.getUserGroup({}));
|
||||
}
|
||||
|
||||
filterCollection(filter: UmbUserGroupCollectionFilterModel) {
|
||||
getCollection(filter: UmbUserGroupCollectionFilterModel) {
|
||||
// TODO: Switch this to the filter endpoint when available
|
||||
return tryExecuteAndNotify(this.#host, UserGroupResource.getUserGroup({}));
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { UmbCollectionDataSource, UmbCollectionRepository } from '@umbraco-cms/b
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
|
||||
export class UmbUserCollectionRepository extends UmbUserRepositoryBase implements UmbCollectionRepository {
|
||||
#collectionSource: UmbCollectionDataSource<UserResponseModel>;
|
||||
#collectionSource: UmbCollectionDataSource<UserResponseModel, UmbUserCollectionFilterModel>;
|
||||
|
||||
constructor(host: UmbControllerHost) {
|
||||
super(host);
|
||||
@@ -16,7 +16,7 @@ export class UmbUserCollectionRepository extends UmbUserRepositoryBase implement
|
||||
async requestCollection(filter: UmbUserCollectionFilterModel = { skip: 0, take: 100 }) {
|
||||
await this.init;
|
||||
|
||||
const { data, error } = await this.#collectionSource.filterCollection(filter);
|
||||
const { data, error } = await this.#collectionSource.getCollection(filter);
|
||||
|
||||
if (data) {
|
||||
this.detailStore!.appendItems(data.items);
|
||||
|
||||
@@ -22,25 +22,13 @@ export class UmbUserCollectionServerDataSource implements UmbCollectionDataSourc
|
||||
this.#host = host;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user collection from the server.
|
||||
* @return {*}
|
||||
* @memberof UmbUserCollectionServerDataSource
|
||||
*/
|
||||
async getCollection() {
|
||||
const response = await tryExecuteAndNotify(this.#host, UserResource.getUser({}));
|
||||
return extendDataSourcePagedResponseData<UmbUserDetail>(response, {
|
||||
entityType: USER_ENTITY_TYPE,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user collection filtered by the given filter.
|
||||
* @param {UmbUserCollectionFilterModel} filter
|
||||
* @return {*}
|
||||
* @memberof UmbUserCollectionServerDataSource
|
||||
*/
|
||||
async filterCollection(filter: UmbUserCollectionFilterModel) {
|
||||
async getCollection(filter: UmbUserCollectionFilterModel) {
|
||||
const response = await tryExecuteAndNotify(this.#host, UserResource.getUserFilter(filter));
|
||||
return extendDataSourcePagedResponseData<UmbUserDetail>(response, {
|
||||
entityType: USER_ENTITY_TYPE,
|
||||
|
||||
@@ -6,7 +6,7 @@ import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api
|
||||
|
||||
export class UmbUserCollectionContext extends UmbCollectionContext<UmbUserDetail, UmbUserCollectionFilterModel> {
|
||||
constructor(host: UmbControllerHostElement) {
|
||||
super(host, USER_ENTITY_TYPE, USER_COLLECTION_REPOSITORY_ALIAS);
|
||||
super(host, USER_ENTITY_TYPE, USER_COLLECTION_REPOSITORY_ALIAS, { pageSize: 50 });
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,6 +8,7 @@ export * from './path-folder-name.function.js';
|
||||
export * from './selection-manager.js';
|
||||
export * from './udi-service.js';
|
||||
export * from './umbraco-path.function.js';
|
||||
export * from './pagination-manager/pagination.manager.js';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
import { expect, oneEvent } from '@open-wc/testing';
|
||||
import { UmbPaginationManager } from './pagination.manager.js';
|
||||
import { Observable } from '@umbraco-cms/backoffice/external/rxjs';
|
||||
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'
|
||||
|
||||
describe('UmbPaginationManager', () => {
|
||||
let manager: UmbPaginationManager;
|
||||
|
||||
beforeEach(() => {
|
||||
manager = new UmbPaginationManager();
|
||||
manager.setPageSize(10);
|
||||
manager.setTotalItems(100);
|
||||
});
|
||||
|
||||
describe('Public API', () => {
|
||||
describe('properties', () => {
|
||||
it('has a pageSize property', () => {
|
||||
expect(manager).to.have.property('pageSize').to.be.an.instanceOf(Observable);
|
||||
});
|
||||
|
||||
it('has a totalItems property', () => {
|
||||
expect(manager).to.have.property('totalItems').to.be.an.instanceOf(Observable);
|
||||
});
|
||||
|
||||
it('has a currentPage property', () => {
|
||||
expect(manager).to.have.property('currentPage').to.be.an.instanceOf(Observable);
|
||||
});
|
||||
|
||||
it('has a skip property', () => {
|
||||
expect(manager).to.have.property('skip').to.be.an.instanceOf(Observable);
|
||||
});
|
||||
});
|
||||
|
||||
describe('methods', () => {
|
||||
it('has a setPageSize method', () => {
|
||||
expect(manager).to.have.property('setPageSize').that.is.a('function');
|
||||
});
|
||||
|
||||
it('has a getPageSize method', () => {
|
||||
expect(manager).to.have.property('getPageSize').that.is.a('function');
|
||||
});
|
||||
|
||||
it('has a getTotalItems method', () => {
|
||||
expect(manager).to.have.property('getTotalItems').that.is.a('function');
|
||||
});
|
||||
|
||||
it('has a getTotalPages method', () => {
|
||||
expect(manager).to.have.property('getTotalPages').that.is.a('function');
|
||||
});
|
||||
|
||||
it('has a getCurrentPageNumber method', () => {
|
||||
expect(manager).to.have.property('getCurrentPageNumber').that.is.a('function');
|
||||
});
|
||||
|
||||
it('has a setCurrentPageNumber method', () => {
|
||||
expect(manager).to.have.property('setCurrentPageNumber').that.is.a('function');
|
||||
});
|
||||
|
||||
it('has a getSkip method', () => {
|
||||
expect(manager).to.have.property('getSkip').that.is.a('function');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Page Size', () => {
|
||||
it('sets and gets the pageSize value', () => {
|
||||
manager.setPageSize(5);
|
||||
expect(manager.getPageSize()).to.equal(5);
|
||||
});
|
||||
|
||||
it('updates the observable', (done) => {
|
||||
manager.setPageSize(2);
|
||||
|
||||
manager.pageSize.subscribe((value) => {
|
||||
expect(value).to.equal(2);
|
||||
done();
|
||||
})
|
||||
.unsubscribe();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Total Items', () => {
|
||||
it('sets and gets the totalItems value', () => {
|
||||
manager.setTotalItems(200);
|
||||
expect(manager.getTotalItems()).to.equal(200);
|
||||
});
|
||||
|
||||
it('updates the observable', (done) => {
|
||||
manager.totalItems.subscribe((value) => {
|
||||
expect(value).to.equal(100);
|
||||
done();
|
||||
})
|
||||
.unsubscribe();
|
||||
});
|
||||
|
||||
it('recalculates the total pages', () => {
|
||||
expect(manager.getTotalPages()).to.equal(10);
|
||||
});
|
||||
|
||||
it('it fall backs to the last page number if the totalPages is less than the currentPage', () => {
|
||||
manager.setCurrentPageNumber(10);
|
||||
manager.setTotalItems(20);
|
||||
expect(manager.getCurrentPageNumber()).to.equal(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Current Page', () => {
|
||||
it('sets and gets the currentPage value', () => {
|
||||
manager.setCurrentPageNumber(2);
|
||||
expect(manager.getCurrentPageNumber()).to.equal(2);
|
||||
});
|
||||
|
||||
it ('cant be set to a value less than 1', () => {
|
||||
manager.setCurrentPageNumber(0);
|
||||
expect(manager.getCurrentPageNumber()).to.equal(1);
|
||||
});
|
||||
|
||||
it ('cant be set to a value greater than the total pages', () => {
|
||||
manager.setPageSize(1);
|
||||
manager.setTotalItems(2);
|
||||
manager.setCurrentPageNumber(10);
|
||||
expect(manager.getCurrentPageNumber()).to.equal(2);
|
||||
});
|
||||
|
||||
it('updates the observable', (done) => {
|
||||
manager.setCurrentPageNumber(2);
|
||||
|
||||
manager.currentPage.subscribe((value) => {
|
||||
expect(value).to.equal(2);
|
||||
done();
|
||||
})
|
||||
.unsubscribe();
|
||||
});
|
||||
|
||||
it('updates the skip value', () => {
|
||||
manager.setCurrentPageNumber(5);
|
||||
expect(manager.getSkip()).to.equal(40);
|
||||
});
|
||||
|
||||
it('dispatches a change event', async () => {
|
||||
const listener = oneEvent(manager, UmbChangeEvent.TYPE);
|
||||
manager.setCurrentPageNumber(2);
|
||||
const event = (await listener) as unknown as UmbChangeEvent;
|
||||
const target = event.target as UmbPaginationManager;
|
||||
expect(event).to.exist;
|
||||
expect(event.type).to.equal(UmbChangeEvent.TYPE);
|
||||
expect(target.getCurrentPageNumber()).to.equal(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Skip', () => {
|
||||
it('gets the skip value', () => {
|
||||
manager.setCurrentPageNumber(5);
|
||||
expect(manager.getSkip()).to.equal(40);
|
||||
});
|
||||
|
||||
it('updates the observable', (done) => {
|
||||
manager.setCurrentPageNumber(5);
|
||||
|
||||
manager.skip.subscribe((value) => {
|
||||
expect(value).to.equal(40);
|
||||
done();
|
||||
})
|
||||
.unsubscribe();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,124 @@
|
||||
import { UmbChangeEvent } from "@umbraco-cms/backoffice/event";
|
||||
import { UmbNumberState } from "@umbraco-cms/backoffice/observable-api";
|
||||
|
||||
export class UmbPaginationManager extends EventTarget {
|
||||
|
||||
#pageSize = new UmbNumberState(10);
|
||||
public readonly pageSize = this.#pageSize.asObservable();
|
||||
|
||||
#totalItems = new UmbNumberState(0);
|
||||
public readonly totalItems = this.#totalItems.asObservable();
|
||||
|
||||
#totalPages = new UmbNumberState(0);
|
||||
public readonly totalPages = this.#totalPages.asObservable();
|
||||
|
||||
#currentPage = new UmbNumberState(1);
|
||||
public readonly currentPage = this.#currentPage.asObservable();
|
||||
|
||||
#skip = new UmbNumberState(0);
|
||||
public readonly skip = this.#skip.asObservable();
|
||||
|
||||
/**
|
||||
* Sets the number of items per page and recalculates the total number of pages
|
||||
* @param {number} pageSize
|
||||
* @memberof UmbPaginationManager
|
||||
*/
|
||||
public setPageSize(pageSize: number) {
|
||||
this.#pageSize.next(pageSize);
|
||||
this.#calculateTotalPages();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of items per page
|
||||
* @return {number}
|
||||
* @memberof UmbPaginationManager
|
||||
*/
|
||||
public getPageSize() {
|
||||
return this.#pageSize.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the total number of items
|
||||
* @return {number}
|
||||
* @memberof UmbPaginationManager
|
||||
*/
|
||||
public getTotalItems() {
|
||||
return this.#totalItems.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the total number of items and recalculates the total number of pages
|
||||
* @param {number} totalItems
|
||||
* @memberof UmbPaginationManager
|
||||
*/
|
||||
public setTotalItems(totalItems: number) {
|
||||
this.#totalItems.next(totalItems);
|
||||
this.#calculateTotalPages();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the total number of pages
|
||||
* @return {number}
|
||||
* @memberof UmbPaginationManager
|
||||
*/
|
||||
public getTotalPages() {
|
||||
return this.#totalPages.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current page number
|
||||
* @return {number}
|
||||
* @memberof UmbPaginationManager
|
||||
*/
|
||||
public getCurrentPageNumber() {
|
||||
return this.#currentPage.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current page number
|
||||
* @param {number} pageNumber
|
||||
* @memberof UmbPaginationManager
|
||||
*/
|
||||
public setCurrentPageNumber(pageNumber: number) {
|
||||
if (pageNumber < 1) {
|
||||
pageNumber = 1;
|
||||
}
|
||||
|
||||
if (pageNumber > this.#totalPages.getValue()) {
|
||||
pageNumber = this.#totalPages.getValue();
|
||||
}
|
||||
|
||||
this.#currentPage.next(pageNumber);
|
||||
this.#calculateSkip();
|
||||
this.dispatchEvent(new UmbChangeEvent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of items to skip
|
||||
* @return {number}
|
||||
* @memberof UmbPaginationManager
|
||||
*/
|
||||
public getSkip() {
|
||||
return this.#skip.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the total number of pages
|
||||
* @memberof UmbPaginationManager
|
||||
*/
|
||||
#calculateTotalPages() {
|
||||
const totalPages = Math.ceil(this.#totalItems.getValue() / this.#pageSize.getValue());
|
||||
this.#totalPages.next(totalPages);
|
||||
|
||||
/* If we currently are on a page higher than the total pages. We need to reset the current page to the last page.
|
||||
This can happen if we have a filter that returns less items than the current page size. */
|
||||
if (this.getCurrentPageNumber() > totalPages) {
|
||||
this.setCurrentPageNumber(totalPages);
|
||||
}
|
||||
}
|
||||
|
||||
#calculateSkip() {
|
||||
const skip = (this.#currentPage.getValue() - 1) * this.#pageSize.getValue();
|
||||
this.#skip.next(skip);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user