Limit terminal grid to 3x2

This commit is contained in:
2569718930@qq.com
2026-06-10 15:52:23 +08:00
parent 5a7d302d11
commit f171049f60
4 changed files with 44 additions and 21 deletions
@@ -271,29 +271,38 @@ const TERM = {
closed: { en: "Closed", zh: "已关闭" },
} as const;
const MAX_TERMINAL_GRID_SIDE = 3;
const MAX_TERMINAL_CHARTS = 9;
const MAX_TERMINAL_GRID_COLS = 3;
const MAX_TERMINAL_GRID_ROWS = 2;
const MAX_TERMINAL_CHARTS = 6;
const MOBILE_TERMINAL_CHARTS = 1;
const DEFAULT_TERMINAL_GRID_SIDE = 2;
const TERMINAL_SLOTS_STORAGE_KEY = "polyweather_terminal_slots";
function clampGridSide(value: number) {
function clampGridDimension(value: number, max: number) {
if (!Number.isFinite(value)) return DEFAULT_TERMINAL_GRID_SIDE;
return Math.max(1, Math.min(MAX_TERMINAL_GRID_SIDE, Math.floor(value)));
return Math.max(1, Math.min(max, Math.floor(value)));
}
function getStoredGridSide(key: string) {
function clampGridCols(value: number) {
return clampGridDimension(value, MAX_TERMINAL_GRID_COLS);
}
function clampGridRows(value: number) {
return clampGridDimension(value, MAX_TERMINAL_GRID_ROWS);
}
function getStoredGridSide(key: string, max: number) {
if (typeof window === "undefined") return DEFAULT_TERMINAL_GRID_SIDE;
try {
const raw = localStorage.getItem(key);
if (!raw) return DEFAULT_TERMINAL_GRID_SIDE;
return clampGridSide(parseInt(raw, 10));
return clampGridDimension(parseInt(raw, 10), max);
} catch {}
return DEFAULT_TERMINAL_GRID_SIDE;
}
function getSlotCount(cols: number, rows: number) {
return Math.min(MAX_TERMINAL_CHARTS, clampGridSide(cols) * clampGridSide(rows));
return Math.min(MAX_TERMINAL_CHARTS, clampGridCols(cols) * clampGridRows(rows));
}
function normalizeSlotList(slots: Array<string | null>, totalSlots: number) {
@@ -717,11 +726,11 @@ function PolyWeatherTerminal({
}, []);
const [gridCols, setGridCols] = useState<number>(() => {
return getStoredGridSide("polyweather_terminal_grid_cols");
return getStoredGridSide("polyweather_terminal_grid_cols", MAX_TERMINAL_GRID_COLS);
});
const [gridRows, setGridRows] = useState<number>(() => {
return getStoredGridSide("polyweather_terminal_grid_rows");
return getStoredGridSide("polyweather_terminal_grid_rows", MAX_TERMINAL_GRID_ROWS);
});
const totalSlots = getSlotCount(gridCols, gridRows);
@@ -783,8 +792,8 @@ function PolyWeatherTerminal({
}, []);
const handleSetGridSize = (cols: number, rows: number) => {
const safeCols = clampGridSide(cols);
const safeRows = clampGridSide(rows);
const safeCols = clampGridCols(cols);
const safeRows = clampGridRows(rows);
const nextTotalSlots = getSlotCount(safeCols, safeRows);
setGridCols(safeCols);
@@ -66,7 +66,7 @@ export function GridLayoutSelector({
setHoveredRows(0);
}}
>
{[1, 2, 3].map((r) =>
{[1, 2].map((r) =>
[1, 2, 3].map((c) => {
const isHighlighted = c <= previewCols && r <= previewRows;
return (
@@ -83,13 +83,13 @@ const legendItems: Record<"zh" | "en", GuideCopy[]> = {
const operations: Record<"zh" | "en", GuideCopy[]> = {
zh: [
{ title: "布局", body: "右上角可切换 1x1 到 3x3,适合从单城复盘切到多城巡检。" },
{ title: "布局", body: "右上角可切换 1x1 到 3x2,适合从单城复盘切到多城巡检。" },
{ title: "换城市", body: "点击图表标题栏城市名,在当前卡片内搜索并替换城市。" },
{ title: "高温模式", body: "卡片右上角高温按钮用于聚焦最高温兑现窗口。" },
{ title: "曲线显隐", body: "图例可自定义显示机场报文、模型线和跑道明细。" },
],
en: [
{ title: "Layout", body: "Switch from 1x1 to 3x3 in the top-right control for review or multi-city scanning." },
{ title: "Layout", body: "Switch from 1x1 to 3x2 in the top-right control for review or multi-city scanning." },
{ title: "Change city", body: "Click the city name in a chart header to search and replace that slot." },
{ title: "High mode", body: "Use the High button to focus the chart on the high-temperature payoff window." },
{ title: "Layer toggles", body: "Use the legend to customize airport reports, model lines, and runway details." },
@@ -16,6 +16,10 @@ export function runTests() {
path.join(projectRoot, "components", "dashboard", "scan-terminal", "GridLayoutSelector.tsx"),
"utf8",
);
const usageGuideSource = fs.readFileSync(
path.join(projectRoot, "components", "dashboard", "scan-terminal", "UsageGuideDashboard.tsx"),
"utf8",
);
const chartSource = fs.readFileSync(
path.join(projectRoot, "components", "dashboard", "scan-terminal", "LiveTemperatureThresholdChart.tsx"),
"utf8",
@@ -42,17 +46,18 @@ export function runTests() {
);
assert(
dashboardSource.includes("MAX_TERMINAL_CHARTS = 9"),
"terminal dashboard must cap the desktop homepage at 9 charts",
dashboardSource.includes("MAX_TERMINAL_CHARTS = 6"),
"terminal dashboard must cap the desktop homepage at 6 charts",
);
assert(
dashboardSource.includes("MOBILE_TERMINAL_CHARTS = 1"),
"terminal dashboard must document that mobile renders exactly one chart",
);
assert(
dashboardSource.includes("clampGridSide") &&
dashboardSource.includes("MAX_TERMINAL_GRID_ROWS = 2") &&
dashboardSource.includes("clampGridRows") &&
dashboardSource.includes("Math.min(MAX_TERMINAL_CHARTS"),
"terminal grid dimensions must be clamped before rendering or persisting",
"terminal grid dimensions must clamp saved 3x3 layouts down to the 3x2 maximum before rendering or persisting",
);
assert(
dashboardSource.includes("mobileChartRow") &&
@@ -83,8 +88,17 @@ export function runTests() {
);
assert(
selectorSource.includes("[1, 2, 3].map") &&
selectorSource.includes("grid grid-cols-3"),
"grid selector must expose at most a 3 by 3 chart layout",
selectorSource.includes("[1, 2].map") &&
selectorSource.includes("grid grid-cols-3") &&
!selectorSource.includes("[1, 2, 3].map((r)"),
"grid selector must expose at most a 3 by 2 chart layout",
);
assert(
usageGuideSource.includes("1x1 到 3x2") &&
usageGuideSource.includes("1x1 to 3x2") &&
!usageGuideSource.includes("1x1 到 3x3") &&
!usageGuideSource.includes("1x1 to 3x3"),
"terminal guide must describe the new 3x2 maximum layout instead of 3x3",
);
assert(
dashboardSource.includes("if (!cityInSlot || !rowForSlot)") &&
@@ -135,7 +149,7 @@ export function runTests() {
chartSource.includes('compact ? "min-h-0" : "min-h-[300px]"') &&
chartCanvasSource.includes("const minChartHeight = compact ? 120 : 220") &&
chartCanvasSource.includes('compact ? "min-h-[120px]" : "min-h-[220px]"'),
"compact grid charts must not force desktop minimum heights that get clipped inside 3x3 terminal slots",
"compact grid charts must not force desktop minimum heights that get clipped inside dense terminal slots",
);
const signedOutBlock = dashboardSource.slice(
dashboardSource.indexOf('if (event === "SIGNED_OUT")'),