Apply formatting
and use a for...of loop instead of forEach
This commit is contained in:
parent
05038b6ead
commit
21cf2d5e46
@ -1,12 +1,13 @@
|
|||||||
const introSkipper = {
|
const introSkipper = {
|
||||||
originalFetch: window.fetch.bind(window),
|
originalFetch: window.fetch.bind(window),
|
||||||
originalXHROpen: XMLHttpRequest.prototype.open,
|
originalXHROpen: XMLHttpRequest.prototype.open,
|
||||||
d: msg => console.debug("[intro skipper] ", msg),
|
d: (msg) => console.debug("[intro skipper] ", msg),
|
||||||
setup() {
|
setup() {
|
||||||
const self = this;
|
const self = this;
|
||||||
this.initializeState();
|
this.initializeState();
|
||||||
this.initializeObserver();
|
this.initializeObserver();
|
||||||
this.currentOption = localStorage.getItem('introskipperOption') || 'Show Button';
|
this.currentOption =
|
||||||
|
localStorage.getItem("introskipperOption") || "Show Button";
|
||||||
window.fetch = this.fetchWrapper.bind(this);
|
window.fetch = this.fetchWrapper.bind(this);
|
||||||
XMLHttpRequest.prototype.open = function (...args) {
|
XMLHttpRequest.prototype.open = function (...args) {
|
||||||
self.xhrOpenWrapper(this, ...args);
|
self.xhrOpenWrapper(this, ...args);
|
||||||
@ -17,19 +18,32 @@ const introSkipper = {
|
|||||||
this.d("Registered hooks");
|
this.d("Registered hooks");
|
||||||
},
|
},
|
||||||
initializeState() {
|
initializeState() {
|
||||||
Object.assign(this, { allowEnter: true, skipSegments: {}, videoPlayer: null, skipButton: null, osdElement: null, skipperData: null, currentEpisodeId: null, injectMetadata: false });
|
Object.assign(this, {
|
||||||
|
allowEnter: true,
|
||||||
|
skipSegments: {},
|
||||||
|
videoPlayer: null,
|
||||||
|
skipButton: null,
|
||||||
|
osdElement: null,
|
||||||
|
skipperData: null,
|
||||||
|
currentEpisodeId: null,
|
||||||
|
injectMetadata: false,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
initializeObserver() {
|
initializeObserver() {
|
||||||
this.observer = new MutationObserver(mutations => {
|
this.observer = new MutationObserver((mutations) => {
|
||||||
const actionSheet = mutations[mutations.length - 1].target.querySelector('.actionSheet');
|
const actionSheet =
|
||||||
if (actionSheet && !actionSheet.querySelector(`[data-id="${'introskipperMenu'}"]`)) this.injectIntroSkipperOptions(actionSheet);
|
mutations[mutations.length - 1].target.querySelector(".actionSheet");
|
||||||
|
if (
|
||||||
|
actionSheet &&
|
||||||
|
!actionSheet.querySelector(`[data-id="${"introskipperMenu"}"]`)
|
||||||
|
)
|
||||||
|
this.injectIntroSkipperOptions(actionSheet);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
fetchWrapper(resource, options) {
|
fetchWrapper(resource, options) {
|
||||||
const response = this.originalFetch(resource, options);
|
const response = this.originalFetch(resource, options);
|
||||||
const url = new URL(resource);
|
const url = new URL(resource);
|
||||||
if (this.injectMetadata && url.pathname.includes("/MetadataEditor"))
|
if (this.injectMetadata && url.pathname.includes("/MetadataEditor")) {
|
||||||
{
|
|
||||||
this.processMetadata(url.pathname);
|
this.processMetadata(url.pathname);
|
||||||
}
|
}
|
||||||
return response;
|
return response;
|
||||||
@ -42,7 +56,9 @@ const introSkipper = {
|
|||||||
const id = this.extractId(url);
|
const id = this.extractId(url);
|
||||||
if (id) {
|
if (id) {
|
||||||
try {
|
try {
|
||||||
this.skipSegments = await this.secureFetch(`Episode/${id}/IntroSkipperSegments`);
|
this.skipSegments = await this.secureFetch(
|
||||||
|
`Episode/${id}/IntroSkipperSegments`,
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.d(`Error fetching skip segments: ${error.message}`);
|
this.d(`Error fetching skip segments: ${error.message}`);
|
||||||
}
|
}
|
||||||
@ -56,7 +72,9 @@ const introSkipper = {
|
|||||||
if (this.skipperData) {
|
if (this.skipperData) {
|
||||||
this.currentEpisodeId = id;
|
this.currentEpisodeId = id;
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
const metadataFormFields = document.querySelector('.metadataFormFields');
|
const metadataFormFields = document.querySelector(
|
||||||
|
".metadataFormFields",
|
||||||
|
);
|
||||||
metadataFormFields && this.injectSkipperFields(metadataFormFields);
|
metadataFormFields && this.injectSkipperFields(metadataFormFields);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -66,9 +84,11 @@ const introSkipper = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
extractId(searchString) {
|
extractId(searchString) {
|
||||||
const startIndex = searchString.indexOf('Items/') + 6;
|
const startIndex = searchString.indexOf("Items/") + 6;
|
||||||
const endIndex = searchString.indexOf('/', startIndex);
|
const endIndex = searchString.indexOf("/", startIndex);
|
||||||
return endIndex !== -1 ? searchString.substring(startIndex, endIndex) : searchString.substring(startIndex);
|
return endIndex !== -1
|
||||||
|
? searchString.substring(startIndex, endIndex)
|
||||||
|
: searchString.substring(startIndex);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Event handler that runs whenever the current view changes.
|
* Event handler that runs whenever the current view changes.
|
||||||
@ -85,12 +105,17 @@ const introSkipper = {
|
|||||||
this.videoPlayer = document.querySelector("video");
|
this.videoPlayer = document.querySelector("video");
|
||||||
if (this.videoPlayer) {
|
if (this.videoPlayer) {
|
||||||
this.d("Hooking video timeupdate");
|
this.d("Hooking video timeupdate");
|
||||||
this.videoPlayer.addEventListener("timeupdate", this.videoPositionChanged);
|
this.videoPlayer.addEventListener(
|
||||||
this.osdElement = document.querySelector("div.videoOsdBottom")
|
"timeupdate",
|
||||||
this.observer.observe(document.body, { childList: true, subtree: false });
|
this.videoPositionChanged,
|
||||||
|
);
|
||||||
|
this.osdElement = document.querySelector("div.videoOsdBottom");
|
||||||
|
this.observer.observe(document.body, {
|
||||||
|
childList: true,
|
||||||
|
subtree: false,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
this.observer.disconnect();
|
this.observer.disconnect();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -193,15 +218,22 @@ const introSkipper = {
|
|||||||
},
|
},
|
||||||
/** Tests if the OSD controls are visible. */
|
/** Tests if the OSD controls are visible. */
|
||||||
osdVisible() {
|
osdVisible() {
|
||||||
return this.osdElement ? !this.osdElement.classList.contains("hide") : false;
|
return this.osdElement
|
||||||
|
? !this.osdElement.classList.contains("hide")
|
||||||
|
: false;
|
||||||
},
|
},
|
||||||
/** Get the currently playing skippable segment. */
|
/** Get the currently playing skippable segment. */
|
||||||
getCurrentSegment(position) {
|
getCurrentSegment(position) {
|
||||||
for (const [key, segment] of Object.entries(this.skipSegments)) {
|
for (const key in this.skipSegments) {
|
||||||
if ((position > segment.ShowSkipPromptAt && position < segment.HideSkipPromptAt - 1) ||
|
const segment = this.skipSegments[key];
|
||||||
(this.osdVisible() && position > segment.IntroStart && position < segment.IntroEnd - 1)) {
|
if (
|
||||||
segment.SegmentType = key;
|
(position > segment.ShowSkipPromptAt &&
|
||||||
return segment;
|
position < segment.HideSkipPromptAt - 1) ||
|
||||||
|
(this.osdVisible() &&
|
||||||
|
position > segment.IntroStart &&
|
||||||
|
position < segment.IntroEnd - 1)
|
||||||
|
) {
|
||||||
|
return { ...segment, SegmentType: key };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { SegmentType: "None" };
|
return { SegmentType: "None" };
|
||||||
@ -220,34 +252,50 @@ const introSkipper = {
|
|||||||
videoPositionChanged() {
|
videoPositionChanged() {
|
||||||
if (!this.skipButton) return;
|
if (!this.skipButton) return;
|
||||||
const embyButton = this.skipButton.querySelector(".emby-button");
|
const embyButton = this.skipButton.querySelector(".emby-button");
|
||||||
const segmentType = this.getCurrentSegment(this.videoPlayer.currentTime).SegmentType;
|
const segmentType = this.getCurrentSegment(
|
||||||
if (segmentType === "None" || this.currentOption === "Off" || !this.allowEnter) {
|
this.videoPlayer.currentTime,
|
||||||
if (this.skipButton.classList.contains('show')) {
|
).SegmentType;
|
||||||
this.skipButton.classList.remove('show');
|
if (
|
||||||
embyButton.addEventListener("transitionend", () => {
|
segmentType === "None" ||
|
||||||
|
this.currentOption === "Off" ||
|
||||||
|
!this.allowEnter
|
||||||
|
) {
|
||||||
|
if (this.skipButton.classList.contains("show")) {
|
||||||
|
this.skipButton.classList.remove("show");
|
||||||
|
embyButton.addEventListener(
|
||||||
|
"transitionend",
|
||||||
|
() => {
|
||||||
this.skipButton.classList.add("hide");
|
this.skipButton.classList.add("hide");
|
||||||
if (this.osdVisible()) {
|
if (this.osdVisible()) {
|
||||||
this.osdElement.querySelector('button.btnPause').focus();
|
this.osdElement.querySelector("button.btnPause").focus();
|
||||||
} else {
|
} else {
|
||||||
embyButton.originalBlur();
|
embyButton.originalBlur();
|
||||||
}
|
}
|
||||||
}, { once: true });
|
},
|
||||||
|
{ once: true },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.currentOption === "Automatically Skip" || (this.currentOption === "Button w/ auto PiP" && document.pictureInPictureElement)) {
|
if (
|
||||||
|
this.currentOption === "Automatically Skip" ||
|
||||||
|
(this.currentOption === "Button w/ auto PiP" &&
|
||||||
|
document.pictureInPictureElement)
|
||||||
|
) {
|
||||||
this.doSkip();
|
this.doSkip();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.skipButton.querySelector("#btnSkipSegmentText").textContent = this.skipButton.dataset[segmentType];
|
this.skipButton.querySelector("#btnSkipSegmentText").textContent =
|
||||||
|
this.skipButton.dataset[segmentType];
|
||||||
if (!this.skipButton.classList.contains("hide")) {
|
if (!this.skipButton.classList.contains("hide")) {
|
||||||
if (!this.osdVisible() && !embyButton.contains(document.activeElement)) embyButton.focus();
|
if (!this.osdVisible() && !embyButton.contains(document.activeElement))
|
||||||
|
embyButton.focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
this.skipButton.classList.remove("hide");
|
this.skipButton.classList.remove("hide");
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
this.skipButton.classList.add('show');
|
this.skipButton.classList.add("show");
|
||||||
this.overrideBlur(embyButton);
|
this.overrideBlur(embyButton);
|
||||||
embyButton.focus();
|
embyButton.focus();
|
||||||
});
|
});
|
||||||
@ -264,66 +312,83 @@ const introSkipper = {
|
|||||||
this.d(`Skipping ${segment.SegmentType}`);
|
this.d(`Skipping ${segment.SegmentType}`);
|
||||||
this.allowEnter = false;
|
this.allowEnter = false;
|
||||||
const seekedHandler = () => {
|
const seekedHandler = () => {
|
||||||
this.videoPlayer.removeEventListener('seeked', seekedHandler);
|
this.videoPlayer.removeEventListener("seeked", seekedHandler);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.allowEnter = true;
|
this.allowEnter = true;
|
||||||
}, 500);
|
}, 500);
|
||||||
};
|
};
|
||||||
this.videoPlayer.addEventListener('seeked', seekedHandler);
|
this.videoPlayer.addEventListener("seeked", seekedHandler);
|
||||||
this.videoPlayer.currentTime = segment.SegmentType === "Credits" && this.videoPlayer.duration - segment.IntroEnd < 3
|
this.videoPlayer.currentTime =
|
||||||
|
segment.SegmentType === "Credits" &&
|
||||||
|
this.videoPlayer.duration - segment.IntroEnd < 3
|
||||||
? this.videoPlayer.duration + 10
|
? this.videoPlayer.duration + 10
|
||||||
: segment.IntroEnd;
|
: segment.IntroEnd;
|
||||||
},
|
},
|
||||||
createButton(ref, id, innerHTML, clickHandler) {
|
createButton(ref, id, innerHTML, clickHandler) {
|
||||||
const button = ref.cloneNode(true);
|
const button = ref.cloneNode(true);
|
||||||
button.setAttribute('data-id', id);
|
button.setAttribute("data-id", id);
|
||||||
button.innerHTML = innerHTML;
|
button.innerHTML = innerHTML;
|
||||||
button.addEventListener('click', clickHandler);
|
button.addEventListener("click", clickHandler);
|
||||||
return button;
|
return button;
|
||||||
},
|
},
|
||||||
closeSubmenu(fullscreen) {
|
closeSubmenu(fullscreen) {
|
||||||
document.querySelector('.dialogContainer').remove();
|
document.querySelector(".dialogContainer").remove();
|
||||||
document.querySelector('.dialogBackdrop').remove()
|
document.querySelector(".dialogBackdrop").remove();
|
||||||
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Control' }));
|
document.dispatchEvent(new KeyboardEvent("keydown", { key: "Control" }));
|
||||||
if (!fullscreen) return;
|
if (!fullscreen) return;
|
||||||
document.removeEventListener('keydown', this.handleEscapeKey);
|
document.removeEventListener("keydown", this.handleEscapeKey);
|
||||||
document.querySelector('.btnVideoOsdSettings').focus();
|
document.querySelector(".btnVideoOsdSettings").focus();
|
||||||
},
|
},
|
||||||
openSubmenu(ref, menu) {
|
openSubmenu(ref, menu) {
|
||||||
const options = ['Show Button', 'Button w/ auto PiP', 'Automatically Skip', 'Off'];
|
const options = [
|
||||||
|
"Show Button",
|
||||||
|
"Button w/ auto PiP",
|
||||||
|
"Automatically Skip",
|
||||||
|
"Off",
|
||||||
|
];
|
||||||
const submenu = menu.cloneNode(true);
|
const submenu = menu.cloneNode(true);
|
||||||
const scroller = submenu.querySelector('.actionSheetScroller');
|
const scroller = submenu.querySelector(".actionSheetScroller");
|
||||||
scroller.innerHTML = '';
|
scroller.innerHTML = "";
|
||||||
options.forEach(option => {
|
for (const option of options) {
|
||||||
if (option !== 'Button w/ auto PiP' || document.pictureInPictureEnabled) {
|
if (option !== "Button w/ auto PiP" || document.pictureInPictureEnabled) {
|
||||||
const button = this.createButton(ref, `introskipper-${option.toLowerCase().replace(' ', '-')}`,
|
const button = this.createButton(
|
||||||
`<span class="actionsheetMenuItemIcon listItemIcon listItemIcon-transparent material-icons check" aria-hidden="true" style="visibility:${option === this.currentOption ? 'visible' : 'hidden'};"></span><div class="listItemBody actionsheetListItemBody"><div class="listItemBodyText actionSheetItemText">${option}</div></div>`,
|
ref,
|
||||||
() => this.selectOption(option));
|
`introskipper-${option.toLowerCase().replace(" ", "-")}`,
|
||||||
|
`<span class="actionsheetMenuItemIcon listItemIcon listItemIcon-transparent material-icons check" aria-hidden="true" style="visibility:${option === this.currentOption ? "visible" : "hidden"};"></span><div class="listItemBody actionsheetListItemBody"><div class="listItemBodyText actionSheetItemText">${option}</div></div>`,
|
||||||
|
() => this.selectOption(option),
|
||||||
|
);
|
||||||
scroller.appendChild(button);
|
scroller.appendChild(button);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
const backdrop = document.createElement('div');
|
const backdrop = document.createElement("div");
|
||||||
backdrop.className = 'dialogBackdrop dialogBackdropOpened';
|
backdrop.className = "dialogBackdrop dialogBackdropOpened";
|
||||||
document.body.append(backdrop, submenu);
|
document.body.append(backdrop, submenu);
|
||||||
const actionSheet = submenu.querySelector('.actionSheet');
|
const actionSheet = submenu.querySelector(".actionSheet");
|
||||||
if (actionSheet.classList.contains('actionsheet-not-fullscreen')) {
|
if (actionSheet.classList.contains("actionsheet-not-fullscreen")) {
|
||||||
this.adjustPosition(actionSheet, document.querySelector('.btnVideoOsdSettings'));
|
this.adjustPosition(
|
||||||
submenu.addEventListener('click', () => this.closeSubmenu(false));
|
actionSheet,
|
||||||
|
document.querySelector(".btnVideoOsdSettings"),
|
||||||
|
);
|
||||||
|
submenu.addEventListener("click", () => this.closeSubmenu(false));
|
||||||
} else {
|
} else {
|
||||||
submenu.querySelector('.btnCloseActionSheet').addEventListener('click', () => this.closeSubmenu(true))
|
submenu
|
||||||
scroller.addEventListener('click', () => this.closeSubmenu(true))
|
.querySelector(".btnCloseActionSheet")
|
||||||
document.addEventListener('keydown', this.handleEscapeKey);
|
.addEventListener("click", () => this.closeSubmenu(true));
|
||||||
|
scroller.addEventListener("click", () => this.closeSubmenu(true));
|
||||||
|
document.addEventListener("keydown", this.handleEscapeKey);
|
||||||
setTimeout(() => scroller.firstElementChild.focus(), 240);
|
setTimeout(() => scroller.firstElementChild.focus(), 240);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
selectOption(option) {
|
selectOption(option) {
|
||||||
this.currentOption = option;
|
this.currentOption = option;
|
||||||
localStorage.setItem('introskipperOption', option);
|
localStorage.setItem("introskipperOption", option);
|
||||||
this.d(`Introskipper option selected and saved: ${option}`);
|
this.d(`Introskipper option selected and saved: ${option}`);
|
||||||
},
|
},
|
||||||
isAutoSkipLocked(config) {
|
isAutoSkipLocked(config) {
|
||||||
const isAutoSkip = config.AutoSkip && config.AutoSkipCredits;
|
const isAutoSkip = config.AutoSkip && config.AutoSkipCredits;
|
||||||
const isAutoSkipClient = new Set(config.ClientList.split(',')).has(ApiClient.appName());
|
const isAutoSkipClient = new Set(config.ClientList.split(",")).has(
|
||||||
|
ApiClient.appName(),
|
||||||
|
);
|
||||||
return isAutoSkip || (config.SkipButtonVisible && isAutoSkipClient);
|
return isAutoSkip || (config.SkipButtonVisible && isAutoSkipClient);
|
||||||
},
|
},
|
||||||
async injectIntroSkipperOptions(actionSheet) {
|
async injectIntroSkipperOptions(actionSheet) {
|
||||||
@ -335,19 +400,24 @@ const introSkipper = {
|
|||||||
}
|
}
|
||||||
const statsButton = actionSheet.querySelector('[data-id="stats"]');
|
const statsButton = actionSheet.querySelector('[data-id="stats"]');
|
||||||
if (!statsButton) return;
|
if (!statsButton) return;
|
||||||
const menuItem = this.createButton(statsButton, 'introskipperMenu',
|
const menuItem = this.createButton(
|
||||||
|
statsButton,
|
||||||
|
"introskipperMenu",
|
||||||
`<div class="listItemBody actionsheetListItemBody"><div class="listItemBodyText actionSheetItemText">Intro Skipper</div></div><div class="listItemAside actionSheetItemAsideText">${this.currentOption}</div>`,
|
`<div class="listItemBody actionsheetListItemBody"><div class="listItemBodyText actionSheetItemText">Intro Skipper</div></div><div class="listItemAside actionSheetItemAsideText">${this.currentOption}</div>`,
|
||||||
() => this.openSubmenu(statsButton, actionSheet.closest('.dialogContainer')));
|
() =>
|
||||||
|
this.openSubmenu(statsButton, actionSheet.closest(".dialogContainer")),
|
||||||
|
);
|
||||||
const originalWidth = actionSheet.offsetWidth;
|
const originalWidth = actionSheet.offsetWidth;
|
||||||
statsButton.before(menuItem);
|
statsButton.before(menuItem);
|
||||||
if (actionSheet.classList.contains('actionsheet-not-fullscreen')) this.adjustPosition(actionSheet, menuItem, originalWidth);
|
if (actionSheet.classList.contains("actionsheet-not-fullscreen"))
|
||||||
|
this.adjustPosition(actionSheet, menuItem, originalWidth);
|
||||||
},
|
},
|
||||||
adjustPosition(element, reference, originalWidth) {
|
adjustPosition(element, reference, originalWidth) {
|
||||||
if (originalWidth) {
|
if (originalWidth) {
|
||||||
const currentTop = parseInt(element.style.top, 10) || 0;
|
const currentTop = Number.parseInt(element.style.top, 10) || 0;
|
||||||
element.style.top = `${currentTop - reference.offsetHeight}px`;
|
element.style.top = `${currentTop - reference.offsetHeight}px`;
|
||||||
const newWidth = Math.max(reference.offsetWidth - originalWidth, 0);
|
const newWidth = Math.max(reference.offsetWidth - originalWidth, 0);
|
||||||
const originalLeft = parseInt(element.style.left, 10) || 0;
|
const originalLeft = Number.parseInt(element.style.left, 10) || 0;
|
||||||
element.style.left = `${originalLeft - newWidth / 2}px`;
|
element.style.left = `${originalLeft - newWidth / 2}px`;
|
||||||
} else {
|
} else {
|
||||||
const rect = reference.getBoundingClientRect();
|
const rect = reference.getBoundingClientRect();
|
||||||
@ -356,8 +426,8 @@ const introSkipper = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
injectSkipperFields(metadataFormFields) {
|
injectSkipperFields(metadataFormFields) {
|
||||||
const skipperFields = document.createElement('div');
|
const skipperFields = document.createElement("div");
|
||||||
skipperFields.className = 'detailSection introskipperSection';
|
skipperFields.className = "detailSection introskipperSection";
|
||||||
skipperFields.innerHTML = `
|
skipperFields.innerHTML = `
|
||||||
<h2>Intro Skipper</h2>
|
<h2>Intro Skipper</h2>
|
||||||
<div class="inlineForm">
|
<div class="inlineForm">
|
||||||
@ -385,92 +455,131 @@ const introSkipper = {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
metadataFormFields.querySelector('#metadataSettingsCollapsible').insertAdjacentElement('afterend', skipperFields);
|
metadataFormFields
|
||||||
|
.querySelector("#metadataSettingsCollapsible")
|
||||||
|
.insertAdjacentElement("afterend", skipperFields);
|
||||||
this.attachSaveListener(metadataFormFields);
|
this.attachSaveListener(metadataFormFields);
|
||||||
this.updateSkipperFields(skipperFields);
|
this.updateSkipperFields(skipperFields);
|
||||||
this.setTimeInputs(skipperFields);
|
this.setTimeInputs(skipperFields);
|
||||||
},
|
},
|
||||||
updateSkipperFields(skipperFields) {
|
updateSkipperFields(skipperFields) {
|
||||||
const { Introduction = {}, Credits = {} } = this.skipperData;
|
const { Introduction = {}, Credits = {} } = this.skipperData;
|
||||||
skipperFields.querySelector('#introStartEdit').value = Introduction.Start || 0;
|
skipperFields.querySelector("#introStartEdit").value =
|
||||||
skipperFields.querySelector('#introEndEdit').value = Introduction.End || 0;
|
Introduction.Start || 0;
|
||||||
skipperFields.querySelector('#creditsStartEdit').value = Credits.Start || 0;
|
skipperFields.querySelector("#introEndEdit").value = Introduction.End || 0;
|
||||||
skipperFields.querySelector('#creditsEndEdit').value = Credits.End || 0;
|
skipperFields.querySelector("#creditsStartEdit").value = Credits.Start || 0;
|
||||||
|
skipperFields.querySelector("#creditsEndEdit").value = Credits.End || 0;
|
||||||
},
|
},
|
||||||
attachSaveListener(metadataFormFields) {
|
attachSaveListener(metadataFormFields) {
|
||||||
const saveButton = metadataFormFields.querySelector('.formDialogFooter .btnSave');
|
const saveButton = metadataFormFields.querySelector(
|
||||||
|
".formDialogFooter .btnSave",
|
||||||
|
);
|
||||||
if (saveButton) {
|
if (saveButton) {
|
||||||
saveButton.addEventListener('click', this.saveSkipperData.bind(this));
|
saveButton.addEventListener("click", this.saveSkipperData.bind(this));
|
||||||
} else {
|
} else {
|
||||||
console.error('Save button not found');
|
console.error("Save button not found");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setTimeInputs(skipperFields) {
|
setTimeInputs(skipperFields) {
|
||||||
const inputContainers = skipperFields.querySelectorAll('.inputContainer');
|
const inputContainers = skipperFields.querySelectorAll(".inputContainer");
|
||||||
inputContainers.forEach(container => {
|
for (const container of inputContainers) {
|
||||||
const displayInput = container.querySelector('[id$="Display"]');
|
const displayInput = container.querySelector('[id$="Display"]');
|
||||||
const editInput = container.querySelector('[id$="Edit"]');
|
const editInput = container.querySelector('[id$="Edit"]');
|
||||||
displayInput.addEventListener('pointerdown', (e) => {
|
displayInput.addEventListener("pointerdown", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.switchToEdit(displayInput, editInput);
|
this.switchToEdit(displayInput, editInput);
|
||||||
});
|
});
|
||||||
editInput.addEventListener('blur', () => this.switchToDisplay(displayInput, editInput));
|
editInput.addEventListener("blur", () =>
|
||||||
displayInput.value = this.formatTime(parseFloat(editInput.value) || 0);
|
this.switchToDisplay(displayInput, editInput),
|
||||||
});
|
);
|
||||||
|
displayInput.value = this.formatTime(
|
||||||
|
Number.parseFloat(editInput.value) || 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
formatTime(totalSeconds) {
|
formatTime(totalSeconds) {
|
||||||
|
if (!totalSeconds) return "0 seconds";
|
||||||
const totalRoundedSeconds = Math.round(totalSeconds);
|
const totalRoundedSeconds = Math.round(totalSeconds);
|
||||||
const hours = Math.floor(totalRoundedSeconds / 3600);
|
const hours = Math.floor(totalRoundedSeconds / 3600);
|
||||||
const minutes = Math.floor((totalRoundedSeconds % 3600) / 60);
|
const minutes = Math.floor((totalRoundedSeconds % 3600) / 60);
|
||||||
const seconds = totalRoundedSeconds % 60;
|
const seconds = totalRoundedSeconds % 60;
|
||||||
let result = [];
|
const result = [];
|
||||||
if (hours > 0) result.push(`${hours} hour${hours !== 1 ? 's' : ''}`);
|
if (hours) result.push(`${hours} hour${hours !== 1 ? "s" : ""}`);
|
||||||
if (minutes > 0) result.push(`${minutes} minute${minutes !== 1 ? 's' : ''}`);
|
if (minutes) result.push(`${minutes} minute${minutes !== 1 ? "s" : ""}`);
|
||||||
if (seconds > 0 || result.length === 0) result.push(`${seconds} second${seconds !== 1 ? 's' : ''}`);
|
if (seconds || !result.length)
|
||||||
return result.join(' ');
|
result.push(`${seconds} second${seconds !== 1 ? "s" : ""}`);
|
||||||
|
return result.join(" ");
|
||||||
},
|
},
|
||||||
switchToEdit(displayInput, editInput) {
|
switchToEdit(displayInput, editInput) {
|
||||||
displayInput.style.display = 'none';
|
displayInput.style.display = "none";
|
||||||
editInput.style.display = '';
|
editInput.style.display = "";
|
||||||
editInput.focus();
|
editInput.focus();
|
||||||
},
|
},
|
||||||
switchToDisplay(displayInput, editInput) {
|
switchToDisplay(displayInput, editInput) {
|
||||||
editInput.style.display = 'none';
|
editInput.style.display = "none";
|
||||||
displayInput.style.display = '';
|
displayInput.style.display = "";
|
||||||
displayInput.value = this.formatTime(parseFloat(editInput.value) || 0);
|
displayInput.value = this.formatTime(
|
||||||
|
Number.parseFloat(editInput.value) || 0,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
async saveSkipperData() {
|
async saveSkipperData() {
|
||||||
const newTimestamps = {
|
const newTimestamps = {
|
||||||
Introduction: {
|
Introduction: {
|
||||||
Start: parseFloat(document.getElementById('introStartEdit').value || 0),
|
Start: Number.parseFloat(
|
||||||
End: parseFloat(document.getElementById('introEndEdit').value || 0)
|
document.getElementById("introStartEdit").value || 0,
|
||||||
|
),
|
||||||
|
End: Number.parseFloat(
|
||||||
|
document.getElementById("introEndEdit").value || 0,
|
||||||
|
),
|
||||||
},
|
},
|
||||||
Credits: {
|
Credits: {
|
||||||
Start: parseFloat(document.getElementById('creditsStartEdit').value || 0),
|
Start: Number.parseFloat(
|
||||||
End: parseFloat(document.getElementById('creditsEndEdit').value || 0)
|
document.getElementById("creditsStartEdit").value || 0,
|
||||||
}
|
),
|
||||||
|
End: Number.parseFloat(
|
||||||
|
document.getElementById("creditsEndEdit").value || 0,
|
||||||
|
),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
const { Introduction = {}, Credits = {} } = this.skipperData;
|
const { Introduction = {}, Credits = {} } = this.skipperData;
|
||||||
if (newTimestamps.Introduction.Start !== (Introduction.Start || 0) ||
|
if (
|
||||||
|
newTimestamps.Introduction.Start !== (Introduction.Start || 0) ||
|
||||||
newTimestamps.Introduction.End !== (Introduction.End || 0) ||
|
newTimestamps.Introduction.End !== (Introduction.End || 0) ||
|
||||||
newTimestamps.Credits.Start !== (Credits.Start || 0) ||
|
newTimestamps.Credits.Start !== (Credits.Start || 0) ||
|
||||||
newTimestamps.Credits.End !== (Credits.End || 0)) {
|
newTimestamps.Credits.End !== (Credits.End || 0)
|
||||||
const response = await this.secureFetch(`Episode/${this.currentEpisodeId}/Timestamps`, "POST", JSON.stringify(newTimestamps));
|
) {
|
||||||
this.d(response.ok ? 'Timestamps updated successfully' : 'Failed to update timestamps:', response.status);
|
const response = await this.secureFetch(
|
||||||
|
`Episode/${this.currentEpisodeId}/Timestamps`,
|
||||||
|
"POST",
|
||||||
|
JSON.stringify(newTimestamps),
|
||||||
|
);
|
||||||
|
this.d(
|
||||||
|
response.ok
|
||||||
|
? "Timestamps updated successfully"
|
||||||
|
: "Failed to update timestamps:",
|
||||||
|
response.status,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
this.d('Timestamps have not changed, skipping update');
|
this.d("Timestamps have not changed, skipping update");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
/** Make an authenticated fetch to the Jellyfin server and parse the response body as JSON. */
|
/** Make an authenticated fetch to the Jellyfin server and parse the response body as JSON. */
|
||||||
async secureFetch(url, method = "GET", body = null) {
|
async secureFetch(url, method = "GET", body = null) {
|
||||||
const response = await fetch(`${ApiClient.serverAddress()}/${url}`, {
|
const response = await fetch(`${ApiClient.serverAddress()}/${url}`, {
|
||||||
method,
|
method,
|
||||||
headers: Object.assign({ "Authorization": `MediaBrowser Token=${ApiClient.accessToken()}` },
|
headers: Object.assign(
|
||||||
method === "POST" ? {"Content-Type": "application/json"} : {}),
|
{ Authorization: `MediaBrowser Token=${ApiClient.accessToken()}` },
|
||||||
body });
|
method === "POST" ? { "Content-Type": "application/json" } : {},
|
||||||
return response.ok ? (method === "POST" ? response : response.json()) :
|
),
|
||||||
response.status === 404 ? null :
|
body,
|
||||||
console.error(`Error ${response.status} from ${url}`) || null;
|
});
|
||||||
|
return response.ok
|
||||||
|
? method === "POST"
|
||||||
|
? response
|
||||||
|
: response.json()
|
||||||
|
: response.status === 404
|
||||||
|
? null
|
||||||
|
: console.error(`Error ${response.status} from ${url}`) || null;
|
||||||
},
|
},
|
||||||
/** Handle keydown events. */
|
/** Handle keydown events. */
|
||||||
eventHandler(e) {
|
eventHandler(e) {
|
||||||
@ -480,10 +589,10 @@ const introSkipper = {
|
|||||||
this.doSkip();
|
this.doSkip();
|
||||||
},
|
},
|
||||||
handleEscapeKey(e) {
|
handleEscapeKey(e) {
|
||||||
if (e.key === 'Escape' || e.keyCode === 461 || e.keyCode === 10009) {
|
if (e.key === "Escape" || e.keyCode === 461 || e.keyCode === 10009) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
this.closeSubmenu(true);
|
this.closeSubmenu(true);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
introSkipper.setup();
|
introSkipper.setup();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user