mirror of
https://github.com/ajayyy/SponsorBlock.git
synced 2025-12-06 11:37:02 +03:00
245 lines
7.5 KiB
TypeScript
245 lines
7.5 KiB
TypeScript
import Config from "../config";
|
|
import { SegmentUUID, SponsorTime } from "../types";
|
|
import { getSkippingText } from "../utils/categoryUtils";
|
|
import { AnimationUtils } from "../../maze-utils/src/animationUtils";
|
|
import { keybindToString } from "../../maze-utils/src/config";
|
|
import { isMobileControlsOpen } from "../utils/mobileUtils";
|
|
|
|
export interface SkipButtonControlBarProps {
|
|
skip: (segment: SponsorTime) => void;
|
|
selectSegment: (UUID: SegmentUUID) => void;
|
|
onMobileYouTube: boolean;
|
|
onYTTV: boolean;
|
|
}
|
|
|
|
export class SkipButtonControlBar {
|
|
|
|
container: HTMLElement;
|
|
skipIcon: HTMLImageElement;
|
|
textContainer: HTMLElement;
|
|
chapterText: HTMLElement;
|
|
segment: SponsorTime;
|
|
|
|
showKeybindHint = true;
|
|
onMobileYouTube: boolean;
|
|
onYTTV: boolean;
|
|
|
|
enabled = false;
|
|
|
|
timeout: NodeJS.Timeout;
|
|
duration = 0;
|
|
|
|
skip: (segment: SponsorTime) => void;
|
|
|
|
// Used if on mobile page
|
|
hideButton: () => void;
|
|
showButton: () => void;
|
|
|
|
// Used by mobile only for swiping away
|
|
left = 0;
|
|
swipeStart = 0;
|
|
|
|
constructor(props: SkipButtonControlBarProps) {
|
|
this.skip = props.skip;
|
|
this.onMobileYouTube = props.onMobileYouTube;
|
|
this.onYTTV = props.onYTTV;
|
|
|
|
this.container = document.createElement("div");
|
|
this.container.classList.add("skipButtonControlBarContainer");
|
|
this.container.classList.add("sbhidden");
|
|
if (this.onMobileYouTube) this.container.classList.add("mobile");
|
|
|
|
this.skipIcon = document.createElement("img");
|
|
this.skipIcon.src = chrome.runtime.getURL("icons/skipIcon.svg");
|
|
this.skipIcon.classList.add("ytp-button");
|
|
this.skipIcon.id = "sbSkipIconControlBarImage";
|
|
if (this.onYTTV) {
|
|
this.skipIcon.style.width = "24px";
|
|
this.skipIcon.style.height = "24px";
|
|
}
|
|
|
|
this.textContainer = document.createElement("div");
|
|
|
|
this.container.appendChild(this.skipIcon);
|
|
this.container.appendChild(this.textContainer);
|
|
this.container.addEventListener("click", () => this.toggleSkip());
|
|
this.container.addEventListener("mouseenter", () => {
|
|
this.stopTimer();
|
|
|
|
if (this.segment) {
|
|
props.selectSegment(this.segment.UUID);
|
|
}
|
|
});
|
|
this.container.addEventListener("mouseleave", () => {
|
|
this.startTimer();
|
|
|
|
props.selectSegment(null);
|
|
});
|
|
if (this.onMobileYouTube) {
|
|
this.container.addEventListener("touchstart", (e) => this.handleTouchStart(e));
|
|
this.container.addEventListener("touchmove", (e) => this.handleTouchMove(e));
|
|
this.container.addEventListener("touchend", () => this.handleTouchEnd());
|
|
}
|
|
}
|
|
|
|
getElement(): HTMLElement {
|
|
return this.container;
|
|
}
|
|
|
|
attachToPage(): void {
|
|
const mountingContainer = this.getMountingContainer();
|
|
this.chapterText = document.querySelector(".ytp-chapter-container");
|
|
|
|
if (mountingContainer && !mountingContainer.contains(this.container)) {
|
|
if (this.onMobileYouTube || this.onYTTV) {
|
|
mountingContainer.appendChild(this.container);
|
|
} else {
|
|
mountingContainer.insertBefore(this.container, this.chapterText);
|
|
}
|
|
|
|
if (!this.onMobileYouTube) {
|
|
AnimationUtils.setupAutoHideAnimation(this.skipIcon, mountingContainer, false, false);
|
|
} else {
|
|
const { hide, show } = AnimationUtils.setupCustomHideAnimation(this.skipIcon, mountingContainer, false, false);
|
|
this.hideButton = hide;
|
|
this.showButton = show;
|
|
}
|
|
}
|
|
}
|
|
|
|
private getMountingContainer(): HTMLElement {
|
|
if (!this.onMobileYouTube && !this.onYTTV) {
|
|
return document.querySelector(".ytp-left-controls");
|
|
} else if (this.onYTTV) {
|
|
return document.querySelector(".ypcs-control-buttons-left");
|
|
} else {
|
|
return document.getElementById("player-container-id");
|
|
}
|
|
}
|
|
|
|
enable(segment: SponsorTime, duration?: number): void {
|
|
if (duration) this.duration = duration;
|
|
this.segment = segment;
|
|
this.enabled = true;
|
|
|
|
this.refreshText();
|
|
this.container?.classList?.remove("textDisabled");
|
|
this.textContainer?.classList?.remove("sbhidden");
|
|
AnimationUtils.disableAutoHideAnimation(this.skipIcon);
|
|
|
|
this.startTimer();
|
|
}
|
|
|
|
refreshText(): void {
|
|
if (this.segment) {
|
|
this.chapterText?.classList?.add("sbhidden");
|
|
this.container.classList.remove("sbhidden");
|
|
this.textContainer.innerText = this.getTitle();
|
|
this.skipIcon.setAttribute("title", this.getTitle());
|
|
}
|
|
}
|
|
|
|
setShowKeybindHint(show: boolean): void {
|
|
this.showKeybindHint = show;
|
|
|
|
this.refreshText();
|
|
}
|
|
|
|
stopTimer(): void {
|
|
if (this.timeout) clearTimeout(this.timeout);
|
|
}
|
|
|
|
startTimer(): void {
|
|
this.stopTimer();
|
|
this.timeout = setTimeout(() => this.disableText(), Math.max(Config.config.skipNoticeDuration, this.duration) * 1000);
|
|
}
|
|
|
|
disable(): void {
|
|
this.container.classList.add("sbhidden");
|
|
|
|
this.chapterText?.classList?.remove("sbhidden");
|
|
this.getChapterPrefix()?.classList?.remove("sbhidden");
|
|
|
|
this.enabled = false;
|
|
}
|
|
|
|
isEnabled(): boolean {
|
|
return this.enabled;
|
|
}
|
|
|
|
toggleSkip(): void {
|
|
if (this.segment && this.enabled) {
|
|
this.skip(this.segment);
|
|
this.disableText();
|
|
}
|
|
}
|
|
|
|
disableText(): void {
|
|
if (Config.config.hideSkipButtonPlayerControls) {
|
|
this.disable();
|
|
return;
|
|
}
|
|
|
|
this.container.classList.add("textDisabled");
|
|
this.textContainer?.classList?.add("sbhidden");
|
|
this.chapterText?.classList?.remove("sbhidden");
|
|
|
|
this.getChapterPrefix()?.classList?.add("sbhidden");
|
|
|
|
AnimationUtils.enableAutoHideAnimation(this.skipIcon);
|
|
if (this.onMobileYouTube) {
|
|
this.hideButton();
|
|
}
|
|
}
|
|
|
|
updateMobileControls(): void {
|
|
if (this.enabled) {
|
|
if (isMobileControlsOpen()) {
|
|
this.showButton();
|
|
} else {
|
|
this.hideButton();
|
|
}
|
|
}
|
|
}
|
|
|
|
private getTitle(): string {
|
|
return getSkippingText([this.segment], false) + (this.showKeybindHint ? " (" + keybindToString(Config.config.skipToHighlightKeybind) + ")" : "");
|
|
}
|
|
|
|
private getChapterPrefix(): HTMLElement {
|
|
return document.querySelector(".ytp-chapter-title-prefix");
|
|
}
|
|
|
|
// Swiping away on mobile
|
|
private handleTouchStart(event: TouchEvent): void {
|
|
this.swipeStart = event.touches[0].clientX;
|
|
}
|
|
|
|
// Swiping away on mobile
|
|
private handleTouchMove(event: TouchEvent): void {
|
|
const distanceMoved = this.swipeStart - event.touches[0].clientX;
|
|
this.left = Math.min(-distanceMoved, 0);
|
|
|
|
this.updateLeftStyle();
|
|
}
|
|
|
|
// Swiping away on mobile
|
|
private handleTouchEnd(): void {
|
|
if (this.left < -this.container.offsetWidth / 2) {
|
|
this.disableText();
|
|
|
|
// Don't let animation play
|
|
this.skipIcon.style.display = "none";
|
|
setTimeout(() => this.skipIcon.style.removeProperty("display"), 200);
|
|
}
|
|
|
|
this.left = 0;
|
|
this.updateLeftStyle();
|
|
}
|
|
|
|
// Swiping away on mobile
|
|
private updateLeftStyle() {
|
|
this.container.style.left = this.left + "px";
|
|
}
|
|
}
|