mirror of
https://github.com/ajayyy/SponsorBlock.git
synced 2025-12-06 11:37:02 +03:00
Add chapter name autocomplete
This commit is contained in:
@@ -1,3 +1,12 @@
|
||||
:root {
|
||||
--skip-notice-right: 10px;
|
||||
--skip-notice-padding: 5px;
|
||||
--skip-notice-margin: 5px;
|
||||
--skip-notice-border-horizontal: 5px;
|
||||
--skip-notice-border-vertical: 10px;
|
||||
--sb-dark-red-outline: rgb(130,0,0,0.9);
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
@@ -112,8 +121,8 @@ div:hover > .sponsorBlockChapterBar {
|
||||
.sponsorSkipObject {
|
||||
font-family: Roboto, Arial, Helvetica, sans-serif;
|
||||
|
||||
margin-left: 2px;
|
||||
margin-right: 2px;
|
||||
margin-left: var(--skip-notice-margin);
|
||||
margin-right: var(--skip-notice-margin);
|
||||
}
|
||||
|
||||
.sponsorSkipLogo {
|
||||
@@ -144,23 +153,23 @@ div:hover > .sponsorBlockChapterBar {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
bottom: 100px;
|
||||
right: 10px;
|
||||
right: var(--skip-notice-right);
|
||||
}
|
||||
|
||||
.sponsorSkipNoticeParent {
|
||||
position: absolute;
|
||||
|
||||
bottom: 100px;
|
||||
right: 10px;
|
||||
right: var(--skip-notice-right);
|
||||
}
|
||||
|
||||
.sponsorSkipNoticeParent, .sponsorSkipNotice {
|
||||
min-width: 350px;
|
||||
max-width: 50%;
|
||||
|
||||
border-spacing: 5px 10px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
border-spacing: var(--skip-notice-border-horizontal) var(--skip-notice-border-vertical);
|
||||
padding-left: var(--skip-notice-padding);
|
||||
padding-right: var(--skip-notice-padding);
|
||||
|
||||
border-collapse: unset;
|
||||
}
|
||||
@@ -505,12 +514,51 @@ input::-webkit-inner-spin-button {
|
||||
margin-bottom: 5px;
|
||||
|
||||
background-color: rgba(28, 28, 28, 0.9);
|
||||
border-color: rgb(130,0,0,0.9);
|
||||
border-color: var(--sb-dark-red-outline);
|
||||
color: white;
|
||||
border-width: 3px;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
/* Start SelectorComponent */
|
||||
|
||||
.sbSelector {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
width: calc(100% - var(--skip-notice-right) - var(--skip-notice-padding) * 2 - var(--skip-notice-margin) * 2 - var(--skip-notice-border-horizontal) * 2);
|
||||
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.sbSelectorBackground {
|
||||
text-align: center;
|
||||
|
||||
background-color: rgba(28, 28, 28, 0.9);
|
||||
border-radius: 6px;
|
||||
padding: 3px;
|
||||
margin: auto;
|
||||
width: 170px;
|
||||
}
|
||||
|
||||
.sbSelectorOption {
|
||||
cursor: pointer;
|
||||
background-color: rgb(43, 43, 43);
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
color: white;
|
||||
border-radius: 5px;
|
||||
font-size: 14px;
|
||||
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.sbSelectorOption:hover {
|
||||
background-color: #3a0000;
|
||||
}
|
||||
|
||||
/* End SelectorComponent */
|
||||
|
||||
.helpButton {
|
||||
height: 25px;
|
||||
cursor: pointer;
|
||||
|
||||
55
src/components/SelectorComponent.tsx
Normal file
55
src/components/SelectorComponent.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import * as React from "react";
|
||||
|
||||
export interface SelectorOption {
|
||||
label: string;
|
||||
}
|
||||
|
||||
export interface SelectorProps {
|
||||
id: string;
|
||||
options: SelectorOption[];
|
||||
onChange: (value: string) => void;
|
||||
}
|
||||
|
||||
export interface SelectorState {
|
||||
|
||||
}
|
||||
|
||||
class SelectorComponent extends React.Component<SelectorProps, SelectorState> {
|
||||
|
||||
constructor(props: SelectorProps) {
|
||||
super(props);
|
||||
|
||||
// Setup state
|
||||
this.state = {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
render(): React.ReactElement {
|
||||
return (
|
||||
<div id={this.props.id}
|
||||
className="sbSelector">
|
||||
<div className="sbSelectorBackground">
|
||||
{this.getOptions()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
getOptions(): React.ReactElement[] {
|
||||
const result: React.ReactElement[] = [];
|
||||
for (const option of this.props.options) {
|
||||
result.push(
|
||||
<div className="sbSelectorOption"
|
||||
onClick={() => this.props.onChange(option.label)}
|
||||
key={option.label}>
|
||||
{option.label}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export default SelectorComponent;
|
||||
@@ -1,11 +1,12 @@
|
||||
import * as React from "react";
|
||||
import * as CompileConfig from "../../config.json";
|
||||
import Config from "../config";
|
||||
import { ActionType, ActionTypes, Category, CategoryActionType, ContentContainer, SponsorTime } from "../types";
|
||||
import { ActionType, ActionTypes, Category, CategoryActionType, ChannelIDStatus, ContentContainer, SponsorTime } from "../types";
|
||||
import Utils from "../utils";
|
||||
import { getCategoryActionType } from "../utils/categoryUtils";
|
||||
import SubmissionNoticeComponent from "./SubmissionNoticeComponent";
|
||||
import { RectangleTooltip } from "../render/RectangleTooltip";
|
||||
import SelectorComponent, { SelectorOption } from "./SelectorComponent";
|
||||
|
||||
|
||||
const utils = new Utils();
|
||||
@@ -25,6 +26,9 @@ export interface SponsorTimeEditState {
|
||||
editing: boolean;
|
||||
sponsorTimeEdits: [string, string];
|
||||
selectedCategory: Category;
|
||||
description: string;
|
||||
suggestedNames: SelectorOption[];
|
||||
chapterNameSelectorOpen: boolean;
|
||||
}
|
||||
|
||||
const DEFAULT_CATEGORY = "chooseACategory";
|
||||
@@ -42,6 +46,9 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
||||
previousSkipType: CategoryActionType;
|
||||
timeBeforeChangingToPOI: number; // Initialized when first selecting POI
|
||||
|
||||
// For description auto-complete
|
||||
fetchingSuggestions: boolean;
|
||||
|
||||
constructor(props: SponsorTimeEditProps) {
|
||||
super(props);
|
||||
|
||||
@@ -52,10 +59,14 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
||||
this.idSuffix = this.props.idSuffix;
|
||||
this.previousSkipType = CategoryActionType.Skippable;
|
||||
|
||||
const sponsorTime = this.props.contentContainer().sponsorTimesSubmitting[this.props.index];
|
||||
this.state = {
|
||||
editing: false,
|
||||
sponsorTimeEdits: [null, null],
|
||||
selectedCategory: DEFAULT_CATEGORY as Category
|
||||
selectedCategory: DEFAULT_CATEGORY as Category,
|
||||
description: sponsorTime.description || "",
|
||||
suggestedNames: [],
|
||||
chapterNameSelectorOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
@@ -206,14 +217,22 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
||||
|
||||
{/* Chapter Name */}
|
||||
{sponsorTime.actionType === ActionType.Chapter ? (
|
||||
<div style={{position: "relative"}}>
|
||||
<div onMouseLeave={() => this.setState({chapterNameSelectorOpen: false})}>
|
||||
<input id={"chapterName" + this.idSuffix}
|
||||
className="sponsorTimeEdit"
|
||||
ref={this.descriptionOptionRef}
|
||||
type="text"
|
||||
value={sponsorTime.description || ""}
|
||||
onChange={() => this.saveEditTimes()}>
|
||||
value={this.state.description}
|
||||
onChange={(e) => this.descriptionUpdate(e.target.value)}
|
||||
onFocus={() => this.setState({chapterNameSelectorOpen: true})}>
|
||||
</input>
|
||||
{this.state.chapterNameSelectorOpen && this.state.description &&
|
||||
<SelectorComponent
|
||||
id={"chapterNameSelector" + this.idSuffix}
|
||||
options={this.state.suggestedNames}
|
||||
onChange={(v) => this.descriptionUpdate(v)}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
): ""}
|
||||
|
||||
@@ -520,6 +539,41 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
||||
}
|
||||
}
|
||||
|
||||
descriptionUpdate(description: string): void {
|
||||
this.setState({
|
||||
description
|
||||
});
|
||||
|
||||
if (!this.fetchingSuggestions) {
|
||||
this.fetchSuggestions(description);
|
||||
}
|
||||
|
||||
this.saveEditTimes();
|
||||
}
|
||||
|
||||
async fetchSuggestions(description: string): Promise<void> {
|
||||
if (this.props.contentContainer().channelIDInfo.status !== ChannelIDStatus.Found) return;
|
||||
|
||||
this.fetchingSuggestions = true;
|
||||
const result = await utils.asyncRequestToServer("GET", "/api/chapterNames", {
|
||||
description,
|
||||
channelID: this.props.contentContainer().channelIDInfo.id
|
||||
});
|
||||
|
||||
if (result.ok) {
|
||||
try {
|
||||
const names = JSON.parse(result.responseText) as {description: string}[];
|
||||
this.setState({
|
||||
suggestedNames: names.map(n => ({
|
||||
label: n.description
|
||||
}))
|
||||
});
|
||||
} catch (e) {} //eslint-disable-line no-empty
|
||||
}
|
||||
|
||||
this.fetchingSuggestions = false;
|
||||
}
|
||||
|
||||
configUpdate(): void {
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
@@ -124,7 +124,8 @@ const skipNoticeContentContainer: ContentContainer = () => ({
|
||||
previewTime,
|
||||
videoInfo,
|
||||
getRealCurrentTime: getRealCurrentTime,
|
||||
lockedCategories
|
||||
lockedCategories,
|
||||
channelIDInfo
|
||||
});
|
||||
|
||||
// value determining when to count segment as skipped and send telemetry to server (percent based)
|
||||
|
||||
@@ -21,7 +21,8 @@ export interface ContentContainer {
|
||||
previewTime: (time: number, unpause?: boolean) => void,
|
||||
videoInfo: VideoInfo,
|
||||
getRealCurrentTime: () => number,
|
||||
lockedCategories: string[]
|
||||
lockedCategories: string[],
|
||||
channelIDInfo: ChannelIDInfo
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user