feat(web): forge icons + language bars in activity summary cards
All checks were successful
deploy / Build api + worker (static musl) (push) Successful in 6m6s
deploy / Build prerendered web (push) Successful in 7m50s
deploy / Deploy moments-worker to frootmig (push) Successful in 17s
deploy / Deploy web to oolon (push) Successful in 23s
deploy / Deploy moments-api to nikola (push) Successful in 30s
refresh / Rebuild prerendered web (push) Successful in 8m11s
refresh / Deploy refreshed web to oolon (push) Successful in 22s
All checks were successful
deploy / Build api + worker (static musl) (push) Successful in 6m6s
deploy / Build prerendered web (push) Successful in 7m50s
deploy / Deploy moments-worker to frootmig (push) Successful in 17s
deploy / Deploy web to oolon (push) Successful in 23s
deploy / Deploy moments-api to nikola (push) Successful in 30s
refresh / Rebuild prerendered web (push) Successful in 8m11s
refresh / Deploy refreshed web to oolon (push) Successful in 22s
Summary rows now use the dash cards' visual language: a 3-column grid of forge icon + repo link, the repo's language distribution bar, and a right-aligned change count — filling the dead space between name and count. Sources without an icon (bugzilla) keep the text label. github.svg and mozilla.svg are white-filled for the dark dash cards and vanished on the white timeline cards, so forgeIcon() is now background-aware and serves new dark-filled variants on light surfaces. The previously copy-pasted forgeIcon in DashPage and ProjectPage moved to a shared lib/forge.ts, and DashPage's repo-languages shaping became the shared useRepoLanguages() hook used by both dash and timeline. The /activity prerender prefetches repo-languages so icons and bars bake into the crawler snapshot. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LY4nbfHC9qt726gVrdAh3X
This commit is contained in:
14
ui/public/github-dark.svg
Normal file
14
ui/public/github-dark.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<svg
|
||||
fill="#24292f"
|
||||
width="800px"
|
||||
height="800px"
|
||||
viewBox="0 0 24 24"
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<title>github</title>
|
||||
<path
|
||||
d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 956 B |
30
ui/public/mozilla-dark.svg
Normal file
30
ui/public/mozilla-dark.svg
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" standalone="no" ?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg
|
||||
width="800px"
|
||||
height="800px"
|
||||
viewBox="-10 -5 1034 1034"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
version="1.1"
|
||||
>
|
||||
<path
|
||||
fill="#24292f"
|
||||
d="M411 237q-31 1 -64 10q-27 8 -54 22q-20 9 -38 20l-14 10q-50 33 -120 94q-67 57 -108 100q8 1 23 -5q12 -4 18 -5l99 -27l-32 26q-47 38 -69 59q-39 36 -52 62q11 -4 42 -19t46 -21q26 -11 40 -12l-18 16q-33 30 -49 46q-28 27 -38 44q1 1 1 4v2l10 -6q33 -20 52 -29
|
||||
q32 -16 47 -18q-77 83 -89 125q19 -13 55 -32.5t55 -26.5q-3 8 -15 23q-9 12 -12.5 18.5t-15.5 21.5t-17 23q-8 14 -9 25q30 -29 73 -53q-4 10 -15 27q-10 15 -14 23q-7 13 -9 26q9 -10 27 -18q11 -5 33 -12l11 -4l-4 4q-27 21 -42 39l8 -1q2 -1 9 1l6 1l-32 13
|
||||
q143 188 395 166l-13 -28q10 -18 19 -63t19 -66q16 -34 48 -51l25 -14q24 -14 37 -17q18 -4 49 2q8 1 32 7q35 9 52 12q30 6 46 2l10 -5q36 -22 58 -39q24 5 34.5 1.5t18.5 -16.5l5 -14q18 -50 28 -72l1 -15q-7 -21 -11 -75q-2 -32 -5 -36q-13 -18 -45 -34q-20 -10 -67 -27
|
||||
q-54 -20 -79 -33q-43 -21 -66 -47q8 -25 -5 -51q-10 -19 -32 -40q-25 -18 -63 -16q-24 1 -64 12q-30 -6 -86 -24q-29 -9 -44 -14q-8 -1 -18 -1h-4zM410 285q22 0 64 12q27 7 41 9q-1 4 -6.5 8t-6.5 7l-4 6q-4 5 -5.5 8.5t0.5 7.5l32 -12q13 -4 38 -12q32 -11 48 -14
|
||||
q28 -6 51 -3l24 31q-24 -20 -55 -15q-19 3 -56 20q-33 15 -49 18q-11 2 -33 8q-17 5 -25 7q29 22 75 17q3 15 18 34.5t29 22.5q-1 -6 -7 -20.5t-7 -22.5q-3 -14 4 -25l9 -6q17 -10 26 -13q16 -5 27 4q-10 1 -26 11l-8 5q-6 4 -8.5 14t-0.5 14q7 20 39 33q36 14 90 14
|
||||
q-3 -4 -16 -18.5t-18 -21.5q-9 -11 -8 -17q30 30 81 55q30 14 89 35q35 12 49 18q22 10 31 19q1 5 -13 22q-7 9 -9 13q-4 7 -2 10q-3 7 7 22q5 8 22 29l3 4q-3 -25 -1.5 -36.5t6 -9.5t8.5 14.5t3 28.5q0 19 -7 37l-35 -8q-47 -3 -84 -15q-33 -9 -66 -28q-21 -12 -65 -42
|
||||
l-30 -20q-27 -18 -79 -60l-14 -11l-50 -19q14 17 36 41q20 22 25 30q7 12 5 24.5t-16 36.5l-17 -31l-7 -55q-11 40 -13 66q-2 36 13 62q17 30 58 49l50 7q-22 -18 -30 -29q-12 -16 -8 -31q15 30 78 55q35 14 105 32l24 6q15 6 24 15q-7 6 -13 9q-4 2 -13 4.5t-15 4.5l-28 -9
|
||||
q-36 -11 -51 -18q-26 -11 -36 -25q4 6 -27 14q-18 4 -63 13q-29 5 -37 7q-13 3 -3 4q-35 -1 -72 -23q-33 -19 -61 -50l-9 -3q0 17 11 35q6 11 22.5 32t22.5 31l38 17l-10 6q-16 10 -23 16q-13 10 -19 21l-2 6q-5 9 -6 14q-2 8 1 14q21 -23 61 -33l-16 25q2 18 -9 49
|
||||
q-2 -5 -5 -10q-47 -20 -71 -39q6 20 25 44q16 21 37 38q-5 12 -12 23q-36 -25 -54 -42q5 9 11 27q9 24 15 34q-133 -10 -217 -82l60 -34q-10 2 -47 11l-30 8l-7 -8l24 -12q29 -14 42 -22q23 -14 29 -26v0l-6 2l-61 3q-4 -13 0 -27q2 -9 10.5 -24.5t9.5 -23.5q2 -13 -7 -25
|
||||
q-35 -46 -49 -80q-20 -46 -15 -90l1 2q7 15 13 20l2 -17q2 -18 5 -26v1q6 20 11 29q8 16 21 24l1 -2q-14 -50 -2 -95q13 -51 56 -78q-6 2 -23 4q-26 4 -41 10q2 -1 5 -10q11 -29 23 -39q6 -4 15 -13q11 -10 19 -15q24 -19 43 -28q26 -14 59 -19q8 -1 16 -1h4zM425 332
|
||||
q-28 0 -60 12l-13 10q-14 17 -19 26q-10 14 -12 29l3 4q13 -16 37 -28q0 18 9.5 33.5t25.5 24.5h3q-3 -19 -1 -39.5t9 -37.5q7 -6 23 -14q18 -10 25 -17q-14 -4 -30 -3zM625 391v0q8 0 15 3q-6 3 -8 11q-1 4 0 7q-11 -8 -11 -21h4zM462 392q2 7 14 22l9 11q-28 2 -60 11
|
||||
q5 4 14.5 9t13.5 9l-16 6q-21 7 -31 11q-16 8 -28 19l5 2q31 -6 66 -5.5t67 7.5l3 -2l-25 -46l38 -8q-39 -37 -70 -46zM662 406q8 7 15 16q-11 3 -22 0q5 -3 7 -10v-6zM599 430q13 39 37 41q33 6 62 1q-12 -7 -39 -13q-22 -6 -32 -10q-17 -7 -28 -19zM368 492
|
||||
q-23 17 -32.5 42t-5.5 53q2 7 1 17q-1 6 -4 17q-4 17 -3 25q0 13 8 22q22 25 63 55l-2 -8q-35 -41 -46 -74l17 6q36 13 55 16q-6 -10 -22 -29q-28 -34 -36 -53q-14 -32 -4 -62q0 -5 6 -12q7 -10 5 -15zM728 621q2 0 5 1q8 2 9 4q-3 2 -8.5 7t-8.5 6l1 -6q0 -9 2 -12z
|
||||
M768 642v0q4 0 9 4h1l-13 13l1 -17h2zM798 653v0l10 6q-1 0 -6 7t-5.5 6.5t-0.5 -6.5q0 -13 2 -13zM829 667v0l9 2q-1 1 -4 7q-6 11 -11 11l4 -8v-9q0 -3 2 -3zM861 673l12 3l-13 22zM896 679q1 0 8 2l4 2l-15 11q-1 -9 -0.5 -12t3.5 -3zM933 685q5 0 7 2q0 5 -7 12
|
||||
q-4 4 -5 7v-6q2 -10 0 -15h5z"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
@@ -1,7 +1,10 @@
|
||||
import { Fragment } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Calendar3 } from 'react-bootstrap-icons';
|
||||
import { VerticalTimelineElement } from 'react-vertical-timeline-component';
|
||||
import type { RepoPeriodCount, SummaryBucket } from '../api/client';
|
||||
import { LanguageBar } from './LanguageBar';
|
||||
import { forgeIcon } from '../lib/forge';
|
||||
import { fmtDate } from '../lib/ranges';
|
||||
|
||||
interface Props {
|
||||
@@ -10,11 +13,15 @@ interface Props {
|
||||
bucket: SummaryBucket;
|
||||
/** Rows for this period, already sorted by count descending. */
|
||||
repos: RepoPeriodCount[];
|
||||
/** Bytes-by-language keyed by `source:repo` (see useRepoLanguages). */
|
||||
langsByRepo: Map<string, Record<string, number>>;
|
||||
colorMap: Record<string, string>;
|
||||
}
|
||||
|
||||
/** One timeline card summarising a period: each repo touched and how many
|
||||
* changes landed in it, with a drill-down link to the per-event view. */
|
||||
export function SummaryEntry({ period, bucket, repos }: Props) {
|
||||
/** One timeline card summarising a period: each repo touched, its language
|
||||
* mix and how many changes landed in it, with a drill-down link to the
|
||||
* per-event view. */
|
||||
export function SummaryEntry({ period, bucket, repos, langsByRepo, colorMap }: Props) {
|
||||
const total = repos.reduce((sum, r) => sum + r.count, 0);
|
||||
const end = periodEnd(period, bucket);
|
||||
const timespan = bucket === 'day' ? period : `${period}..${end}`;
|
||||
@@ -29,20 +36,38 @@ export function SummaryEntry({ period, bucket, repos }: Props) {
|
||||
{total} {total === 1 ? 'change' : 'changes'} in {repos.length}{' '}
|
||||
{repos.length === 1 ? 'repository' : 'repositories'}
|
||||
</h4>
|
||||
<ul style={{ listStyle: 'none', paddingLeft: 0, marginBottom: '0.5rem' }}>
|
||||
{repos.map((r) => (
|
||||
<li
|
||||
key={`${r.source}:${r.repo}`}
|
||||
className="d-flex justify-content-between"
|
||||
<div
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'max-content 1fr max-content',
|
||||
columnGap: '0.75rem',
|
||||
alignItems: 'center',
|
||||
marginBottom: '0.5rem',
|
||||
}}
|
||||
>
|
||||
{repos.map((r) => {
|
||||
const icon = forgeIcon(r.source, /* onLight */ true);
|
||||
const langs = langsByRepo.get(`${r.source}:${r.repo}`);
|
||||
return (
|
||||
<Fragment key={`${r.source}:${r.repo}`}>
|
||||
<span>
|
||||
<Link to={`/project/${r.source}/${r.repo}`}>{r.repo}</Link>{' '}
|
||||
{icon && <img src={icon} alt={r.source} className="forge-icon" />}
|
||||
<Link to={`/project/${r.source}/${r.repo}`}>{r.repo}</Link>
|
||||
{!icon && (
|
||||
<>
|
||||
{' '}
|
||||
<small className="text-muted">{r.source}</small>
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
<span>{r.count}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<span>
|
||||
{langs && <LanguageBar languages={langs} colorMap={colorMap} compact />}
|
||||
</span>
|
||||
<span style={{ textAlign: 'right' }}>{r.count}</span>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<Link to={`/activity/${timespan}`} style={{ fontSize: '85%' }}>
|
||||
view activity →
|
||||
</Link>
|
||||
|
||||
19
ui/src/lib/forge.ts
Normal file
19
ui/src/lib/forge.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
/** Icon asset for a forge source, or null when we have none (e.g. bugzilla —
|
||||
* callers fall back to a text label).
|
||||
*
|
||||
* The default assets are white-filled for the dark dashboard cards; pass
|
||||
* `onLight` for surfaces with a light background (e.g. the timeline cards),
|
||||
* which swaps in dark-filled variants where the white ones would vanish. */
|
||||
export function forgeIcon(source: string, onLight = false): string | null {
|
||||
switch (source) {
|
||||
case 'github':
|
||||
return onLight ? '/github-dark.svg' : '/github.svg';
|
||||
case 'gitea':
|
||||
// The gitea mark is a green pot on a white tile — it reads on both.
|
||||
return '/gitea.svg';
|
||||
case 'hg':
|
||||
return onLight ? '/mozilla-dark.svg' : '/mozilla.svg';
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
34
ui/src/lib/useRepoLanguages.ts
Normal file
34
ui/src/lib/useRepoLanguages.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { fetchRepoLanguages } from '../api/client';
|
||||
|
||||
/** The repo-languages query shaped for rendering: bytes-by-language keyed by
|
||||
* `source:repo`, plus a global language → color map. */
|
||||
export function useRepoLanguages(enabled = true) {
|
||||
const langsQ = useQuery({
|
||||
queryKey: ['repo-languages'],
|
||||
queryFn: fetchRepoLanguages,
|
||||
staleTime: 10 * 60_000,
|
||||
enabled,
|
||||
});
|
||||
|
||||
const langsByRepo = useMemo(() => {
|
||||
const map = new Map<string, Record<string, number>>();
|
||||
for (const entry of langsQ.data ?? []) {
|
||||
const key = `${entry.source}:${entry.repo}`;
|
||||
if (!map.has(key)) map.set(key, {});
|
||||
map.get(key)![entry.language] = entry.bytes;
|
||||
}
|
||||
return map;
|
||||
}, [langsQ.data]);
|
||||
|
||||
const colorMap = useMemo(() => {
|
||||
const map: Record<string, string> = {};
|
||||
for (const e of langsQ.data ?? []) {
|
||||
if (e.color && !map[e.language]) map[e.language] = e.color;
|
||||
}
|
||||
return map;
|
||||
}, [langsQ.data]);
|
||||
|
||||
return { langsByRepo, colorMap };
|
||||
}
|
||||
@@ -1,15 +1,16 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Link } from 'react-router-dom';
|
||||
import Col from 'react-bootstrap/Col';
|
||||
import Row from 'react-bootstrap/Row';
|
||||
|
||||
import { fetchProjects, fetchRepoLanguages, type ProjectSummary } from '../api/client';
|
||||
import { fetchProjects, type ProjectSummary } from '../api/client';
|
||||
import { LanguageBar } from '../components/LanguageBar';
|
||||
import { ContributionGraph, AllTimeGraph } from '../components/ContributionGraph';
|
||||
import { ContributionStats } from '../components/ContributionStats';
|
||||
import { LanguageStreamGraph } from '../components/LanguageStreamGraph';
|
||||
import { TopLanguages } from '../components/TopLanguages';
|
||||
import { forgeIcon } from '../lib/forge';
|
||||
import { useRepoLanguages } from '../lib/useRepoLanguages';
|
||||
|
||||
export function DashPage() {
|
||||
const projectsQ = useQuery({
|
||||
@@ -18,29 +19,7 @@ export function DashPage() {
|
||||
refetchInterval: 60_000,
|
||||
});
|
||||
|
||||
const langsQ = useQuery({
|
||||
queryKey: ['repo-languages'],
|
||||
queryFn: fetchRepoLanguages,
|
||||
staleTime: 10 * 60_000,
|
||||
});
|
||||
|
||||
const langsByRepo = useMemo(() => {
|
||||
const map = new Map<string, Record<string, number>>();
|
||||
for (const entry of langsQ.data ?? []) {
|
||||
const key = `${entry.source}:${entry.repo}`;
|
||||
if (!map.has(key)) map.set(key, {});
|
||||
map.get(key)![entry.language] = entry.bytes;
|
||||
}
|
||||
return map;
|
||||
}, [langsQ.data]);
|
||||
|
||||
const langColors = useMemo(() => {
|
||||
const map: Record<string, string> = {};
|
||||
for (const e of langsQ.data ?? []) {
|
||||
if (e.color && !map[e.language]) map[e.language] = e.color;
|
||||
}
|
||||
return map;
|
||||
}, [langsQ.data]);
|
||||
const { langsByRepo, colorMap: langColors } = useRepoLanguages();
|
||||
|
||||
const projects = projectsQ.data ?? [];
|
||||
const ranked = rankProjects(projects);
|
||||
@@ -84,10 +63,11 @@ export function DashPage() {
|
||||
}
|
||||
|
||||
function ProjectCard({ project: p, langs, colorMap }: { project: ProjectSummary; langs: Record<string, number> | null; colorMap: Record<string, string> }) {
|
||||
const icon = forgeIcon(p.source);
|
||||
return (
|
||||
<Link to={`/project/${p.source}/${p.repo}`} className="text-decoration-none">
|
||||
<div className="project-card p-3">
|
||||
<h5 className="mb-1"><img src={forgeIcon(p.source)} alt={p.source} className="forge-icon" />{p.repo}</h5>
|
||||
<h5 className="mb-1">{icon && <img src={icon} alt={p.source} className="forge-icon" />}{p.repo}</h5>
|
||||
{langs && <LanguageBar languages={langs} colorMap={colorMap} compact />}
|
||||
<div className="d-flex gap-3" style={{ fontSize: '0.8rem' }}>
|
||||
{p.commit_count > 0 && <span>{p.commit_count} commits</span>}
|
||||
@@ -102,15 +82,6 @@ function ProjectCard({ project: p, langs, colorMap }: { project: ProjectSummary;
|
||||
);
|
||||
}
|
||||
|
||||
function forgeIcon(source: string): string {
|
||||
switch (source) {
|
||||
case 'github': return '/github.svg';
|
||||
case 'gitea': return '/gitea.svg';
|
||||
case 'hg': return '/mozilla.svg';
|
||||
default: return '/github.svg';
|
||||
}
|
||||
}
|
||||
|
||||
function formatRange(first: string | null, last: string | null): string {
|
||||
const fmt = (iso: string) =>
|
||||
new Date(iso)
|
||||
|
||||
@@ -9,6 +9,7 @@ import { fetchEvents, fetchReadme, fetchRepoLanguages, fetchProjects, type Sourc
|
||||
import { LanguageBar } from '../components/LanguageBar';
|
||||
import { Markdown } from '../components/Markdown';
|
||||
import { TimelineEntry } from '../components/TimelineEntry';
|
||||
import { forgeIcon } from '../lib/forge';
|
||||
|
||||
export function ProjectPage() {
|
||||
const { source, '*': repoPath } = useParams();
|
||||
@@ -73,7 +74,7 @@ export function ProjectPage() {
|
||||
<>
|
||||
<Row className="mb-3">
|
||||
<Col>
|
||||
<h2><a href={repoUrl(source ?? '', host, repo)} target="_blank" rel="noopener noreferrer"><img src={forgeIcon(source ?? '')} alt={source} className="forge-icon" style={{ width: 24, height: 24 }} /></a>{repo}</h2>
|
||||
<h2><a href={repoUrl(source ?? '', host, repo)} target="_blank" rel="noopener noreferrer">{forgeIcon(source ?? '') && <img src={forgeIcon(source ?? '')!} alt={source} className="forge-icon" style={{ width: 24, height: 24 }} />}</a>{repo}</h2>
|
||||
{langs && <LanguageBar languages={langs} colorMap={langColors} />}
|
||||
</Col>
|
||||
</Row>
|
||||
@@ -117,13 +118,3 @@ function repoUrl(source: string, host: string, repo: string): string {
|
||||
}
|
||||
}
|
||||
|
||||
function forgeIcon(source: string): string {
|
||||
switch (source) {
|
||||
case 'github': return '/github.svg';
|
||||
case 'gitea': return '/gitea.svg';
|
||||
case 'hg': return '/mozilla.svg';
|
||||
default: return '/github.svg';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
import { Filters } from '../components/Filters';
|
||||
import { SummaryEntry } from '../components/SummaryEntry';
|
||||
import { TimelineEntry } from '../components/TimelineEntry';
|
||||
import { useRepoLanguages } from '../lib/useRepoLanguages';
|
||||
import {
|
||||
defaultActivityRange,
|
||||
endOfTodayMs,
|
||||
@@ -137,6 +138,9 @@ export function TimelineHome() {
|
||||
enabled: summaryMode,
|
||||
});
|
||||
|
||||
// Language mix per repo for the summary cards' distribution bars.
|
||||
const { langsByRepo, colorMap } = useRepoLanguages(summaryMode);
|
||||
|
||||
// Group the flat rows into periods. The API orders by period desc then
|
||||
// count desc, so Map insertion order is already the display order.
|
||||
const periods = useMemo(() => {
|
||||
@@ -222,6 +226,8 @@ export function TimelineHome() {
|
||||
period={period}
|
||||
bucket={bucket}
|
||||
repos={repos}
|
||||
langsByRepo={langsByRepo}
|
||||
colorMap={colorMap}
|
||||
/>
|
||||
))
|
||||
: events.map((item) => (
|
||||
|
||||
@@ -80,6 +80,7 @@ async function prefetchActivity(qc: QueryClient): Promise<void> {
|
||||
const range = summaryRange('day', endOfTodayMs());
|
||||
await Promise.all([
|
||||
qc.prefetchQuery({ queryKey: ['sources'], queryFn: fetchSources }),
|
||||
qc.prefetchQuery({ queryKey: ['repo-languages'], queryFn: fetchRepoLanguages }),
|
||||
qc.prefetchQuery({
|
||||
queryKey: ['activity-summary', range.fromStr, range.toStr, 'day', ALL_SOURCES],
|
||||
queryFn: () =>
|
||||
|
||||
Reference in New Issue
Block a user