Add chapter name autocomplete

This commit is contained in:
Ajay Ramachandran
2021-11-07 01:05:32 -04:00
parent 9e6e3b023d
commit a3e67b6cde
5 changed files with 174 additions and 15 deletions

View File

@@ -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 { .hidden {
display: none; display: none;
} }
@@ -112,8 +121,8 @@ div:hover > .sponsorBlockChapterBar {
.sponsorSkipObject { .sponsorSkipObject {
font-family: Roboto, Arial, Helvetica, sans-serif; font-family: Roboto, Arial, Helvetica, sans-serif;
margin-left: 2px; margin-left: var(--skip-notice-margin);
margin-right: 2px; margin-right: var(--skip-notice-margin);
} }
.sponsorSkipLogo { .sponsorSkipLogo {
@@ -144,23 +153,23 @@ div:hover > .sponsorBlockChapterBar {
position: absolute; position: absolute;
right: 5px; right: 5px;
bottom: 100px; bottom: 100px;
right: 10px; right: var(--skip-notice-right);
} }
.sponsorSkipNoticeParent { .sponsorSkipNoticeParent {
position: absolute; position: absolute;
bottom: 100px; bottom: 100px;
right: 10px; right: var(--skip-notice-right);
} }
.sponsorSkipNoticeParent, .sponsorSkipNotice { .sponsorSkipNoticeParent, .sponsorSkipNotice {
min-width: 350px; min-width: 350px;
max-width: 50%; max-width: 50%;
border-spacing: 5px 10px; border-spacing: var(--skip-notice-border-horizontal) var(--skip-notice-border-vertical);
padding-left: 5px; padding-left: var(--skip-notice-padding);
padding-right: 5px; padding-right: var(--skip-notice-padding);
border-collapse: unset; border-collapse: unset;
} }
@@ -505,12 +514,51 @@ input::-webkit-inner-spin-button {
margin-bottom: 5px; margin-bottom: 5px;
background-color: rgba(28, 28, 28, 0.9); 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; color: white;
border-width: 3px; border-width: 3px;
padding: 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 { .helpButton {
height: 25px; height: 25px;
cursor: pointer; cursor: pointer;

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

View File

@@ -1,11 +1,12 @@
import * as React from "react"; import * as React from "react";
import * as CompileConfig from "../../config.json"; import * as CompileConfig from "../../config.json";
import Config from "../config"; 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 Utils from "../utils";
import { getCategoryActionType } from "../utils/categoryUtils"; import { getCategoryActionType } from "../utils/categoryUtils";
import SubmissionNoticeComponent from "./SubmissionNoticeComponent"; import SubmissionNoticeComponent from "./SubmissionNoticeComponent";
import { RectangleTooltip } from "../render/RectangleTooltip"; import { RectangleTooltip } from "../render/RectangleTooltip";
import SelectorComponent, { SelectorOption } from "./SelectorComponent";
const utils = new Utils(); const utils = new Utils();
@@ -25,6 +26,9 @@ export interface SponsorTimeEditState {
editing: boolean; editing: boolean;
sponsorTimeEdits: [string, string]; sponsorTimeEdits: [string, string];
selectedCategory: Category; selectedCategory: Category;
description: string;
suggestedNames: SelectorOption[];
chapterNameSelectorOpen: boolean;
} }
const DEFAULT_CATEGORY = "chooseACategory"; const DEFAULT_CATEGORY = "chooseACategory";
@@ -42,6 +46,9 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
previousSkipType: CategoryActionType; previousSkipType: CategoryActionType;
timeBeforeChangingToPOI: number; // Initialized when first selecting POI timeBeforeChangingToPOI: number; // Initialized when first selecting POI
// For description auto-complete
fetchingSuggestions: boolean;
constructor(props: SponsorTimeEditProps) { constructor(props: SponsorTimeEditProps) {
super(props); super(props);
@@ -52,10 +59,14 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
this.idSuffix = this.props.idSuffix; this.idSuffix = this.props.idSuffix;
this.previousSkipType = CategoryActionType.Skippable; this.previousSkipType = CategoryActionType.Skippable;
const sponsorTime = this.props.contentContainer().sponsorTimesSubmitting[this.props.index];
this.state = { this.state = {
editing: false, editing: false,
sponsorTimeEdits: [null, null], 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 */} {/* Chapter Name */}
{sponsorTime.actionType === ActionType.Chapter ? ( {sponsorTime.actionType === ActionType.Chapter ? (
<div style={{position: "relative"}}> <div onMouseLeave={() => this.setState({chapterNameSelectorOpen: false})}>
<input id={"chapterName" + this.idSuffix} <input id={"chapterName" + this.idSuffix}
className="sponsorTimeEdit" className="sponsorTimeEdit"
ref={this.descriptionOptionRef} ref={this.descriptionOptionRef}
type="text" type="text"
value={sponsorTime.description || ""} value={this.state.description}
onChange={() => this.saveEditTimes()}> onChange={(e) => this.descriptionUpdate(e.target.value)}
onFocus={() => this.setState({chapterNameSelectorOpen: true})}>
</input> </input>
{this.state.chapterNameSelectorOpen && this.state.description &&
<SelectorComponent
id={"chapterNameSelector" + this.idSuffix}
options={this.state.suggestedNames}
onChange={(v) => this.descriptionUpdate(v)}
/>
}
</div> </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 { configUpdate(): void {
this.forceUpdate(); this.forceUpdate();
} }

View File

@@ -124,7 +124,8 @@ const skipNoticeContentContainer: ContentContainer = () => ({
previewTime, previewTime,
videoInfo, videoInfo,
getRealCurrentTime: getRealCurrentTime, getRealCurrentTime: getRealCurrentTime,
lockedCategories lockedCategories,
channelIDInfo
}); });
// value determining when to count segment as skipped and send telemetry to server (percent based) // value determining when to count segment as skipped and send telemetry to server (percent based)

View File

@@ -21,7 +21,8 @@ export interface ContentContainer {
previewTime: (time: number, unpause?: boolean) => void, previewTime: (time: number, unpause?: boolean) => void,
videoInfo: VideoInfo, videoInfo: VideoInfo,
getRealCurrentTime: () => number, getRealCurrentTime: () => number,
lockedCategories: string[] lockedCategories: string[],
channelIDInfo: ChannelIDInfo
} }
} }