| 182 | const ROW_HEIGHT = 40 |
| 183 | |
| 184 | function RailsSVG({ |
| 185 | row, |
| 186 | prevRow, |
| 187 | railWidth, |
| 188 | maxRails, |
| 189 | }: { |
| 190 | row: GraphRow |
| 191 | prevRow: GraphRow | null |
| 192 | railWidth: number |
| 193 | maxRails: number |
| 194 | }) { |
| 195 | const w = maxRails * railWidth |
| 196 | const h = ROW_HEIGHT |
| 197 | const cy = h / 2 |
| 198 | |
| 199 | function rx(rail: number) { |
| 200 | return rail * railWidth + railWidth / 2 |
| 201 | } |
| 202 | |
| 203 | const commitX = rx(row.rail) |
| 204 | |
| 205 | // Collect which rails are active above this row (from previous row's post-state) |
| 206 | const activeAbove = new Set<number>() |
| 207 | if (prevRow) { |
| 208 | for (let r = 0; r < prevRow.rails.length; r++) { |
| 209 | if (prevRow.rails[r] !== null) activeAbove.add(r) |
| 210 | } |
| 211 | } |
| 212 | |
| 213 | // Collect which rails are active below this row |
| 214 | const activeBelow = new Set<number>() |
| 215 | for (let r = 0; r < row.rails.length; r++) { |
| 216 | if (row.rails[r] !== null) activeBelow.add(r) |
| 217 | } |
| 218 | |
| 219 | return ( |
| 220 | <svg |
| 221 | width={w} |
| 222 | height={h} |
| 223 | viewBox={`0 0 ${w} ${h}`} |
| 224 | className="shrink-0" |
| 225 | aria-hidden="true" |
| 226 | > |
| 227 | {/* Pass-through rails: any rail active both above and below that isn't the commit rail */} |
| 228 | {Array.from(activeAbove).map((r) => { |
| 229 | if (r === row.rail) return null |
| 230 | if (!activeBelow.has(r)) return null |
| 231 | const x = rx(r) |
| 232 | return ( |
| 233 | <line |
| 234 | key={`pt-${r}`} |
| 235 | x1={x} |
| 236 | y1={0} |
| 237 | x2={x} |
| 238 | y2={h} |
| 239 | stroke={color(r)} |
| 240 | strokeWidth={2} |
| 241 | strokeOpacity={0.6} |