mirror of
https://github.com/ajayyy/SponsorBlock.git
synced 2025-12-08 12:37:05 +03:00
Add pill beside title for full video reports
This commit is contained in:
@@ -613,3 +613,15 @@ input::-webkit-inner-spin-button {
|
|||||||
line-height: 1.5em;
|
line-height: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sponsorBlockCategoryPill {
|
||||||
|
border-radius: 25px;
|
||||||
|
padding-left: 8px;
|
||||||
|
padding-right: 8px;
|
||||||
|
margin-right: 3px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sponsorBlockCategoryPillTitleSection {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
47
src/components/CategoryPillComponent.tsx
Normal file
47
src/components/CategoryPillComponent.tsx
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import Config from "../config";
|
||||||
|
import { SponsorTime } from "../types";
|
||||||
|
|
||||||
|
export interface CategoryPillProps {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CategoryPillState {
|
||||||
|
segment?: SponsorTime;
|
||||||
|
show: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
class CategoryPillComponent extends React.Component<CategoryPillProps, CategoryPillState> {
|
||||||
|
|
||||||
|
constructor(props: CategoryPillProps) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
segment: null,
|
||||||
|
show: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
render(): React.ReactElement {
|
||||||
|
const style: React.CSSProperties = {
|
||||||
|
backgroundColor: Config.config.barTypes["preview-" + this.state.segment?.category]?.color,
|
||||||
|
display: this.state.show ? "flex" : "none"
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span style={style}
|
||||||
|
className="sponsorBlockCategoryPill" >
|
||||||
|
<span className="sponsorBlockCategoryPillTitleSection">
|
||||||
|
<img className="sponsorSkipLogo sponsorSkipObject"
|
||||||
|
src={chrome.extension.getURL("icons/IconSponsorBlocker256px.png")}>
|
||||||
|
</img>
|
||||||
|
<span className="sponsorBlockCategoryPillTitle">
|
||||||
|
{chrome.i18n.getMessage("category_" + this.state.segment?.category)}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CategoryPillComponent;
|
||||||
@@ -18,6 +18,7 @@ import { SkipButtonControlBar } from "./js-components/skipButtonControlBar";
|
|||||||
import { Tooltip } from "./render/Tooltip";
|
import { Tooltip } from "./render/Tooltip";
|
||||||
import { getStartTimeFromUrl } from "./utils/urlParser";
|
import { getStartTimeFromUrl } from "./utils/urlParser";
|
||||||
import { getControls } from "./utils/pageUtils";
|
import { getControls } from "./utils/pageUtils";
|
||||||
|
import { CategoryPill } from "./render/CategoryPill";
|
||||||
|
|
||||||
// Hack to get the CSS loaded on permission-based sites (Invidious)
|
// Hack to get the CSS loaded on permission-based sites (Invidious)
|
||||||
utils.wait(() => Config.config !== null, 5000, 10).then(addCSS);
|
utils.wait(() => Config.config !== null, 5000, 10).then(addCSS);
|
||||||
@@ -75,9 +76,11 @@ let lastCheckVideoTime = -1;
|
|||||||
//is this channel whitelised from getting sponsors skipped
|
//is this channel whitelised from getting sponsors skipped
|
||||||
let channelWhitelisted = false;
|
let channelWhitelisted = false;
|
||||||
|
|
||||||
// create preview bar
|
|
||||||
let previewBar: PreviewBar = null;
|
let previewBar: PreviewBar = null;
|
||||||
|
// Skip to highlight button
|
||||||
let skipButtonControlBar: SkipButtonControlBar = null;
|
let skipButtonControlBar: SkipButtonControlBar = null;
|
||||||
|
// For full video sponsors/selfpromo
|
||||||
|
let categoryPill: CategoryPill = null;
|
||||||
|
|
||||||
/** Element containing the player controls on the YouTube player. */
|
/** Element containing the player controls on the YouTube player. */
|
||||||
let controls: HTMLElement | null = null;
|
let controls: HTMLElement | null = null;
|
||||||
@@ -263,6 +266,7 @@ function resetValues() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
skipButtonControlBar?.disable();
|
skipButtonControlBar?.disable();
|
||||||
|
categoryPill?.setVisibility(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function videoIDChange(id) {
|
async function videoIDChange(id) {
|
||||||
@@ -549,6 +553,7 @@ function refreshVideoAttachments() {
|
|||||||
|
|
||||||
setupVideoListeners();
|
setupVideoListeners();
|
||||||
setupSkipButtonControlBar();
|
setupSkipButtonControlBar();
|
||||||
|
setupCategoryPill();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -637,6 +642,14 @@ function setupSkipButtonControlBar() {
|
|||||||
skipButtonControlBar.attachToPage();
|
skipButtonControlBar.attachToPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setupCategoryPill() {
|
||||||
|
if (!categoryPill) {
|
||||||
|
categoryPill = new CategoryPill();
|
||||||
|
}
|
||||||
|
|
||||||
|
categoryPill.attachToPage();
|
||||||
|
}
|
||||||
|
|
||||||
async function sponsorsLookup(id: string, keepOldSubmissions = true) {
|
async function sponsorsLookup(id: string, keepOldSubmissions = true) {
|
||||||
if (!video) refreshVideoAttachments();
|
if (!video) refreshVideoAttachments();
|
||||||
//there is still no video here
|
//there is still no video here
|
||||||
@@ -672,7 +685,7 @@ async function sponsorsLookup(id: string, keepOldSubmissions = true) {
|
|||||||
const hashPrefix = (await utils.getHash(id, 1)).substr(0, 4);
|
const hashPrefix = (await utils.getHash(id, 1)).substr(0, 4);
|
||||||
const response = await utils.asyncRequestToServer('GET', "/api/skipSegments/" + hashPrefix, {
|
const response = await utils.asyncRequestToServer('GET', "/api/skipSegments/" + hashPrefix, {
|
||||||
categories,
|
categories,
|
||||||
actionTypes: Config.config.muteSegments ? [ActionType.Skip, ActionType.Mute] : [ActionType.Skip],
|
actionTypes: Config.config.muteSegments ? [ActionType.Skip, ActionType.Mute, ActionType.Full] : [ActionType.Skip, ActionType.Full],
|
||||||
userAgent: `${chrome.runtime.id}`,
|
userAgent: `${chrome.runtime.id}`,
|
||||||
...extraRequestData
|
...extraRequestData
|
||||||
});
|
});
|
||||||
@@ -864,6 +877,11 @@ function startSkipScheduleCheckingForStartSponsors() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fullVideoSegment = sponsorTimes.filter((time) => time.actionType === ActionType.Full)[0];
|
||||||
|
if (fullVideoSegment) {
|
||||||
|
categoryPill?.setSegment(fullVideoSegment);
|
||||||
|
}
|
||||||
|
|
||||||
if (startingSegmentTime !== -1) {
|
if (startingSegmentTime !== -1) {
|
||||||
startSponsorSchedule(undefined, startingSegmentTime);
|
startSponsorSchedule(undefined, startingSegmentTime);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
71
src/render/CategoryPill.tsx
Normal file
71
src/render/CategoryPill.tsx
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import * as ReactDOM from "react-dom";
|
||||||
|
import CategoryPillComponent, { CategoryPillState } from "../components/CategoryPillComponent";
|
||||||
|
import { SponsorTime } from "../types";
|
||||||
|
import { GenericUtils } from "../utils/genericUtils";
|
||||||
|
|
||||||
|
export class CategoryPill {
|
||||||
|
container: HTMLElement;
|
||||||
|
ref: React.RefObject<CategoryPillComponent>;
|
||||||
|
|
||||||
|
unsavedState: CategoryPillState;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.ref = React.createRef();
|
||||||
|
}
|
||||||
|
|
||||||
|
async attachToPage(): Promise<void> {
|
||||||
|
// TODO: Mobile and invidious
|
||||||
|
const referenceNode = await GenericUtils.wait(() => document.querySelector(".ytd-video-primary-info-renderer.title") as HTMLElement);
|
||||||
|
|
||||||
|
if (referenceNode && !referenceNode.contains(this.container)) {
|
||||||
|
this.container = document.createElement('span');
|
||||||
|
this.container.id = "categoryPill";
|
||||||
|
this.container.style.display = "relative";
|
||||||
|
|
||||||
|
referenceNode.prepend(this.container);
|
||||||
|
referenceNode.style.display = "flex";
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<CategoryPillComponent ref={this.ref} />,
|
||||||
|
this.container
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this.unsavedState) {
|
||||||
|
this.ref.current?.setState(this.unsavedState);
|
||||||
|
this.unsavedState = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close(): void {
|
||||||
|
ReactDOM.unmountComponentAtNode(this.container);
|
||||||
|
this.container.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
setVisibility(show: boolean): void {
|
||||||
|
const newState = {
|
||||||
|
show
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.ref.current) {
|
||||||
|
this.ref.current?.setState(newState);
|
||||||
|
} else {
|
||||||
|
this.unsavedState = newState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setSegment(segment: SponsorTime): void {
|
||||||
|
const newState = {
|
||||||
|
segment,
|
||||||
|
show: true
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.ref.current) {
|
||||||
|
this.ref.current?.setState(newState);
|
||||||
|
} else {
|
||||||
|
this.unsavedState = newState;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/utils.ts
22
src/utils.ts
@@ -2,6 +2,7 @@ import Config from "./config";
|
|||||||
import { CategorySelection, SponsorTime, FetchResponse, BackgroundScriptContainer, Registration } from "./types";
|
import { CategorySelection, SponsorTime, FetchResponse, BackgroundScriptContainer, Registration } from "./types";
|
||||||
|
|
||||||
import * as CompileConfig from "../config.json";
|
import * as CompileConfig from "../config.json";
|
||||||
|
import { GenericUtils } from "./utils/genericUtils";
|
||||||
|
|
||||||
export default class Utils {
|
export default class Utils {
|
||||||
|
|
||||||
@@ -23,27 +24,8 @@ export default class Utils {
|
|||||||
this.backgroundScriptContainer = backgroundScriptContainer;
|
this.backgroundScriptContainer = backgroundScriptContainer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Function that can be used to wait for a condition before returning. */
|
|
||||||
async wait<T>(condition: () => T | false, timeout = 5000, check = 100): Promise<T> {
|
async wait<T>(condition: () => T | false, timeout = 5000, check = 100): Promise<T> {
|
||||||
return await new Promise((resolve, reject) => {
|
return GenericUtils.wait(condition, timeout, check);
|
||||||
setTimeout(() => {
|
|
||||||
clearInterval(interval);
|
|
||||||
reject("TIMEOUT");
|
|
||||||
}, timeout);
|
|
||||||
|
|
||||||
const intervalCheck = () => {
|
|
||||||
const result = condition();
|
|
||||||
if (result !== false) {
|
|
||||||
resolve(result);
|
|
||||||
clearInterval(interval);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const interval = setInterval(intervalCheck, check);
|
|
||||||
|
|
||||||
//run the check once first, this speeds it up a lot
|
|
||||||
intervalCheck();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
containsPermission(permissions: chrome.permissions.Permissions): Promise<boolean> {
|
containsPermission(permissions: chrome.permissions.Permissions): Promise<boolean> {
|
||||||
|
|||||||
26
src/utils/genericUtils.ts
Normal file
26
src/utils/genericUtils.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/** Function that can be used to wait for a condition before returning. */
|
||||||
|
async function wait<T>(condition: () => T | false, timeout = 5000, check = 100): Promise<T> {
|
||||||
|
return await new Promise((resolve, reject) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
clearInterval(interval);
|
||||||
|
reject("TIMEOUT");
|
||||||
|
}, timeout);
|
||||||
|
|
||||||
|
const intervalCheck = () => {
|
||||||
|
const result = condition();
|
||||||
|
if (result) {
|
||||||
|
resolve(result);
|
||||||
|
clearInterval(interval);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const interval = setInterval(intervalCheck, check);
|
||||||
|
|
||||||
|
//run the check once first, this speeds it up a lot
|
||||||
|
intervalCheck();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GenericUtils = {
|
||||||
|
wait
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user