Limit terminal grid to 3x2
This commit is contained in:
@@ -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")'),
|
||||
|
||||
Reference in New Issue
Block a user