perf: improve mobile display

This commit is contained in:
moonrailgun 2024-05-12 17:02:03 +08:00
parent 6606b253d8
commit b2fb1832e1
11 changed files with 130 additions and 47 deletions

View File

@ -4,12 +4,22 @@ import { DateUnit } from '@tianji/shared';
import React from 'react'; import React from 'react';
import { formatDate, formatDateWithUnit } from '../utils/date'; import { formatDate, formatDateWithUnit } from '../utils/date';
import { Column, ColumnConfig } from '@ant-design/charts'; import { Column, ColumnConfig } from '@ant-design/charts';
import { useIsMobile } from '@/hooks/useIsMobile';
import { useTranslation } from '@i18next-toolkit/react';
export const TimeEventChart: React.FC<{ export const TimeEventChart: React.FC<{
labelMapping?: Record<string, string>;
data: { x: string; y: number; type: string }[]; data: { x: string; y: number; type: string }[];
unit: DateUnit; unit: DateUnit;
}> = React.memo((props) => { }> = React.memo((props) => {
const { colors } = useTheme(); const { colors } = useTheme();
const isMobile = useIsMobile();
const { t } = useTranslation();
const labelMapping = props.labelMapping ?? {
pageview: t('pageview'),
session: t('session'),
};
const config = useMemo( const config = useMemo(
() => () =>
@ -30,6 +40,13 @@ export const TimeEventChart: React.FC<{
title: (t) => formatDate(t), title: (t) => formatDate(t),
}, },
color: [colors.chart.pv, colors.chart.uv], color: [colors.chart.pv, colors.chart.uv],
legend: isMobile
? false
: {
itemName: {
formatter: (text) => labelMapping[text] ?? text,
},
},
xAxis: { xAxis: {
label: { label: {
autoHide: true, autoHide: true,
@ -38,7 +55,7 @@ export const TimeEventChart: React.FC<{
}, },
}, },
}) satisfies ColumnConfig, }) satisfies ColumnConfig,
[props.data, props.unit] [props.data, props.unit, props.labelMapping]
); );
return <Column {...config} />; return <Column {...config} />;

View File

@ -177,7 +177,7 @@ export const MonitorInfo: React.FC<MonitorInfoProps> = React.memo((props) => {
<div className="h-full w-full overflow-auto"> <div className="h-full w-full overflow-auto">
<Spin spinning={isLoading}> <Spin spinning={isLoading}>
<Space className="w-full" direction="vertical"> <Space className="w-full" direction="vertical">
<div className="flex justify-between"> <div className="flex flex-col-reverse sm:flex-row sm:justify-between">
<Space direction="vertical"> <Space direction="vertical">
<div> <div>
<ColorTag label={monitorInfo.type} /> <ColorTag label={monitorInfo.type} />

View File

@ -11,7 +11,7 @@ export const MonitorStatsBlock: React.FC<MonitorStatsBlockProps> = React.memo(
(props) => { (props) => {
return ( return (
<div> <div>
<div className="mb-0.5 font-bold"> <div className="mb-0.5 text-xs font-bold sm:text-base">
{props.title} {props.title}
{props.tooltip && ( {props.tooltip && (
<TipIcon className="ml-1" content={props.tooltip} /> <TipIcon className="ml-1" content={props.tooltip} />

View File

@ -17,7 +17,7 @@ export const WebsiteOnlineCount: React.FC<{
if (typeof count === 'number' && count > 0) { if (typeof count === 'number' && count > 0) {
return ( return (
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<div className="h-2.5 w-2.5 rounded-full bg-green-500" /> <div className="h-2.5 w-2.5 flex-shrink-0 rounded-full bg-green-500" />
<span> <span>
{count} {t('current visitor')} {count} {t('current visitor')}
</span> </span>

View File

@ -83,8 +83,8 @@ export const WebsiteOverview: React.FC<{
return ( return (
<Spin spinning={loading}> <Spin spinning={loading}>
<div className="flex"> <div className="flex flex-col-reverse sm:flex-row">
<div className="flex flex-1 items-center text-2xl font-bold"> <div className="flex flex-1 flex-col gap-2 text-2xl font-bold sm:flex-row sm:items-center">
<span className="mr-2" title={website.domain ?? ''}> <span className="mr-2" title={website.domain ?? ''}>
{website.name} {website.name}
</span> </span>
@ -102,7 +102,7 @@ export const WebsiteOverview: React.FC<{
</Link> </Link>
)} )}
<div className="ml-4 text-base font-normal"> <div className="text-base font-normal">
<WebsiteOnlineCount <WebsiteOnlineCount
workspaceId={website.workspaceId} workspaceId={website.workspaceId}
websiteId={website.id} websiteId={website.id}

View File

@ -0,0 +1,34 @@
import React, { useState } from 'react';
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet';
import { LuMenu } from 'react-icons/lu';
import { Button } from '@/components/ui/button';
import { ScrollArea } from '@/components/ui/scroll-area';
import { useGlobalEventSubscribe } from '@/utils/event';
export const MobileLayoutMenu: React.FC<{
list?: React.ReactNode;
}> = React.memo((props) => {
const [open, setOpen] = useState(false);
useGlobalEventSubscribe('commonListSelected', () => {
setOpen(false);
});
if (!props.list) {
return <div />;
}
return (
<Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger>
<Button variant="outline" size="icon">
<LuMenu />
</Button>
</SheetTrigger>
<SheetContent side="left" className="w-11/12">
<ScrollArea className="h-full">{props.list}</ScrollArea>
</SheetContent>
</Sheet>
);
});
MobileLayoutMenu.displayName = 'MobileLayoutMenu';

View File

@ -2,7 +2,6 @@ import React from 'react';
import { import {
LuAreaChart, LuAreaChart,
LuFilePieChart, LuFilePieChart,
LuMenu,
LuMonitorDot, LuMonitorDot,
LuMoreVertical, LuMoreVertical,
LuServer, LuServer,
@ -16,19 +15,8 @@ import { cn } from '@/utils/style';
import { Separator } from '@/components/ui/separator'; import { Separator } from '@/components/ui/separator';
import { LayoutProps } from './types'; import { LayoutProps } from './types';
import { UserConfig } from './UserConfig'; import { UserConfig } from './UserConfig';
import { ScrollArea } from '@/components/ui/scroll-area'; import { Drawer, DrawerContent, DrawerTrigger } from '@/components/ui/drawer';
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet'; import { MobileLayoutMenu } from './Menu';
import { Button } from '@/components/ui/button';
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from '@/components/ui/drawer';
export const MobileLayout: React.FC<LayoutProps> = React.memo((props) => { export const MobileLayout: React.FC<LayoutProps> = React.memo((props) => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -36,20 +24,7 @@ export const MobileLayout: React.FC<LayoutProps> = React.memo((props) => {
return ( return (
<div className="flex h-svh flex-col"> <div className="flex h-svh flex-col">
<div className="flex h-[52px] items-center justify-between px-2"> <div className="flex h-[52px] items-center justify-between px-2">
<Sheet> <MobileLayoutMenu list={props.list} />
<SheetTrigger disabled={!Boolean(props.list)}>
<Button
variant="outline"
size="icon"
disabled={!Boolean(props.list)}
>
<LuMenu />
</Button>
</SheetTrigger>
<SheetContent side="left" className="w-11/12">
<ScrollArea className="h-full">{props.list}</ScrollArea>
</SheetContent>
</Sheet>
<div className="rounded-md dark:bg-white/10"> <div className="rounded-md dark:bg-white/10">
<img className="m-auto h-8 w-8" src="/icon.svg" /> <img className="m-auto h-8 w-8" src="/icon.svg" />

View File

@ -172,7 +172,10 @@ function TelemetryDetailComponent() {
/> />
</Card.Grid> </Card.Grid>
<Card.Grid hoverable={false} className="min-h-[470px] !w-1/3"> <Card.Grid
hoverable={false}
className="min-h-[470px] !w-full sm:!w-1/3"
>
<TelemetryMetricsTable <TelemetryMetricsTable
telemetryId={telemetryId} telemetryId={telemetryId}
type="source" type="source"
@ -182,7 +185,10 @@ function TelemetryDetailComponent() {
/> />
</Card.Grid> </Card.Grid>
<Card.Grid hoverable={false} className="min-h-[470px] !w-1/3"> <Card.Grid
hoverable={false}
className="min-h-[470px] !w-full sm:!w-1/3"
>
<TelemetryMetricsTable <TelemetryMetricsTable
telemetryId={telemetryId} telemetryId={telemetryId}
type="event" type="event"
@ -192,7 +198,10 @@ function TelemetryDetailComponent() {
/> />
</Card.Grid> </Card.Grid>
<Card.Grid hoverable={false} className="min-h-[470px] !w-1/3"> <Card.Grid
hoverable={false}
className="min-h-[470px] !w-full sm:!w-1/3"
>
<TelemetryMetricsTable <TelemetryMetricsTable
telemetryId={telemetryId} telemetryId={telemetryId}
type="country" type="country"

View File

@ -83,7 +83,10 @@ function WebsiteDetailComponent() {
<Card.Grid hoverable={false} className="!w-full"> <Card.Grid hoverable={false} className="!w-full">
<WebsiteOverview website={website} showDateFilter={true} /> <WebsiteOverview website={website} showDateFilter={true} />
</Card.Grid> </Card.Grid>
<Card.Grid hoverable={false} className="min-h-[470px] !w-1/2"> <Card.Grid
hoverable={false}
className="!w-full sm:min-h-[470px] sm:!w-1/2"
>
<WebsiteMetricsTable <WebsiteMetricsTable
websiteId={websiteId} websiteId={websiteId}
type="url" type="url"
@ -92,7 +95,10 @@ function WebsiteDetailComponent() {
endAt={endAt} endAt={endAt}
/> />
</Card.Grid> </Card.Grid>
<Card.Grid hoverable={false} className="min-h-[470px] !w-1/2"> <Card.Grid
hoverable={false}
className="!w-full sm:min-h-[470px] sm:!w-1/2"
>
<WebsiteMetricsTable <WebsiteMetricsTable
websiteId={websiteId} websiteId={websiteId}
type="referrer" type="referrer"
@ -101,7 +107,10 @@ function WebsiteDetailComponent() {
endAt={endAt} endAt={endAt}
/> />
</Card.Grid> </Card.Grid>
<Card.Grid hoverable={false} className="min-h-[470px] !w-1/3"> <Card.Grid
hoverable={false}
className="!w-full sm:min-h-[470px] sm:!w-1/3"
>
<WebsiteMetricsTable <WebsiteMetricsTable
websiteId={websiteId} websiteId={websiteId}
type="browser" type="browser"
@ -110,7 +119,10 @@ function WebsiteDetailComponent() {
endAt={endAt} endAt={endAt}
/> />
</Card.Grid> </Card.Grid>
<Card.Grid hoverable={false} className="min-h-[470px] !w-1/3"> <Card.Grid
hoverable={false}
className="!w-full sm:min-h-[470px] sm:!w-1/3"
>
<WebsiteMetricsTable <WebsiteMetricsTable
websiteId={websiteId} websiteId={websiteId}
type="os" type="os"
@ -119,7 +131,10 @@ function WebsiteDetailComponent() {
endAt={endAt} endAt={endAt}
/> />
</Card.Grid> </Card.Grid>
<Card.Grid hoverable={false} className="min-h-[470px] !w-1/3"> <Card.Grid
hoverable={false}
className="!w-full sm:min-h-[470px] sm:!w-1/3"
>
<WebsiteMetricsTable <WebsiteMetricsTable
websiteId={websiteId} websiteId={websiteId}
type="device" type="device"
@ -128,7 +143,10 @@ function WebsiteDetailComponent() {
endAt={endAt} endAt={endAt}
/> />
</Card.Grid> </Card.Grid>
<Card.Grid hoverable={false} className="min-h-[470px] !w-1/3"> <Card.Grid
hoverable={false}
className="!w-full sm:min-h-[470px] sm:!w-1/3"
>
<WebsiteMetricsTable <WebsiteMetricsTable
websiteId={websiteId} websiteId={websiteId}
type="title" type="title"
@ -137,7 +155,10 @@ function WebsiteDetailComponent() {
endAt={endAt} endAt={endAt}
/> />
</Card.Grid> </Card.Grid>
<Card.Grid hoverable={false} className="min-h-[470px] !w-1/3"> <Card.Grid
hoverable={false}
className="!w-full sm:min-h-[470px] sm:!w-1/3"
>
<WebsiteMetricsTable <WebsiteMetricsTable
websiteId={websiteId} websiteId={websiteId}
type="country" type="country"
@ -150,7 +171,10 @@ function WebsiteDetailComponent() {
<WebsiteVisitorMapBtn websiteId={websiteId} /> <WebsiteVisitorMapBtn websiteId={websiteId} />
</div> </div>
</Card.Grid> </Card.Grid>
<Card.Grid hoverable={false} className="min-h-[470px] !w-1/3"> <Card.Grid
hoverable={false}
className="!w-full sm:min-h-[470px] sm:!w-1/3"
>
<WebsiteMetricsTable <WebsiteMetricsTable
websiteId={websiteId} websiteId={websiteId}
type="event" type="event"

24
src/client/utils/event.ts Normal file
View File

@ -0,0 +1,24 @@
import { useEvent } from '@/hooks/useEvent';
import { EventEmitter } from 'eventemitter-strict';
import { useEffect } from 'react';
export interface GlobalEventMap {
commonListSelected: () => void;
}
export const globalEventBus = new EventEmitter<GlobalEventMap>();
export function useGlobalEventSubscribe<T extends keyof GlobalEventMap>(
eventName: T,
callback: GlobalEventMap[T]
) {
const fn = useEvent(callback);
useEffect(() => {
globalEventBus.on(eventName, fn);
return () => {
globalEventBus.off(eventName, fn);
};
}, [eventName]);
}

View File

@ -82,7 +82,7 @@ function HomepageMain() {
</div> </div>
<div className="text-center"> <div className="text-center">
<div className="rounded-lg border-8 border-solid border-gray-200 shadow-lg"> <div className="rounded-lg border-8 border-solid border-gray-200 shadow-lg dark:border-gray-800">
<Carousel <Carousel
className="cursor-move" className="cursor-move"
showThumbs={false} showThumbs={false}