gotosocial/web/source/settings/components/pageable-list.tsx

114 lines
2.9 KiB
TypeScript
Raw Permalink Normal View History

/*
GoToSocial
Copyright (C) GoToSocial Authors admin@gotosocial.org
SPDX-License-Identifier: AGPL-3.0-or-later
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { ReactNode } from "react";
import { useLocation } from "wouter";
import { Error } from "./error";
import { SerializedError } from "@reduxjs/toolkit";
import { FetchBaseQueryError } from "@reduxjs/toolkit/query";
import { Links } from "parse-link-header";
import Loading from "./loading";
export interface PageableListProps<T> {
isSuccess: boolean;
items?: T[];
itemToEntry: (_item: T) => ReactNode;
isLoading: boolean;
isFetching: boolean;
isError: boolean;
error: FetchBaseQueryError | SerializedError | undefined;
emptyMessage: ReactNode;
prevNextLinks?: Links | null | undefined;
}
export function PageableList<T>({
isLoading,
isFetching,
isSuccess,
items,
itemToEntry,
isError,
error,
emptyMessage,
prevNextLinks,
}: PageableListProps<T>) {
const [ location, setLocation ] = useLocation();
if (!(isSuccess || isError)) {
// Hasn't been called yet.
return null;
}
if (isLoading || isFetching) {
return <Loading />;
}
if (error) {
return <Error error={error} />;
}
// Map response to items if possible.
let content: ReactNode;
if (items == undefined || items.length == 0) {
content = <b>{emptyMessage}</b>;
} else {
content = (
<div className="entries">
{items.map(item => itemToEntry(item))}
</div>
);
}
// If it's possible to page to next and previous
// pages, instantiate button handlers for this.
let prevClick: (() => void) | undefined;
let nextClick: (() => void) | undefined;
if (prevNextLinks) {
const prev = prevNextLinks["prev"];
if (prev) {
const prevUrl = new URL(prev.url);
const prevParams = prevUrl.search;
prevClick = () => {
setLocation(location + prevParams.toString());
};
}
const next = prevNextLinks["next"];
if (next) {
const nextUrl = new URL(next.url);
const nextParams = nextUrl.search;
nextClick = () => {
setLocation(location + nextParams.toString());
};
}
}
return (
<div className="list pageable-list">
{ content }
{ prevNextLinks &&
<div className="prev-next">
{ prevClick && <button onClick={prevClick}>Previous page</button> }
{ nextClick && <button onClick={nextClick}>Next page</button> }
</div>
}
</div>
);
}