import { 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; } }