Add pill beside title for full video reports

This commit is contained in:
Ajay 2022-01-05 02:35:58 -05:00
parent d36b4a54f3
commit 2883a50f27
6 changed files with 178 additions and 22 deletions

View file

@ -613,3 +613,15 @@ input::-webkit-inner-spin-button {
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;
}

View 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;

View file

@ -18,6 +18,7 @@ import { SkipButtonControlBar } from "./js-components/skipButtonControlBar";
import { Tooltip } from "./render/Tooltip";
import { getStartTimeFromUrl } from "./utils/urlParser";
import { getControls } from "./utils/pageUtils";
import { CategoryPill } from "./render/CategoryPill";
// Hack to get the CSS loaded on permission-based sites (Invidious)
utils.wait(() => Config.config !== null, 5000, 10).then(addCSS);
@ -75,9 +76,11 @@ let lastCheckVideoTime = -1;
//is this channel whitelised from getting sponsors skipped
let channelWhitelisted = false;
// create preview bar
let previewBar: PreviewBar = null;
// Skip to highlight button
let skipButtonControlBar: SkipButtonControlBar = null;
// For full video sponsors/selfpromo
let categoryPill: CategoryPill = null;
/** Element containing the player controls on the YouTube player. */
let controls: HTMLElement | null = null;
@ -263,6 +266,7 @@ function resetValues() {
}
skipButtonControlBar?.disable();
categoryPill?.setVisibility(false);
}
async function videoIDChange(id) {
@ -549,6 +553,7 @@ function refreshVideoAttachments() {
setupVideoListeners();
setupSkipButtonControlBar();
setupCategoryPill();
}
}
}
@ -637,6 +642,14 @@ function setupSkipButtonControlBar() {
skipButtonControlBar.attachToPage();
}
function setupCategoryPill() {
if (!categoryPill) {
categoryPill = new CategoryPill();
}
categoryPill.attachToPage();
}
async function sponsorsLookup(id: string, keepOldSubmissions = true) {
if (!video) refreshVideoAttachments();
//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 response = await utils.asyncRequestToServer('GET', "/api/skipSegments/" + hashPrefix, {
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}`,
...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) {
startSponsorSchedule(undefined, startingSegmentTime);
} else {

View 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;
}
}
}

View file

@ -2,6 +2,7 @@ import Config from "./config";
import { CategorySelection, SponsorTime, FetchResponse, BackgroundScriptContainer, Registration } from "./types";
import * as CompileConfig from "../config.json";
import { GenericUtils } from "./utils/genericUtils";
export default class Utils {
@ -23,27 +24,8 @@ export default class Utils {
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> {
return await new Promise((resolve, reject) => {
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();
});
return GenericUtils.wait(condition, timeout, check);
}
containsPermission(permissions: chrome.permissions.Permissions): Promise<boolean> {

26
src/utils/genericUtils.ts Normal file
View 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
}