// Top.jsx — 顶部本子横条 + 时空轴(左栏竖向时间轴 / 展开:线路图 + 数字地图)
const { useState: useStateTop } = React;
// ============ 本子横条(书架式 · 统一深色书脊)============
function NotebookRow({ notebooks, counts, active, onPick, onAdd, onNewNote }) {
const activeNb = notebooks.find(nb => nb.id === active);
return (
本子
{notebooks.map(nb => (
))}
{/* 选中一个本子后,在这里写新的一页 */}
{activeNb && (
)}
);
}
// ============ 时空轴 · 左栏(时间 × 地点合一 · 以现在为主轴 · 可伸缩)============
function SpacetimeColumn({ onOpenNote, onExpand, selected, cityFilter, setCityFilter }) {
const [scale, setScale] = useStateTop(1); // 时间疏密(伸缩)
const asc = buildStations();
const all = [...asc].reverse(); // 以现在为主轴 → 最新在上,向下走入过去
const cities = [...new Set(asc.map(s => s.note.city))];
const stations = cityFilter ? all.filter(s => s.note.city === cityFilter) : all;
const gap = Math.round(4 * scale);
let lastYear = null;
return (
{cities.map(c => (
))}
{/* 伸缩控制:把时间轴拉疏 / 收密 */}
时间疏密
{/* 现在 · 主轴锚点 */}
{!cityFilter && (
)}
{stations.map((st, i) => {
const showYear = st.note.year !== lastYear;
lastYear = st.note.year;
const prevCity = i > 0 ? stations[i - 1].note.city : null;
const transfer = prevCity && prevCity !== st.note.city;
const place = st.note.city ? st.note.city + (st.note.scene ? ' · ' + st.note.scene : '') : '未记地点';
return (
{showYear && {st.note.year}
}
);
})}
更早的时空 · 向下滑
);
}
// ============ 展开覆层:线路图 + 数字地图 ============
function MetroMap({ onClose, onOpenNote }) {
const [view, setView] = useStateTop('route'); // route | map
const stations = buildStations();
const cities = [...new Set(stations.map(s => s.note.city))];
return (
{ if (e.target === e.currentTarget) onClose(); }}>
时空地图
你的旅程 · {stations.length} 站 · 跨越 {cities.length} 城 · 2021 — 2026
{view === 'route'
?
: }
{view === 'route' ? '时间自上而下,城市分列成线 — 点任意一站,回到那个时空的笔记' : '按经纬度落点的数字地图 — 虚线是你走过的顺序'}
);
}
// —— 线路图:时间(行) × 城市(列) 地铁式 —— //
function RouteDiagram({ stations, cities, onOpenNote }) {
const colX = {}; cities.forEach((c, i) => colX[c] = 150 + i * 150);
const rowY = i => 70 + i * 74;
const W = 150 + cities.length * 150 + 40;
const H = rowY(stations.length - 1) + 60;
const pos = stations.map((st, i) => ({ x: colX[st.note.city], y: rowY(i), st, i }));
const segs = [];
for (let i = 1; i < pos.length; i++) {
const a = pos[i - 1], b = pos[i];
segs.push({ a, b, color: b.st.line, transfer: a.st.note.city !== b.st.note.city });
}
let lastYear = null;
return (
);
}
// —— 数字地图:东亚经纬度落点 —— //
function DigitalMap({ stations, cities, onOpenNote }) {
const W = 760, H = 460;
const lonMin = 110, lonMax = 140, latMin = 19, latMax = 38;
const px = lon => 60 + (lon - lonMin) / (lonMax - lonMin) * (W - 120);
const py = lat => 40 + (latMax - lat) / (latMax - latMin) * (H - 90);
// 经纬网格
const lonLines = []; for (let lo = 110; lo <= 140; lo += 5) lonLines.push(lo);
const latLines = []; for (let la = 20; la <= 35; la += 5) latLines.push(la);
// 城市落点(去重)
const cityPos = {}; cities.forEach(c => { cityPos[c] = { x: px(CITY_GEO[c].lon), y: py(CITY_GEO[c].lat) }; });
const counts = {}; stations.forEach(s => counts[s.note.city] = (counts[s.note.city] || 0) + 1);
// 行程弧线(按时间顺序的城市变化)
const path = [];
let prev = null;
stations.forEach(s => { if (s.note.city !== prev) { path.push(s.note.city); prev = s.note.city; } });
const arcs = [];
for (let i = 1; i < path.length; i++) {
const a = cityPos[path[i - 1]], b = cityPos[path[i]];
const mx = (a.x + b.x) / 2, my = (a.y + b.y) / 2 - Math.abs(b.x - a.x) * .18 - 14;
arcs.push({ d: `M${a.x} ${a.y} Q${mx} ${my} ${b.x} ${b.y}`, color: CITY_LINE[path[i]] });
}
return (
);
}
Object.assign(window, { NotebookRow, SpacetimeColumn, MetroMap });