diff --git a/src/popup/SegmentListComponent.tsx b/src/popup/SegmentListComponent.tsx
index 4bf438b6..1d6aa396 100644
--- a/src/popup/SegmentListComponent.tsx
+++ b/src/popup/SegmentListComponent.tsx
@@ -28,8 +28,8 @@ enum SegmentListTab {
Chapter
}
-interface segmentWithNesting extends SponsorTime {
- innerChapters?: (segmentWithNesting|SponsorTime)[];
+interface SegmentWithNesting extends SponsorTime {
+ innerChapters?: (SegmentWithNesting|SponsorTime)[];
}
export const SegmentListComponent = (props: SegmentListComponentProps) => {
@@ -58,37 +58,43 @@ export const SegmentListComponent = (props: SegmentListComponentProps) => {
}
};
- const segmentsWithNesting: segmentWithNesting[] = [];
- let nbTrailingNonChapters = 0;
- function nestChapters(segments: segmentWithNesting[], seg: SponsorTime, topLevel?: boolean) {
- if (seg.actionType === ActionType.Chapter && segments.length) {
- // trailing non-chapters can only exist at top level
- const lastElement = segments[segments.length - (topLevel ? nbTrailingNonChapters + 1 : 1)]
-
- if (lastElement.actionType === ActionType.Chapter
- && lastElement.segment[0] <= seg.segment[0]
- && lastElement.segment[1] >= seg.segment[1]) {
- if (lastElement.innerChapters){
- nestChapters(lastElement.innerChapters, seg);
- } else {
- lastElement.innerChapters = [seg];
- }
- } else {
- if (topLevel) {
- nbTrailingNonChapters = 0;
- }
-
- segments.push(seg);
- }
- } else {
+ const segmentsWithNesting = React.useMemo(() => {
+ const result: SegmentWithNesting[] = [];
+ const chapterStack: SegmentWithNesting[] = [];
+ for (let seg of props.segments) {
+ seg = {...seg};
+ // non-chapter, do not nest
if (seg.actionType !== ActionType.Chapter) {
- nbTrailingNonChapters++;
+ result.push(seg);
+ continue;
}
+ // traverse the stack
+ while (chapterStack.length !== 0) {
+ // where's Array.prototype.at() :sob:
+ const lastChapter = chapterStack[chapterStack.length - 1];
+ // we know lastChapter.startTime <= seg.startTime, as content.ts sorts these
+ // so only compare endTime - if new ends before last, new is nested inside last
+ if (lastChapter.segment[1] >= seg.segment[1]) {
+ lastChapter.innerChapters ??= [];
+ lastChapter.innerChapters.push(seg);
+ chapterStack.push(seg);
+ break;
+ }
+ // last did not match, pop it off the stack
+ chapterStack.pop();
+ }
+ // chapter stack not empty = we found a place for the chapter
+ if (chapterStack.length !== 0) {
+ continue;
+ }
+ // push the chapter to the top-level list and to the stack
+ result.push(seg);
+ chapterStack.push(seg);
- segments.push(seg);
}
- }
- props.segments.forEach((seg) => nestChapters(segmentsWithNesting, {...seg}, true));
+ return result;
+ }, [props.segments])
+
return (
@@ -136,7 +142,7 @@ export const SegmentListComponent = (props: SegmentListComponentProps) => {
};
function SegmentListItem({ segment, videoID, currentTime, isVip, loopedChapter, tabFilter, sendMessage }: {
- segment: segmentWithNesting;
+ segment: SegmentWithNesting;
videoID: VideoID;
currentTime: number;
isVip: boolean;
@@ -146,18 +152,32 @@ function SegmentListItem({ segment, videoID, currentTime, isVip, loopedChapter,
sendMessage: (request: Message) => Promise
;
}) {
const [voteMessage, setVoteMessage] = React.useState(null);
- const [hidden, setHidden] = React.useState(segment.hidden || SponsorHideType.Visible);
+ const [hidden, setHidden] = React.useState(segment.hidden ?? SponsorHideType.Visible); // undefined ?? undefined lol
const [isLooped, setIsLooped] = React.useState(loopedChapter === segment.UUID);
- let extraInfo = "";
- if (segment.hidden === SponsorHideType.Downvoted) {
- // This one is downvoted
- extraInfo = " (" + chrome.i18n.getMessage("hiddenDueToDownvote") + ")";
- } else if (segment.hidden === SponsorHideType.MinimumDuration) {
- // This one is too short
- extraInfo = " (" + chrome.i18n.getMessage("hiddenDueToDuration") + ")";
- } else if (segment.hidden === SponsorHideType.Hidden) {
- extraInfo = " (" + chrome.i18n.getMessage("manuallyHidden") + ")";
+ // Update internal state if the hidden property of the segment changes
+ React.useEffect(() => {
+ setHidden(segment.hidden ?? SponsorHideType.Visible);
+ }, [segment.hidden])
+
+ let extraInfo: string;
+ switch (hidden) {
+ case SponsorHideType.Visible:
+ extraInfo = "";
+ break;
+ case SponsorHideType.Downvoted:
+ extraInfo = " (" + chrome.i18n.getMessage("hiddenDueToDownvote") + ")";
+ break;
+ case SponsorHideType.MinimumDuration:
+ extraInfo = " (" + chrome.i18n.getMessage("hiddenDueToDuration") + ")";
+ break;
+ case SponsorHideType.Hidden:
+ extraInfo = " (" + chrome.i18n.getMessage("manuallyHidden") + ")";
+ break;
+ default:
+ // hidden satisfies never; // need to upgrade TS
+ console.warn(`[SB] Unhandled variant of SponsorHideType in SegmentListItem: ${hidden}`);
+ extraInfo = "";
}
return (
@@ -279,7 +299,7 @@ function SegmentListItem({ segment, videoID, currentTime, isVip, loopedChapter,
{
(segment.actionType === ActionType.Skip || segment.actionType === ActionType.Mute
|| segment.actionType === ActionType.Poi
- && [SponsorHideType.Visible, SponsorHideType.Hidden].includes(segment.hidden)) &&
+ && [SponsorHideType.Visible, SponsorHideType.Hidden].includes(hidden)) &&
@@ -343,7 +357,7 @@ function SegmentListItem({ segment, videoID, currentTime, isVip, loopedChapter,
}
function InnerChapterList({ chapters, videoID, currentTime, isVip, loopedChapter, tabFilter, sendMessage }: {
- chapters: (segmentWithNesting)[];
+ chapters: (SegmentWithNesting)[];
videoID: VideoID;
currentTime: number;
isVip: boolean;