mirror of
https://github.com/umami-software/umami.git
synced 2026-02-07 06:07:17 +01:00
Refactored icons.
This commit is contained in:
parent
18eceee4c4
commit
99330a1a4d
86 changed files with 310 additions and 273 deletions
|
|
@ -1,7 +1,8 @@
|
|||
import Link from 'next/link';
|
||||
import { Flexbox, Icon, Icons, Text } from '@umami/react-zen';
|
||||
import styles from './Breadcrumb.module.css';
|
||||
import { Fragment } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { Row, Icon, Text } from '@umami/react-zen';
|
||||
import { Chevron } from '@/components/icons';
|
||||
import styles from './Breadcrumb.module.css';
|
||||
|
||||
export interface BreadcrumbProps {
|
||||
data: {
|
||||
|
|
@ -12,7 +13,7 @@ export interface BreadcrumbProps {
|
|||
|
||||
export function Breadcrumb({ data }: BreadcrumbProps) {
|
||||
return (
|
||||
<Flexbox alignItems="center" gap={3} className={styles.bar}>
|
||||
<Row alignItems="center" gap className={styles.bar}>
|
||||
{data.map((a, i) => {
|
||||
return (
|
||||
<Fragment key={i}>
|
||||
|
|
@ -25,12 +26,12 @@ export function Breadcrumb({ data }: BreadcrumbProps) {
|
|||
)}
|
||||
{i !== data.length - 1 ? (
|
||||
<Icon rotate={270}>
|
||||
<Icons.Chevron />
|
||||
<Chevron />
|
||||
</Icon>
|
||||
) : null}
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</Flexbox>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Icon, Row, Text } from '@umami/react-zen';
|
||||
import { differenceInDays, isSameDay } from 'date-fns';
|
||||
import { useLocale } from '@/components/hooks';
|
||||
import { Lucide } from '@/components/icons';
|
||||
import { Calendar } from '@/components/icons';
|
||||
import { formatDate } from '@/lib/date';
|
||||
|
||||
export function DateDisplay({ startDate, endDate }) {
|
||||
|
|
@ -11,7 +11,7 @@ export function DateDisplay({ startDate, endDate }) {
|
|||
return (
|
||||
<Row gap="3" alignItems="center" wrap="nowrap">
|
||||
<Icon>
|
||||
<Lucide.Calendar />
|
||||
<Calendar />
|
||||
</Icon>
|
||||
<Text wrap="nowrap">
|
||||
{isSingleDate ? (
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { ReactNode } from 'react';
|
||||
import { Icon, Text, Column } from '@umami/react-zen';
|
||||
import { Icons } from '@/components/icons';
|
||||
import { Logo } from '@/components/icons';
|
||||
|
||||
export interface EmptyPlaceholderProps {
|
||||
message?: string;
|
||||
|
|
@ -11,7 +11,7 @@ export function EmptyPlaceholder({ message, children }: EmptyPlaceholderProps) {
|
|||
return (
|
||||
<Column alignItems="center" justifyContent="center" gap="5" height="100%" width="100%">
|
||||
<Icon size="xl" fillColor="3" strokeColor="3">
|
||||
<Icons.Logo />
|
||||
<Logo />
|
||||
</Icon>
|
||||
<Text>{message}</Text>
|
||||
<div>{children}</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { Icon, Icons, Text } from '@umami/react-zen';
|
||||
import { Icon, Text } from '@umami/react-zen';
|
||||
import styles from './ErrorMessage.module.css';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import { Alert } from '@/components/icons';
|
||||
|
||||
export function ErrorMessage() {
|
||||
const { formatMessage, messages } = useMessages();
|
||||
|
|
@ -8,7 +9,7 @@ export function ErrorMessage() {
|
|||
return (
|
||||
<div className={styles.error}>
|
||||
<Icon className={styles.icon} size="lg">
|
||||
<Icons.Alert />
|
||||
<Alert />
|
||||
</Icon>
|
||||
<Text>{formatMessage(messages.error)}</Text>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { ReactNode } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import Link from 'next/link';
|
||||
import { Icon, Icons } from '@umami/react-zen';
|
||||
import { Icon } from '@umami/react-zen';
|
||||
import { useMessages, useNavigation } from '@/components/hooks';
|
||||
import { ExternalLink } from '@/components/icons';
|
||||
import styles from './FilterLink.module.css';
|
||||
|
||||
export interface FilterLinkProps {
|
||||
|
|
@ -44,7 +45,7 @@ export function FilterLink({
|
|||
{externalUrl && (
|
||||
<a className={styles.link} href={externalUrl} target="_blank" rel="noreferrer noopener">
|
||||
<Icon className={styles.icon}>
|
||||
<Icons.ExternalLink />
|
||||
<ExternalLink />
|
||||
</Icon>
|
||||
</a>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ import {
|
|||
ListItem,
|
||||
Select,
|
||||
Icon,
|
||||
Icons,
|
||||
Button,
|
||||
} from '@umami/react-zen';
|
||||
import { useFilters } from '@/components/hooks';
|
||||
import { Close } from '@/components/icons';
|
||||
|
||||
export interface FilterRecordProps {
|
||||
name: string;
|
||||
|
|
@ -55,7 +55,7 @@ export function FilterRecord({
|
|||
<Column justifyContent="flex-end">
|
||||
<Button variant="quiet" onPress={() => onRemove?.(name)}>
|
||||
<Icon>
|
||||
<Icons.Close />
|
||||
<Close />
|
||||
</Icon>
|
||||
</Button>
|
||||
</Column>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { Button, Icon, Icons } from '@umami/react-zen';
|
||||
import { Button, Icon } from '@umami/react-zen';
|
||||
import { useState } from 'react';
|
||||
import { Close, Menu } from '@/components/icons';
|
||||
import { MobileMenu } from './MobileMenu';
|
||||
|
||||
export function HamburgerButton({ menuItems }: { menuItems: any[] }) {
|
||||
|
|
@ -11,7 +12,7 @@ export function HamburgerButton({ menuItems }: { menuItems: any[] }) {
|
|||
return (
|
||||
<>
|
||||
<Button variant="quiet" onClick={handleClick}>
|
||||
<Icon>{active ? <Icons.Close /> : <Icons.Menu />}</Icon>
|
||||
<Icon>{active ? <Close /> : <Menu />}</Icon>
|
||||
</Button>
|
||||
{active && <MobileMenu items={menuItems} onClose={handleClose} />}
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { Button, Icon, Icons, Row, Text } from '@umami/react-zen';
|
||||
import { Button, Icon, Row, Text } from '@umami/react-zen';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import { Chevron } from '@/components/icons';
|
||||
|
||||
export interface PagerProps {
|
||||
page: string | number;
|
||||
|
|
@ -38,12 +39,12 @@ export function Pager({ page, pageSize, count, onPageChange }: PagerProps) {
|
|||
<Text>{formatMessage(labels.pageOf, { current: page, total: maxPage })}</Text>
|
||||
<Button onPress={() => handlePageChange(-1)} isDisabled={firstPage}>
|
||||
<Icon size="sm" rotate={180}>
|
||||
<Icons.Chevron />
|
||||
<Chevron />
|
||||
</Icon>
|
||||
</Button>
|
||||
<Button onPress={() => handlePageChange(1)} isDisabled={lastPage}>
|
||||
<Icon size="sm">
|
||||
<Icons.Chevron />
|
||||
<Chevron />
|
||||
</Icon>
|
||||
</Button>
|
||||
</Row>
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ export * from './useFields';
|
|||
export * from './useFilters';
|
||||
export * from './useForceUpdate';
|
||||
export * from './useFormat';
|
||||
export * from './useGlobalState';
|
||||
export * from './useLanguageNames';
|
||||
export * from './useLocale';
|
||||
export * from './useMessages';
|
||||
|
|
|
|||
|
|
@ -10,4 +10,4 @@ const useGlobalState = (key: string, value?: any) => {
|
|||
return [store(state => state[key]), (value: any) => store.setState({ [key]: value })];
|
||||
};
|
||||
|
||||
export default useGlobalState;
|
||||
export { useGlobalState };
|
||||
|
|
|
|||
|
|
@ -1,12 +1,33 @@
|
|||
import { Icons as ZenIcons } from '@umami/react-zen';
|
||||
import * as lucide from 'lucide-react';
|
||||
import * as LocalIcons from '@/components/svg';
|
||||
|
||||
const icons = {
|
||||
...ZenIcons,
|
||||
...LocalIcons,
|
||||
};
|
||||
|
||||
export const Lucide = lucide;
|
||||
|
||||
export const Icons = icons;
|
||||
export {
|
||||
AlertTriangle as Alert,
|
||||
ArrowRight as Arrow,
|
||||
Calendar,
|
||||
ChevronRight as Chevron,
|
||||
X as Close,
|
||||
Copy,
|
||||
Edit,
|
||||
Ellipsis,
|
||||
Eye,
|
||||
ExternalLink,
|
||||
Globe,
|
||||
Grid2X2,
|
||||
LayoutDashboard,
|
||||
Link,
|
||||
ListFilter,
|
||||
LockKeyhole,
|
||||
LogOut,
|
||||
Menu,
|
||||
Moon,
|
||||
MoreHorizontal as More,
|
||||
PanelLeft,
|
||||
Plus,
|
||||
RefreshCw as Refresh,
|
||||
Settings,
|
||||
Share,
|
||||
Slash,
|
||||
SquarePen,
|
||||
Sun,
|
||||
Trash,
|
||||
Users,
|
||||
} from 'lucide-react';
|
||||
export * from '@/components/svg';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { MouseEvent } from 'react';
|
||||
import { Button, Icon, Icons, Text, Row, TooltipTrigger, Tooltip } from '@umami/react-zen';
|
||||
import { Button, Icon, Text, Row, TooltipTrigger, Tooltip } from '@umami/react-zen';
|
||||
import { useNavigation, useMessages, useFormat, useFilters } from '@/components/hooks';
|
||||
import { Close } from '@/components/icons';
|
||||
import { isSearchOperator } from '@/lib/params';
|
||||
|
||||
export function FilterBar() {
|
||||
|
|
@ -51,8 +52,8 @@ export function FilterBar() {
|
|||
{paramValue}
|
||||
</Text>
|
||||
</Row>
|
||||
<Icon onClick={e => handleCloseFilter(name, e)} size="xs" color>
|
||||
<Icons.Close />
|
||||
<Icon onClick={e => handleCloseFilter(name, e)} size="xs">
|
||||
<Close />
|
||||
</Icon>
|
||||
</Row>
|
||||
</Row>
|
||||
|
|
@ -62,7 +63,7 @@ export function FilterBar() {
|
|||
<TooltipTrigger delay={0}>
|
||||
<Button variant="quiet" onPress={handleResetFilter}>
|
||||
<Icon>
|
||||
<Icons.Close />
|
||||
<Close />
|
||||
</Icon>
|
||||
</Button>
|
||||
<Tooltip>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { ReactNode, Key } from 'react';
|
||||
import { DialogTrigger, Button, Menu, Popover, Icon } from '@umami/react-zen';
|
||||
import { Lucide } from '@/components/icons';
|
||||
import { Ellipsis } from '@/components/icons';
|
||||
|
||||
export function MenuButton({
|
||||
children,
|
||||
|
|
@ -17,7 +17,7 @@ export function MenuButton({
|
|||
<DialogTrigger>
|
||||
<Button variant="outline">
|
||||
<Icon>
|
||||
<Lucide.Ellipsis />
|
||||
<Ellipsis />
|
||||
</Icon>
|
||||
</Button>
|
||||
<Popover placement="bottom start">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Row, Text, Icon, Button, MenuTrigger, Popover, Menu, MenuItem } from '@umami/react-zen';
|
||||
import { startOfMonth, endOfMonth, startOfYear, addMonths, subYears } from 'date-fns';
|
||||
import { Icons } from '@/components/icons';
|
||||
import { Chevron } from '@/components/icons';
|
||||
import { useLocale } from '@/components/hooks';
|
||||
import { formatDate } from '@/lib/date';
|
||||
|
||||
|
|
@ -9,7 +9,7 @@ export function MonthSelect({ date = new Date(), onChange }) {
|
|||
const month = formatDate(date, 'MMMM', locale);
|
||||
const year = date.getFullYear();
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
// eslint-disable-next-line
|
||||
const handleChange = (close: () => void, date: Date) => {
|
||||
onChange(`range:${startOfMonth(date).getTime()}:${endOfMonth(date).getTime()}`);
|
||||
close();
|
||||
|
|
@ -31,7 +31,7 @@ export function MonthSelect({ date = new Date(), onChange }) {
|
|||
<Button variant="quiet">
|
||||
<Text>{month}</Text>
|
||||
<Icon size="sm">
|
||||
<Icons.Chevron />
|
||||
<Chevron />
|
||||
</Icon>
|
||||
</Button>
|
||||
<Popover>
|
||||
|
|
@ -50,13 +50,17 @@ export function MonthSelect({ date = new Date(), onChange }) {
|
|||
<Button variant="quiet">
|
||||
<Text>{year}</Text>
|
||||
<Icon size="sm">
|
||||
<Icons.Chevron />
|
||||
<Chevron />
|
||||
</Icon>
|
||||
</Button>
|
||||
<Popover>
|
||||
<Menu>
|
||||
{years.map(year => {
|
||||
return <MenuItem id={year}>{year}</MenuItem>;
|
||||
return (
|
||||
<MenuItem key={year} id={year}>
|
||||
{year}
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Menu>
|
||||
</Popover>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { LoadingButton, Icon, Tooltip, TooltipTrigger } from '@umami/react-zen';
|
||||
import { setWebsiteDateRange } from '@/store/websites';
|
||||
import { useDateRange } from '@/components/hooks';
|
||||
import { Icons } from '@/components/icons';
|
||||
import { Refresh } from '@/components/icons';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
|
||||
export function RefreshButton({
|
||||
|
|
@ -24,7 +24,7 @@ export function RefreshButton({
|
|||
<TooltipTrigger>
|
||||
<LoadingButton isLoading={isLoading} onPress={handleClick}>
|
||||
<Icon>
|
||||
<Icons.Refresh />
|
||||
<Refresh />
|
||||
</Icon>
|
||||
</LoadingButton>
|
||||
<Tooltip>{formatMessage(labels.refresh)}</Tooltip>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Button, Icon, DialogTrigger, Popover, Column, Label } from '@umami/react-zen';
|
||||
import { TimezoneSetting } from '@/app/(main)/settings/profile/TimezoneSetting';
|
||||
import { DateRangeSetting } from '@/app/(main)/settings/profile/DateRangeSetting';
|
||||
import { Icons } from '@/components/icons';
|
||||
import { Gear } from '@/components/icons';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
|
||||
export function SettingsButton() {
|
||||
|
|
@ -11,7 +11,7 @@ export function SettingsButton() {
|
|||
<DialogTrigger>
|
||||
<Button variant="quiet">
|
||||
<Icon>
|
||||
<Icons.Gear />
|
||||
<Gear />
|
||||
</Icon>
|
||||
</Button>
|
||||
<Popover placement="bottom end">
|
||||
|
|
|
|||
|
|
@ -12,10 +12,10 @@ import {
|
|||
Popover,
|
||||
Row,
|
||||
Box,
|
||||
Icons,
|
||||
} from '@umami/react-zen';
|
||||
import { User, Users } from 'lucide-react';
|
||||
import { useLoginQuery, useMessages, useTeamsQuery, useNavigation } from '@/components/hooks';
|
||||
import { Chevron } from '@/components/icons';
|
||||
|
||||
export function TeamsButton({
|
||||
className,
|
||||
|
|
@ -46,8 +46,8 @@ export function TeamsButton({
|
|||
<Row alignItems="center" gap="3">
|
||||
<Icon>{teamId ? <Users /> : <User />}</Icon>
|
||||
{showText && <Text>{teamId ? team?.name : user.username}</Text>}
|
||||
<Icon rotate={90} size="xs">
|
||||
<Icons.Chevron />
|
||||
<Icon rotate={90} size="sm">
|
||||
<Chevron />
|
||||
</Icon>
|
||||
</Row>
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {
|
|||
Tooltip,
|
||||
} from '@umami/react-zen';
|
||||
import { isAfter } from 'date-fns';
|
||||
import { Icons } from '@/components/icons';
|
||||
import { Chevron, Close, Compare } from '@/components/icons';
|
||||
import { useDateRange, useMessages, useNavigation } from '@/components/hooks';
|
||||
import { getOffsetDateRange } from '@/lib/date';
|
||||
import { DateRange } from '@/lib/types';
|
||||
|
|
@ -65,12 +65,12 @@ export function WebsiteDateFilter({
|
|||
<Row gap="1">
|
||||
<Button onPress={() => handleIncrement(-1)} variant="quiet">
|
||||
<Icon size="xs" rotate={180}>
|
||||
<Icons.Chevron />
|
||||
<Chevron />
|
||||
</Icon>
|
||||
</Button>
|
||||
<Button onPress={() => handleIncrement(1)} variant="quiet" isDisabled={disableForward}>
|
||||
<Icon size="xs">
|
||||
<Icons.Chevron />
|
||||
<Chevron />
|
||||
</Icon>
|
||||
</Button>
|
||||
</Row>
|
||||
|
|
@ -95,7 +95,7 @@ export function WebsiteDateFilter({
|
|||
{!isAllTime && allowCompare && (
|
||||
<TooltipTrigger delay={0}>
|
||||
<Button variant="quiet" onPress={handleCompare}>
|
||||
<Icon fillColor>{compare ? <Icons.Close /> : <Icons.Compare />}</Icon>
|
||||
<Icon fillColor>{compare ? <Close /> : <Compare />}</Icon>
|
||||
</Button>
|
||||
<Tooltip>{formatMessage(compare ? labels.cancel : labels.compareDates)}</Tooltip>
|
||||
</TooltipTrigger>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import classNames from 'classnames';
|
||||
import { Icon, Icons, Text } from '@umami/react-zen';
|
||||
import { Icon, Text } from '@umami/react-zen';
|
||||
import { ReactNode } from 'react';
|
||||
import { Arrow } from '@/components/icons';
|
||||
import styles from './ChangeLabel.module.css';
|
||||
|
||||
export function ChangeLabel({
|
||||
|
|
@ -35,7 +36,7 @@ export function ChangeLabel({
|
|||
>
|
||||
{!neutral && (
|
||||
<Icon rotate={positive ? -90 : 90} size={size}>
|
||||
<Icons.Arrow />
|
||||
<Arrow />
|
||||
</Icon>
|
||||
)}
|
||||
<Text>{children || value}</Text>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { LinkButton } from '@/components/common/LinkButton';
|
|||
import { DEFAULT_ANIMATION_DURATION } from '@/lib/constants';
|
||||
import { percentFilter } from '@/lib/filters';
|
||||
import { useNavigation, useWebsiteMetricsQuery, useMessages, useFormat } from '@/components/hooks';
|
||||
import { Icons } from '@/components/icons';
|
||||
import { Arrow } from '@/components/icons';
|
||||
import { ListTable, ListTableProps } from './ListTable';
|
||||
|
||||
export interface MetricsTableProps extends ListTableProps {
|
||||
|
|
@ -98,7 +98,7 @@ export function MetricsTable({
|
|||
<LinkButton href={renderUrl({ view: type })} variant="quiet">
|
||||
<Text>{formatMessage(labels.more)}</Text>
|
||||
<Icon size="sm">
|
||||
<Icons.Arrow />
|
||||
<Arrow />
|
||||
</Icon>
|
||||
</LinkButton>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
import * as React from 'react';
|
||||
import type { SVGProps } from 'react';
|
||||
const SvgEye = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512" {...props}>
|
||||
<path d="M288 144a111 111 0 0 0-31.24 5 55.4 55.4 0 0 1 7.24 27 56 56 0 0 1-56 56 55.4 55.4 0 0 1-27-7.24A111.71 111.71 0 1 0 288 144m284.52 97.4C518.29 135.59 410.93 64 288 64S57.68 135.64 3.48 241.41a32.35 32.35 0 0 0 0 29.19C57.71 376.41 165.07 448 288 448s230.32-71.64 284.52-177.41a32.35 32.35 0 0 0 0-29.19M288 400c-98.65 0-189.09-55-237.93-144C98.91 167 189.34 112 288 112s189.09 55 237.93 144C477.1 345 386.66 400 288 400" />
|
||||
</svg>
|
||||
);
|
||||
export default SvgEye;
|
||||
|
|
@ -3,27 +3,22 @@ export { default as BarChart } from './BarChart';
|
|||
export { default as Bars } from './Bars';
|
||||
export { default as Bolt } from './Bolt';
|
||||
export { default as Bookmark } from './Bookmark';
|
||||
export { default as Calendar } from './Calendar';
|
||||
export { default as Change } from './Change';
|
||||
export { default as Clock } from './Clock';
|
||||
export { default as Compare } from './Compare';
|
||||
export { default as Dashboard } from './Dashboard';
|
||||
export { default as Expand } from './Expand';
|
||||
export { default as Eye } from './Eye';
|
||||
export { default as Flag } from './Flag';
|
||||
export { default as Funnel } from './Funnel';
|
||||
export { default as Gear } from './Gear';
|
||||
export { default as Globe } from './Globe';
|
||||
export { default as Lightbulb } from './Lightbulb';
|
||||
export { default as Lightning } from './Lightning';
|
||||
export { default as Link } from './Link';
|
||||
export { default as Location } from './Location';
|
||||
export { default as Lock } from './Lock';
|
||||
export { default as LogoWhite } from './LogoWhite';
|
||||
export { default as Logo } from './Logo';
|
||||
export { default as Magnet } from './Magnet';
|
||||
export { default as Money } from './Money';
|
||||
export { default as Moon } from './Moon';
|
||||
export { default as Network } from './Network';
|
||||
export { default as Nodes } from './Nodes';
|
||||
export { default as Overview } from './Overview';
|
||||
|
|
@ -34,10 +29,8 @@ export { default as Redo } from './Redo';
|
|||
export { default as Reports } from './Reports';
|
||||
export { default as Security } from './Security';
|
||||
export { default as Speaker } from './Speaker';
|
||||
export { default as Sun } from './Sun';
|
||||
export { default as Tag } from './Tag';
|
||||
export { default as Target } from './Target';
|
||||
export { default as User } from './User';
|
||||
export { default as Users } from './Users';
|
||||
export { default as Visitor } from './Visitor';
|
||||
export { default as Website } from './Website';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue