Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
5.0 kB · 132 lines
JavaScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133import { createElement, RefreshCw, User, UserPlus, UserMinus, Sprout, ArrowRight, Footprints } from 'lucide';
function makeIcon(iconNode, size = 14) { return createElement(iconNode, { width: size, height: size, 'stroke-width': 2 });}
export function renderProgressMap(el, timelineEvents, myAvatar, cursor) { if (!el) return;
el.innerHTML = ''; el.className = 'flex items-start gap-1.5 overflow-x-auto py-2 px-2';
const nodes = buildNodes(timelineEvents, myAvatar); nodes.forEach((node, i) => { if (i > 0) el.appendChild(makeArrow()); const highlighted = node.indices ? node.indices.includes(cursor) : node.cursorAt === cursor; el.appendChild(node.type === 'refresh' ? makeRefreshNode(node, highlighted) : makeAvatarNode(node, highlighted)); });
requestAnimationFrame(() => { el.scrollTo({ left: el.scrollWidth, behavior: 'smooth' }); });}
function buildNodes(timelineEvents, myAvatar) { const nodes = [{ type: 'start', avatar: myAvatar, cursorAt: 0 }];
timelineEvents.forEach((e, i) => { const cursorAt = i + 1; const last = nodes[nodes.length - 1]; if (e.type === 'refresh' && last.type === 'refresh') { last.count++; last.indices.push(cursorAt); } else if (e.type === 'refresh') { nodes.push({ type: 'refresh', count: 1, indices: [cursorAt] }); } else { nodes.push({ type: e.type, avatar: e.avatar, handle: e.handle, strike: e.type === 'unfollow', cursorAt }); } });
nodes.push({ type: 'end', cursorAt: timelineEvents.length + 1 }); return nodes;}
function makeArrow() { const wrap = document.createElement('span'); wrap.className = 'flex flex-col items-center shrink-0 gap-1 text-gray-600 dark:text-gray-400'; const box = document.createElement('span'); box.className = 'h-10 flex items-center justify-center'; box.appendChild(makeIcon(ArrowRight)); const spacer = document.createElement('span'); spacer.className = 'text-[0.65rem]'; spacer.textContent = ' '; wrap.appendChild(box); wrap.appendChild(spacer); return wrap;}
function makeRefreshNode(node, highlighted) { const wrap = document.createElement('span'); wrap.className = 'flex flex-col items-center shrink-0 w-[52px] gap-1';
const box = document.createElement('span'); const outline = highlighted ? ' outline outline-2 outline-black dark:outline-gray-300 outline-offset-2' : ''; box.className = `w-10 h-10 shrink-0 border border-black dark:border-gray-600 flex items-center justify-center${outline}`; box.appendChild(makeIcon(RefreshCw));
const lbl = document.createElement('span'); lbl.className = 'text-[0.65rem] max-w-[52px] text-center tabular-nums'; lbl.textContent = node.count > 1 ? `×${node.count}` : ' ';
wrap.appendChild(box); wrap.appendChild(lbl); return wrap;}
function makeAvatarNode(item, highlighted) { const label = item.type === 'start' ? 'start' : item.type === 'end' ? '' : '@' + item.handle;
const wrap = document.createElement('span'); wrap.className = 'flex flex-col items-center shrink-0 w-[52px] gap-1';
const box = document.createElement('span'); const outline = highlighted ? ' outline outline-2 outline-black dark:outline-gray-300 outline-offset-2' : ''; const placeholderCls = `w-10 h-10 shrink-0 border border-black dark:border-gray-600 flex items-center justify-center text-xs${outline}`;
if (item.avatar && item.type !== 'start') { box.className = `relative block w-10 h-10 shrink-0${outline}`; const img = document.createElement('img'); img.src = item.avatar; img.className = `w-10 h-10 object-cover block${item.strike ? ' opacity-50' : ''}`; img.onerror = () => { img.remove(); box.className = `relative ${placeholderCls}`; if (item.type === 'end') box.textContent = '?'; else box.appendChild(makeIcon(User)); }; box.appendChild(img); } else { box.className = `relative ${placeholderCls}`; if (item.type === 'end') box.textContent = '?'; else if (item.type === 'start') box.appendChild(makeIcon(Footprints)); else box.appendChild(makeIcon(User)); }
const badge = actionIcon(item.type); if (badge) box.appendChild(makeBadge(badge));
const lbl = document.createElement('span'); lbl.className = `text-[0.65rem] max-w-[52px] overflow-hidden text-ellipsis whitespace-nowrap text-center${item.strike ? ' line-through' : ''}`; lbl.textContent = label;
wrap.appendChild(box); wrap.appendChild(lbl); return wrap;}
function makeBadge(iconNode) { const badge = document.createElement('span'); badge.className = 'absolute -bottom-1 -right-1 bg-white dark:bg-stone-900 border border-black dark:border-gray-600 flex items-center justify-center w-4 h-4'; badge.appendChild(makeIcon(iconNode, 10)); return badge;}
function actionIcon(type) { switch (type) { case 'seed': return Sprout; case 'follow': return UserPlus; case 'unfollow': return UserMinus; default: return null; }}