mirror of
https://github.com/ajayyy/SponsorBlock.git
synced 2024-11-10 01:01:55 +01:00
Add pill beside title for full video reports
This commit is contained in:
parent
d36b4a54f3
commit
2883a50f27
6 changed files with 178 additions and 22 deletions
|
@ -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;
|
||||
}
|
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 { 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 {
|
||||
|
|
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 * 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
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
|
||||
}
|
Loading…
Reference in a new issue