Skip to content

Commit 9fb3921

Browse files
committed
Merge branch 'master' of https://github.com/ajayyy/SponsorBlock
2 parents 7982ccb + ff43268 commit 9fb3921

File tree

1 file changed

+65
-51
lines changed

1 file changed

+65
-51
lines changed

src/popup/SegmentListComponent.tsx

Lines changed: 65 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ enum SegmentListTab {
2828
Chapter
2929
}
3030

31-
interface segmentWithNesting extends SponsorTime {
32-
innerChapters?: (segmentWithNesting|SponsorTime)[];
31+
interface SegmentWithNesting extends SponsorTime {
32+
innerChapters?: (SegmentWithNesting|SponsorTime)[];
3333
}
3434

3535
export const SegmentListComponent = (props: SegmentListComponentProps) => {
@@ -58,37 +58,43 @@ export const SegmentListComponent = (props: SegmentListComponentProps) => {
5858
}
5959
};
6060

61-
const segmentsWithNesting: segmentWithNesting[] = [];
62-
let nbTrailingNonChapters = 0;
63-
function nestChapters(segments: segmentWithNesting[], seg: SponsorTime, topLevel?: boolean) {
64-
if (seg.actionType === ActionType.Chapter && segments.length) {
65-
// trailing non-chapters can only exist at top level
66-
const lastElement = segments[segments.length - (topLevel ? nbTrailingNonChapters + 1 : 1)]
67-
68-
if (lastElement.actionType === ActionType.Chapter
69-
&& lastElement.segment[0] <= seg.segment[0]
70-
&& lastElement.segment[1] >= seg.segment[1]) {
71-
if (lastElement.innerChapters){
72-
nestChapters(lastElement.innerChapters, seg);
73-
} else {
74-
lastElement.innerChapters = [seg];
75-
}
76-
} else {
77-
if (topLevel) {
78-
nbTrailingNonChapters = 0;
79-
}
80-
81-
segments.push(seg);
82-
}
83-
} else {
61+
const segmentsWithNesting = React.useMemo(() => {
62+
const result: SegmentWithNesting[] = [];
63+
const chapterStack: SegmentWithNesting[] = [];
64+
for (let seg of props.segments) {
65+
seg = {...seg};
66+
// non-chapter, do not nest
8467
if (seg.actionType !== ActionType.Chapter) {
85-
nbTrailingNonChapters++;
68+
result.push(seg);
69+
continue;
70+
}
71+
// traverse the stack
72+
while (chapterStack.length !== 0) {
73+
// where's Array.prototype.at() :sob:
74+
const lastChapter = chapterStack[chapterStack.length - 1];
75+
// we know lastChapter.startTime <= seg.startTime, as content.ts sorts these
76+
// so only compare endTime - if new ends before last, new is nested inside last
77+
if (lastChapter.segment[1] >= seg.segment[1]) {
78+
lastChapter.innerChapters ??= [];
79+
lastChapter.innerChapters.push(seg);
80+
chapterStack.push(seg);
81+
break;
82+
}
83+
// last did not match, pop it off the stack
84+
chapterStack.pop();
8685
}
86+
// chapter stack not empty = we found a place for the chapter
87+
if (chapterStack.length !== 0) {
88+
continue;
89+
}
90+
// push the chapter to the top-level list and to the stack
91+
result.push(seg);
92+
chapterStack.push(seg);
8793

88-
segments.push(seg);
8994
}
90-
}
91-
props.segments.forEach((seg) => nestChapters(segmentsWithNesting, {...seg}, true));
95+
return result;
96+
}, [props.segments])
97+
9298

9399
return (
94100
<div id="issueReporterContainer">
@@ -136,7 +142,7 @@ export const SegmentListComponent = (props: SegmentListComponentProps) => {
136142
};
137143

138144
function SegmentListItem({ segment, videoID, currentTime, isVip, loopedChapter, tabFilter, sendMessage }: {
139-
segment: segmentWithNesting;
145+
segment: SegmentWithNesting;
140146
videoID: VideoID;
141147
currentTime: number;
142148
isVip: boolean;
@@ -146,18 +152,32 @@ function SegmentListItem({ segment, videoID, currentTime, isVip, loopedChapter,
146152
sendMessage: (request: Message) => Promise<MessageResponse>;
147153
}) {
148154
const [voteMessage, setVoteMessage] = React.useState<string | null>(null);
149-
const [hidden, setHidden] = React.useState(segment.hidden || SponsorHideType.Visible);
155+
const [hidden, setHidden] = React.useState(segment.hidden ?? SponsorHideType.Visible); // undefined ?? undefined lol
150156
const [isLooped, setIsLooped] = React.useState(loopedChapter === segment.UUID);
151157

152-
let extraInfo = "";
153-
if (segment.hidden === SponsorHideType.Downvoted) {
154-
// This one is downvoted
155-
extraInfo = " (" + chrome.i18n.getMessage("hiddenDueToDownvote") + ")";
156-
} else if (segment.hidden === SponsorHideType.MinimumDuration) {
157-
// This one is too short
158-
extraInfo = " (" + chrome.i18n.getMessage("hiddenDueToDuration") + ")";
159-
} else if (segment.hidden === SponsorHideType.Hidden) {
160-
extraInfo = " (" + chrome.i18n.getMessage("manuallyHidden") + ")";
158+
// Update internal state if the hidden property of the segment changes
159+
React.useEffect(() => {
160+
setHidden(segment.hidden ?? SponsorHideType.Visible);
161+
}, [segment.hidden])
162+
163+
let extraInfo: string;
164+
switch (hidden) {
165+
case SponsorHideType.Visible:
166+
extraInfo = "";
167+
break;
168+
case SponsorHideType.Downvoted:
169+
extraInfo = " (" + chrome.i18n.getMessage("hiddenDueToDownvote") + ")";
170+
break;
171+
case SponsorHideType.MinimumDuration:
172+
extraInfo = " (" + chrome.i18n.getMessage("hiddenDueToDuration") + ")";
173+
break;
174+
case SponsorHideType.Hidden:
175+
extraInfo = " (" + chrome.i18n.getMessage("manuallyHidden") + ")";
176+
break;
177+
default:
178+
// hidden satisfies never; // need to upgrade TS
179+
console.warn(`[SB] Unhandled variant of SponsorHideType in SegmentListItem: ${hidden}`);
180+
extraInfo = "";
161181
}
162182

163183
return (
@@ -279,7 +299,7 @@ function SegmentListItem({ segment, videoID, currentTime, isVip, loopedChapter,
279299
{
280300
(segment.actionType === ActionType.Skip || segment.actionType === ActionType.Mute
281301
|| segment.actionType === ActionType.Poi
282-
&& [SponsorHideType.Visible, SponsorHideType.Hidden].includes(segment.hidden)) &&
302+
&& [SponsorHideType.Visible, SponsorHideType.Hidden].includes(hidden)) &&
283303
<img
284304
className="voteButton"
285305
title={chrome.i18n.getMessage("hideSegment")}
@@ -288,17 +308,11 @@ function SegmentListItem({ segment, videoID, currentTime, isVip, loopedChapter,
288308
const stopAnimation = AnimationUtils.applyLoadingAnimation(e.currentTarget, 0.4);
289309
stopAnimation();
290310

291-
if (segment.hidden === SponsorHideType.Hidden) {
292-
segment.hidden = SponsorHideType.Visible;
293-
setHidden(SponsorHideType.Visible);
294-
} else {
295-
segment.hidden = SponsorHideType.Hidden;
296-
setHidden(SponsorHideType.Hidden);
297-
}
298-
311+
const newState = hidden === SponsorHideType.Hidden ? SponsorHideType.Visible : SponsorHideType.Hidden;
312+
setHidden(newState);
299313
sendMessage({
300314
message: "hideSegment",
301-
type: segment.hidden,
315+
type: newState,
302316
UUID: segment.UUID
303317
});
304318
}}/>
@@ -343,7 +357,7 @@ function SegmentListItem({ segment, videoID, currentTime, isVip, loopedChapter,
343357
}
344358

345359
function InnerChapterList({ chapters, videoID, currentTime, isVip, loopedChapter, tabFilter, sendMessage }: {
346-
chapters: (segmentWithNesting)[];
360+
chapters: (SegmentWithNesting)[];
347361
videoID: VideoID;
348362
currentTime: number;
349363
isVip: boolean;

0 commit comments

Comments
 (0)