diff --git a/pdl-live-react/src/view/timeline/Timeline.tsx b/pdl-live-react/src/view/timeline/Timeline.tsx index e429bf33..be7f2277 100644 --- a/pdl-live-react/src/view/timeline/Timeline.tsx +++ b/pdl-live-react/src/view/timeline/Timeline.tsx @@ -1,7 +1,7 @@ import { useMemo } from "react" -import TimelineRow, { type Position } from "./TimelineRow" -import { type TimelineRow as TimelineRowModel, computeModel } from "./model" +import TimelineRow from "./TimelineRow" +import { computeModel, pushPopsFor } from "./model" import "./Timeline.css" @@ -40,79 +40,3 @@ export default function Timeline({ block }: Props) { ) } - -function positionOf( - row: TimelineRowModel, - idx: number, - A: TimelineRowModel[], -): Position { - return idx === A.length - 1 || A[idx + 1].depth < row.depth - ? "pop" - : idx === 0 || A[idx - 1].depth < row.depth - ? "push" - : A[idx - 1].depth === row.depth - ? "middle" - : "pop" -} - -function nextSibling( - row: TimelineRowModel, - idx: number, - A: TimelineRowModel[], -) { - let sidx = idx + 1 - while (sidx < A.length && A[sidx].depth > row.depth) { - sidx++ - } - return sidx < A.length && A[sidx].depth === row.depth ? sidx : -1 -} - -type PushPop = { prefix: boolean[]; position: Position } - -function pushPopsFor(model: TimelineRowModel[]): PushPop[] { - if (model.length === 0) { - return [] - } - - const result: PushPop[] = [] - const stack: number[] = [0] - const prefix: boolean[] = [] - let n = 0 - while (stack.length > 0) { - if (n++ > model.length * 2) { - break - } - const rootIdx = stack.pop() - - if (rootIdx === undefined) { - break - } else if (rootIdx < 0) { - prefix.pop() - continue - } - - const root = model[rootIdx] - const mine = { - prefix: prefix.slice(0), - position: positionOf(root, rootIdx, model), - } - result.push(mine) - - stack.push(-rootIdx) - for (let idx = model.length - 1; idx >= rootIdx + 1; idx--) { - if (model[idx].parent === root) { - stack.push(idx) - } - } - - const nextSibIdx = nextSibling(root, rootIdx, model) - if (nextSibIdx < 0) { - prefix.push(false) - mine.position = "pop" - } else { - prefix.push(true) - } - } - - return result -} diff --git a/pdl-live-react/src/view/timeline/TimelineRow.tsx b/pdl-live-react/src/view/timeline/TimelineRow.tsx index bd5c3bea..ee3cbfa0 100644 --- a/pdl-live-react/src/view/timeline/TimelineRow.tsx +++ b/pdl-live-react/src/view/timeline/TimelineRow.tsx @@ -3,11 +3,9 @@ import prettyMs from "pretty-ms" import TimelineBar from "./TimelineBar" import { capitalizeAndUnSnakeCase } from "../../helpers" -export type Position = "push" | "middle" | "pop" - type Props = import("./model").TimelineRowWithExtrema & { prefix: boolean[] - position: Position + position: import("./model").Position } export default function TimelineRow(row: Props) { @@ -36,7 +34,7 @@ function treeSymbols(row: Props) { } function prefixSymbols(row: Props) { - return row.prefix.slice(1).reduce((s, p) => s + (p ? "│ " : " "), "") + return row.prefix.slice(1).reduce((s, p) => s + (p ? "│ " : " "), "") } function finalSymbol(row: Props) { @@ -47,9 +45,9 @@ function finalSymbol(row: Props) { switch (row.position) { case "push": case "middle": - return "├── " + return "├─ " default: case "pop": - return "└── " + return "└─ " } } diff --git a/pdl-live-react/src/view/timeline/model.ts b/pdl-live-react/src/view/timeline/model.ts index 65765d10..26f6c827 100644 --- a/pdl-live-react/src/view/timeline/model.ts +++ b/pdl-live-react/src/view/timeline/model.ts @@ -8,7 +8,7 @@ import { type NonScalarPdlBlock, } from "../../helpers" -export type TimelineRow = Pick< +type TimelineRow = Pick< PdlBlockWithTiming, "start_nanos" | "end_nanos" | "timezone" | "kind" > & { @@ -29,6 +29,8 @@ export type TimelineRowWithExtrema = TimelineRow & { export type TimelineModel = TimelineRow[] +export type Position = "push" | "middle" | "pop" + export function computeModel( block: unknown | PdlBlock, depth = 0, @@ -75,3 +77,80 @@ function childrenOf(block: NonScalarPdlBlock) { .flat() .filter(nonNullable) } + +function positionOf(row: TimelineRow, idx: number, A: TimelineRow[]): Position { + return idx === A.length - 1 || A[idx + 1].depth < row.depth + ? "pop" + : idx === 0 || A[idx - 1].depth < row.depth + ? "push" + : A[idx - 1].depth === row.depth + ? "middle" + : "pop" +} + +function nextSibling(row: TimelineRow, idx: number, A: TimelineRow[]) { + let sidx = idx + 1 + while (sidx < A.length && A[sidx].depth > row.depth) { + sidx++ + } + return sidx < A.length && A[sidx].depth === row.depth ? sidx : -1 +} + +type PushPop = { prefix: boolean[]; position: Position } + +export function pushPopsFor(model: TimelineRow[]): PushPop[] { + if (model.length === 0) { + return [] + } + + // Push all roots for the initial set + const stack: number[] = model + .map((_, idx) => (_.depth === 0 ? idx : undefined)) + .filter(nonNullable) + + // This is the return value + const result: PushPop[] = [] + + // This is an array of parents; false indicates that the parent has + // no nextSibling; true indicates it does + const prefix: boolean[] = [] + + let n = 0 + while (stack.length > 0) { + if (n++ > model.length * 2) { + break + } + const rootIdx = stack.pop() + + if (rootIdx === undefined) { + break + } else if (rootIdx < 0) { + prefix.pop() + continue + } + + const root = model[rootIdx] + const mine = { + prefix: prefix.slice(0), + position: positionOf(root, rootIdx, model), + } + result.push(mine) + + stack.push(-rootIdx) + for (let idx = model.length - 1; idx >= rootIdx + 1; idx--) { + if (model[idx].parent === root) { + stack.push(idx) + } + } + + const nextSibIdx = nextSibling(root, rootIdx, model) + if (nextSibIdx < 0) { + prefix.push(false) + mine.position = "pop" + } else { + prefix.push(true) + } + } + + return result +} diff --git a/pdl-live-react/src/view/transcript/TranscriptItem.tsx b/pdl-live-react/src/view/transcript/TranscriptItem.tsx index c884ecaa..56f2a6d9 100644 --- a/pdl-live-react/src/view/transcript/TranscriptItem.tsx +++ b/pdl-live-react/src/view/transcript/TranscriptItem.tsx @@ -71,7 +71,7 @@ export default function TranscriptItem(props: Props) { const headerContent = ( {icon && {icon}} - {breadcrumbs} + {breadcrumbs} )