Subida del módulo y tema de PrestaShop

This commit is contained in:
Kaloyan
2026-04-09 18:31:51 +02:00
parent 12c253296f
commit 16b3ff9424
39262 changed files with 7418797 additions and 0 deletions

View File

@@ -0,0 +1,359 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import PaginationServiceType from '@PSTypes/services';
import RendererType from '@PSTypes/renderers';
const {$} = window;
/**
* Related html template src/PrestaShopBundle/Resources/views/Admin/Common/javascript_pagination.html.twig
*
* Usage
*```
* $paginator new DynamicPaginator(
* '#foo-container',
* FooDataService,
* FooRenderer
* );
* this.eventEmitter.on('fooEventThatShouldTriggerPagination', () => $paginator.paginate(1));
*```
* You can also provide the starting page to initiate it automatically on page load:
*```
* $paginator new DynamicPaginator(
* '#foo-container',
* FooDataService,
* FooRenderer,
* 1
* );
*```
* There is also a possibility to provide custom selectorsMap as 5th argument. See this.setSelectorsMap().
*
* Pagination service must have a method fetch(offset, limit) which returns data.{any resources name} & data.total
* e.g.
* ```
* class FooDataService {
* fetch(offset, limit) {
* return $.get(this.router.generate('admin_products_combinations', {
* productId: this.productId,
* page,
* limit,
* }));
* }
* }
*```
* * In this case the action of route `admin_products_combinations` returns following json:
* ```
* {
* total: 100,
* combinations: [{combinationId: 1, name: foo...}, {combinationId: 2, name: bar...}]
* }
*```
*
* The renderer must have a method render(data) which accepts the data from PaginationService
* and renders it depending on needs
*/
export default class DynamicPaginator {
private $paginationContainer: JQuery;
private paginationService: PaginationServiceType;
private renderer: RendererType;
private currentPage: number;
private selectorsMap: Record<string, string>;
private pagesCount: number;
private total: number;
private totalInPage: number;
/**
* @param {String} containerSelector
* @param {Object} paginationService
* @param {Object} renderer
* @param {Number|null} startingPage If provided it will load the provided page data on page load
* @param {Object|null} selectorsMap If provided it will override css selectors used for all the actions.
*/
constructor(
containerSelector: string,
paginationService: PaginationServiceType,
renderer: RendererType,
startingPage = 0,
selectorsMap = {},
) {
this.$paginationContainer = $(containerSelector);
this.paginationService = paginationService;
this.renderer = renderer;
this.selectorsMap = {};
this.setSelectorsMap(selectorsMap);
this.pagesCount = 0;
this.total = 0;
this.totalInPage = 0;
this.init();
this.currentPage = startingPage;
if (startingPage > 0) {
this.paginate(startingPage);
}
}
/**
* @param {Number} page
*/
async paginate(page: number): Promise<void> {
this.currentPage = page > 0 ? page : 1;
this.renderer.setLoading(true);
const limit = this.getLimit();
const data: FetchResponse = await this.paginationService.fetch(
this.calculateOffset(page, limit),
limit,
);
$(this.selectorsMap.jumpToPageInput).val(page);
this.countPages(<number>data.total);
this.refreshButtonsData(page);
this.refreshInfoLabel(page, <number>data.total);
this.total = data.total;
this.setTotalInPage(page, limit, data.total);
this.toggleTargetAvailability(this.selectorsMap.firstPageItem, page > 1);
this.toggleTargetAvailability(this.selectorsMap.previousPageItem, page > 1);
this.toggleTargetAvailability(
this.selectorsMap.nextPageItem,
page < this.pagesCount,
);
this.toggleTargetAvailability(
this.selectorsMap.lastPageItem,
page < this.pagesCount,
);
this.toggleTargetAvailability(
this.selectorsMap.jumpToPageInput,
this.getPagesCount() > 1,
);
this.renderer.render(data);
this.$paginationContainer.toggleClass('d-none', this.getTotal() <= this.getMinLimit());
this.renderer.setLoading(false);
window.prestaShopUiKit.initToolTips();
}
getCurrentPage(): number {
return this.currentPage;
}
getPagesCount(): number {
return this.pagesCount;
}
getTotal(): number {
return this.total;
}
getTotalInPage(): number {
return this.totalInPage;
}
/**
* Initiates the pagination component
*
* @private
*/
private init(): void {
this.$paginationContainer.on('click', this.selectorsMap.pageLink, (e) => {
this.paginate(Number($(e.currentTarget).data('page')));
});
this.$paginationContainer
.find(this.selectorsMap.jumpToPageInput)
.on('keypress', (e) => {
if (e.which === 13) {
e.preventDefault();
const input = <HTMLInputElement>e.currentTarget;
const page = this.getValidPageNumber(Number(input.value));
this.paginate(page);
}
});
this.$paginationContainer.on(
'change',
this.selectorsMap.limitSelect,
() => {
this.paginate(1);
},
);
}
/**
* @param page
* @param limit
*
* @returns {Number}
*
* @private
*/
private calculateOffset(page: number, limit: number): number {
return (page - 1) * limit;
}
/**
* @param {Number} page
*
* @private
*/
private refreshButtonsData(page: number): void {
this.$paginationContainer
.find(this.selectorsMap.nextPageBtn)
.data('page', page + 1);
this.$paginationContainer
.find(this.selectorsMap.previousPageBtn)
.data('page', page - 1);
this.$paginationContainer
.find(this.selectorsMap.lastPageBtn)
.data('page', this.pagesCount);
}
/**
* @param {Number} page
* @param {Number} total
*
* @private
*/
private refreshInfoLabel(page: number, total: number): void {
const infoLabel = this.$paginationContainer.find(
this.selectorsMap.paginationInfoLabel,
);
const limit = this.getLimit();
const modifiedInfoText = infoLabel
.data('pagination-info')
.replace(/%from%/g, this.calculateFrom(page, limit))
.replace(/%to%/g, this.calculateTo(page, limit, total))
.replace(/%total%/g, total)
.replace(/%current_page%/g, page)
.replace(/%page_count%/g, this.pagesCount);
infoLabel.text(modifiedInfoText);
}
/**
* @param {String} targetSelector
* @param {Boolean} enable
*
* @private
*/
private toggleTargetAvailability(
targetSelector: string,
enable: boolean,
): void {
const target = this.$paginationContainer.find(targetSelector);
target.toggleClass('disabled', !enable);
target.prop('disabled', !enable);
}
/**
* @param {Number} total
*
* @private
*/
private countPages(total: number): void {
this.pagesCount = Math.ceil(total / this.getLimit());
const lastPageItem = this.$paginationContainer.find(
this.selectorsMap.lastPageBtn,
);
lastPageItem.data('page', this.pagesCount);
lastPageItem.text(this.pagesCount);
}
/**
* @returns {Number}
*
* @private
*/
private getLimit(): number {
return <number>(
this.$paginationContainer.find(this.selectorsMap.limitSelect).val()
);
}
/**
* @returns {Number}
*
* @private
*/
private getMinLimit(): number {
const limitSelections = this.$paginationContainer.find(`${this.selectorsMap.limitSelect} option`).get();
const limitValues = limitSelections.map((option:HTMLElement) => {
if (!(option instanceof HTMLOptionElement)) {
console.error('Only <option> elements are expected in <select> for list limit choices');
return 0;
}
return Number(option.value);
});
return Math.min(...limitValues);
}
/**
* @param page
*
* @returns {Number}
*
* @private
*/
private getValidPageNumber(page: number): number {
if (page > this.pagesCount) {
return this.pagesCount;
}
if (page < 1) {
return 1;
}
return page;
}
/**
* @param {Object} selectorsMap
*
* @private
*/
private setSelectorsMap(selectorsMap: Record<string, string>): void {
this.selectorsMap = {
jumpToPageInput: 'input[name="paginator-jump-page"]',
firstPageBtn: 'button.page-link.first',
firstPageItem: 'li.page-item.first',
nextPageBtn: 'button.page-link.next',
nextPageItem: 'li.page-item.next',
previousPageBtn: 'button.page-link.previous',
previousPageItem: 'li.page-item.previous',
lastPageItem: 'li.page-item.last',
lastPageBtn: 'button.page-link.last',
pageLink: 'button.page-link',
limitSelect: '#paginator-limit',
paginationInfoLabel: '#pagination-info',
//override with custom selectors if any provided
...selectorsMap,
};
}
private calculateFrom(page: number, limit: number): number {
// increment by 1 because offset, starts from 0, but lowest "from" can be 1
return page === 1 ? 1 : Math.round((page - 1) * limit + 1);
}
private calculateTo(page: number, limit:number, total: number) {
return page === this.pagesCount ? total : Math.round(page * limit);
}
private setTotalInPage(page: number, limit: number, total: number): void {
// increment by 1 to include the first "from" result to total count
this.totalInPage = this.calculateTo(page, limit, total) - this.calculateFrom(page, limit) + 1;
}
}