style: enlarge calendar tiles to 12.5px with comfortable 16px step and auto-scroll

This commit is contained in:
katR 2026-10-09 23:34:07 +05:30
parent 2e3f1d8a25
commit 4b907b9dcd
4 changed files with 152 additions and 116 deletions

View file

@ -70,6 +70,7 @@ function HeatmapView() {
const [selectedDate, setSelectedDate] = (0, import_react.useState)(null); const [selectedDate, setSelectedDate] = (0, import_react.useState)(null);
const [hoveredDay, setHoveredDay] = (0, import_react.useState)(null); const [hoveredDay, setHoveredDay] = (0, import_react.useState)(null);
const [tooltipPos, setTooltipPos] = (0, import_react.useState)(null); const [tooltipPos, setTooltipPos] = (0, import_react.useState)(null);
const scrollContainerRef = import_react.default.useRef(null);
const fetchStats = () => { const fetchStats = () => {
setLoading(true); setLoading(true);
fetch("/api/dsh-token-heatmap/stats").then((r) => { fetch("/api/dsh-token-heatmap/stats").then((r) => {
@ -86,6 +87,11 @@ function HeatmapView() {
(0, import_react.useEffect)(() => { (0, import_react.useEffect)(() => {
fetchStats(); fetchStats();
}, []); }, []);
(0, import_react.useEffect)(() => {
if (scrollContainerRef.current) {
scrollContainerRef.current.scrollLeft = scrollContainerRef.current.scrollWidth;
}
}, [calendarGrid]);
const activeView = (0, import_react.useMemo)(() => { const activeView = (0, import_react.useMemo)(() => {
if (!data) return null; if (!data) return null;
if (selectedSessionId === "all") { if (selectedSessionId === "all") {
@ -436,101 +442,115 @@ function HeatmapView() {
/* @__PURE__ */ import_react.default.createElement("span", null, "Contribution Calendar"), /* @__PURE__ */ import_react.default.createElement("span", null, "Contribution Calendar"),
summary.peakDay && /* @__PURE__ */ import_react.default.createElement("span", { style: { fontSize: 12, fontWeight: 400, color: "var(--dsw-alias-label-secondary)" } }, "Peak day: ", /* @__PURE__ */ import_react.default.createElement("strong", { style: { color: "var(--dsw-alias-label-primary)" } }, summary.peakDay.date), " (", formatNumber(summary.peakDay.tokens), " tokens)") summary.peakDay && /* @__PURE__ */ import_react.default.createElement("span", { style: { fontSize: 12, fontWeight: 400, color: "var(--dsw-alias-label-secondary)" } }, "Peak day: ", /* @__PURE__ */ import_react.default.createElement("strong", { style: { color: "var(--dsw-alias-label-primary)" } }, summary.peakDay.date), " (", formatNumber(summary.peakDay.tokens), " tokens)")
), ),
calendarGrid && /* @__PURE__ */ import_react.default.createElement("div", { style: { width: "100%", overflow: "hidden" } }, /* @__PURE__ */ import_react.default.createElement( calendarGrid && /* @__PURE__ */ import_react.default.createElement(
"svg",
{
viewBox: "0 0 740 112",
style: {
width: "100%",
height: "auto",
display: "block",
overflow: "visible"
}
},
monthHeaders.map((m, i) => /* @__PURE__ */ import_react.default.createElement(
"text",
{
key: i,
x: 30 + m.col * 13.2,
y: 10,
fontSize: 10,
fill: "var(--dsw-alias-label-tertiary, #888888)",
fontFamily: "inherit"
},
m.label
)),
/* @__PURE__ */ import_react.default.createElement("text", { x: 2, y: 38, fontSize: 9, fill: "var(--dsw-alias-label-tertiary, #888888)", fontFamily: "inherit" }, "Mon"),
/* @__PURE__ */ import_react.default.createElement("text", { x: 2, y: 64, fontSize: 9, fill: "var(--dsw-alias-label-tertiary, #888888)", fontFamily: "inherit" }, "Wed"),
/* @__PURE__ */ import_react.default.createElement("text", { x: 2, y: 90, fontSize: 9, fill: "var(--dsw-alias-label-tertiary, #888888)", fontFamily: "inherit" }, "Fri"),
calendarGrid.map((week, colIdx) => /* @__PURE__ */ import_react.default.createElement("g", { key: colIdx, transform: `translate(${28 + colIdx * 13.2}, 16)` }, week.map((day, rowIdx) => {
if (!day) return null;
const lvl = day.stats.level || 0;
const bg = DEEPSEEK_LEVELS[lvl];
const isSelected = selectedDate === day.date;
const isHovered = hoveredDay?.date === day.date;
const strokeColor = isSelected ? "#ffffff" : isHovered ? "#5686fe" : lvl === 0 ? "rgba(255, 255, 255, 0.05)" : "transparent";
const strokeWidth = isSelected ? 1.5 : isHovered ? 1.2 : 1;
return /* @__PURE__ */ import_react.default.createElement(
"rect",
{
key: rowIdx,
x: 0,
y: rowIdx * 13,
width: 10.5,
height: 10.5,
rx: 2,
ry: 2,
fill: bg,
stroke: strokeColor,
strokeWidth,
style: {
cursor: "pointer",
transition: "fill 0.12s ease, stroke 0.12s ease",
filter: isSelected ? "drop-shadow(0 0 6px rgba(86, 134, 254, 0.9))" : isHovered ? "drop-shadow(0 0 4px rgba(86, 134, 254, 0.7))" : lvl === 4 ? "drop-shadow(0 0 3px rgba(79, 133, 255, 0.5))" : "none"
},
onClick: () => {
setSelectedDate(isSelected ? null : day.date);
},
onMouseEnter: (e) => {
setHoveredDay({ date: day.date, stats: day.stats });
const rect = e.currentTarget.getBoundingClientRect();
setTooltipPos({ x: rect.left + rect.width / 2, y: rect.top - 8 });
},
onMouseLeave: () => {
setHoveredDay(null);
setTooltipPos(null);
}
}
);
})))
), /* @__PURE__ */ import_react.default.createElement(
"div", "div",
{ {
ref: scrollContainerRef,
style: { style: {
display: "flex", width: "100%",
alignItems: "center", overflowX: "auto",
justifyContent: "space-between", WebkitOverflowScrolling: "touch",
marginTop: 14, paddingBottom: "8px"
fontSize: 11,
color: "var(--dsw-alias-label-tertiary, #a0a0a0)"
} }
}, },
/* @__PURE__ */ import_react.default.createElement("span", { style: { fontSize: 11 } }, "Tip: Click any day to inspect sessions and token breakdown"), /* @__PURE__ */ import_react.default.createElement(
/* @__PURE__ */ import_react.default.createElement("div", { style: { display: "flex", alignItems: "center", gap: 6 } }, /* @__PURE__ */ import_react.default.createElement("span", null, "Less"), DEEPSEEK_LEVELS.map((c, i) => /* @__PURE__ */ import_react.default.createElement( "svg",
{
viewBox: "0 0 895 142",
style: {
width: "100%",
minWidth: "860px",
height: "142px",
display: "block",
overflow: "visible"
}
},
monthHeaders.map((m, i) => /* @__PURE__ */ import_react.default.createElement(
"text",
{
key: i,
x: 34 + m.col * 16,
y: 12,
fontSize: 11,
fill: "var(--dsw-alias-label-tertiary, #888888)",
fontFamily: "inherit"
},
m.label
)),
/* @__PURE__ */ import_react.default.createElement("text", { x: 4, y: 49, fontSize: 10, fill: "var(--dsw-alias-label-tertiary, #888888)", fontFamily: "inherit" }, "Mon"),
/* @__PURE__ */ import_react.default.createElement("text", { x: 4, y: 81, fontSize: 10, fill: "var(--dsw-alias-label-tertiary, #888888)", fontFamily: "inherit" }, "Wed"),
/* @__PURE__ */ import_react.default.createElement("text", { x: 4, y: 113, fontSize: 10, fill: "var(--dsw-alias-label-tertiary, #888888)", fontFamily: "inherit" }, "Fri"),
calendarGrid.map((week, colIdx) => /* @__PURE__ */ import_react.default.createElement("g", { key: colIdx, transform: `translate(${34 + colIdx * 16}, 22)` }, week.map((day, rowIdx) => {
if (!day) return null;
const lvl = day.stats.level || 0;
const bg = DEEPSEEK_LEVELS[lvl];
const isSelected = selectedDate === day.date;
const isHovered = hoveredDay?.date === day.date;
const strokeColor = isSelected ? "#ffffff" : isHovered ? "#5686fe" : lvl === 0 ? "rgba(255, 255, 255, 0.06)" : "transparent";
const strokeWidth = isSelected ? 1.8 : isHovered ? 1.4 : 1;
return /* @__PURE__ */ import_react.default.createElement(
"rect",
{
key: rowIdx,
x: 0,
y: rowIdx * 16,
width: 12.5,
height: 12.5,
rx: 2.5,
ry: 2.5,
fill: bg,
stroke: strokeColor,
strokeWidth,
style: {
cursor: "pointer",
transition: "fill 0.12s ease, stroke 0.12s ease",
filter: isSelected ? "drop-shadow(0 0 6px rgba(86, 134, 254, 0.9))" : isHovered ? "drop-shadow(0 0 4px rgba(86, 134, 254, 0.7))" : lvl === 4 ? "drop-shadow(0 0 4px rgba(79, 133, 255, 0.6))" : "none"
},
onClick: () => {
setSelectedDate(isSelected ? null : day.date);
},
onMouseEnter: (e) => {
setHoveredDay({ date: day.date, stats: day.stats });
const rect = e.currentTarget.getBoundingClientRect();
setTooltipPos({ x: rect.left + rect.width / 2, y: rect.top - 8 });
},
onMouseLeave: () => {
setHoveredDay(null);
setTooltipPos(null);
}
}
);
})))
),
/* @__PURE__ */ import_react.default.createElement(
"div", "div",
{ {
key: i,
style: { style: {
width: 10.5, display: "flex",
height: 10.5, alignItems: "center",
borderRadius: 2, justifyContent: "space-between",
backgroundColor: c, marginTop: 14,
border: i === 0 ? "1px solid rgba(255, 255, 255, 0.05)" : "none", fontSize: 11,
filter: i === 4 ? "drop-shadow(0 0 3px rgba(79, 133, 255, 0.5))" : "none" color: "var(--dsw-alias-label-tertiary, #a0a0a0)"
} }
} },
)), /* @__PURE__ */ import_react.default.createElement("span", null, "More")) /* @__PURE__ */ import_react.default.createElement("span", { style: { fontSize: 11 } }, "Tip: Click any day to inspect sessions and token breakdown"),
)) /* @__PURE__ */ import_react.default.createElement("div", { style: { display: "flex", alignItems: "center", gap: 6 } }, /* @__PURE__ */ import_react.default.createElement("span", null, "Less"), DEEPSEEK_LEVELS.map((c, i) => /* @__PURE__ */ import_react.default.createElement(
"div",
{
key: i,
style: {
width: 12.5,
height: 12.5,
borderRadius: 2.5,
backgroundColor: c,
border: i === 0 ? "1px solid rgba(255, 255, 255, 0.06)" : "none",
filter: i === 4 ? "drop-shadow(0 0 3px rgba(79, 133, 255, 0.5))" : "none"
}
}
)), /* @__PURE__ */ import_react.default.createElement("span", null, "More"))
)
)
), ),
selectedDate && /* @__PURE__ */ import_react.default.createElement( selectedDate && /* @__PURE__ */ import_react.default.createElement(
"div", "div",
@ -676,8 +696,8 @@ function HeatmapModal({ open, onClose }) {
style: { style: {
position: "relative", position: "relative",
width: "100%", width: "100%",
maxWidth: "960px", maxWidth: "1040px",
maxHeight: "90vh", maxHeight: "92vh",
overflowY: "auto", overflowY: "auto",
background: "var(--dsw-alias-bg-layer-1, #1e1e1e)", background: "var(--dsw-alias-bg-layer-1, #1e1e1e)",
border: "1px solid var(--dsw-alias-border-l2, #494949)", border: "1px solid var(--dsw-alias-border-l2, #494949)",

File diff suppressed because one or more lines are too long

View file

@ -33,8 +33,8 @@ export function HeatmapModal({ open, onClose }: HeatmapModalProps) {
style={{ style={{
position: "relative", position: "relative",
width: "100%", width: "100%",
maxWidth: "960px", maxWidth: "1040px",
maxHeight: "90vh", maxHeight: "92vh",
overflowY: "auto", overflowY: "auto",
background: "var(--dsw-alias-bg-layer-1, #1e1e1e)", background: "var(--dsw-alias-bg-layer-1, #1e1e1e)",
border: "1px solid var(--dsw-alias-border-l2, #494949)", border: "1px solid var(--dsw-alias-border-l2, #494949)",

View file

@ -86,6 +86,7 @@ export function HeatmapView() {
const [selectedDate, setSelectedDate] = useState<string | null>(null); const [selectedDate, setSelectedDate] = useState<string | null>(null);
const [hoveredDay, setHoveredDay] = useState<{ date: string; stats: DayStats } | null>(null); const [hoveredDay, setHoveredDay] = useState<{ date: string; stats: DayStats } | null>(null);
const [tooltipPos, setTooltipPos] = useState<{ x: number; y: number } | null>(null); const [tooltipPos, setTooltipPos] = useState<{ x: number; y: number } | null>(null);
const scrollContainerRef = React.useRef<HTMLDivElement>(null);
const fetchStats = () => { const fetchStats = () => {
setLoading(true); setLoading(true);
@ -108,6 +109,12 @@ export function HeatmapView() {
fetchStats(); fetchStats();
}, []); }, []);
useEffect(() => {
if (scrollContainerRef.current) {
scrollContainerRef.current.scrollLeft = scrollContainerRef.current.scrollWidth;
}
}, [calendarGrid]);
// Compute active days & stats based on session filter // Compute active days & stats based on session filter
const activeView = useMemo(() => { const activeView = useMemo(() => {
if (!data) return null; if (!data) return null;
@ -530,14 +537,23 @@ export function HeatmapView() {
)} )}
</div> </div>
{/* Responsive GitHub-Style Calendar SVG (scales with container width) */} {/* Comfortably sized GitHub-Style Calendar SVG with smooth horizontal scroll */}
{calendarGrid && ( {calendarGrid && (
<div style={{ width: "100%", overflow: "hidden" }}> <div
ref={scrollContainerRef}
style={{
width: "100%",
overflowX: "auto",
WebkitOverflowScrolling: "touch",
paddingBottom: "8px",
}}
>
<svg <svg
viewBox="0 0 740 112" viewBox="0 0 895 142"
style={{ style={{
width: "100%", width: "100%",
height: "auto", minWidth: "860px",
height: "142px",
display: "block", display: "block",
overflow: "visible", overflow: "visible",
}} }}
@ -546,9 +562,9 @@ export function HeatmapView() {
{monthHeaders.map((m, i) => ( {monthHeaders.map((m, i) => (
<text <text
key={i} key={i}
x={30 + m.col * 13.2} x={34 + m.col * 16}
y={10} y={12}
fontSize={10} fontSize={11}
fill="var(--dsw-alias-label-tertiary, #888888)" fill="var(--dsw-alias-label-tertiary, #888888)"
fontFamily="inherit" fontFamily="inherit"
> >
@ -557,19 +573,19 @@ export function HeatmapView() {
))} ))}
{/* Day of week labels */} {/* Day of week labels */}
<text x={2} y={38} fontSize={9} fill="var(--dsw-alias-label-tertiary, #888888)" fontFamily="inherit"> <text x={4} y={49} fontSize={10} fill="var(--dsw-alias-label-tertiary, #888888)" fontFamily="inherit">
Mon Mon
</text> </text>
<text x={2} y={64} fontSize={9} fill="var(--dsw-alias-label-tertiary, #888888)" fontFamily="inherit"> <text x={4} y={81} fontSize={10} fill="var(--dsw-alias-label-tertiary, #888888)" fontFamily="inherit">
Wed Wed
</text> </text>
<text x={2} y={90} fontSize={9} fill="var(--dsw-alias-label-tertiary, #888888)" fontFamily="inherit"> <text x={4} y={113} fontSize={10} fill="var(--dsw-alias-label-tertiary, #888888)" fontFamily="inherit">
Fri Fri
</text> </text>
{/* Day Tiles */} {/* Day Tiles: comfortable 12.5px size with 16px step */}
{calendarGrid.map((week, colIdx) => ( {calendarGrid.map((week, colIdx) => (
<g key={colIdx} transform={`translate(${28 + colIdx * 13.2}, 16)`}> <g key={colIdx} transform={`translate(${34 + colIdx * 16}, 22)`}>
{week.map((day, rowIdx) => { {week.map((day, rowIdx) => {
if (!day) return null; if (!day) return null;
@ -584,20 +600,20 @@ export function HeatmapView() {
: isHovered : isHovered
? "#5686fe" ? "#5686fe"
: lvl === 0 : lvl === 0
? "rgba(255, 255, 255, 0.05)" ? "rgba(255, 255, 255, 0.06)"
: "transparent"; : "transparent";
const strokeWidth = isSelected ? 1.5 : isHovered ? 1.2 : 1; const strokeWidth = isSelected ? 1.8 : isHovered ? 1.4 : 1;
return ( return (
<rect <rect
key={rowIdx} key={rowIdx}
x={0} x={0}
y={rowIdx * 13} y={rowIdx * 16}
width={10.5} width={12.5}
height={10.5} height={12.5}
rx={2} rx={2.5}
ry={2} ry={2.5}
fill={bg} fill={bg}
stroke={strokeColor} stroke={strokeColor}
strokeWidth={strokeWidth} strokeWidth={strokeWidth}
@ -609,7 +625,7 @@ export function HeatmapView() {
: isHovered : isHovered
? "drop-shadow(0 0 4px rgba(86, 134, 254, 0.7))" ? "drop-shadow(0 0 4px rgba(86, 134, 254, 0.7))"
: lvl === 4 : lvl === 4
? "drop-shadow(0 0 3px rgba(79, 133, 255, 0.5))" ? "drop-shadow(0 0 4px rgba(79, 133, 255, 0.6))"
: "none", : "none",
}} }}
onClick={() => { onClick={() => {
@ -652,11 +668,11 @@ export function HeatmapView() {
<div <div
key={i} key={i}
style={{ style={{
width: 10.5, width: 12.5,
height: 10.5, height: 12.5,
borderRadius: 2, borderRadius: 2.5,
backgroundColor: c, backgroundColor: c,
border: i === 0 ? "1px solid rgba(255, 255, 255, 0.05)" : "none", border: i === 0 ? "1px solid rgba(255, 255, 255, 0.06)" : "none",
filter: i === 4 ? "drop-shadow(0 0 3px rgba(79, 133, 255, 0.5))" : "none", filter: i === 4 ? "drop-shadow(0 0 3px rgba(79, 133, 255, 0.5))" : "none",
}} }}
/> />