feat: pagination for Radar Rules (#889)
This commit is contained in:
parent
abfbd7f9f1
commit
81d0581451
|
|
@ -148,5 +148,11 @@
|
|||
},
|
||||
"current": {
|
||||
"message": "Current"
|
||||
},
|
||||
"paginationPrevious": {
|
||||
"message": "Previous"
|
||||
},
|
||||
"paginationNext": {
|
||||
"message": "Next"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -148,5 +148,11 @@
|
|||
},
|
||||
"current": {
|
||||
"message": "当前"
|
||||
},
|
||||
"paginationPrevious": {
|
||||
"message": "上一页"
|
||||
},
|
||||
"paginationNext": {
|
||||
"message": "下一页"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,102 @@
|
|||
import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react"
|
||||
import * as React from "react"
|
||||
|
||||
import { Button, type ButtonProps } from "~/lib/components/Button"
|
||||
import { cn } from "~/lib/utils"
|
||||
|
||||
const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
|
||||
<nav
|
||||
role="navigation"
|
||||
aria-label="pagination"
|
||||
className={cn("mx-auto flex w-full justify-center", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
Pagination.displayName = "Pagination"
|
||||
|
||||
const PaginationContent = React.forwardRef<
|
||||
HTMLUListElement,
|
||||
React.ComponentProps<"ul">
|
||||
>(({ className, ...props }, ref) => (
|
||||
<ul
|
||||
ref={ref}
|
||||
className={cn("flex flex-row items-center gap-1", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
PaginationContent.displayName = "PaginationContent"
|
||||
|
||||
const PaginationItem = React.forwardRef<
|
||||
HTMLLIElement,
|
||||
React.ComponentProps<"li">
|
||||
>(({ className, ...props }, ref) => (
|
||||
<li ref={ref} className={cn("", className)} {...props} />
|
||||
))
|
||||
PaginationItem.displayName = "PaginationItem"
|
||||
|
||||
type PaginationLinkProps = {
|
||||
isActive?: boolean
|
||||
} & Omit<ButtonProps, "variant">
|
||||
|
||||
const PaginationLink = ({ isActive, ...props }: PaginationLinkProps) => (
|
||||
<Button
|
||||
aria-current={isActive ? "page" : undefined}
|
||||
variant={isActive ? "outline" : "ghost"}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
PaginationLink.displayName = "PaginationLink"
|
||||
|
||||
const PaginationPrevious = ({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof PaginationLink>) => (
|
||||
<PaginationLink
|
||||
size="default"
|
||||
className={cn("gap-1 pl-2.5", className)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
<span>{chrome.i18n.getMessage("paginationPrevious")}</span>
|
||||
</PaginationLink>
|
||||
)
|
||||
PaginationPrevious.displayName = "PaginationPrevious"
|
||||
|
||||
const PaginationNext = ({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof PaginationLink>) => (
|
||||
<PaginationLink
|
||||
size="default"
|
||||
className={cn("gap-1 pr-2.5", className)}
|
||||
{...props}
|
||||
>
|
||||
<span>{chrome.i18n.getMessage("paginationNext")}</span>
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</PaginationLink>
|
||||
)
|
||||
PaginationNext.displayName = "PaginationNext"
|
||||
|
||||
const PaginationEllipsis = ({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"span">) => (
|
||||
<span
|
||||
aria-hidden
|
||||
className={cn("flex h-9 w-9 items-center justify-center", className)}
|
||||
{...props}
|
||||
>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</span>
|
||||
)
|
||||
PaginationEllipsis.displayName = "PaginationEllipsis"
|
||||
|
||||
export {
|
||||
Pagination,
|
||||
PaginationContent,
|
||||
PaginationEllipsis,
|
||||
PaginationItem,
|
||||
PaginationLink,
|
||||
PaginationNext,
|
||||
PaginationPrevious,
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import _ from "lodash"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
|
||||
import { sendToBackground } from "@plasmohq/messaging"
|
||||
|
||||
|
|
@ -9,13 +9,139 @@ import {
|
|||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "~/lib/components/Accordion"
|
||||
import { Card, CardContent } from "~/lib/components/Card"
|
||||
import { Card, CardContent, CardFooter } from "~/lib/components/Card"
|
||||
import {
|
||||
Pagination,
|
||||
PaginationContent,
|
||||
PaginationEllipsis,
|
||||
PaginationItem,
|
||||
PaginationLink,
|
||||
PaginationNext,
|
||||
PaginationPrevious,
|
||||
} from "~/lib/components/Pagination"
|
||||
import report from "~/lib/report"
|
||||
import { getRulesCount, parseRules } from "~/lib/rules"
|
||||
import type { Rules as IRules } from "~/lib/types"
|
||||
|
||||
function RulesPagination({
|
||||
currentPage,
|
||||
setCurrentPage,
|
||||
pageSize,
|
||||
itemsCount,
|
||||
}: {
|
||||
currentPage: number
|
||||
setCurrentPage: (page: number) => void
|
||||
pageSize: number
|
||||
itemsCount: number
|
||||
}) {
|
||||
const totalPage = Math.ceil(itemsCount / pageSize)
|
||||
return (
|
||||
<Pagination>
|
||||
<PaginationContent>
|
||||
<PaginationItem>
|
||||
<PaginationPrevious
|
||||
disabled={currentPage <= 1}
|
||||
onClick={() => setCurrentPage(currentPage - 1)}
|
||||
/>
|
||||
</PaginationItem>
|
||||
{Array.from({ length: totalPage }).map((_, index) => {
|
||||
const page = index + 1
|
||||
if (
|
||||
page === 1 ||
|
||||
page === totalPage ||
|
||||
(page >= currentPage - 1 && page <= currentPage + 1)
|
||||
) {
|
||||
return (
|
||||
<PaginationItem key={page}>
|
||||
<PaginationLink
|
||||
isActive={page === currentPage}
|
||||
onClick={() => setCurrentPage(page)}
|
||||
>
|
||||
{page}
|
||||
</PaginationLink>
|
||||
</PaginationItem>
|
||||
)
|
||||
} else if (page === currentPage - 2 || page === currentPage + 2) {
|
||||
return (
|
||||
<PaginationItem key={page}>
|
||||
<PaginationEllipsis />
|
||||
</PaginationItem>
|
||||
)
|
||||
}
|
||||
})}
|
||||
<PaginationItem>
|
||||
<PaginationNext
|
||||
disabled={currentPage >= totalPage}
|
||||
onClick={() => setCurrentPage(currentPage + 1)}
|
||||
/>
|
||||
</PaginationItem>
|
||||
</PaginationContent>
|
||||
</Pagination>
|
||||
)
|
||||
}
|
||||
|
||||
function RulesList({
|
||||
rules,
|
||||
start,
|
||||
end,
|
||||
}: {
|
||||
rules: IRules
|
||||
start: number
|
||||
end: number
|
||||
}) {
|
||||
const rulesKeys = useMemo(() => Object.keys(rules), [rules])
|
||||
|
||||
return rulesKeys.slice(start, end).map((key) => {
|
||||
const rule = rules[key]
|
||||
return (
|
||||
<div key={key}>
|
||||
<Accordion type="single" collapsible>
|
||||
<AccordionItem value="item-1">
|
||||
<AccordionTrigger>
|
||||
<div className="font-medium">
|
||||
{rule._name} - {key}
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className="space-y-2">
|
||||
{Object.keys(rule).map((item) => {
|
||||
const subdomainRules = rule[item]
|
||||
if (Array.isArray(subdomainRules)) {
|
||||
return subdomainRules.map((subdomainRule, index) => {
|
||||
return (
|
||||
<div key={key + item + index}>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex-1">
|
||||
<p className="text-sm text-zinc-700">
|
||||
{subdomainRule.title}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const pageSize = 20
|
||||
|
||||
function Rules() {
|
||||
const [rules, setRules] = useState<IRules>({})
|
||||
const rulesCount = useMemo(() => getRulesCount(rules), [rules])
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(1)
|
||||
const itemsCount = useMemo(() => Object.keys(rules).length, [rules])
|
||||
|
||||
useEffect(() => {
|
||||
sendToBackground({
|
||||
name: "requestDisplayedRules",
|
||||
|
|
@ -25,11 +151,6 @@ function Rules() {
|
|||
})
|
||||
}, [])
|
||||
|
||||
const [count, setCount] = useState(0)
|
||||
useEffect(() => {
|
||||
setCount(getRulesCount(rules))
|
||||
}, [rules])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 className="text-3xl font-medium leading-10 mb-6 text-orange-500 border-b pb-4">
|
||||
|
|
@ -37,7 +158,7 @@ function Rules() {
|
|||
</h1>
|
||||
<div className="content mb-6 space-y-2">
|
||||
<p>
|
||||
{chrome.i18n.getMessage("totalNumberOfRules")}: {count}
|
||||
{chrome.i18n.getMessage("totalNumberOfRules")}: {rulesCount}
|
||||
</p>
|
||||
<p
|
||||
dangerouslySetInnerHTML={{
|
||||
|
|
@ -48,49 +169,20 @@ function Rules() {
|
|||
<div className="space-y-4">
|
||||
<Card>
|
||||
<CardContent>
|
||||
{Object.keys(rules).map((key) => {
|
||||
const rule = rules[key]
|
||||
return (
|
||||
<div key={key}>
|
||||
<Accordion type="single" collapsible>
|
||||
<AccordionItem value="item-1">
|
||||
<AccordionTrigger>
|
||||
<div className="font-medium">
|
||||
{rule._name} - {key}
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className="space-y-2">
|
||||
{Object.keys(rule).map((item) => {
|
||||
const subdomainRules = rule[item]
|
||||
if (Array.isArray(subdomainRules)) {
|
||||
return subdomainRules.map(
|
||||
(subdomainRule, index) => {
|
||||
return (
|
||||
<div key={key + item + index}>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex-1">
|
||||
<p className="text-sm text-zinc-700">
|
||||
{subdomainRule.title}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<RulesList
|
||||
rules={rules}
|
||||
start={(currentPage - 1) * pageSize}
|
||||
end={currentPage * pageSize}
|
||||
/>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<RulesPagination
|
||||
currentPage={currentPage}
|
||||
setCurrentPage={setCurrentPage}
|
||||
pageSize={pageSize}
|
||||
itemsCount={itemsCount}
|
||||
/>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue