mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-24 18:56:32 -05:00
History pagination (#58)
Adds pagination for the history page. fixes #32
This commit is contained in:
parent
70ab02431c
commit
11f01094b4
7 changed files with 185 additions and 37 deletions
|
@ -1,18 +1,23 @@
|
||||||
import React, {Fragment} from 'react'
|
import React from 'react'
|
||||||
import {HistoryEntriesProps} from "../history-content/history-content";
|
import {HistoryEntriesProps} from "../history-content/history-content";
|
||||||
import {HistoryCard} from "./history-card";
|
import {HistoryCard} from "./history-card";
|
||||||
|
import {Pager} from '../../../../pagination/pager';
|
||||||
|
import {Row} from 'react-bootstrap';
|
||||||
|
|
||||||
|
export const HistoryCardList: React.FC<HistoryEntriesProps> = ({entries, onPinClick, pageIndex, onLastPageIndexChange}) => {
|
||||||
|
|
||||||
export const HistoryCardList: React.FC<HistoryEntriesProps> = ({entries, onPinClick}) => {
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Row className="justify-content-center">
|
||||||
{
|
<Pager numberOfElementsPerPage={6} pageIndex={pageIndex} onLastPageIndexChange={onLastPageIndexChange}>
|
||||||
entries.map((entry) => (
|
{
|
||||||
<HistoryCard
|
entries.map((entry) => (
|
||||||
key={entry.id}
|
<HistoryCard
|
||||||
entry={entry}
|
key={entry.id}
|
||||||
onPinClick={onPinClick}
|
entry={entry}
|
||||||
/>))
|
onPinClick={onPinClick}
|
||||||
}
|
/>))
|
||||||
</Fragment>
|
}
|
||||||
)
|
</Pager>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import React from "react";
|
import React, {Fragment, useState} from "react";
|
||||||
import {HistoryEntry, pinClick} from "../history";
|
import {HistoryEntry, pinClick} from "../history";
|
||||||
import {HistoryTable} from "../history-table/history-table";
|
import {HistoryTable} from "../history-table/history-table";
|
||||||
import {Alert} from "react-bootstrap";
|
import {Alert, Row} from "react-bootstrap";
|
||||||
import {Trans} from "react-i18next";
|
import {Trans} from "react-i18next";
|
||||||
import {HistoryCardList} from "../history-card/history-card-list";
|
import {HistoryCardList} from "../history-card/history-card-list";
|
||||||
import {ViewStateEnum} from "../history-toolbar/history-toolbar";
|
import {ViewStateEnum} from "../history-toolbar/history-toolbar";
|
||||||
|
import {PagerPagination} from "../../../../pagination/pager-pagination";
|
||||||
|
|
||||||
export interface HistoryContentProps {
|
export interface HistoryContentProps {
|
||||||
viewState: ViewStateEnum
|
viewState: ViewStateEnum
|
||||||
|
@ -20,23 +21,43 @@ export interface HistoryEntryProps {
|
||||||
export interface HistoryEntriesProps {
|
export interface HistoryEntriesProps {
|
||||||
entries: HistoryEntry[]
|
entries: HistoryEntry[]
|
||||||
onPinClick: pinClick
|
onPinClick: pinClick
|
||||||
|
pageIndex: number
|
||||||
|
onLastPageIndexChange: (lastPageIndex: number) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const HistoryContent: React.FC<HistoryContentProps> = ({viewState, entries, onPinClick}) => {
|
export const HistoryContent: React.FC<HistoryContentProps> = ({viewState, entries, onPinClick}) => {
|
||||||
|
const [pageIndex, setPageIndex] = useState(0);
|
||||||
|
const [lastPageIndex, setLastPageIndex] = useState(0);
|
||||||
|
|
||||||
if (entries.length === 0) {
|
if (entries.length === 0) {
|
||||||
return (
|
return (
|
||||||
<Alert variant={"secondary"}>
|
<Row className={"justify-content-center"}>
|
||||||
<Trans i18nKey={"noHistory"}/>
|
<Alert variant={"secondary"}>
|
||||||
</Alert>
|
<Trans i18nKey={"noHistory"}/>
|
||||||
|
</Alert>
|
||||||
|
</Row>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (viewState) {
|
const mapViewStateToComponent = (viewState: ViewStateEnum) => {
|
||||||
default:
|
switch (viewState) {
|
||||||
case ViewStateEnum.card:
|
default:
|
||||||
return <HistoryCardList entries={entries} onPinClick={onPinClick}/>
|
case ViewStateEnum.card:
|
||||||
case ViewStateEnum.table:
|
return <HistoryCardList entries={entries} onPinClick={onPinClick} pageIndex={pageIndex}
|
||||||
return <HistoryTable entries={entries} onPinClick={onPinClick}/>;
|
onLastPageIndexChange={setLastPageIndex}/>
|
||||||
|
case ViewStateEnum.table:
|
||||||
|
return <HistoryTable entries={entries} onPinClick={onPinClick} pageIndex={pageIndex}
|
||||||
|
onLastPageIndexChange={setLastPageIndex}/>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
{mapViewStateToComponent(viewState)}
|
||||||
|
<Row className="justify-content-center">
|
||||||
|
<PagerPagination numberOfPageButtonsToShowAfterAndBeforeCurrent={2} lastPageIndex={lastPageIndex}
|
||||||
|
onPageChange={setPageIndex}/>
|
||||||
|
</Row>
|
||||||
|
</Fragment>);
|
||||||
}
|
}
|
|
@ -3,8 +3,9 @@ import {Table} from "react-bootstrap"
|
||||||
import {HistoryTableRow} from "./history-table-row";
|
import {HistoryTableRow} from "./history-table-row";
|
||||||
import {HistoryEntriesProps} from "../history-content/history-content";
|
import {HistoryEntriesProps} from "../history-content/history-content";
|
||||||
import {Trans} from "react-i18next";
|
import {Trans} from "react-i18next";
|
||||||
|
import {Pager} from "../../../../pagination/pager";
|
||||||
|
|
||||||
const HistoryTable: React.FC<HistoryEntriesProps> = ({entries, onPinClick}) => {
|
export const HistoryTable: React.FC<HistoryEntriesProps> = ({entries, onPinClick, pageIndex, onLastPageIndexChange}) => {
|
||||||
return (
|
return (
|
||||||
<Table striped bordered hover size="sm" variant="dark">
|
<Table striped bordered hover size="sm" variant="dark">
|
||||||
<thead>
|
<thead>
|
||||||
|
@ -16,17 +17,17 @@ const HistoryTable: React.FC<HistoryEntriesProps> = ({entries, onPinClick}) => {
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{
|
<Pager numberOfElementsPerPage={6} pageIndex={pageIndex} onLastPageIndexChange={onLastPageIndexChange}>
|
||||||
entries.map((entry) =>
|
{
|
||||||
<HistoryTableRow
|
entries.map((entry) =>
|
||||||
key={entry.id}
|
<HistoryTableRow
|
||||||
entry={entry}
|
key={entry.id}
|
||||||
onPinClick={onPinClick}
|
entry={entry}
|
||||||
/>)
|
onPinClick={onPinClick}
|
||||||
}
|
/>)
|
||||||
|
}
|
||||||
|
</Pager>
|
||||||
</tbody>
|
</tbody>
|
||||||
</Table>
|
</Table>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export {HistoryTable}
|
|
||||||
|
|
|
@ -57,11 +57,9 @@ export const History: React.FC = () => {
|
||||||
<Row className={"justify-content-center mb-3"}>
|
<Row className={"justify-content-center mb-3"}>
|
||||||
<HistoryToolbar onSettingsChange={setViewState} tags={tags}/>
|
<HistoryToolbar onSettingsChange={setViewState} tags={tags}/>
|
||||||
</Row>
|
</Row>
|
||||||
<div className="d-flex flex-wrap justify-content-center">
|
|
||||||
<HistoryContent viewState={viewState.viewState}
|
<HistoryContent viewState={viewState.viewState}
|
||||||
entries={entriesToShow}
|
entries={entriesToShow}
|
||||||
onPinClick={pinClick}/>
|
onPinClick={pinClick}/>
|
||||||
</div>
|
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
17
src/components/pagination/pager-item.tsx
Normal file
17
src/components/pagination/pager-item.tsx
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export interface PageItemProps {
|
||||||
|
onClick: (index: number) => void
|
||||||
|
index: number
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const PagerItem: React.FC<PageItemProps> = ({index, onClick}) => {
|
||||||
|
return (
|
||||||
|
<li className="page-item">
|
||||||
|
<span className="page-link" role="button" onClick={() => onClick(index)}>
|
||||||
|
{index + 1}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
82
src/components/pagination/pager-pagination.tsx
Normal file
82
src/components/pagination/pager-pagination.tsx
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
import React, {Fragment, useEffect, useState} from "react";
|
||||||
|
import {Pagination} from "react-bootstrap";
|
||||||
|
import {PagerItem} from "./pager-item";
|
||||||
|
|
||||||
|
export interface PaginationProps {
|
||||||
|
numberOfPageButtonsToShowAfterAndBeforeCurrent: number
|
||||||
|
onPageChange: (pageIndex: number) => void
|
||||||
|
lastPageIndex: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PagerPagination: React.FC<PaginationProps> = ({numberOfPageButtonsToShowAfterAndBeforeCurrent, onPageChange, lastPageIndex}) => {
|
||||||
|
if (numberOfPageButtonsToShowAfterAndBeforeCurrent % 2 !== 0) {
|
||||||
|
throw new Error("number of pages to show must be even!")
|
||||||
|
}
|
||||||
|
|
||||||
|
const [pageIndex, setPageIndex] = useState(0);
|
||||||
|
const correctedPageIndex = Math.min(pageIndex, lastPageIndex);
|
||||||
|
const wantedUpperPageIndex = correctedPageIndex + numberOfPageButtonsToShowAfterAndBeforeCurrent;
|
||||||
|
const wantedLowerPageIndex = correctedPageIndex - numberOfPageButtonsToShowAfterAndBeforeCurrent;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
onPageChange(pageIndex)
|
||||||
|
}, [onPageChange, pageIndex])
|
||||||
|
|
||||||
|
const correctedLowerPageIndex =
|
||||||
|
Math.min(
|
||||||
|
Math.max(
|
||||||
|
Math.min(
|
||||||
|
wantedLowerPageIndex,
|
||||||
|
wantedLowerPageIndex + lastPageIndex - wantedUpperPageIndex
|
||||||
|
),
|
||||||
|
0
|
||||||
|
),
|
||||||
|
lastPageIndex
|
||||||
|
);
|
||||||
|
|
||||||
|
const correctedUpperPageIndex =
|
||||||
|
Math.max(
|
||||||
|
Math.min(
|
||||||
|
Math.max(
|
||||||
|
wantedUpperPageIndex,
|
||||||
|
wantedUpperPageIndex - wantedLowerPageIndex
|
||||||
|
),
|
||||||
|
lastPageIndex
|
||||||
|
),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
const paginationItemsBefore = Array.from(new Array(correctedPageIndex - correctedLowerPageIndex)).map((k, index) => {
|
||||||
|
const itemIndex = correctedLowerPageIndex + index;
|
||||||
|
return <PagerItem key={itemIndex} index={itemIndex} onClick={setPageIndex}/>
|
||||||
|
});
|
||||||
|
|
||||||
|
const paginationItemsAfter = Array.from(new Array(correctedUpperPageIndex - correctedPageIndex)).map((k, index) => {
|
||||||
|
const itemIndex = correctedPageIndex + index + 1;
|
||||||
|
return <PagerItem key={itemIndex} index={itemIndex} onClick={setPageIndex}/>
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Pagination>
|
||||||
|
{
|
||||||
|
correctedLowerPageIndex > 0 ?
|
||||||
|
<Fragment>
|
||||||
|
<PagerItem key={0} index={0} onClick={setPageIndex}/>
|
||||||
|
<Pagination.Ellipsis disabled/>
|
||||||
|
</Fragment>
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
{paginationItemsBefore}
|
||||||
|
<Pagination.Item active>{correctedPageIndex + 1}</Pagination.Item>
|
||||||
|
{paginationItemsAfter}
|
||||||
|
{
|
||||||
|
correctedUpperPageIndex < lastPageIndex ?
|
||||||
|
<Fragment>
|
||||||
|
<Pagination.Ellipsis disabled/>
|
||||||
|
<PagerItem key={lastPageIndex} index={lastPageIndex} onClick={setPageIndex}/>
|
||||||
|
</Fragment>
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
</Pagination>
|
||||||
|
);
|
||||||
|
}
|
24
src/components/pagination/pager.tsx
Normal file
24
src/components/pagination/pager.tsx
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import React, {Fragment, useEffect} from "react";
|
||||||
|
|
||||||
|
export interface PagerPageProps {
|
||||||
|
pageIndex: number
|
||||||
|
numberOfElementsPerPage: number
|
||||||
|
onLastPageIndexChange: (lastPageIndex: number) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Pager: React.FC<PagerPageProps> = ({children, numberOfElementsPerPage, pageIndex, onLastPageIndexChange}) => {
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const lastPageIndex = Math.ceil(React.Children.count(children) / numberOfElementsPerPage) - 1;
|
||||||
|
onLastPageIndexChange(lastPageIndex)
|
||||||
|
}, [children, numberOfElementsPerPage, onLastPageIndexChange])
|
||||||
|
|
||||||
|
return <Fragment>
|
||||||
|
{
|
||||||
|
React.Children.toArray(children).filter((value, index) => {
|
||||||
|
const pageOfElement = Math.floor((index) / numberOfElementsPerPage);
|
||||||
|
return (pageOfElement === pageIndex);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</Fragment>
|
||||||
|
}
|
Loading…
Reference in a new issue