Prevent collector snapshot rerender stalls
Memoize the collector content across unrelated app snapshots and release collapsed child rows from the DOM after their closing animation. Draw the idle speed sparkline as a pixel-aligned one-physical-pixel stroke so its thickness remains uniform across display scales.
This commit is contained in:
+29
-7
@@ -74,8 +74,8 @@ import {
|
||||
type CollectorWorkspaceFilter
|
||||
} from "./views/collector/collector-model";
|
||||
import {
|
||||
CollectorContent,
|
||||
CollectorInputDialog,
|
||||
MemoizedCollectorContent,
|
||||
CollectorSidebar,
|
||||
CollectorToolbar,
|
||||
type CollectorViewActions
|
||||
@@ -1120,6 +1120,15 @@ export function readDownloadSpeedSparklinePalette(
|
||||
};
|
||||
}
|
||||
|
||||
export function getIdleSparklineStroke(cssHeight: number, devicePixelRatio: number): { y: number; lineWidth: number } {
|
||||
const dpr = Number.isFinite(devicePixelRatio) && devicePixelRatio > 0 ? devicePixelRatio : 1;
|
||||
const baseline = Math.max(0, cssHeight - 2);
|
||||
return {
|
||||
y: (Math.floor(baseline * dpr) + 0.5) / dpr,
|
||||
lineWidth: 1 / dpr
|
||||
};
|
||||
}
|
||||
|
||||
export function appendBandwidthSample(
|
||||
history: { time: number; speed: number }[],
|
||||
speed: number,
|
||||
@@ -1337,16 +1346,29 @@ const DownloadSpeedSparkline = memo(function DownloadSpeedSparkline({ speedBps,
|
||||
const rootStyle = getComputedStyle(document.documentElement);
|
||||
const palette = readDownloadSpeedSparklinePalette((property) => rootStyle.getPropertyValue(property));
|
||||
|
||||
let maxV = 0;
|
||||
for (const v of hist) if (v > maxV) maxV = v;
|
||||
maxV = Math.max(maxV, 1024 * 1024);
|
||||
let maxV = 0;
|
||||
for (const v of hist) if (v > maxV) maxV = v;
|
||||
const idle = maxV <= 0;
|
||||
maxV = Math.max(maxV, 1024 * 1024);
|
||||
|
||||
const pad = 2;
|
||||
const h = cssH - pad * 2;
|
||||
const step = cssW / (DOWNLOAD_SPEED_MAX_SAMPLES - 1);
|
||||
const startIdx = DOWNLOAD_SPEED_MAX_SAMPLES - hist.length;
|
||||
const px = (i: number): number => (startIdx + i) * step;
|
||||
const py = (v: number): number => pad + h - (v / maxV) * h;
|
||||
const px = (i: number): number => (startIdx + i) * step;
|
||||
const py = (v: number): number => pad + h - (v / maxV) * h;
|
||||
|
||||
if (idle) {
|
||||
const stroke = getIdleSparklineStroke(cssH, dpr);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(px(0), stroke.y);
|
||||
ctx.lineTo(px(hist.length - 1), stroke.y);
|
||||
ctx.strokeStyle = palette.accent;
|
||||
ctx.lineWidth = stroke.lineWidth;
|
||||
ctx.lineCap = "butt";
|
||||
ctx.stroke();
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(px(0), py(hist[0]));
|
||||
@@ -6245,7 +6267,7 @@ export function App(): ReactElement {
|
||||
>
|
||||
<main className="md-runtime-view-content">
|
||||
{tab === "collector" && (
|
||||
<CollectorContent actions={collectorActions} model={collectorViewModel} />
|
||||
<MemoizedCollectorContent actions={collectorActions} model={collectorViewModel} />
|
||||
)}
|
||||
|
||||
{tab === "downloads" && <DownloadsContent actions={downloadsActions} model={downloadsViewModel} />}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ChangeEvent, ReactElement } from "react";
|
||||
import { memo, useEffect, useState, type ChangeEvent, type ReactElement } from "react";
|
||||
import { formatDateTime, formatHosterLabel, humanSize } from "../../download-format";
|
||||
import { DataTable, DataTableBody, DataTableEmpty, DataTableHeader } from "../../ui/DataTable";
|
||||
import { Dialog } from "../../ui/Dialog";
|
||||
@@ -165,7 +165,19 @@ function CollectorPackageGroup({ row, model, actions, selected }: {
|
||||
const allSelected = row.selectedCount === row.totalCount;
|
||||
const partiallySelected = row.selectedCount > 0 && !allSelected;
|
||||
const animateItems = model.animationsEnabled && row.allLinks.length <= 64;
|
||||
const renderItems = !row.collapsed || animateItems;
|
||||
const [renderItems, setRenderItems] = useState(!row.collapsed);
|
||||
useEffect(() => {
|
||||
if (!row.collapsed) {
|
||||
setRenderItems(true);
|
||||
return;
|
||||
}
|
||||
if (!animateItems) {
|
||||
setRenderItems(false);
|
||||
return;
|
||||
}
|
||||
const timer = window.setTimeout(() => setRenderItems(false), 300);
|
||||
return () => window.clearTimeout(timer);
|
||||
}, [animateItems, row.collapsed]);
|
||||
return (
|
||||
<div className={`collector-package-group${row.collapsed ? " is-collapsed" : ""}${model.animationsEnabled ? " is-motion-enabled" : ""}`} role="rowgroup">
|
||||
<div className={`collector-package-row${row.selectedCount > 0 ? " is-selected" : ""}`} role="row">
|
||||
@@ -256,16 +268,21 @@ export function CollectorContent({ model, actions }: CollectorViewProps): ReactE
|
||||
);
|
||||
}
|
||||
|
||||
export const MemoizedCollectorContent = memo(
|
||||
CollectorContent,
|
||||
(previous, next) => previous.model === next.model
|
||||
);
|
||||
|
||||
export function CollectorView({ model, actions, region = "all" }: CollectorViewProps): ReactElement {
|
||||
if (region === "sidebar") return <CollectorSidebar actions={actions} model={model} />;
|
||||
if (region === "toolbar") return <CollectorToolbar actions={actions} model={model} />;
|
||||
if (region === "content") return <CollectorContent actions={actions} model={model} />;
|
||||
if (region === "content") return <MemoizedCollectorContent actions={actions} model={model} />;
|
||||
return (
|
||||
<div className="collector-view">
|
||||
<CollectorSidebar actions={actions} model={model} />
|
||||
<div className="collector-view-main">
|
||||
<CollectorToolbar actions={actions} model={model} />
|
||||
<CollectorContent actions={actions} model={model} />
|
||||
<MemoizedCollectorContent actions={actions} model={model} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -251,6 +251,7 @@
|
||||
}
|
||||
|
||||
.collector-package-items-frame.is-animated {
|
||||
animation: collector-items-expand 300ms cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
transition: grid-template-rows 300ms cubic-bezier(0.2, 0.8, 0.2, 1), opacity 300ms ease;
|
||||
}
|
||||
|
||||
@@ -265,6 +266,17 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@keyframes collector-items-expand {
|
||||
from {
|
||||
grid-template-rows: 0fr;
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
grid-template-rows: 1fr;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.collector-column-select {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
|
||||
Reference in New Issue
Block a user