perf: improve healthbar display, will responsive with container size

This commit is contained in:
moonrailgun 2024-08-18 03:58:10 +08:00
parent 6fecde0caa
commit 3990b0a872
6 changed files with 108 additions and 24 deletions

View File

@ -1,3 +1,4 @@
import { useResizeObserver } from '@/hooks/useResizeObserver';
import clsx from 'clsx'; import clsx from 'clsx';
import React from 'react'; import React from 'react';
@ -14,33 +15,46 @@ export interface HealthBarProps {
} }
export const HealthBar: React.FC<HealthBarProps> = React.memo((props) => { export const HealthBar: React.FC<HealthBarProps> = React.memo((props) => {
const size = props.size ?? 'small'; const size = props.size ?? 'small';
const [containerRef, containerRect] = useResizeObserver();
const cellCount = props.beats.length;
const cellNeedWidth = size === 'small' ? 8 : 12; // include gap
return ( return (
<div <div
ref={containerRef}
className={clsx('flex', { className={clsx('flex', {
'gap-[3px]': size === 'small', 'gap-[3px]': size === 'small',
'gap-1': size === 'large', 'gap-1': size === 'large',
})} })}
> >
{props.beats.map((beat, i) => ( {props.beats
<div .slice(
key={i} Math.floor(
title={beat.title} Math.max(cellNeedWidth * cellCount - containerRect.width, 0) /
className={clsx( cellNeedWidth
'rounded-full transition-transform hover:scale-150', ),
{ cellCount
'h-4 w-[5px]': size === 'small', )
'h-8 w-2': size === 'large', .map((beat, i) => (
}, <div
{ key={i}
'bg-green-500': beat.status === 'health', title={beat.title}
'bg-red-600': beat.status === 'error', className={clsx(
'bg-yellow-400': beat.status === 'warning', 'rounded-full transition-transform hover:scale-150',
'bg-gray-400': beat.status === 'none', {
} 'h-4 w-[5px]': size === 'small',
)} 'h-8 w-2': size === 'large',
/> },
))} {
'bg-green-500': beat.status === 'health',
'bg-red-600': beat.status === 'error',
'bg-yellow-400': beat.status === 'warning',
'bg-gray-400': beat.status === 'none',
}
)}
/>
))}
</div> </div>
); );
}); });

View File

@ -120,7 +120,7 @@ export const MonitorHealthBar: React.FC<MonitorHealthBarProps> = React.memo(
</span> </span>
)} )}
<div className="flex-1"> <div className="flex-1 overflow-hidden">
<HealthBar size={size} beats={beats} /> <HealthBar size={size} beats={beats} />
</div> </div>
@ -129,7 +129,7 @@ export const MonitorHealthBar: React.FC<MonitorHealthBarProps> = React.memo(
{last(beats)?.status === 'health' ? ( {last(beats)?.status === 'health' ? (
<div <div
className={clsx( className={clsx(
'rounded-full bg-green-500 px-4 py-1 text-lg font-bold text-white', 'ml-2 rounded-full bg-green-500 px-4 py-1 text-lg font-bold text-white',
{ {
'text-sm': size === 'small', 'text-sm': size === 'small',
} }
@ -140,7 +140,7 @@ export const MonitorHealthBar: React.FC<MonitorHealthBarProps> = React.memo(
) : last(beats)?.status === 'error' ? ( ) : last(beats)?.status === 'error' ? (
<div <div
className={clsx( className={clsx(
'rounded-full bg-red-600 px-4 py-1 text-lg font-bold text-white', 'ml-2 rounded-full bg-red-600 px-4 py-1 text-lg font-bold text-white',
{ {
'text-sm': size === 'small', 'text-sm': size === 'small',
} }
@ -151,7 +151,7 @@ export const MonitorHealthBar: React.FC<MonitorHealthBarProps> = React.memo(
) : ( ) : (
<div <div
className={clsx( className={clsx(
'rounded-full bg-gray-400 px-4 py-1 text-lg font-bold text-white', 'ml-2 rounded-full bg-gray-400 px-4 py-1 text-lg font-bold text-white',
{ {
'text-sm': size === 'small', 'text-sm': size === 'small',
} }

View File

@ -1,4 +1,3 @@
import { CalendarOutlined } from '@ant-design/icons';
import dayjs, { Dayjs } from 'dayjs'; import dayjs, { Dayjs } from 'dayjs';
import { useMemo, useReducer } from 'react'; import { useMemo, useReducer } from 'react';
import { getMinimumUnit } from '@tianji/shared'; import { getMinimumUnit } from '@tianji/shared';

View File

@ -0,0 +1,70 @@
/**
* Copy from https://github.com/mantinedev/mantine/blob/master/packages/@mantine/hooks/src/use-resize-observer/use-resize-observer.ts
*/
import { useRef, useState, useMemo, useEffect } from 'react';
type ObserverRect = Omit<DOMRectReadOnly, 'toJSON'>;
const defaultState: ObserverRect = {
x: 0,
y: 0,
width: 0,
height: 0,
top: 0,
left: 0,
bottom: 0,
right: 0,
};
export function useResizeObserver<T extends HTMLElement = any>(
options?: ResizeObserverOptions
) {
const frameID = useRef(0);
const ref = useRef<T>(null);
const [rect, setRect] = useState<ObserverRect>(defaultState);
const observer = useMemo(
() =>
typeof window !== 'undefined'
? new ResizeObserver((entries: any) => {
const entry = entries[0];
if (entry) {
cancelAnimationFrame(frameID.current);
frameID.current = requestAnimationFrame(() => {
if (ref.current) {
setRect(entry.contentRect);
}
});
}
})
: null,
[]
);
useEffect(() => {
if (ref.current) {
observer?.observe(ref.current, options);
}
return () => {
observer?.disconnect();
if (frameID.current) {
cancelAnimationFrame(frameID.current);
}
};
}, [ref.current]);
return [ref, rect] as const;
}
export function useElementSize<T extends HTMLElement = any>(
options?: ResizeObserverOptions
) {
const [ref, { width, height }] = useResizeObserver<T>(options);
return { ref, width, height };
}

View File

@ -133,6 +133,7 @@ export const workspaceRouter = router({
workspaceId: z.string(), workspaceId: z.string(),
}) })
) )
.output(z.void())
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
const { workspaceId } = input; const { workspaceId } = input;
const userId = ctx.user.id; const userId = ctx.user.id;